diff --git a/.gitmodules b/.gitmodules index d867f24e45..dcde427ae7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -55,3 +55,9 @@ [submodule "3rdparty/vgmstream"] path = 3rdparty/vgmstream url = https://github.com/vgmstream/vgmstream.git +[submodule "3rdparty/ogg"] + path = 3rdparty/ogg + url = https://github.com/xiph/ogg.git +[submodule "3rdparty/vorbis"] + path = 3rdparty/vorbis + url = https://github.com/xiph/vorbis.git diff --git a/3rdparty/ogg b/3rdparty/ogg new file mode 160000 index 0000000000..be05b13e98 --- /dev/null +++ b/3rdparty/ogg @@ -0,0 +1 @@ +Subproject commit be05b13e98b048f0b5a0f5fa8ce514d56db5f822 diff --git a/3rdparty/vorbis b/3rdparty/vorbis new file mode 160000 index 0000000000..0657aee69d --- /dev/null +++ b/3rdparty/vorbis @@ -0,0 +1 @@ +Subproject commit 0657aee69dec8508a0011f47f3b69d7538e9d262 diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index 0472176eb2..34640dd56d 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -248,6 +248,15 @@ if(BUILD_TESTING) set_tests_properties(guest_posix_scalar_io PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_kernel_sys_sdk_version_tests tests/GuestKernelSysSdkVersion.cpp) + target_include_directories(guest_kernel_sys_sdk_version_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_sdk_version_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_sdk_version_tests) + add_test(NAME guest_kernel_sys_sdk_version COMMAND guest_kernel_sys_sdk_version_tests) + if(WIN32) + set_tests_properties(guest_kernel_sys_sdk_version PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(nid_buffer_validation_tests nid/tests/BufferValidation.cpp) target_include_directories(nid_buffer_validation_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/nid/include) target_compile_features(nid_buffer_validation_tests PRIVATE cxx_std_20) @@ -422,6 +431,17 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_entitlements_tests tests/GuestEntitlements.cpp) + target_include_directories(guest_entitlements_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_entitlements_tests PRIVATE libSceNpEntitlementAccess libc) + configure_windows_unwind(guest_entitlements_tests) + add_test(NAME guest_entitlements COMMAND guest_entitlements_tests) + add_test(NAME guest_entitlements_missing COMMAND guest_entitlements_tests missing) + if(WIN32) + set_tests_properties(guest_entitlements guest_entitlements_missing PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_signin_dialog_tests tests/GuestSigninDialog.cpp) target_include_directories(guest_signin_dialog_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(guest_signin_dialog_tests PRIVATE libSceSigninDialog libc) @@ -621,6 +641,16 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_np_cpp_web_api_signed_out_tests tests/GuestNpCppWebApiSignedOut.cpp) + target_include_directories(guest_np_cpp_web_api_signed_out_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_np_cpp_web_api_signed_out_tests PRIVATE libSceNpCppWebApi libc) + configure_windows_unwind(guest_np_cpp_web_api_signed_out_tests) + add_test(NAME guest_np_cpp_web_api_signed_out COMMAND guest_np_cpp_web_api_signed_out_tests) + if(WIN32) + set_tests_properties(guest_np_cpp_web_api_signed_out PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(savedata_core_write_replacement_tests tests/SaveDataWriteReplacement.cpp) target_include_directories(savedata_core_write_replacement_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(savedata_core_write_replacement_tests PRIVATE libSceSaveData) @@ -713,9 +743,11 @@ if(BUILD_TESTING) add_test_executable(guest_vorbis_dec_tests tests/GuestVorbisDec.cpp) target_include_directories(guest_vorbis_dec_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) - target_link_libraries(guest_vorbis_dec_tests PRIVATE libSceVorbisDec libc) + target_compile_definitions(guest_vorbis_dec_tests PRIVATE OV_EXCLUDE_STATIC_CALLBACKS) + target_link_libraries(guest_vorbis_dec_tests PRIVATE libSceVorbisDec vorbisenc vorbisfile vorbis ogg libc) configure_windows_unwind(guest_vorbis_dec_tests) add_test(NAME guest_vorbis_dec COMMAND guest_vorbis_dec_tests) + set_tests_properties(guest_vorbis_dec PROPERTIES TIMEOUT 30) if(WIN32) set_tests_properties(guest_vorbis_dec PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") diff --git a/core/libs/prx/libSceAgc/CMakeLists.txt b/core/libs/prx/libSceAgc/CMakeLists.txt index faf498b571..afff1e8a73 100644 --- a/core/libs/prx/libSceAgc/CMakeLists.txt +++ b/core/libs/prx/libSceAgc/CMakeLists.txt @@ -267,6 +267,19 @@ if(BUILD_TESTING) endif() endif() +add_test_executable(agc_instrumentation_query_size_tests EXCLUDE_FROM_ALL tests/InstrumentationQuerySize.cpp) +target_include_directories(agc_instrumentation_query_size_tests PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(agc_instrumentation_query_size_tests PRIVATE libSceAgc libc) +configure_windows_unwind(agc_instrumentation_query_size_tests) +if(BUILD_TESTING) + set_target_properties(agc_instrumentation_query_size_tests PROPERTIES EXCLUDE_FROM_ALL FALSE) + add_test(NAME agc_instrumentation_query_size_tests COMMAND agc_instrumentation_query_size_tests) + if(WIN32) + set_tests_properties(agc_instrumentation_query_size_tests PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + add_test_executable(agc_context_state_size_tests EXCLUDE_FROM_ALL tests/ContextStateSize.cpp) target_include_directories(agc_context_state_size_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(agc_context_state_size_tests PRIVATE libSceAgc libc) diff --git a/core/libs/prx/libSceAgc/DcbDraw/src/Stats.cpp b/core/libs/prx/libSceAgc/DcbDraw/src/Stats.cpp index 2681834e0b..5a8ebdd44d 100644 --- a/core/libs/prx/libSceAgc/DcbDraw/src/Stats.cpp +++ b/core/libs/prx/libSceAgc/DcbDraw/src/Stats.cpp @@ -22,13 +22,12 @@ std::uint32_t* APS5_VABI sceAgcDcbGetLodStats(CommandBuffer* buf, std::uint8_t c } std::uint32_t APS5_VABI sceAgcDcbBeginOcclusionQueryGetSize(std::int32_t queryType) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (queryType == 1) return 0x120u; + return queryType == 0 ? 0x10u : 0u; } std::uint32_t APS5_VABI sceAgcDcbEndOcclusionQueryGetSize() { - NotImplemented_nid_no_patch(__func__); - return 0; + return 0x10u; } } diff --git a/core/libs/prx/libSceAgc/Misc/src/Debug.cpp b/core/libs/prx/libSceAgc/Misc/src/Debug.cpp index 8a321110ab..a211959181 100644 --- a/core/libs/prx/libSceAgc/Misc/src/Debug.cpp +++ b/core/libs/prx/libSceAgc/Misc/src/Debug.cpp @@ -1,9 +1,16 @@ #include "prx/libSceAgc/Misc/include/Debug.hpp" +#include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +std::atomic shaderInstrumentation{0}; + +} + extern "C" { int APS5_VABI sceAgcDebugRaiseException(std::uint32_t exceptionId) { @@ -11,4 +18,13 @@ int APS5_VABI sceAgcDebugRaiseException(std::uint32_t exceptionId) { return 0; } +int APS5_VABI sceAgcSetShaderInstrumentation(std::uint32_t flags) { + shaderInstrumentation.store(flags, std::memory_order_relaxed); + return 0; +} + +std::uint32_t APS5_VABI sceAgcGetShaderInstrumentation() { + return shaderInstrumentation.load(std::memory_order_relaxed); +} + } diff --git a/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp b/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp index 8e56d4c0c8..65b337d335 100644 --- a/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp +++ b/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp @@ -53,12 +53,9 @@ std::uint32_t CreateInterpolantValueSplitF16(std::uint32_t psWord, const ShaderS : CreateInterpolantValue(psWord, gsSemantic); } -std::uint32_t UpdateInterpolantValue(std::uint32_t psWord, const ShaderSemantic* gsSemantic) { - const std::uint32_t f16 = (psWord >> 20u) & 0x3u; - if (f16 != 0 && f16 != InterpolantF16HiOnly && ((psWord >> 28u) & 0x3u) != ((psWord >> 30u) & 0x3u)) { - throw std::runtime_error(std::string(__func__) + ": DEFAULT_VAL of an f16 input with different low and high defaults is unknown"); - } - return CreateInterpolantValueSplitF16(psWord, gsSemantic); +std::uint32_t CreateInterpolantValueFlatDefault(std::uint32_t psWord, const ShaderSemantic* gsSemantic) { + const std::uint32_t value = CreateInterpolantValue(psWord, gsSemantic); + return ((psWord & 0x00700000u) == 0x00400000u) ? (value | 0x00000020u) : value; } int CreateInterpolantMapping(const char* fn, ShaderRegister* regs, const Shader* gs, const Shader* ps, CreateInterpolantValueFn createValue, bool fillIdentityTail) { @@ -132,7 +129,15 @@ int APS5_VABI sceAgcCreateInterpolantMapping_10000(ShaderRegister* regs, const S } int APS5_VABI sceAgcUpdateInterpolantMapping_10000(ShaderRegister* regs, const Shader* gs, const Shader* ps) { - return CreateInterpolantMapping(__func__, regs, gs, ps, UpdateInterpolantValue, false); + return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValueSplitF16, false); +} + +int APS5_VABI sceAgcCreateInterpolantMapping_nid_postfix(ShaderRegister* regs, const Shader* gs, const Shader* ps) { + return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValueFlatDefault, true); +} + +int APS5_VABI sceAgcUpdateInterpolantMapping_nid_postfix(ShaderRegister* regs, const Shader* gs, const Shader* ps) { + return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValueFlatDefault, false); } } diff --git a/core/libs/prx/libSceAgc/Unimplemented.cpp b/core/libs/prx/libSceAgc/Unimplemented.cpp index 02dcc73aca..d7751bec51 100644 --- a/core/libs/prx/libSceAgc/Unimplemented.cpp +++ b/core/libs/prx/libSceAgc/Unimplemented.cpp @@ -40,14 +40,4 @@ std::uint32_t* APS5_VABI sceAgcCbMemsetExclusive(CommandBuffer* buffer, void* de return 0; } -int APS5_VABI sceAgcSetShaderInstrumentation(std::uint32_t flags) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAgcGetShaderInstrumentation() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - } diff --git a/core/libs/prx/libSceAgc/tests/InstrumentationQuerySize.cpp b/core/libs/prx/libSceAgc/tests/InstrumentationQuerySize.cpp new file mode 100644 index 0000000000..7f75026d39 --- /dev/null +++ b/core/libs/prx/libSceAgc/tests/InstrumentationQuerySize.cpp @@ -0,0 +1,48 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libc/include/Shutdown.hpp" + +#include +#include +#include + +extern "C" { +int APS5_VABI sceAgcSetShaderInstrumentation(std::uint32_t); +std::uint32_t APS5_VABI sceAgcGetShaderInstrumentation(); +std::uint32_t APS5_VABI sceAgcDcbBeginOcclusionQueryGetSize(std::int32_t); +std::uint32_t APS5_VABI sceAgcDcbEndOcclusionQueryGetSize(); +} + +namespace { + +void check(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +void testShaderInstrumentation() { + check(sceAgcGetShaderInstrumentation() == 0, "shader instrumentation starts cleared"); + check(sceAgcSetShaderInstrumentation(0x80000005u) == 0, "setting shader instrumentation failed"); + check(sceAgcGetShaderInstrumentation() == 0x80000005u, "shader instrumentation flags were not kept"); + check(sceAgcSetShaderInstrumentation(0) == 0 && sceAgcGetShaderInstrumentation() == 0, "shader instrumentation was not cleared"); +} + +void testOcclusionQuerySizes() { + check(sceAgcDcbBeginOcclusionQueryGetSize(0) == 0x10u, "begin occlusion query size for type 0"); + check(sceAgcDcbBeginOcclusionQueryGetSize(1) == 0x120u, "begin occlusion query size for type 1"); + check(sceAgcDcbBeginOcclusionQueryGetSize(2) == 0, "begin occlusion query size for an unknown type"); + check(sceAgcDcbEndOcclusionQueryGetSize() == 0x10u, "end occlusion query size"); +} + +} + +int main() { + try { + testShaderInstrumentation(); + testOcclusionQuerySizes(); + LibcRunShutdown_nid_postfix(); + std::puts("AGC shader instrumentation and occlusion query size tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp b/core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp index 652eacfea7..1b38f7df08 100644 --- a/core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp +++ b/core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp @@ -14,6 +14,8 @@ extern "C" int APS5_VABI sceAgcCreateInterpolantMapping_0100(ShaderRegister* regs, const Shader* gs, const Shader* ps); extern "C" int APS5_VABI sceAgcCreateInterpolantMapping_10000(ShaderRegister* regs, const Shader* gs, const Shader* ps); extern "C" int APS5_VABI sceAgcUpdateInterpolantMapping_10000(ShaderRegister* regs, const Shader* gs, const Shader* ps); +extern "C" int APS5_VABI sceAgcCreateInterpolantMapping_nid_postfix(ShaderRegister* regs, const Shader* gs, const Shader* ps); +extern "C" int APS5_VABI sceAgcUpdateInterpolantMapping_nid_postfix(ShaderRegister* regs, const Shader* gs, const Shader* ps); namespace { @@ -158,13 +160,46 @@ void testUpdateMapping() { check(std::memcmp(create.data(), update.data(), written) == 0, "update differs from the split f16 mapping"); checkIdentity(create, static_cast(inputs.size()), "unused interpolants are not the identity"); - for (const std::uint32_t index : {1u, 3u}) { - inputs[index].default_value = 0; - update = filled(); - expectFailure([&] { sceAgcUpdateInterpolantMapping_10000(update.data(), &gs, &ps); }); - check(std::memcmp(update.data(), untouched.data(), sizeof(update)) == 0, "rejected update wrote registers"); - inputs[index].default_value = inputs[index].default_value_hi; + inputs[1].default_value = 0; + inputs[3].default_value = 0; + update = filled(); + check(sceAgcUpdateInterpolantMapping_10000(update.data(), &gs, &ps) == 0, "update with split f16 defaults failed"); + check(update[1].value == 0x01380007u && update[3].value == 0x03680409u, "f16 DEFAULT_VAL does not come from the low default"); +} + +void testUnversionedMapping() { + std::array inputs{ + semantic(1, 0, 2), semantic(2, 0, 2), semantic(5, 0, 2), semantic(3, 0, 0), semantic(4, 0, 3)}; + inputs[0].is_flat_shaded = 1; + inputs[0].default_value = 1; + inputs[0].default_value_hi = 3; + inputs[1].default_value_hi = 1; + inputs[2].is_flat_shaded = 1; + inputs[2].default_value_hi = 2; + inputs[3].is_flat_shaded = 1; + inputs[3].default_value = 2; + std::array outputs{semantic(1, 7, 3), semantic(2, 4, 1), semantic(3, 9, 0), semantic(4, 2, 1)}; + Shader gs{}; + gs.output_semantics = outputs.data(); + gs.num_output_semantics = static_cast(outputs.size()); + Shader ps{}; + ps.input_semantics = inputs.data(); + ps.num_input_semantics = static_cast(inputs.size()); + const std::array flatDefault{0x02680527u, 0x02380024u, 0x02580020u, 0x00000629u, 0x03180002u}; + const std::size_t written = inputs.size() * sizeof(ShaderRegister); + const auto untouched = filled(); + + auto create = filled(); + check(sceAgcCreateInterpolantMapping_nid_postfix(create.data(), &gs, &ps) == 0, "unversioned mapping failed"); + for (std::uint32_t i = 0; i < inputs.size(); ++i) { + check(create[i].offset == ShaderRegs::SPI_PS_INPUT_CNTL_0 + i && create[i].value == flatDefault[i], "unversioned mapping changed"); } + checkIdentity(create, static_cast(inputs.size()), "unused interpolants are not the identity"); + + auto update = filled(); + check(sceAgcUpdateInterpolantMapping_nid_postfix(update.data(), &gs, &ps) == 0, "unversioned update failed"); + check(std::memcmp(update.data(), create.data(), written) == 0, "unversioned update differs from its mapping"); + check(std::memcmp(update.data() + inputs.size(), untouched.data(), sizeof(update) - written) == 0, "unversioned update wrote past its inputs"); } void testRejections() { @@ -208,8 +243,9 @@ int main() { testIdentity(); testMapping(); testUpdateMapping(); + testUnversionedMapping(); testRejections(); - check(mappings == 7u && links == 5u, "mapping preparation call count changed"); + check(mappings == 10u && links == 8u, "mapping preparation call count changed"); LibcRunShutdown_nid_postfix(); std::puts("AGC interpolant mapping tests passed"); return 0; diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 2bb2a650aa..7902207afd 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -433,6 +433,16 @@ if(MINGW OR CMAKE_SYSTEM_NAME STREQUAL "Linux") endif() if(BUILD_TESTING) + add_test_executable(agc_driver_unsupported_diagnostics_tests tests/UnsupportedDiagnostics.cpp) + target_include_directories(agc_driver_unsupported_diagnostics_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(agc_driver_unsupported_diagnostics_tests PRIVATE libSceAgcDriver) + configure_windows_unwind(agc_driver_unsupported_diagnostics_tests) + add_test(NAME agc_driver_unsupported_diagnostics COMMAND agc_driver_unsupported_diagnostics_tests) + if(WIN32) + set_tests_properties(agc_driver_unsupported_diagnostics PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(agc_driver_draw_program_trace_tests tests/DrawProgramTrace.cpp Execution/src/Driver/Draw/DrawProgramTrace.cpp @@ -459,6 +469,16 @@ if(WIN32) endif() if(BUILD_TESTING) + add_test_executable(agc_driver_nop_data_packet_tests tests/NopDataWritePacket.cpp) + target_include_directories(agc_driver_nop_data_packet_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(agc_driver_nop_data_packet_tests PRIVATE libSceAgcDriver) + configure_windows_unwind(agc_driver_nop_data_packet_tests) + add_test(NAME agc_driver_nop_data_packet COMMAND agc_driver_nop_data_packet_tests) + if(WIN32) + set_tests_properties(agc_driver_nop_data_packet PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(agc_frame_dump_io_tests tests/FrameDumpIo.cpp) target_include_directories(agc_frame_dump_io_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(agc_frame_dump_io_tests PRIVATE libc) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp index 566f251089..c751d4ee51 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp @@ -250,9 +250,10 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe if (program.binary.stage == Stage::Fragment || roles[i] == Role::GeometryBack) return; decodeReads[i].clear(); vertexInfos[i] = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, &decodeReads[i], false, 8u - program.firstUserSgpr); - if (vertexInfos[i]) vertexInfos[i]->paClVsOutCntl = PositionExportLayout(program.snapshot.get(), program.codeOffset, program.binary.code, readRegister(queue.context, 0x207), program.tailContinuation.get()) | graphics.layerExports; if (vertexInfos[i]) { + vertexInfos[i]->paClVsOutCntl = PositionExportLayout(program.snapshot.get(), program.codeOffset, program.binary.code, readRegister(queue.context, 0x207), program.tailContinuation.get()) | graphics.layerExports; vertexInfos[i]->depthRemapMode = graphics.depthRemap.mode; + Graphics::ApplyPointSize(graphics, *vertexInfos[i]); } }; if (!registerKey) { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp index 6bfa9b1fb4..5cb9982751 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp @@ -2,6 +2,7 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include +#include namespace AgcDriver::DriverDetail { @@ -51,6 +52,10 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptr(vertex.pointSize)); + mix(std::bit_cast(vertex.pointSizeMin)); + mix(std::bit_cast(vertex.pointSizeMax)); for (std::uint32_t r = 0; r < vertex.resourcesNum; ++r) { for (const auto field : vertex.resources[r].fields) mix(field); const auto& destination = vertex.resourcesDst[r]; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp index 74fd6ea2c1..288ce32804 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp @@ -213,7 +213,7 @@ void Driver::traceDrawKeyLookup(const std::array& components, } bool Driver::sameVertexInfo(const ShaderRecompiler::ShaderVertexStageInfo& a, const ShaderRecompiler::ShaderVertexStageInfo& b) { - if (a.resourcesNum != b.resourcesNum || a.fetchAttribReg != b.fetchAttribReg || a.fetchBufferReg != b.fetchBufferReg || a.fetchEmbedded != b.fetchEmbedded || a.paClVsOutCntl != b.paClVsOutCntl) return false; + if (a.resourcesNum != b.resourcesNum || a.fetchAttribReg != b.fetchAttribReg || a.fetchBufferReg != b.fetchBufferReg || a.fetchEmbedded != b.fetchEmbedded || a.paClVsOutCntl != b.paClVsOutCntl || a.pointList != b.pointList || a.pointSize != b.pointSize || a.pointSizeMin != b.pointSizeMin || a.pointSizeMax != b.pointSizeMax) return false; if (a.depthRemapMode != b.depthRemapMode) return false; for (std::uint32_t i = 0; i < a.resourcesNum && i < a.resources.size(); ++i) { if (a.resources[i].fields != b.resources[i].fields) return false; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp index 8b9b39afe0..5f93b08490 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -1440,7 +1440,10 @@ std::vector PrepareGraphicsStages(const DrawDecode& decod prepared.push_back({program.snapshot, {program.codeOffset, nullptr}, program.binary.stage, offset, nullptr}); continue; } - if (vertex) vertex->paClVsOutCntl = PositionExportLayout(program.snapshot.get(), program.codeOffset, program.binary.code, decoded.state.paClVsOutCntl, program.tailContinuation.get()) | decoded.state.layerExports; + if (vertex) { + vertex->paClVsOutCntl = PositionExportLayout(program.snapshot.get(), program.codeOffset, program.binary.code, decoded.state.paClVsOutCntl, program.tailContinuation.get()) | decoded.state.layerExports; + Graphics::ApplyPointSize(decoded.state, *vertex); + } ShaderRecompiler::RecompileRequest request{program.binary, {wave, program.firstUserSgpr, program.userData, {}, fragment ? std::optional(decoded.pixel) : std::nullopt, vertex, memory, RegisteredFloatMode(*program.snapshot)}, target, {0, 0, offset, room}, ShaderRecompiler::GraphicsCompileContext{program.firstUserSgpr, linked, decoded.state.stages.mesh, decoded.state.stages.tessellation, {}}}; std::vector key; ShaderRecompiler::BuildPreparedShaderKey(request, key); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Screenshot.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Screenshot.cpp index 6a930d4e30..9e60cbac8c 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Screenshot.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Screenshot.cpp @@ -16,8 +16,12 @@ namespace { std::atomic requested{false}; std::filesystem::path directory() { +#ifdef _WIN32 + const wchar_t* configured = _wgetenv(L"ANYPS5_SCREENSHOTS"); +#else const char* configured = std::getenv("ANYPS5_SCREENSHOTS"); - if (configured != nullptr && configured[0] != '\0') return configured; +#endif + if (configured != nullptr && configured[0] != 0) return configured; const std::unique_ptr basePath(SDL_GetBasePath(), SDL_free); if (!basePath) throw std::runtime_error(std::string("Screenshot: cannot locate the executable: ") + SDL_GetError()); return std::filesystem::path(reinterpret_cast(basePath.get())) / "anyps5-screenshots"; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index c1e000a91c..9d93a5127f 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -316,6 +316,7 @@ struct VulkanDevice::State { bool fragmentShaderBarycentric = false; bool shaderTerminateInvocation = false; bool geometryShader = false; + bool largePoints = false; bool sampleRateShading = false; bool storageImageMultisample = false; bool storageImageWriteWithoutFormat = false; @@ -1718,6 +1719,8 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : enabled.sampleRateShading = available.sampleRateShading; enabled.geometryShader = available.geometryShader; state->geometryShader = enabled.geometryShader == VK_TRUE; + enabled.largePoints = available.largePoints; + state->largePoints = enabled.largePoints == VK_TRUE; state->sampleRateShading = enabled.sampleRateShading == VK_TRUE; if (enabled.sampleRateShading) state->capabilities.push_back(spv::CapabilityInterpolationFunction); if (enabled.geometryShader) state->capabilities.push_back(spv::CapabilityGeometry); @@ -3847,6 +3850,7 @@ Graphics::Context VulkanDevice::buildContext() const { context.imageTableUpdateAfterBind = ImageTableUpdateAfterBind(context); context.samplerTableUpdateAfterBind = SamplerTableUpdateAfterBind(context); context.geometryShader = state->geometryShader; + context.largePoints = state->largePoints; context.sampleRateShading = state->sampleRateShading; context.fillModeNonSolid = state->fillModeNonSolid; context.maintenance5 = state->maintenance5; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp index 2e9a481930..e1b6966086 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp @@ -239,6 +239,7 @@ struct Context { bool imageTableUpdateAfterBind = false; bool samplerTableUpdateAfterBind = false; bool geometryShader = false; + bool largePoints = false; bool sampleRateShading = false; bool fillModeNonSolid = false; bool maintenance5 = false; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp index c934bd4e04..2008d77be3 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp @@ -156,6 +156,10 @@ struct State { bool customSampleLocations = false; bool sampleLocationGrid = false; std::uint32_t paClVsOutCntl = 0; + bool vertexPoints = false; + float pointSize = 1.0f; + float pointSizeMin = 0.0f; + float pointSizeMax = 0.0f; std::array quadSampleMask{0xffffu, 0xffffu, 0xffffu, 0xffffu}; bool pixelEarlyZ = false; }; @@ -165,6 +169,7 @@ struct State { inline constexpr std::uint32_t PositionExportLayoutBits = 0xffffu | (1u << 21u) | (1u << 22u) | (1u << 23u); ShaderStages DecodeShaderStages(const QueueState& queue, bool vertexWorkgroup = false); +void ApplyPointSize(const State& state, ShaderRecompiler::ShaderVertexStageInfo& vertex); State DecodeState(const QueueState& queue, bool vertexWorkgroup = false); void ApplyDepthRemap(const Context& context, State& state); void NormalizeUnusedViewportDepth(State& state, const ShaderRecompiler::ShaderPixelStageInfo& pixel); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 7926925ff3..bdebdfa453 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -671,6 +671,7 @@ bool ValidationKey(const Context& context, std::span shade add(context.viewportIndexLayer); add(context.shaderTerminateInvocation); add(state.paClVsOutCntl); + add(state.vertexPoints); add(state.stages.mesh.has_value()); if (state.stages.mesh) { const auto& mesh = *state.stages.mesh; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp index 2d46a95acb..1249e08037 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp @@ -5,6 +5,8 @@ #include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp" #include "RdnaDecoder/RdnaDescriptorFormat.hpp" #include +#include +#include #include #include @@ -50,6 +52,45 @@ TextureDimension resolveDimension(std::uint32_t raw) { } } +bool ZeroTail(std::span words) { + return words[4] == 0u && words[5] == 0u && words[6] == 0u && words[7] == 0u; +} + +bool SameLevels(const TileMipLayout& a, const TileMipLayout& b) { + return a.tiledOffset == b.tiledOffset && a.tiledSize == b.tiledSize && a.width == b.width && a.height == b.height && a.blocksPerRow == b.blocksPerRow && a.pitchBytes == b.pitchBytes && a.tail == b.tail && a.tailX == b.tailX && a.tailY == b.tailY; +} + +std::optional> R128ResidentLevelWords(std::span words) { + const auto baseLevel = (words[3] >> 12u) & 0xfu; + const auto lastLevel = (words[3] >> 16u) & 0xfu; + const auto typeRaw = (words[3] >> 28u) & 0xfu; + if (baseLevel == 0u || baseLevel > lastLevel || (typeRaw != 8u && typeRaw != 9u)) return std::nullopt; + const std::array chainWords{words[0], words[1], words[2], words[3], 0u, lastLevel << 4u, 0u, 0u}; + const auto width = (((words[1] >> 30u) & 0x3u) | ((words[2] & 0xfffu) << 2u)) + 1u; + const auto height = ((words[2] >> 14u) & 0x3fffu) + 1u; + const auto minLod = (words[1] >> 8u) & 0xfffu; + const auto widthMinus1 = std::max(width >> baseLevel, 1u) - 1u; + const auto heightMinus1 = std::max(height >> baseLevel, 1u) - 1u; + const auto rebasedMinLod = minLod > baseLevel * 256u ? minLod - baseLevel * 256u : 0u; + const auto levels = lastLevel - baseLevel; + auto rebased = chainWords; + rebased[1] = (words[1] & ~((0xfffu << 8u) | (0x3u << 30u))) | (rebasedMinLod << 8u) | ((widthMinus1 & 0x3u) << 30u); + rebased[2] = (words[2] & ~(0xfffu | (0x3fffu << 14u))) | ((widthMinus1 >> 2u) & 0xfffu) | ((heightMinus1 & 0x3fffu) << 14u); + rebased[3] = (words[3] & ~(0xffu << 12u)) | (levels << 16u); + rebased[5] = levels << 4u; + try { + const auto chain = DescribeSurface(DecodeTextureResource(chainWords)); + const auto view = DescribeSurface(DecodeTextureResource(rebased)); + if (chain.mips.size() != lastLevel + 1u || view.mips.size() != levels + 1u || chain.layers != view.layers) return std::nullopt; + for (std::uint32_t level = baseLevel; level <= lastLevel; ++level) { + if (!SameLevels(chain.mips[level], view.mips[level - baseLevel])) return std::nullopt; + } + } catch (const std::exception&) { + return std::nullopt; + } + return rebased; +} + } bool IsNullTextureResource(std::span words) { @@ -238,7 +279,11 @@ GuestTextureResource DecodeTextureResource(std::span words) result.minLod = minLod; result.samples = samples; if (baseLevel > maxMip) { - Require(LevelsFitAllocation(result, lastLevel + 1u), "guest texture descriptor starts past the surface's last mip level at levels that would move the surface's own" + describe()); + const bool fits = LevelsFitAllocation(result, lastLevel + 1u); + if (!fits && ZeroTail(words)) { + if (const auto resident = R128ResidentLevelWords(words)) return DecodeTextureResource(*resident); + } + Require(fits, "guest texture descriptor starts past the surface's last mip level at levels that would move the surface's own" + describe()); result.mipCount = lastLevel + 1u; } return result; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp index 2eb908e307..ba31cb17a8 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp @@ -347,6 +347,7 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput Require(!state.negativeOneToOne || context.depthClipControl, "negative-one-to-one depth clipping requires VK_EXT_depth_clip_control with depthClipControl enabled"); Require(!state.perSampleShading || context.sampleRateShading, "per-sample pixel shading requires the sampleRateShading device feature"); Require(!state.mergedCoverage || (context.mergedCoverageSampleCounts & state.samples) != 0, "an overrasterized draw (a multisampled rasterizer into one-sample color targets) requires VK_NV_framebuffer_mixed_samples with merged coverage reduction"); + Require(!(state.vertexPoints || (state.stages.mesh.has_value() && state.stages.mesh->outputPrimitiveSize == 1u)) || context.largePoints || (state.pointSize == 1.0f && ((state.paClVsOutCntl & (1u << 16u)) == 0 || (state.pointSizeMin == 1.0f && state.pointSizeMax == 1.0f))), "points of a size other than one pixel require the largePoints device feature"); Require(!state.truncatedCoverage || (context.truncatedCoverageSampleCounts & state.samples) != 0, "one-sample color targets beside a multisampled depth target require VK_AMD_mixed_attachment_samples or VK_NV_framebuffer_mixed_samples with truncated coverage reduction"); Require(state.conservativeRasterization == VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT || context.conservativeRasterization, "conservative rasterization requires VK_EXT_conservative_rasterization with at most 1/256 pixel of overestimation and degenerate triangles rasterized"); ValidateProvokingVertex(context, state, shaders); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp index 82d330d68d..ceea544e4e 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp @@ -35,7 +35,7 @@ static_assert(sizeof(VkStencilOpState) == 28, "VkStencilOpState changed: update static_assert(sizeof(VkVertexInputBindingDescription) == 12, "VkVertexInputBindingDescription changed: update the recipe codec"); static_assert(sizeof(VkVertexInputAttributeDescription) == 16, "VkVertexInputAttributeDescription changed: update the recipe codec"); #if defined(__linux__) && defined(__x86_64__) && defined(__GLIBCXX__) -static_assert(sizeof(State) == 808, "State changed: update CanonicalPipelineState and the recipe codec"); +static_assert(sizeof(State) == 824, "State changed: update CanonicalPipelineState and the recipe codec"); static_assert(sizeof(ShaderStages) == 112, "ShaderStages changed: update the recipe codec"); static_assert(sizeof(ShaderRecompiler::MeshConfiguration) == 40, "MeshConfiguration changed: update the recipe codec"); static_assert(sizeof(ShaderRecompiler::TessellationConfiguration) == 40, "TessellationConfiguration changed: update the recipe codec"); @@ -44,7 +44,7 @@ static_assert(sizeof(ShaderRecompiler::TessellationConfiguration) == 40, "Tessel constexpr std::uint32_t FileMagic = 0x4c4a5041u; // Bumped when the recipe codec, CanonicalPipelineState or the store key change in a way the // layout guards above do not catch (a field renumbered or reinterpreted at the same size). -constexpr std::uint32_t FileFormat = 7u; +constexpr std::uint32_t FileFormat = 8u; constexpr std::uint32_t ModuleRecord = 1u; constexpr std::uint32_t GraphicsRecord = 2u; constexpr std::uint32_t ComputeRecord = 3u; @@ -220,6 +220,10 @@ void encodeState(Writer& writer, const State& state) { writer.Bool(state.truncatedCoverage); for (const auto mask : state.quadSampleMask) writer.Raw(mask); writer.U32(state.depthRemap.mode); + writer.Bool(state.vertexPoints); + writer.Raw(state.pointSize); + writer.Raw(state.pointSizeMin); + writer.Raw(state.pointSizeMax); } void decodeState(Reader& reader, State& state) { @@ -282,6 +286,10 @@ void decodeState(Reader& reader, State& state) { state.truncatedCoverage = reader.Bool(); for (auto& mask : state.quadSampleMask) mask = reader.Raw(); state.depthRemap.mode = reader.U32(); + state.vertexPoints = reader.Bool(); + state.pointSize = reader.Raw(); + state.pointSizeMin = reader.Raw(); + state.pointSizeMax = reader.Raw(); } void encodeRecipe(Writer& writer, const GraphicsPipelineRecipe& recipe) { @@ -1056,6 +1064,10 @@ State CanonicalPipelineState(const State& state) { canonical.truncatedCoverage = state.truncatedCoverage; canonical.quadSampleMask = state.quadSampleMask; canonical.depthRemap.mode = state.depthRemap.mode; + canonical.vertexPoints = state.vertexPoints; + canonical.pointSize = state.pointSize; + canonical.pointSizeMin = state.pointSizeMin; + canonical.pointSizeMax = state.pointSizeMax; return canonical; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp index eca8c4d9e9..abcf13c57c 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp @@ -53,6 +53,8 @@ struct Module { bool layerOutput = false; bool meshCapability = false; std::uint32_t primitiveInputs = 0; + bool pointSizeOutput = false; + bool pointSize = false; std::map> modes; const std::vector& Type(std::uint32_t id) const { @@ -138,6 +140,10 @@ struct Module { Require(input && (clipInput || (value == spv::BuiltInPosition && signature == "f32x4") || (value == spv::BuiltInTessCoord && stage == Stage::TessellationEvaluation && signature == "f32x3") || ((value == spv::BuiltInInvocationId || value == spv::BuiltInPrimitiveId || value == spv::BuiltInPatchVertices) && (signature == "i32" || signature == "u32"))), "unsupported tessellation built-in"); return; } + if (!input && value == spv::BuiltInPointSize && pointSizeOutput && signature == "f32" && !pointSize) { + pointSize = true; + return; + } if (!input && value == spv::BuiltInPrimitivePointIndicesEXT) primitiveIndices = 1u; if (!input && value == spv::BuiltInPrimitiveLineIndicesEXT) primitiveIndices = 2u; if (!input && value == spv::BuiltInPrimitiveTriangleIndicesEXT) primitiveIndices = 3u; @@ -149,6 +155,10 @@ struct Module { Require((value == spv::BuiltInVertexIndex || value == spv::BuiltInInstanceIndex || value == spv::BuiltInBaseInstance) && signature == "i32", "unsupported vertex built-in input"); } else if (vertex && storage == spv::StorageClassOutput) { if (value == spv::BuiltInLayer && layerOutput && (signature == "u32" || signature == "i32")) return; + if (value == spv::BuiltInPointSize && pointSizeOutput && signature == "f32" && !pointSize) { + pointSize = true; + return; + } Require(value == spv::BuiltInPosition && signature == "f32x4" && !position, "unsupported or duplicate vertex built-in output"); position = true; } else { @@ -186,6 +196,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi Require(words[4] == 0, "unsupported SPIR-V schema: " + std::to_string(words[4])); Module module; + module.pointSizeOutput = (state.vertexPoints && stage == Stage::Vertex) || (mesh && state.stages.mesh.has_value() && state.stages.mesh->outputPrimitiveSize == 1u); std::uint32_t entries = 0; std::uint32_t memoryModels = 0; std::uint32_t entryPoint = 0; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index f6faccd0e9..c696cab63a 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -190,6 +190,37 @@ std::uint32_t inactiveVertexPointSize(const QueueState& queue) { } } +bool pointOutput(const QueueState& queue) { + const auto stages = find(queue.context, 0x2d5); + const auto primitive = find(queue.userConfig, 0x242, RegisterBank::UserConfig); + if (stages == queue.context.end()) return false; + if ((stages->second & 0x24u) == 0) return primitive != queue.userConfig.end() && primitive->second == 1u; + const auto output = find(queue.context, 0x29b); + return (stages->second & 0x24u) == 0x20u && output != queue.context.end() && output->second == 0u; +} + +void decodePointSize(const QueueState& queue, State& result) { + const auto& cx = queue.context; + const auto size = read(cx, 0x280); + const auto limits = read(cx, 0x281); + const bool fromVertex = (result.paClVsOutCntl & (1u << 16u)) != 0; + Require(!fromVertex || (result.paClVsOutCntl & (1u << 21u)) != 0, "USE_VTX_POINT_SIZE without VS_OUT_MISC_VEC_ENA is unsupported"); + Require(fromVertex || (size & 0xffffu) == size >> 16u, "points whose PA_SU_POINT_SIZE width and height differ are unsupported"); + Require(fromVertex || (size & 0xffffu) != 0, "points of size 0 (PA_SU_POINT_SIZE 0) are unsupported"); + Require((limits & 0xffffu) <= limits >> 16u, "PA_SU_POINT_MINMAX with a minimum above its maximum is unsupported"); + Require(!fromVertex || (limits >> 16u) != 0, "a vertex point size clamped to 0 (PA_SU_POINT_MINMAX maximum 0) is unsupported"); + result.pointSize = static_cast(size & 0xffffu) / 8.0f; + result.pointSizeMin = static_cast(limits & 0xffffu) / 8.0f; + result.pointSizeMax = static_cast(limits >> 16u) / 8.0f; + const auto interpolation = find(cx, 0x1b5); + const auto inputs = find(cx, 0x1b6); + if (interpolation == cx.end() || (interpolation->second & 2u) == 0 || inputs == cx.end()) return; + for (std::uint32_t input = 0; input < (inputs->second & 0x3fu); ++input) { + const auto control = find(cx, 0x191 + input); + Require(control == cx.end() || (control->second & 0x820000u) == 0, "point sprite texture coordinates (SPI_PS_INPUT_CNTL PT_SPRITE_TEX) are not implemented"); + } +} + constexpr std::uint32_t DepthControlMask = ~0x007007f0u; // EXEC_ON_HIER_FAIL / EXEC_ON_NOOP / EXEC_IF_OVERLAPPED (bits 9, 10, 17) only force the pixel shader // to run, which it always does here. @@ -1082,6 +1113,15 @@ std::array PixelSampleLocations(const State& state, const Shad return locations; } +void ApplyPointSize(const State& state, ShaderRecompiler::ShaderVertexStageInfo& vertex) { + if (!state.vertexPoints && !(state.stages.mesh.has_value() && state.stages.mesh->outputPrimitiveSize == 1u)) return; + vertex.paClVsOutCntl |= state.paClVsOutCntl & (1u << 16u); + vertex.pointList = true; + vertex.pointSize = state.pointSize; + vertex.pointSizeMin = state.pointSizeMin; + vertex.pointSizeMax = state.pointSizeMax; +} + ShaderStages DecodeShaderStages(const QueueState& queue, bool vertexWorkgroup) { const auto value = read(queue.context, 0x2d5); const auto validate = [&](bool condition, const char* reason) { @@ -1256,7 +1296,11 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) { const auto primitive = read(queue.userConfig, 0x242, RegisterBank::UserConfig); APS5_LOG_OUT_DEBUG("DecodeState primitive=%u path=%u vertexWave=%u", primitive, static_cast(result.stages.path), result.stages.vertexWaveSize); switch (primitive) { - case 1: Require(result.stages.mesh.has_value(), "point-list vertex rendering requires point-size output support"); result.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; break; + case 1: + Require(result.stages.mesh.has_value() || result.stages.path == ShaderPath::Vertex, "point lists are only drawn through the vertex and geometry paths"); + result.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; + result.vertexPoints = result.stages.path == ShaderPath::Vertex; + break; case 2: result.topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST; break; case 3: result.topology = VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; break; case 7: @@ -1281,9 +1325,10 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) { } APS5_LOG_OUT_DEBUG("Topology=%u", static_cast(result.topology)); result.primitiveRestart = read(queue.userConfig, 0x24b, RegisterBank::UserConfig) != 0 && !result.rectList && !result.quadList && result.topology != VK_PRIMITIVE_TOPOLOGY_PATCH_LIST; - zero(cx, 0x207, ~(LayerExports | ClipCullExports | inactiveVertexPointSize(queue)), "layer, viewport or auxiliary vertex exports"); + zero(cx, 0x207, ~(LayerExports | ClipCullExports | ((result.vertexPoints || (result.stages.mesh.has_value() && result.stages.mesh->outputPrimitiveSize == 1u)) ? 1u << 16u : inactiveVertexPointSize(queue))), "layer, viewport or auxiliary vertex exports"); Require(std::popcount(read(cx, 0x207) & 0xffffu) <= 8, "more than eight clip and cull distances are unsupported"); result.paClVsOutCntl = read(cx, 0x207); + if (result.vertexPoints || (result.stages.mesh.has_value() && result.stages.mesh->outputPrimitiveSize == 1u)) decodePointSize(queue, result); result.samples = decodeSamples(cx); Require(coveredCentroidSupported(queue), "COVERED_CENTROID_IS_CENTER with active or unspecified centroid inputs is not implemented"); decodeSampleLocations(cx, result); @@ -2053,7 +2098,7 @@ std::string DrawRejection(const QueueState& queue, bool indexed) { if (value(queue.userConfig, 0x242, primitive) && (primitive & 0x3fu) != 1 && (primitive & 0x3fu) != 2 && (primitive & 0x3fu) != 3 && (primitive & 0x3fu) != 4 && (primitive & 0x3fu) != 5 && (primitive & 0x3fu) != 6 && (primitive & 0x3fu) != 21) return "AGC graphics: primitive restart is only supported for point, line and triangle topologies"; if (value(cx, 0x103, resetIndex) && (resetIndex & 0xffffu) != 0xffffu) return "AGC graphics: primitive restart index other than all ones is unsupported"; } - if (auto reason = nonzero(cx, 0x207, ~(LayerExports | ClipCullExports | inactiveVertexPointSize(queue)), "layer, viewport or auxiliary vertex exports"); !reason.empty()) return reason; + if (auto reason = nonzero(cx, 0x207, ~(LayerExports | ClipCullExports | (pointOutput(queue) ? 1u << 16u : inactiveVertexPointSize(queue))), "layer, viewport or auxiliary vertex exports"); !reason.empty()) return reason; if (value(cx, 0x207, word) && std::popcount(word & 0xffffu) > 8) return require(false, "more than eight clip and cull distances are unsupported"); if (value(cx, 0x200, word)) { const auto renderControl = find(cx, 0x000); diff --git a/core/libs/prx/libSceAgcDriver/State/src/Status.cpp b/core/libs/prx/libSceAgcDriver/State/src/Status.cpp index ae1a8543f7..825a9e9001 100644 --- a/core/libs/prx/libSceAgcDriver/State/src/Status.cpp +++ b/core/libs/prx/libSceAgcDriver/State/src/Status.cpp @@ -5,6 +5,12 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr int CAPTURE_UNAVAILABLE = static_cast(0x8A6C1000); + +} + extern "C" { bool APS5_VABI sceAgcDriverIsCaptureInProgress(void) { @@ -21,18 +27,15 @@ bool APS5_VABI sceAgcDriverIsSubmitValidationEnabled(void) { int APS5_VABI sceAgcDriverRequestCaptureStart(const char* path) { (void)path; - NotImplemented_nid_no_patch(__func__); - return 0; + return CAPTURE_UNAVAILABLE; } int APS5_VABI sceAgcDriverRequestCaptureStop() { - NotImplemented_nid_no_patch(__func__); - return 0; + return CAPTURE_UNAVAILABLE; } int APS5_VABI sceAgcDriverTriggerCapture() { - NotImplemented_nid_no_patch(__func__); - return 0; + return CAPTURE_UNAVAILABLE; } } diff --git a/core/libs/prx/libSceAgcDriver/Unimplemented.cpp b/core/libs/prx/libSceAgcDriver/Unimplemented.cpp index be639faf70..dd330964bc 100644 --- a/core/libs/prx/libSceAgcDriver/Unimplemented.cpp +++ b/core/libs/prx/libSceAgcDriver/Unimplemented.cpp @@ -57,6 +57,17 @@ int APS5_VABI sceAgcDriverGetHsOffchipParam(uint16_t* first, uint16_t* second) { return 0; } +int APS5_VABI sceAgcDriverNopDataWritePacket(void* destination, uint32_t address, const void* data, uint32_t bytes) { + const auto alignedBytes = (static_cast(bytes) + 3) & ~std::uint64_t{3}; + const auto totalBytes = alignedBytes + 8; + std::memset(destination, 0, static_cast(totalBytes)); + const auto header = (static_cast(totalBytes) << 14) + 0x3ffe0000U | 0xc0001000U; + std::memcpy(destination, &header, sizeof(header)); + std::memcpy(static_cast(destination) + 4, &address, sizeof(address)); + if (data != nullptr && bytes != 0) std::memcpy(static_cast(destination) + 8, data, bytes); + return data == nullptr || bytes == 0 ? 2 : static_cast(totalBytes / 4); +} + int APS5_VABI sceAgcDriverSetValidationDisabled() { return DEBUG_UNAVAILABLE; @@ -154,9 +165,8 @@ int APS5_VABI sceAgcDriverAcquireHDRScopesACQ() { return 0; } -int APS5_VABI sceAgcDriverGetCoredumpAddress() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI sceAgcDriverGetCoredumpAddress() { + return nullptr; } int APS5_VABI sceAgcDriverHp3dLockEnd() { @@ -174,19 +184,13 @@ int APS5_VABI sceAgcDriverIsEsvmProcess() { return 0; } -int APS5_VABI sceAgcDriverNopDataWritePacket() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceAgcDriverReleaseHDRScopesACQ() { NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceAgcDriverSetTargetRingForDiag() { - NotImplemented_nid_no_patch(__func__); - return 0; + return DRIVER_FEATURE_UNAVAILABLE; } int APS5_VABI sceAgcDriverSetValueToSharedGds() { diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 4dae15015e..4b05f827f4 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -163,9 +163,15 @@ void inactiveVertexPointSizeTests() { } throw std::runtime_error("potentially active point size passed the state decoder"); }; - for (const auto primitive : {1u, 9u}) { + { auto queue = pointSizeState(); - queue.userConfig[0x242] = primitive; + queue.userConfig[0x242] = 9; + rejected(queue); + } + { + auto queue = pointSizeState(); + queue.userConfig[0x242] = 1; + queue.context[0x2d5] = 0x2020; rejected(queue); } for (const auto stages : {0x2004u, 0x2020u}) { @@ -222,6 +228,73 @@ void PolygonPrimitiveTests() { Require(indexedState.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN && indexedState.primitiveRestart, "indexed polygon decoding did not keep primitive restart"); } +void vertexPointListTests() { + const auto pointState = [](std::uint32_t control, std::uint32_t size, std::uint32_t limits) { + auto queue = makeState(); + queue.userConfig[0x242] = 1; + queue.context[0x1b3] = queue.context[0x1b4] = 2; + queue.context[0x207] = control; + queue.context[0x280] = size; + queue.context[0x281] = limits; + return queue; + }; + { + const auto queue = pointState(0x01210000u, 0x00080008u, 0x00080008u); + const auto rejection = AgcDriver::Graphics::DrawRejection(queue, false); + Require(rejection.empty(), "a vertex-path point list with a vertex point size failed the draw precheck: " + rejection); + const auto state = AgcDriver::Graphics::DecodeState(queue); + Require(state.topology == VK_PRIMITIVE_TOPOLOGY_POINT_LIST && state.paClVsOutCntl == 0x01210000u, "a vertex-path point list with a vertex point size did not decode as a point list"); + Require(AgcDriver::Graphics::RasterPrimitiveOf(state) == ShaderRecompiler::RasterPrimitive::Point, "a vertex-path point list is not rasterized as points"); + Require(state.vertexPoints && state.pointSize == 1.0f && state.pointSizeMin == 1.0f && state.pointSizeMax == 1.0f, "PA_SU_POINT_SIZE and PA_SU_POINT_MINMAX of 8 did not decode as one-pixel points"); + ShaderRecompiler::ShaderVertexStageInfo vertex{}; + vertex.paClVsOutCntl = 1u << 21u; + AgcDriver::Graphics::ApplyPointSize(state, vertex); + Require(vertex.pointList && vertex.paClVsOutCntl == ((1u << 16u) | (1u << 21u)) && vertex.pointSize == 1.0f && vertex.pointSizeMin == 1.0f && vertex.pointSizeMax == 1.0f, "the vertex stage did not receive the vertex point size"); + } + { + const auto state = AgcDriver::Graphics::DecodeState(pointState(0, 0x00200020u, 0x00100004u)); + Require(state.vertexPoints && state.pointSize == 4.0f && state.pointSizeMin == 0.5f && state.pointSizeMax == 2.0f, "a register point size did not decode as diameters"); + ShaderRecompiler::ShaderVertexStageInfo vertex{}; + vertex.paClVsOutCntl = 1u << 21u; + AgcDriver::Graphics::ApplyPointSize(state, vertex); + Require(vertex.pointList && vertex.paClVsOutCntl == (1u << 21u) && vertex.pointSize == 4.0f, "a register point size turned on the vertex point size"); + } + { + const auto state = AgcDriver::Graphics::DecodeState(pointState(0x01210000u, 0x00200010u, 0x00400000u)); + Require(state.pointSizeMin == 0.0f && state.pointSizeMax == 8.0f, "a vertex point size refused a register size whose width and height differ"); + } + { + auto queue = makeState(); + queue.context[0x1b3] = queue.context[0x1b4] = 2; + queue.context[0x207] = 0x01210000u; + const auto state = AgcDriver::Graphics::DecodeState(queue); + Require(!state.vertexPoints, "a triangle draw decoded as vertex points"); + ShaderRecompiler::ShaderVertexStageInfo vertex{}; + AgcDriver::Graphics::ApplyPointSize(state, vertex); + Require(!vertex.pointList && vertex.paClVsOutCntl == 0, "a triangle draw received a point size"); + } + expectFailure([&] { AgcDriver::Graphics::DecodeState(pointState(0, 0x00200010u, 0x00080008u)); }, "points whose PA_SU_POINT_SIZE width and height differ are unsupported"); + expectFailure([&] { AgcDriver::Graphics::DecodeState(pointState(0, 0x00080008u, 0x00080010u)); }, "PA_SU_POINT_MINMAX with a minimum above its maximum is unsupported"); + expectFailure([&] { AgcDriver::Graphics::DecodeState(pointState(1u << 16u, 0x00080008u, 0x00080008u)); }, "USE_VTX_POINT_SIZE without VS_OUT_MISC_VEC_ENA is unsupported"); + expectFailure([&] { AgcDriver::Graphics::DecodeState(pointState(0, 0, 0x00080008u)); }, "points of size 0 (PA_SU_POINT_SIZE 0) are unsupported"); + expectFailure([&] { AgcDriver::Graphics::DecodeState(pointState(0x01210000u, 0x00080008u, 0)); }, "a vertex point size clamped to 0 (PA_SU_POINT_MINMAX maximum 0) is unsupported"); + { + const auto state = AgcDriver::Graphics::DecodeState(pointState(0x01210000u, 0, 0x00200000u)); + Require(state.pointSize == 0.0f && state.pointSizeMin == 0.0f && state.pointSizeMax == 4.0f, "a vertex point size refused a zero register size or a zero clamp minimum"); + static_cast(AgcDriver::Graphics::DecodeState(pointState(0, 0x00080008u, 0))); + } + for (const auto sprite : {1u << 17u, 1u << 23u}) { + auto queue = pointState(0x01210000u, 0x00080008u, 0x00080008u); + queue.context[0x1b5] = 0x486bu; + queue.context[0x1b6] = 2; + queue.context[0x191] = 0; + queue.context[0x192] = sprite; + expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "point sprite texture coordinates (SPI_PS_INPUT_CNTL PT_SPRITE_TEX) are not implemented"); + queue.context[0x1b5] = 0x4869u; + Require(AgcDriver::Graphics::DecodeState(queue).vertexPoints, "PT_SPRITE_TEX was refused with point sprites off"); + } +} + void retainedPixelStageTests() { using namespace AgcDriver::Graphics; auto queue = makeState(); @@ -1080,11 +1153,30 @@ void ShaderStageTests() { lines.context[0x29b] = 1; Require(AgcDriver::Graphics::DecodeState(lines).stages.mesh->provokingVertex == 1, "a last-vertex geometry draw does not take its flat inputs and layer from the second vertex of each output line"); lines.context[0x29b] = 0; + expectFailure([&] { AgcDriver::Graphics::DecodeState(lines); }, "missing register in context bank at DWORD 0x280"); + lines.context[0x280] = 0x00080008u; + expectFailure([&] { AgcDriver::Graphics::DecodeState(lines); }, "missing register in context bank at DWORD 0x281"); + lines.context[0x281] = 0xffff0000u; state = AgcDriver::Graphics::DecodeState(lines); mesh = *state.stages.mesh; Require(mesh.outputPrimitiveSize == 1 && mesh.maxPrimitives == 255 && AgcDriver::Graphics::RasterPrimitiveOf(state) == ShaderRecompiler::RasterPrimitive::Point, "a point-list subgroup did not decode as points"); Require(mesh.provokingVertex == 0, "a point draw has provoking vertex 0"); + Require(state.pointSize == 1.0f && state.pointSizeMin == 0.0f && state.pointSizeMax == 8191.875f, "geometry points did not decode the queue's default point size and limits"); lines.context[0x205] = raster; + auto points = lines; + points.userConfig[0x242] = 6; + points.userConfig[0x25b] = 0x5c2e; + points.context[0x1ff] = 0x8a; + points.context[0x2ce] = 3; + points.context[0x29b] = 0; + points.context[0x280] = 0x00180018u; + points.context[0x281] = 0x00400008u; + const auto pointState = AgcDriver::Graphics::DecodeState(points); + const auto& pointMesh = *pointState.stages.mesh; + ShaderRecompiler::ShaderVertexStageInfo pointInfo{}; + AgcDriver::Graphics::ApplyPointSize(pointState, pointInfo); + Require(pointInfo.pointList && pointInfo.pointSize == 3.0f && pointInfo.pointSizeMin == 1.0f && pointInfo.pointSizeMax == 8.0f, "geometry points lost their register sizes"); + Require(pointMesh.outputPrimitiveSize == 1u && pointMesh.primitivesPerGroup == 44u && pointMesh.verticesPerGroup == 46u && pointMesh.maxVertices == 138u && pointMesh.maxPrimitives == 138u && pointMesh.threadsPerGroup == 192u && AgcDriver::Graphics::RasterPrimitiveOf(pointState) == ShaderRecompiler::RasterPrimitive::Point, "triangle-strip input with point output did not decode the reported subgroup"); lines.context[0x29b] = 1; lines.context[0x2ce] = 1; expectFailure([&] { AgcDriver::Graphics::DecodeState(lines); }, "unsupported geometry input or output assembly"); @@ -3386,6 +3478,8 @@ void msaaEnableClearDepthTests() { queue.context[0x2ce] = 5; queue.context[0x29b] = 0; queue.context[0x2ab] = 4; + queue.context[0x280] = 0x00080008u; // point-output geometry programs decode PA_SU_POINT_SIZE / PA_SU_POINT_MINMAX before this rejection + queue.context[0x281] = 0x00080008u; queue.shader[0x8a] = 3u << 29u; queue.shader[0x8b] = 3u << 16u; expectFailure([&] { DecodeState(queue); }, "multisampled targets rasterized without MSAA_ENABLE are unsupported for points or alpha-to-coverage"); @@ -9387,6 +9481,8 @@ void ConservativeRasterizationTests() { geometry.context[0x2ce] = 3; geometry.context[0x29b] = 2; geometry.context[0x2ab] = 4; + geometry.context[0x280] = 0x00080008u; // PA_SU_POINT_SIZE and PA_SU_POINT_MINMAX: a point-output geometry program decodes them + geometry.context[0x281] = 0x00080008u; geometry.shader[0x8a] = 3u << 29u; geometry.shader[0x8b] = 3u << 16u; for (const auto input : {1u, 2u, 4u}) { @@ -10849,6 +10945,22 @@ void pipelineJournalTests(bool longPath = false, bool extended = false) { GraphicsPipelineRecipe lineDecoded; Require(DecodeGraphicsPipelineRecipe(EncodeGraphicsPipelineRecipe(MakeGraphicsPipelineRecipe(lineState, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL)), lineDecoded), "the line strip mesh recipe does not decode"); Require(lineDecoded.state.stages.mesh && lineDecoded.state.stages.mesh->outputPrimitiveSize == 2u && lineDecoded.state.stages.mesh->esgsItemSize == 4u, "the recipe dropped the mesh output primitive size"); + auto pointState = state; + pointState.vertexPoints = true; + pointState.pointSize = 4.0f; + pointState.pointSizeMin = 0.5f; + pointState.pointSizeMax = 8.0f; + GraphicsPipelineRecipe pointDecoded; + Require(DecodeGraphicsPipelineRecipe(EncodeGraphicsPipelineRecipe(MakeGraphicsPipelineRecipe(pointState, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL)), pointDecoded), "the point list recipe does not decode"); + Require(pointDecoded.state.vertexPoints && pointDecoded.state.pointSize == 4.0f && pointDecoded.state.pointSizeMin == 0.5f && pointDecoded.state.pointSizeMax == 8.0f, "the recipe dropped the vertex point list or its point sizes"); + auto meshPointState = pointState; + meshPointState.vertexPoints = false; + meshPointState.stages.mesh = *lineState.stages.mesh; + meshPointState.stages.mesh->outputPrimitiveSize = 1u; + meshPointState.stages.mesh->provokingVertex = 0u; + GraphicsPipelineRecipe meshPointDecoded; + Require(DecodeGraphicsPipelineRecipe(EncodeGraphicsPipelineRecipe(MakeGraphicsPipelineRecipe(meshPointState, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL)), meshPointDecoded), "the mesh point recipe does not decode"); + Require(!meshPointDecoded.state.vertexPoints && meshPointDecoded.state.stages.mesh && meshPointDecoded.state.stages.mesh->outputPrimitiveSize == 1u && meshPointDecoded.state.pointSize == 4.0f && meshPointDecoded.state.pointSizeMin == 0.5f && meshPointDecoded.state.pointSizeMax == 8.0f, "the recipe dropped mesh points or their sizes without the vertex point flag"); auto fourSampleState = state; fourSampleState.samples = 4; auto maskState = fourSampleState; @@ -11330,6 +11442,7 @@ int main(int argc, char** argv) { NggVertexWorkgroupTests(); inactiveVertexPointSizeTests(); PolygonPrimitiveTests(); + vertexPointListTests(); TuningFieldTests(); PixelInputLayoutTests(); UnexportedPixelInputTests(); diff --git a/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp b/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp index 050b2bdc17..0cc0c41ed7 100644 --- a/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp @@ -95,6 +95,52 @@ void rejectFields(const Fields& f, std::string_view reason) { } +static void RunR128TextureTests() { + constexpr std::array streamed{0xa0c18800u, 0xcad00000u, 0x01ffc1ffu, 0x905b2facu, 0u, 0u, 0u, 0u}; + const auto view = DecodeTextureResource(streamed); + Require(view.baseAddress == 0xa0c1880000ull && view.format == 173u && view.tileMode == TextureTileMode::kStandard4KB && view.width == 512u && view.height == 512u && view.baseLevel == 0u && view.lastLevel == 9u && view.mipCount == 10u && view.allocatedMipCount == 10u && view.minLod == 0u, + "a 128-bit BC3 T# (2048x2048, levels 2..11, words 4-7 zero) must read as the 512x512 chain of its 10 resident levels, got " + std::to_string(view.width) + "x" + std::to_string(view.height) + " levels " + std::to_string(view.baseLevel) + ".." + std::to_string(view.lastLevel) + " of " + std::to_string(view.mipCount)); + auto chainWords = streamed; + chainWords[3] &= ~(0xfu << 12u); + chainWords[5] = 11u << 4u; + const auto full = DecodeTextureResource(chainWords); + Require(full.width == 2048u && full.mipCount == 12u, "the same surface with MAX_MIP 11 must be the 12-level 2048x2048 chain"); + const auto chain = DescribeSurface(full); + const auto resident = DescribeSurface(view); + Require(chain.mips.size() == 12u && resident.mips.size() == 10u, "the chain and the resident levels must have 12 and 10 levels"); + for (std::uint32_t level = 0; level < 10u; ++level) { + const auto& a = chain.mips[level + 2u]; + const auto& b = resident.mips[level]; + Require(a.tiledOffset == b.tiledOffset && a.tiledSize == b.tiledSize && a.width == b.width && a.height == b.height && a.blocksPerRow == b.blocksPerRow && a.tail == b.tail && a.tailX == b.tailX && a.tailY == b.tailY, "resident level " + std::to_string(level) + " must be level " + std::to_string(level + 2u) + " of the 12-level chain"); + } + Require(resident.guestBytes <= chain.mips[1].tiledOffset && chain.mips[0].tiledOffset > chain.mips[1].tiledOffset, "the resident levels must end before the chain's levels 1 and 0, which come last in a GFX10 swizzled chain"); + auto levelZero = streamed; + levelZero[3] &= ~(0xfu << 12u); + const auto single = DecodeTextureResource(levelZero); + Require(single.mipCount == 1u && single.lastLevel == 0u, "a T# with words 4-7 zero that starts at level 0 must stay one level"); + auto tailed = streamed; + tailed[5] = 4u << 20u; + reject([&] { DecodeTextureResource(tailed); }, "would move the surface's own"); + + constexpr std::array tall{0xa0c1de00u, 0xcad00000u, 0x01ffc0ffu, 0x905b4facu, 0u, 0u, 0u, 0u}; + const auto tallView = DecodeTextureResource(tall); + Require(tallView.width == 64u && tallView.height == 128u && tallView.lastLevel == 7u && tallView.mipCount == 8u, "a 128-bit 1024x2048 T# of levels 4..11 must read as its 64x128 chain of 8 levels"); + + Fields clamped; + clamped.base40 = 0x55ea000ull; + clamped.format = 20; + clamped.width = 1024; + clamped.height = 1024; + clamped.tileModeRaw = 0x1b; + clamped.baseLevel = 2; + clamped.lastLevel = 10; + clamped.minLod = 2u * 256u + 64u; + const auto clampedView = DecodeTextureResource(pack(clamped)); + Require(clampedView.width == 256u && clampedView.mipCount == 9u && clampedView.minLod == 64u, "reading a 128-bit T# from its resident levels must move its MIN_LOD clamp with the base level"); + clamped.minLod = 256u; + Require(DecodeTextureResource(pack(clamped)).minLod == 0u, "a MIN_LOD clamp below the base level must vanish when read from the resident levels"); +} + void RunGuestTextureResourceTests() { Fields base; const auto baseWords = pack(base); @@ -629,6 +675,7 @@ void RunGuestTextureResourceTests() { pastUntailed.maxMip = 0; pastUntailed.baseLevel = 1; pastUntailed.lastLevel = 1; + pastUntailed.perfMod = 4; rejectFields(pastUntailed, "would move the surface's own"); auto untailedChain = tailView; untailedChain.mipCount = 1; @@ -636,6 +683,8 @@ void RunGuestTextureResourceTests() { untailedChain.mipCount = 2; Require(untailedAllocated == 0xff0000u && DescribeSurface(untailedChain).guestBytes == 0x1470000u, "a second level must grow a 1920x1080 64 bpp SW_64KB_R_X surface from addrlib's 0xff0000 to 0x1470000 bytes"); + RunR128TextureTests(); + std::array shortWords{}; reject([&] { DecodeTextureResource(shortWords); }, "8 dwords"); diff --git a/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp b/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp index a3c2a13217..ce6f2d65f3 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp @@ -5,6 +5,7 @@ #include "prx/libc/include/GuestAllocations.hpp" #include #include +#include #include #include #include @@ -144,6 +145,7 @@ struct MeshDraw { VkCullModeFlags cull = VK_CULL_MODE_NONE; std::span code = {}; std::uint32_t stagesRegister = 0x20u; + ShaderRecompiler::ShaderVertexStageInfo vertex{}; }; bool ReadsNumWorkgroups(std::span words) { @@ -176,7 +178,7 @@ void DrawMesh(AgcDriver::VulkanDevice& device, const MeshDraw& setup) { const std::array geometryMemory{{{reinterpret_cast(code.data()), std::as_bytes(std::span(code))}}}; ShaderRecompiler::RecompileRequest geometry{ {ShaderStage::Mesh, reinterpret_cast(code.data()), code, 0, {}}, - {setup.waveSize, 0, userData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, geometryMemory}, + {setup.waveSize, 0, userData, std::nullopt, std::nullopt, setup.vertex, geometryMemory}, target, {0, 0, 0, setup.geometryPushBytes}, ShaderRecompiler::GraphicsCompileContext{0, {}, setup.mesh, std::nullopt, {setup.draw.indexAddress, setup.draw.indexCount, setup.draw.indexSize, setup.draw.instanceCount}} @@ -207,6 +209,10 @@ void DrawMesh(AgcDriver::VulkanDevice& device, const MeshDraw& setup) { AgcDriver::Graphics::State state{}; state.stages = {AgcDriver::Graphics::ShaderPath::Geometry, setup.stagesRegister, setup.waveSize, 64, setup.mesh, std::nullopt}; + state.pointSize = setup.vertex.pointSize; + state.pointSizeMin = setup.vertex.pointSizeMin; + state.pointSizeMax = setup.vertex.pointSizeMax; + state.paClVsOutCntl = setup.vertex.paClVsOutCntl; state.color = {reinterpret_cast(Pixels.data()), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, Pixels.size(), 0xe4u}; state.colors = {state.color}; state.hasColorTarget = true; @@ -394,6 +400,63 @@ int main() { } } + ClearPixels(); + auto pointSubgroup = SmallSubgroup; + pointSubgroup.outputPrimitiveSize = 1u; + for (const auto pointWave : {32u, 64u}) { + ClearPixels(); + DrawMesh(device, {pointSubgroup, {0, static_cast(Ordered.size()), 0, 1, 0, false}, VertexBufferDescriptor(Ordered.data(), static_cast(Ordered.size())), ShaderRecompiler::MeshDrawPushOffsetBytes, false, pointWave}); + for (std::uint32_t triangle = 0; triangle < Triangles; ++triangle) { + const auto& position = Ordered[3u * triangle].position; + const auto x = static_cast((position[0] + 1.0f) * 0.5f * Width); + const auto y = static_cast((1.0f - position[1]) * 0.5f * Height); + const auto offset = (static_cast(y) * Width + x) * 4u; + Require(PixelIs(offset, TriangleColor(triangle)), "mesh point missing at first exported index for primitive " + std::to_string(triangle)); + Require(PixelIs(CellPixel(triangle), Background), "mesh point output filled the interior of its packed triangle"); + } + } + + for (const auto pointWave : {32u, 64u}) { + for (const auto size : {3.0f, 4.0f}) { + ClearPixels(); + MeshDraw points{pointSubgroup, {0, static_cast(Ordered.size()), 0, 1, 0, false}, VertexBufferDescriptor(Ordered.data(), static_cast(Ordered.size()))}; + points.waveSize = pointWave; + points.vertex.pointSize = size; + DrawMesh(device, points); + for (std::uint32_t primitive = 0; primitive < Triangles; ++primitive) { + std::uint32_t covered = 0; + for (std::size_t pixel = 0; pixel < Pixels.size(); pixel += 4u) covered += PixelIs(pixel, TriangleColor(primitive)); + Require(covered == static_cast(size * size), "mesh point did not cover its register-sized square"); + } + } + for (const auto exportedSize : {6.0f, -3.0f}) { + ClearPixels(); + const auto& baseCode = pointWave == 32u ? Wave32GeometryCode : GeometryCode; + std::array sizedCode{}; + std::copy_n(baseCode.begin(), 101u, sizedCode.begin()); + sizedCode[92] = 0xbf88000eu; + sizedCode[99] = 0xf80000cfu; + sizedCode[101] = 0x7e6802ffu; + sizedCode[102] = std::bit_cast(exportedSize); + sizedCode[103] = 0xf80008d1u; + sizedCode[104] = 0x00000034u; + std::copy(baseCode.begin() + 101u, baseCode.end(), sizedCode.begin() + 105u); + MeshDraw exported{pointSubgroup, {0, static_cast(Ordered.size()), 0, 1, 0, false}, VertexBufferDescriptor(Ordered.data(), static_cast(Ordered.size()))}; + exported.waveSize = pointWave; + exported.code = sizedCode; + exported.vertex.paClVsOutCntl = (1u << 16u) | (1u << 21u); + exported.vertex.pointSizeMin = 3.0f; + exported.vertex.pointSizeMax = 4.0f; + DrawMesh(device, exported); + for (std::uint32_t primitive = 0; primitive < Triangles; ++primitive) { + std::uint32_t covered = 0; + for (std::size_t pixel = 0; pixel < Pixels.size(); pixel += 4u) covered += PixelIs(pixel, TriangleColor(primitive)); + const auto side = exportedSize < 3.0f ? 3u : 4u; + Require(covered == side * side, "mesh point ignored its exported size or clamp"); + } + } + } + ClearPixels(); auto lineSubgroup = FastLaunchSubgroup; lineSubgroup.maxPrimitives = 2u; diff --git a/core/libs/prx/libSceAgcDriver/tests/NopDataWritePacket.cpp b/core/libs/prx/libSceAgcDriver/tests/NopDataWritePacket.cpp new file mode 100644 index 0000000000..587bc082bc --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/NopDataWritePacket.cpp @@ -0,0 +1,38 @@ +#include "SceTypes.hpp" +#include +#include +#include +#include +#include + +extern "C" int APS5_VABI sceAgcDriverNopDataWritePacket(void* destination, uint32_t address, const void* data, uint32_t bytes); + +int main() { + std::array storage; + storage.fill(std::byte{0xaa}); + const std::array data{std::byte{0x11}, std::byte{0x22}, std::byte{0x33}, std::byte{0x44}, std::byte{0x55}}; + const auto words = sceAgcDriverNopDataWritePacket(storage.data() + 4, 0x12345678, data.data(), static_cast(data.size())); + std::uint32_t header = 0; + std::uint32_t address = 0; + std::memcpy(&header, storage.data() + 4, sizeof(header)); + std::memcpy(&address, storage.data() + 8, sizeof(address)); + if (words != 4 || header != 0xc0021000U || address != 0x12345678U) return 1; + if (!std::equal(data.begin(), data.end(), storage.begin() + 12)) return 2; + if (storage[17] != std::byte{0} || storage[18] != std::byte{0} || storage[19] != std::byte{0}) return 3; + if (storage[3] != std::byte{0xaa} || storage[20] != std::byte{0xaa}) return 4; + + storage.fill(std::byte{0xaa}); + const auto emptyWords = sceAgcDriverNopDataWritePacket(storage.data() + 4, 0x87654321, nullptr, 5); + std::memcpy(&header, storage.data() + 4, sizeof(header)); + std::memcpy(&address, storage.data() + 8, sizeof(address)); + if (emptyWords != 2 || header != 0xc0021000U || address != 0x87654321U) return 5; + if (storage[12] != std::byte{0} || storage[19] != std::byte{0} || storage[20] != std::byte{0xaa}) return 6; + + storage.fill(std::byte{0xaa}); + const auto zeroBytesWords = sceAgcDriverNopDataWritePacket(storage.data() + 4, 0x01020304, data.data(), 0); + std::memcpy(&header, storage.data() + 4, sizeof(header)); + std::memcpy(&address, storage.data() + 8, sizeof(address)); + if (zeroBytesWords != 2 || header != 0xc0001000U || address != 0x01020304U) return 7; + if (storage[12] != std::byte{0xaa}) return 8; + return 0; +} diff --git a/core/libs/prx/libSceAgcDriver/tests/Screenshot.cpp b/core/libs/prx/libSceAgcDriver/tests/Screenshot.cpp index e6202ceb1a..4adf0c824d 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Screenshot.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Screenshot.cpp @@ -21,9 +21,9 @@ void require(bool condition, const char* message) { void setScreenshotDirectory(const std::filesystem::path& directory) { #ifdef _WIN32 - require(_putenv_s("ANYPS5_SCREENSHOTS", directory.string().c_str()) == 0, "set ANYPS5_SCREENSHOTS"); + require(_wputenv_s(L"ANYPS5_SCREENSHOTS", directory.wstring().c_str()) == 0, "set ANYPS5_SCREENSHOTS"); #else - require(::setenv("ANYPS5_SCREENSHOTS", directory.string().c_str(), 1) == 0, "set ANYPS5_SCREENSHOTS"); + require(::setenv("ANYPS5_SCREENSHOTS", directory.c_str(), 1) == 0, "set ANYPS5_SCREENSHOTS"); #endif } @@ -73,7 +73,7 @@ void rejectsInvalidFrames() { int main() try { const auto root = std::filesystem::temp_directory_path() / ("anyps5_screenshot-" + std::to_string(std::random_device{}())); - const auto directory = root / "nested"; + const auto directory = root / std::filesystem::path(u8"nested-a\u00e7\u00e3o-\u65e5\u672c"); std::filesystem::remove_all(root); setScreenshotDirectory(directory); requestsMerge(); diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index a35b9ab285..8d8b12086e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -4513,13 +4513,15 @@ void verifyMeshConfiguration() { RecompileCacheKey::Build(other, key); require(key != first && RecompileCacheKey::ContextHash(request) != RecompileCacheKey::ContextHash(other), "the cache keys ignore the mesh configuration"); require(mesh.outputPrimitiveSize == 3u && replay.request.graphics->mesh->outputPrimitiveSize == 3u, "a mesh configuration does not output triangles by default"); - otherMesh = mesh; - otherMesh.outputPrimitiveSize = 2u; - other.graphics = GraphicsCompileContext{0u, {}, otherMesh, std::nullopt, {}}; - const auto lineReplay = RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(other)); - require(lineReplay.request.graphics.has_value() && lineReplay.request.graphics->mesh.has_value() && lineReplay.request.graphics->mesh->outputPrimitiveSize == 2u, "the mesh output primitive size was lost in serialization"); - RecompileCacheKey::Build(other, key); - require(key != first && RecompileCacheKey::ContextHash(request) != RecompileCacheKey::ContextHash(other), "the cache keys ignore the mesh output primitive size"); + for (const auto primitiveSize : {1u, 2u}) { + otherMesh = mesh; + otherMesh.outputPrimitiveSize = primitiveSize; + other.graphics = GraphicsCompileContext{0u, {}, otherMesh, std::nullopt, {}}; + const auto primitiveReplay = RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(other)); + require(primitiveReplay.request.graphics.has_value() && primitiveReplay.request.graphics->mesh.has_value() && primitiveReplay.request.graphics->mesh->outputPrimitiveSize == primitiveSize, "the mesh output primitive size was lost in serialization"); + RecompileCacheKey::Build(other, key); + require(key != first && RecompileCacheKey::ContextHash(request) != RecompileCacheKey::ContextHash(other), "the cache keys ignore the mesh output primitive size"); + } RecompileRequest hull{}; hull.shader = {ShaderStage::TessellationControl, 0x10000u, code, 0, {}}; hull.context.waveSize = 64; @@ -4896,7 +4898,7 @@ void verifyPixelRequestSerialization() { minimal.context.pixel->layerExported = true; minimal.context.pixel->ditheredAlphaToCoverage = true; const auto encoded = serializer.Serialize(minimal); - require(requestPrefix(encoded, 8u) == "NVNQQSoAAAA=", "new requests did not use serialization version 42"); + require(requestPrefix(encoded, 8u) == "NVNQQSsAAAA=", "new requests did not use serialization version 43"); const std::array fallbacks{0x40u, 0x1ac4u}; auto withFallbacks = minimal; withFallbacks.context.linkedCallFallbacks = fallbacks; @@ -5014,7 +5016,7 @@ void verifyPixelRequestSerialization() { for (std::size_t bytes = 0; bytes < 45u; ++bytes) { expectFailure([&] { static_cast(serializer.Deserialize(requestPrefix(encoded, mappingOffset + bytes))); }, "truncated data", "a truncated pixel mapping, packing, dual-source, alpha-to-coverage, stencil reference export flag, primitive class, layer export, dither or front-face flag, quad pixel mask or quad sample mask was accepted"); } - for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQSsAAAA="}) { + for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQSwAAAA="}) { expectFailure([&] { static_cast(serializer.Deserialize(unsupported)); }, "serialization version", "an unsupported request version was accepted"); } static constexpr std::string_view version18Request{ @@ -5303,15 +5305,16 @@ void verifyDepthRemapRequests() { const auto replay = serializer.Deserialize(remapped); require(replay.request.target.depthRangeRemap && replay.request.context.vertex.has_value() && replay.request.context.vertex->paClVsOutCntl == 0x89abcdefu, "the depth remap target flag or the vertex info did not round-trip"); require(replay.request.context.vertex->depthRemapMode == 2u, "the depth remap mode did not round-trip"); - require(serializer.Serialize(replay.request) == remapped, "a version 35 depth remap request changed on re-serialization"); + require(serializer.Serialize(replay.request) == remapped, "the depth remap request changed on re-serialization"); const auto plainBytes = decodeBase64(plain); const auto remappedBytes = decodeBase64(remapped); - require(plainBytes.size() == remappedBytes.size() && plainBytes.size() > 5u && std::equal(plainBytes.begin(), plainBytes.end() - 7, remappedBytes.begin()), "the depth remap flag and mode are not the 6 bytes before the f32 flush and denormal preserve flags of a vertex request"); + constexpr std::size_t pointTailBytes = 13u; + require(plainBytes.size() == remappedBytes.size() && plainBytes.size() > 7u + pointTailBytes && std::equal(plainBytes.begin(), plainBytes.end() - 7 - pointTailBytes, remappedBytes.begin()) && std::equal(plainBytes.end() - pointTailBytes, plainBytes.end(), remappedBytes.end() - pointTailBytes), "the depth remap flag and mode are not the 6 bytes before the f32 flush and denormal preserve flags and the point tail of a vertex request"); for (const std::uint8_t legacyVersion : {32u, 34u}) { auto legacyBytes = remappedBytes; legacyBytes.pop_back(); legacyBytes[4] = legacyVersion; - legacyBytes.resize(legacyBytes.size() - 6u); + legacyBytes.resize(legacyBytes.size() - 6u - pointTailBytes); const auto legacy = serializer.Deserialize(encodeBase64(legacyBytes)); require(!legacy.request.target.depthRangeRemap && legacy.request.context.vertex.has_value() && legacy.request.context.vertex->depthRemapMode == 0u, ("a version " + std::to_string(legacyVersion) + " vertex request read depth remap fields").c_str()); require(serializer.Serialize(legacy.request) == plain, ("a version " + std::to_string(legacyVersion) + " vertex request misread the fields before the depth remap").c_str()); @@ -5319,10 +5322,10 @@ void verifyDepthRemapRequests() { auto version35 = remappedBytes; version35.pop_back(); version35[4] = 35u; - version35.pop_back(); + version35.resize(version35.size() - 1u - pointTailBytes); const auto replay35 = serializer.Deserialize(encodeBase64(version35)); require(replay35.request.target.depthRangeRemap && replay35.request.context.vertex->depthRemapMode == 2u && !replay35.request.target.float32FlushToZero && serializer.Serialize(replay35.request) == remapped, "a version 35 request misread the depth remap fields or read an f32 flush flag"); - auto truncated = remappedBytes; + auto truncated = version35; truncated.resize(truncated.size() - 2u); expectFailure([&] { static_cast(serializer.Deserialize(encodeBase64(truncated))); }, "truncated data", "a truncated depth remap mode was accepted"); auto trailing = remappedBytes; @@ -5363,9 +5366,12 @@ void verifyMergedHullConfiguration() { const auto replay = serializer.Deserialize(merged); require(replay.request.graphics.has_value() && replay.request.graphics->tessellation.has_value() && replay.request.graphics->tessellation->mergedHull, "a merged hull configuration was lost in serialization"); require(!serializer.Deserialize(separate).request.graphics->tessellation->mergedHull, "a separate hull configuration was read as merged"); + constexpr std::size_t pointTailBytes = 13u; auto legacy = decodeBase64(merged); + require(legacy.size() > 53u + pointTailBytes && legacy[4] == 43u, "a merged hull request did not use serialization version 43"); + legacy.resize(legacy.size() - pointTailBytes); legacy.pop_back(); - require(legacy.size() > 52u && legacy[legacy.size() - 52u] == 1u, "the merged hull flag is not the last byte of the tessellation configuration"); + require(legacy[legacy.size() - 52u] == 1u, "the merged hull flag is not the last byte of the tessellation configuration"); legacy.erase(legacy.end() - 52); for (const auto version : {30u, 31u, 32u, 34u, 35u, 36u, 37u, 38u}) { auto older = legacy; diff --git a/core/libs/prx/libSceAgcDriver/tests/UnsupportedDiagnostics.cpp b/core/libs/prx/libSceAgcDriver/tests/UnsupportedDiagnostics.cpp new file mode 100644 index 0000000000..db2f77f1ec --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/UnsupportedDiagnostics.cpp @@ -0,0 +1,10 @@ +#include "SceTypes.hpp" + +extern "C" void* APS5_VABI sceAgcDriverGetCoredumpAddress(); +extern "C" int APS5_VABI sceAgcDriverSetTargetRingForDiag(); + +int main() { + if (sceAgcDriverGetCoredumpAddress() != nullptr) return 1; + if (sceAgcDriverSetTargetRingForDiag() != static_cast(0x8A6D0001)) return 2; + return 0; +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageArrayWindowPastLayerLimit.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageArrayWindowPastLayerLimit.cpp index 528cad640e..d6a5e73941 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageArrayWindowPastLayerLimit.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageArrayWindowPastLayerLimit.cpp @@ -120,7 +120,7 @@ void Run(AgcDriver::VulkanDevice& device, std::uint32_t baseArray = BaseArray) { device.WaitIdle(); for (std::uint32_t tid = 0; tid < Threads; ++tid) { const float u = std::bit_cast(Input[tid * 4u + 0u]); - const float layer = std::clamp(std::nearbyint(std::bit_cast(Input[tid * 4u + 1u])), 0.0f, static_cast(Layers - 1u)); + const float layer = std::clamp(std::nearbyint(std::bit_cast(Input[tid * 4u + 1u])), 0.0f, static_cast(LastArray - baseArray)); const auto x = static_cast(std::clamp(static_cast(std::floor(u * static_cast(Width))), 0, static_cast(Width) - 1)); Expect(tid, "image_sample_lz on a window of a 1D array past maxImageArrayLayers", &Output[tid * 8u], Texel(x, baseArray + static_cast(layer))); Expect(tid, "image_load on a window of a 1D array past maxImageArrayLayers", &Output[tid * 8u + 4u], Texel(Input[tid * 4u + 2u], baseArray + Input[tid * 4u + 3u])); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/VertexPointSize.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/VertexPointSize.cpp new file mode 100644 index 0000000000..253201b452 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/VertexPointSize.cpp @@ -0,0 +1,165 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Extent = 64u; +constexpr std::uint32_t MiscVector = 1u << 21u; +constexpr std::uint32_t VertexPointSize = 1u << 16u; +alignas(256) std::array pixels{}; +alignas(256) std::array vertices{}; +constexpr std::array positionCode{0xf4080100u, 0xfa000000u, 0x4a0a0a02u, 0x4a0a0b08u, 0x4a0a0a03u, 0xe00c2000u, 0x80010005u, 0xbf8c3f70u, 0xf80008cfu, 0x03020100u, 0xbf810000u}; +constexpr std::array pixelCode{0x7e0002f2u, 0x7e020280u, 0xf800180fu, 0x00010100u, 0xbf810000u}; + +std::vector SizeExportCode(float size) { + std::vector code(positionCode.begin(), positionCode.begin() + 8); + code.insert(code.end(), {0x7e0802ffu, std::bit_cast(size), 0xf80000cfu, 0x03020100u, 0xf80008d1u, 0x00000004u, 0xbf810000u}); + return code; +} + +struct PointSize { + float size = 1.0f; + float minimum = 0.0f; + float maximum = 0.0f; + bool fromVertex = false; +}; + +struct Square { + unsigned pixels = 0; + std::uint32_t left = Extent, top = Extent, right = 0, bottom = 0; +}; + +std::vector RunCase(AgcDriver::VulkanDevice& device, const char* name, std::span vertexCode, const PointSize& points, float offset) { + const std::array, 4> centres{{{16.0f, 16.0f}, {48.0f, 16.0f}, {16.0f, 48.0f}, {48.0f, 48.0f}}}; + vertices.fill(std::byte{0}); + constexpr std::uint32_t stride = 16u; + for (std::size_t index = 0; index < centres.size(); ++index) { + const std::array position{(centres[index][0] + offset) / Extent * 2.0f - 1.0f, (centres[index][1] + offset) / Extent * 2.0f - 1.0f, 0.0f, 1.0f}; + std::memcpy(vertices.data() + index * stride, position.data(), sizeof(position)); + } + std::array descriptor{}; + const auto tableAddress = reinterpret_cast(descriptor.data()); + std::array userData{static_cast(tableAddress), static_cast(tableAddress >> 32u), 0u, 0u}; + const std::array memory{{{tableAddress, std::as_bytes(std::span(descriptor))}}}; + ShaderRecompiler::ShaderVertexStageInfo vertexInfo{}; + vertexInfo.fetchEmbedded = true; + vertexInfo.resourcesNum = 1u; + vertexInfo.resourcesDst[0] = {0, 4, 0, 0}; + vertexInfo.paClVsOutCntl = points.fromVertex ? MiscVector | VertexPointSize : 0u; + vertexInfo.pointList = true; + vertexInfo.pointSize = points.size; + vertexInfo.pointSizeMin = points.minimum; + vertexInfo.pointSizeMax = points.maximum; + ShaderRecompiler::RecompileRequest request{{ShaderStage::Vertex, reinterpret_cast(vertexCode.data()), vertexCode, 0, {}}, {32u, 0u, userData, std::nullopt, std::nullopt, vertexInfo, memory}, device.Target(), {0u, 0u, 0u, 96u}}; + const auto address = reinterpret_cast(vertices.data()); + descriptor = {static_cast(address), static_cast(address >> 32u) | (stride << 16u), static_cast(centres.size()), 0x01000facu | (77u << 12u)}; + request.context.vertex->resources[0].fields = descriptor; + const auto vertex = ShaderRecompiler::Recompile(request); + const auto pushBytes = static_cast(vertex.pushConstants.size()); + ShaderRecompiler::ShaderPixelStageInfo pixelInfo{}; + pixelInfo.wave32 = true; + pixelInfo.targetOutputMode[0] = 9u; + pixelInfo.targetExportMapping.fill(0xe4u); + pixelInfo.rasterPrimitive = ShaderRecompiler::RasterPrimitive::Point; + ShaderRecompiler::RecompileRequest pixelRequest{{ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, {32u, 0u, {}, std::nullopt, pixelInfo, std::nullopt, {}}, device.Target(), PixelPushLayout(pushBytes, device.Target())}; + const auto pixel = ShaderRecompiler::Recompile(pixelRequest); + const std::array shaders{{{ShaderStage::Vertex, &vertex, 0u}, {ShaderStage::Fragment, &pixel, PixelPushOffset(pushBytes, device.Target())}}}; + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, 32u, 32u, std::nullopt, std::nullopt}; + state.color = {reinterpret_cast(pixels.data()), {Extent, Extent}, VK_FORMAT_R8G8B8A8_UNORM, pixels.size(), 0xe4u}; + state.colors = {state.color}; + state.hasColorTarget = true; + state.renderExtent = {Extent, Extent}; + state.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; + state.paClVsOutCntl = vertexInfo.paClVsOutCntl; + state.vertexPoints = true; + state.pointSize = points.size; + state.pointSizeMin = points.minimum; + state.pointSizeMax = points.maximum; + state.viewport = {0.0f, static_cast(Extent), static_cast(Extent), -static_cast(Extent), 0.0f, 1.0f}; + state.scissor = {{0, 0}, {Extent, Extent}}; + state.cullMode = VK_CULL_MODE_NONE; + state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + state.blend.colorWriteMask = 15u; + state.blends = {state.blend}; + AgcDriver::Pm4::DrawParameters draw{0u, static_cast(centres.size()), 0u, 1u, 0u, false}; + draw.firstInstance = 0u; + pixels.fill(std::byte{0}); + device.Draw(state, draw, shaders); + device.WaitIdle(); + std::vector squares(centres.size()); + for (std::uint32_t y = 0; y < Extent; ++y) { + for (std::uint32_t x = 0; x < Extent; ++x) { + const auto index = (y * Extent + x) * 4u; + if (pixels[index] == std::byte{0} && pixels[index + 3u] == std::byte{0}) continue; + auto& square = squares[(y >= Extent / 2u ? 2u : 0u) + (x >= Extent / 2u ? 1u : 0u)]; + ++square.pixels; + square.left = std::min(square.left, x); + square.top = std::min(square.top, y); + square.right = std::max(square.right, x + 1u); + square.bottom = std::max(square.bottom, y + 1u); + } + } + std::cout << "case " << name << ":"; + for (const auto& square : squares) std::cout << " " << square.pixels << " [" << square.left << "," << square.top << ")-(" << square.right << "," << square.bottom << ")"; + std::cout << "\n"; + return squares; +} + +void RequireSquares(const std::vector& squares, std::uint32_t side, const char* name) { + for (const auto& square : squares) { + Require(square.pixels == side * side && square.right - square.left == side && square.bottom - square.top == side, std::string(name) + ": a point is not a " + std::to_string(side) + "x" + std::to_string(side) + " square"); + } +} + +void RequireAtMostOnePixel(const std::vector& squares, const char* name) { + for (const auto& square : squares) Require(square.pixels <= 1u, std::string(name) + ": a point with no positive size covers more than one pixel"); +} + +void Run(AgcDriver::VulkanDevice& device) { + std::cout << "device: " << device.DeviceName() << "\n"; + RequireSquares(RunCase(device, "register size 1", positionCode, {1.0f, 1.0f, 1.0f, false}, 0.5f), 1u, "register size 1"); + RequireSquares(RunCase(device, "register size 4", positionCode, {4.0f, 1.0f, 1.0f, false}, 0.0f), 4u, "register size 4"); + RequireSquares(RunCase(device, "register size 3 ignores the clamp", positionCode, {3.0f, 6.0f, 8.0f, false}, 0.5f), 3u, "register size 3 ignores the clamp"); + const auto two = SizeExportCode(2.0f); + RequireSquares(RunCase(device, "vertex size 2 within [1, 8]", two, {1.0f, 1.0f, 8.0f, true}, 0.0f), 2u, "vertex size 2 within [1, 8]"); + const auto six = SizeExportCode(6.0f); + RequireSquares(RunCase(device, "vertex size 6 clamped to 4", six, {1.0f, 0.5f, 4.0f, true}, 0.0f), 4u, "vertex size 6 clamped to 4"); + const auto one = SizeExportCode(1.0f); + RequireSquares(RunCase(device, "vertex size 1 clamped to 5", one, {1.0f, 5.0f, 8.0f, true}, 0.5f), 5u, "vertex size 1 clamped to 5"); + RequireSquares(RunCase(device, "vertex size over a register size", six, {1.0f, 1.0f, 8.0f, true}, 0.0f), 6u, "vertex size over a register size"); + const auto zero = SizeExportCode(0.0f); + RequireAtMostOnePixel(RunCase(device, "vertex size 0 within [0, 4]", zero, {0.0f, 0.0f, 4.0f, true}, 0.5f), "vertex size 0 within [0, 4]"); + const auto negative = SizeExportCode(-3.0f); + RequireAtMostOnePixel(RunCase(device, "vertex size -3 within [0, 4]", negative, {1.0f, 0.0f, 4.0f, true}, 0.5f), "vertex size -3 within [0, 4]"); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAjm.native/CMakeLists.txt b/core/libs/prx/libSceAjm.native/CMakeLists.txt index c69ccc483c..20f6cf1f20 100644 --- a/core/libs/prx/libSceAjm.native/CMakeLists.txt +++ b/core/libs/prx/libSceAjm.native/CMakeLists.txt @@ -62,6 +62,11 @@ if(BUILD_TESTING) target_link_libraries(guest_ajm_vibration_tests PRIVATE libSceAjm.native libc) configure_windows_unwind(guest_ajm_vibration_tests) add_test(NAME guest_ajm_vibration COMMAND guest_ajm_vibration_tests) + add_test_executable(guest_ajm_wvorbis_tests ${LIBS_INCLUDE_DIR}/tests/GuestAjmWVorbis.cpp) + target_include_directories(guest_ajm_wvorbis_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_ajm_wvorbis_tests PRIVATE libSceAjm.native libc) + configure_windows_unwind(guest_ajm_wvorbis_tests) + add_test(NAME guest_ajm_wvorbis COMMAND guest_ajm_wvorbis_tests) if(UNIX) add_test_executable(guest_ajm_teardown_tests ${LIBS_INCLUDE_DIR}/tests/GuestAjmTeardown.cpp) target_include_directories(guest_ajm_teardown_tests PRIVATE ${LIBS_INCLUDE_DIR}) @@ -70,7 +75,7 @@ if(BUILD_TESTING) add_test(NAME guest_ajm_teardown COMMAND guest_ajm_teardown_tests $) endif() if(WIN32) - set_tests_properties(guest_ajm guest_ajm_vibration PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(guest_ajm guest_ajm_vibration guest_ajm_wvorbis PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() endif() diff --git a/core/libs/prx/libSceAjm.native/Export.cpp b/core/libs/prx/libSceAjm.native/Export.cpp index 2a7d6a4f1f..3b0b5e4a8a 100644 --- a/core/libs/prx/libSceAjm.native/Export.cpp +++ b/core/libs/prx/libSceAjm.native/Export.cpp @@ -4,6 +4,7 @@ #include "prx/libc/include/General.hpp" #include #include +#include // Entry points this library does not implement: reported before the unimplemented-call exception so an // APS5_TRACE_AJM run shows which one a title reached. @@ -43,6 +44,38 @@ static_assert(offsetof(WVorbisHeaderPacketParameter, setupPacket) == 32); static_assert(sizeof(WVorbisSetupPacketParameter) == 56); static_assert(offsetof(WVorbisSetupPacketParameter, input) == 16); +namespace { + +constexpr int AJM_WVORBIS_ERROR_INVALID_PARAMETER = static_cast(0x8A6E0001); +constexpr int AJM_WVORBIS_ERROR_INVALID_SIZE = static_cast(0x8A6E0002); +constexpr int AJM_WVORBIS_ERROR_OUTPUT_TOO_SMALL = static_cast(0x8A6E0003); +constexpr int AJM_WVORBIS_ERROR_INVALID_SETUP = static_cast(0x8A6E0004); +constexpr std::size_t WVORBIS_LENGTH_TABLE_SIZE = 10; +constexpr std::size_t VORBIS_IDENTIFICATION_SIZE = 30; +constexpr std::size_t VORBIS_COMMENT_SIZE = 16; +constexpr std::size_t WVORBIS_SETUP_MIN_SIZE = 9; +constexpr std::size_t WVORBIS_SETUP_PART1_LENGTH_OFFSET = 7; +constexpr std::int32_t VORBIS_BLOCK_SIZE_MIN = 64; +constexpr std::int32_t VORBIS_BLOCK_SIZE_MAX = 8192; +constexpr std::uint8_t VORBIS_MAGIC[] {'v', 'o', 'r', 'b', 'i', 's'}; + +std::int32_t WVorbisBlockSize(std::int32_t log2) { + return static_cast(1u << (static_cast(log2) & 31u)); +} + +std::uint8_t* PutLittle(std::uint8_t* out, std::uint32_t value, std::size_t bytes) { + for (std::size_t i = 0; i < bytes; ++i) out[i] = static_cast(value >> (8 * i)); + return out + bytes; +} + +std::uint8_t* PutVorbisHeaderStart(std::uint8_t* out, std::uint8_t type) { + *out++ = type; + std::memcpy(out, VORBIS_MAGIC, sizeof(VORBIS_MAGIC)); + return out + sizeof(VORBIS_MAGIC); +} + +} + extern "C" { int APS5_VABI sceAjmBatchJobEncode(AjmBatchInfo* info, uint32_t instance, const void* pcm_input, size_t pcm_input_size, void* bitstream_output, size_t bitstream_output_size, void* result) { @@ -63,8 +96,45 @@ const char* APS5_VABI sceAjmStrError(int error) { return nullptr; } -int APS5_VABI sceAjmDecWVorbisCreateHeaderPacket(WVorbisHeaderPacketParameter* parameter) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceAjmDecWVorbisCreateHeaderPacket(WVorbisHeaderPacketParameter* param) { + if (param == nullptr) return AJM_WVORBIS_ERROR_INVALID_PARAMETER; + if (param->size != sizeof(WVorbisHeaderPacketParameter)) return AJM_WVORBIS_ERROR_INVALID_SIZE; + if (param->bytesPerSecond < 0) return AJM_WVORBIS_ERROR_INVALID_PARAMETER; + if (WVorbisBlockSize(param->shortBlockExponent) < VORBIS_BLOCK_SIZE_MIN) return AJM_WVORBIS_ERROR_INVALID_PARAMETER; + if (param->longBlockExponent < param->shortBlockExponent || WVorbisBlockSize(param->longBlockExponent) > VORBIS_BLOCK_SIZE_MAX) + return AJM_WVORBIS_ERROR_INVALID_PARAMETER; + if (param->setupPacket == nullptr) return AJM_WVORBIS_ERROR_INVALID_PARAMETER; + if (param->setupPacketSize < WVORBIS_SETUP_MIN_SIZE) return AJM_WVORBIS_ERROR_INVALID_SETUP; + if (param->output == nullptr) return AJM_WVORBIS_ERROR_INVALID_PARAMETER; + constexpr std::size_t headers = WVORBIS_LENGTH_TABLE_SIZE + VORBIS_IDENTIFICATION_SIZE + VORBIS_COMMENT_SIZE; + if (param->outputCapacity < headers || param->setupPacketSize > param->outputCapacity - headers) return AJM_WVORBIS_ERROR_OUTPUT_TOO_SMALL; + + const auto* setup = static_cast(param->setupPacket); + const auto part1 = static_cast(setup[WVORBIS_SETUP_PART1_LENGTH_OFFSET] | setup[WVORBIS_SETUP_PART1_LENGTH_OFFSET + 1] << 8); + auto* out = static_cast(param->output); + out = PutLittle(out, 0, 2); + out = PutLittle(out, VORBIS_IDENTIFICATION_SIZE, 2); + out = PutLittle(out, VORBIS_COMMENT_SIZE, 2); + out = PutLittle(out, part1, 2); + out = PutLittle(out, static_cast(param->setupPacketSize) - part1, 2); + + out = PutVorbisHeaderStart(out, 1); + out = PutLittle(out, 0, 4); + out = PutLittle(out, static_cast(param->channels), 1); + out = PutLittle(out, param->sampleRate, 4); + out = PutLittle(out, 0, 4); + out = PutLittle(out, static_cast(param->bytesPerSecond) * 8u, 4); + out = PutLittle(out, 0, 4); + *out++ = static_cast((param->shortBlockExponent & 0xF) | (param->longBlockExponent & 0xF) << 4); + *out++ = 1; + + out = PutVorbisHeaderStart(out, 3); + out = PutLittle(out, 0, 4); + out = PutLittle(out, 0, 4); + *out++ = 1; + + std::memcpy(out, setup, param->setupPacketSize); + param->outputSize = headers + param->setupPacketSize; return 0; } diff --git a/core/libs/prx/libSceAudio3d/CMakeLists.txt b/core/libs/prx/libSceAudio3d/CMakeLists.txt index 791d2fac7e..2d4e024734 100644 --- a/core/libs/prx/libSceAudio3d/CMakeLists.txt +++ b/core/libs/prx/libSceAudio3d/CMakeLists.txt @@ -22,6 +22,15 @@ if(BUILD_TESTING) set_tests_properties(guest_audio3d_port PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_audio3d_speaker_array_tests ${LIBS_INCLUDE_DIR}/tests/GuestAudio3dSpeakerArray.cpp) + target_include_directories(guest_audio3d_speaker_array_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_audio3d_speaker_array_tests PRIVATE libSceAudio3d libSceAudioOut libc) + configure_windows_unwind(guest_audio3d_speaker_array_tests) + add_test(NAME guest_audio3d_speaker_array COMMAND guest_audio3d_speaker_array_tests) + if(WIN32) + set_tests_properties(guest_audio3d_speaker_array PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_audio3d_mixer_tests ${LIBS_INCLUDE_DIR}/tests/GuestAudio3dMixer.cpp) target_include_directories(guest_audio3d_mixer_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/core/libs/prx/libSceAudio3d/Export.cpp b/core/libs/prx/libSceAudio3d/Export.cpp index 4420537497..eed6da479e 100644 --- a/core/libs/prx/libSceAudio3d/Export.cpp +++ b/core/libs/prx/libSceAudio3d/Export.cpp @@ -472,29 +472,42 @@ int APS5_VABI sceAudio3dReportUnregisterHandler(void) { return 0; } -int APS5_VABI sceAudio3dGetSpeakerArrayMemorySize(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +size_t APS5_VABI sceAudioOut2GetSpeakerArrayMemorySize(uint32_t num_speakers, uint8_t is_3d, uint8_t is_ambisonics); +int APS5_VABI sceAudioOut2SpeakerArrayCreate(AudioOut2SpeakerArrayHandle* handle, const void* vbap_params, const void* ambi_params); +int APS5_VABI sceAudioOut2SpeakerArrayDestroy(AudioOut2SpeakerArrayHandle handle); +int APS5_VABI sceAudioOut2GetSpeakerArrayCoefficients(AudioOut2SpeakerArrayHandle handle, AudioOut2Position pos, float spread, float* coefficients, uint32_t num_coefficients, uint8_t height_aware, float downmix_spread_radius); + +struct Audio3dSpeakerArrayParameters { + const AudioOut2Position* positions; + std::uint32_t numSpeakers; + std::uint8_t is3d; + void* memory; + std::size_t memorySize; +}; + +static_assert(sizeof(Audio3dSpeakerArrayParameters) == 0x20); + +size_t APS5_VABI sceAudio3dGetSpeakerArrayMemorySize(uint32_t num_speakers, uint8_t is_3d) { + return sceAudioOut2GetSpeakerArrayMemorySize(num_speakers, is_3d, 0); } -int APS5_VABI sceAudio3dCreateSpeakerArray(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAudio3dCreateSpeakerArray(AudioOut2SpeakerArrayHandle* handle, const Audio3dSpeakerArrayParameters* parameters) { + if (!handle || !parameters) return AUDIO3D_ERROR_INVALID_PARAMETER; + unsigned char extended[0x28] = {}; + std::memcpy(extended, parameters, sizeof(*parameters)); + return sceAudioOut2SpeakerArrayCreate(handle, extended, nullptr); } -int APS5_VABI sceAudio3dDeleteSpeakerArray(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAudio3dDeleteSpeakerArray(AudioOut2SpeakerArrayHandle handle) { + return sceAudioOut2SpeakerArrayDestroy(handle); } -int APS5_VABI sceAudio3dGetSpeakerArrayMixCoefficients(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAudio3dGetSpeakerArrayMixCoefficients(AudioOut2SpeakerArrayHandle handle, AudioOut2Position pos, float spread, float* coefficients, uint32_t num_coefficients) { + return sceAudioOut2GetSpeakerArrayCoefficients(handle, pos, spread, coefficients, num_coefficients, 0, 0.1f); } -int APS5_VABI sceAudio3dGetSpeakerArrayMixCoefficients2(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAudio3dGetSpeakerArrayMixCoefficients2(AudioOut2SpeakerArrayHandle handle, AudioOut2Position pos, float spread, float* coefficients, uint32_t num_coefficients, uint8_t height_aware, float downmix_spread_radius) { + return sceAudioOut2GetSpeakerArrayCoefficients(handle, pos, spread, coefficients, num_coefficients, height_aware, downmix_spread_radius); } } diff --git a/core/libs/prx/libSceAudioIn/Export.cpp b/core/libs/prx/libSceAudioIn/Export.cpp index 38cd4e212c..0a1712b5da 100644 --- a/core/libs/prx/libSceAudioIn/Export.cpp +++ b/core/libs/prx/libSceAudioIn/Export.cpp @@ -191,9 +191,9 @@ int32_t APS5_VABI sceAudioInAsyncOpen(int32_t userId, uint32_t type, uint32_t in } int32_t APS5_VABI sceAudioInIsPadEmbeddedMic(int32_t handle) { - (void)handle; - NotImplemented_nid_no_patch(__func__); - return 0; + std::lock_guard lock(g_mutex); + if (!find(handle)) return AUDIO_IN_ERROR_INVALID_HANDLE; + return 0; } int32_t APS5_VABI sceAudioInGetGain(int32_t handle, float* gain) { diff --git a/core/libs/prx/libSceAudioOut/CMakeLists.txt b/core/libs/prx/libSceAudioOut/CMakeLists.txt index af79a20cf1..d13571aad4 100644 --- a/core/libs/prx/libSceAudioOut/CMakeLists.txt +++ b/core/libs/prx/libSceAudioOut/CMakeLists.txt @@ -47,6 +47,21 @@ if(BUILD_TESTING) endif() endforeach() + add_test_executable(audio_out_buffer_size_tests tests/AudioOutBufferSize.cpp src/AudioOut.cpp src/AudioOut2PadMix.cpp src/AudioOutShutdown.cpp) + target_compile_definitions(audio_out_buffer_size_tests PRIVATE SDL_MAIN_HANDLED) + target_include_directories(audio_out_buffer_size_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR}) + target_link_libraries(audio_out_buffer_size_tests PRIVATE libc libkernel libScePad) + configure_windows_unwind(audio_out_buffer_size_tests) + foreach(bufferSizeMode IN ITEMS pass_through wrap mix threshold) + set(bufferSizeTest "audio_out_buffer_size_${bufferSizeMode}") + add_test(NAME ${bufferSizeTest} COMMAND audio_out_buffer_size_tests ${bufferSizeMode}) + set_tests_properties(${bufferSizeTest} PROPERTIES TIMEOUT 5) + if(WIN32) + set_tests_properties(${bufferSizeTest} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() + add_test_executable(audio_out_vibration_tests tests/AudioOutVibration.cpp) target_include_directories(audio_out_vibration_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(audio_out_vibration_tests PRIVATE libSceAudioOut libScePad libc) diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp index e48537db95..fa03043577 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp @@ -22,6 +22,11 @@ int APS5_VABI sceAudioOut2MasteringInit(uint32_t flags) { return 0; } +int APS5_VABI sceAudioOut2MasteringInitEx(uint32_t flags) { + if ((flags & 0x7FFFFFFFu) != 0) NotImplemented_nid_no_patch(__func__); + return 0; +} + int APS5_VABI sceAudioOut2MasteringTerm(void) { return 0; } diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2SpeakerArray.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2SpeakerArray.cpp index 26170f4622..7b6a07017e 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2SpeakerArray.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2SpeakerArray.cpp @@ -68,7 +68,7 @@ extern "C" { int APS5_VABI sceAudioOut2SpeakerArrayCreate(AudioOut2SpeakerArrayHandle* handle, const void* vbap_params, const void* ambi_params) { const auto* params = static_cast(vbap_params); - if (handle == nullptr || params == nullptr || ambi_params == nullptr || params->positions == nullptr || + if (handle == nullptr || params == nullptr || params->positions == nullptr || params->numSpeakers == 0 || params->numSpeakers > MaxSpeakers || params->memory == nullptr || params->memorySize < sizeof(SpeakerArray) || reinterpret_cast(params->memory) % alignof(SpeakerArray) != 0) { APS5_INVALID_ARG_EX; diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp index 62492d43da..89c681ad9c 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp @@ -1,4 +1,5 @@ #include +#include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" @@ -33,7 +34,15 @@ int APS5_VABI sceAudioOut2GetSystemState(AudioOut2SystemState* state) { } int APS5_VABI sceAudioOut2SetSystemDebugState(const AudioOut2SystemDebugStateParam* param) { - if (param == nullptr) return static_cast(0x80260502); + constexpr int ErrorInvalidPointer = static_cast(0x8026800C); + constexpr std::uint32_t DebugStateIdLoudness = 1; + constexpr std::uint32_t DebugStateIdUnmodelled = 0x20; + constexpr std::size_t LoudnessParamSize = 8; + constexpr std::size_t UnmodelledParamSize = 4; + if (param == nullptr) return ErrorInvalidPointer; + if (param->debug_state_id == DebugStateIdUnmodelled && param->param_size == UnmodelledParamSize) NotImplemented_nid_no_patch(__func__); + if (param->debug_state_id != DebugStateIdLoudness || param->param_size != LoudnessParamSize) return SCE_AUDIO_OUT2_ERROR_INVALID_PARAM; + if (param->param == nullptr) NotImplemented_nid_no_patch(__func__); AUDIOOUT2_TRACE("t=%.3f SetSystemDebugState: debug state %u recorded; the host has no system mix to apply it to\n", AudioOut2TraceSeconds(), param->debug_state_id); return 0; } diff --git a/core/libs/prx/libSceAudioOut/src/Unimplemented.cpp b/core/libs/prx/libSceAudioOut/src/Unimplemented.cpp index 3457538766..24829f4035 100644 --- a/core/libs/prx/libSceAudioOut/src/Unimplemented.cpp +++ b/core/libs/prx/libSceAudioOut/src/Unimplemented.cpp @@ -365,11 +365,6 @@ int APS5_VABI sceAudioOut2GetSystemStateEx(void) { return 0; } -int APS5_VABI sceAudioOut2MasteringInitEx(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceAudioOut2SetDebugHeadphoneMode(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp index d39724ff94..09822f5c74 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp @@ -14,6 +14,7 @@ int APS5_VABI sceAudioOut2LoInit(); int APS5_VABI sceAudioOut2LoTerminate(); int APS5_VABI sceAudioOut2Set3DLatency(int, std::uint32_t); int APS5_VABI sceAudioOut2MasteringInit(std::uint32_t); +int APS5_VABI sceAudioOut2MasteringInitEx(std::uint32_t); int APS5_VABI sceAudioOut2MasteringTerm(); int APS5_VABI sceAudioOut2MasteringSetParam(const void*, std::uint32_t, std::uint32_t); int APS5_VABI sceAudioOut2MasteringGetState(void*, std::uint32_t, std::uint64_t); @@ -49,7 +50,7 @@ static_assert(sizeof(AudioOut2MasteringStatesV2) == 400); constexpr int systemUser = 0xFF; constexpr int user = 0x10000000; constexpr int errorInvalidParam = static_cast(0x80268001); -constexpr int errorInvalidArgument = static_cast(0x80260502); +constexpr int errorInvalidPointer = static_cast(0x8026800C); constexpr int errorMasteringInvalidApiParam = static_cast(0x80268201); constexpr int errorMasteringInvalidStatesId = static_cast(0x80268204); @@ -69,6 +70,10 @@ void TestSet3DLatency() { void TestMasteringInit() { Require(sceAudioOut2MasteringInit(0) == 0, "flags 0 must be accepted"); Require(ThrowsRuntimeError([] { sceAudioOut2MasteringInit(1); }), "flags 1 must throw"); + Require(sceAudioOut2MasteringInitEx(0) == 0, "InitEx flags 0 must be accepted"); + Require(sceAudioOut2MasteringInitEx(0x80000000u) == 0, "InitEx flag bit 31 must be accepted"); + Require(ThrowsRuntimeError([] { sceAudioOut2MasteringInitEx(1); }), "InitEx flag bit 0 must throw"); + Require(ThrowsRuntimeError([] { sceAudioOut2MasteringInitEx(0x40000000u); }), "InitEx flag bit 30 must throw"); } void TestMasteringTerm() { @@ -109,10 +114,25 @@ void TestMasteringGetState() { } void TestSetSystemDebugState() { - const std::uint64_t param[4] = {1, 0, 0, 0}; - Require(sceAudioOut2SetSystemDebugState(reinterpret_cast(param)) == 0, "a debug state must be accepted"); - Require(sceAudioOut2SetSystemDebugState(reinterpret_cast(param)) == 0, "a debug state must be accepted again"); - Require(sceAudioOut2SetSystemDebugState(nullptr) == errorInvalidArgument, "a null debug state is refused"); + std::uint32_t loudness[2] = {1, 0}; + const auto set = [&loudness](std::uint32_t id, void* data, std::size_t size) { + const AudioOut2SystemDebugStateParam param{id, 0, data, size}; + return sceAudioOut2SetSystemDebugState(¶m); + }; + Require(set(1, loudness, 8) == 0, "the loudness debug state must be accepted"); + Require(set(1, loudness, 8) == 0, "the loudness debug state must be accepted again"); + Require(set(1, loudness, 0) == errorInvalidParam, "a loudness debug state of size 0 is refused"); + Require(set(1, loudness, 4) == errorInvalidParam, "a loudness debug state of size 4 is refused"); + Require(set(1, loudness, 16) == errorInvalidParam, "a loudness debug state of size 16 is refused"); + Require(set(0, loudness, 8) == errorInvalidParam, "debug state id 0 is refused"); + Require(set(2, loudness, 8) == errorInvalidParam, "debug state id 2 is refused"); + Require(set(0x21, loudness, 8) == errorInvalidParam, "debug state id 0x21 is refused"); + Require(set(0xFFFFFFFFu, loudness, 8) == errorInvalidParam, "a large debug state id is refused"); + Require(set(0x20, loudness, 8) == errorInvalidParam, "debug state id 0x20 of size 8 is refused"); + Require(set(0x20, loudness, 0) == errorInvalidParam, "debug state id 0x20 of size 0 is refused"); + Require(ThrowsRuntimeError([&] { set(0x20, loudness, 4); }), "debug state id 0x20 of size 4 must throw"); + Require(ThrowsRuntimeError([&] { set(1, nullptr, 8); }), "a loudness debug state without a param must throw"); + Require(sceAudioOut2SetSystemDebugState(nullptr) == errorInvalidPointer, "a null debug state is refused"); } void TestEnableChat() { diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutBufferSize.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutBufferSize.cpp new file mode 100644 index 0000000000..d51eda399a --- /dev/null +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutBufferSize.cpp @@ -0,0 +1,100 @@ +#include "SDL.h" +#include "prx/libSceAudioOut/include/AudioOutSdl.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +extern "C" { +int APS5_VABI sceAudioOutOpen(int, int, int, std::uint32_t, std::uint32_t, std::uint32_t); +int APS5_VABI sceAudioOutClose(int); +int APS5_VABI sceAudioOutOutput(int, const void*); +} + +namespace { +constexpr SDL_AudioDeviceID device = 7; +int opened = 0; +int closed = 0; +int queued = 0; +std::uint32_t queuedBytes = 0; +const void* supplied = nullptr; +std::uint32_t suppliedBytes = 0; + +void Require(bool value, const char* message) { + if (!value) throw std::runtime_error(message); +} + +void RunPassThrough() { + std::vector samples(512u * 8u); + for (std::size_t i = 0; i < samples.size(); ++i) samples[i] = static_cast(i % 97u) / 97.0f; + const int handle = sceAudioOutOpen(0, 0, 0, 512, 48000, 5); + Require(handle > 0 && opened == 1, "a 512-frame F32 8-channel port did not open its device"); + supplied = samples.data(); + suppliedBytes = static_cast(samples.size() * sizeof(float)); + Require(sceAudioOutOutput(handle, samples.data()) == 512, "a 512-frame output did not return 512"); + Require(queued == 1 && queuedBytes == 16384, "a 512-frame F32 8-channel output did not queue 16384 bytes"); + Require(sceAudioOutClose(handle) == 0 && closed == 1, "the port did not close its device"); +} + +void RunOverflow(std::uint32_t frames, std::uint32_t format, std::uint32_t frameBytes) { + std::vector frame(frameBytes); + const int handle = sceAudioOutOpen(0, 0, 0, frames, 48000, format); + Require(handle > 0 && opened == 1, "a port with an oversized length did not open its device"); + supplied = frame.data(); + suppliedBytes = frameBytes; + bool threw = false; + try { + static_cast(sceAudioOutOutput(handle, frame.data())); + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw, "an output whose buffer size overflows did not throw"); + Require(queued == 0, "an output whose buffer size overflows queued audio"); + Require(sceAudioOutClose(handle) == 0 && closed == 1, "the port did not close after the refused output"); +} +} + +int InitializeSdlAudio_nid_no_patch() { return 0; } +extern "C" Uint32 SDLCALL SDL_WasInit(Uint32) { return SDL_INIT_AUDIO; } +extern "C" int SDLCALL SDL_GetNumAudioDevices(int) { return 1; } +extern "C" const char* SDLCALL SDL_GetAudioDeviceName(int, int) { return "Main speakers"; } +extern "C" int SDLCALL SDL_GetAudioDeviceSpec(int, int, SDL_AudioSpec* spec) { *spec = {}; spec->channels = 2; return 0; } +extern "C" const char* SDLCALL SDL_GetCurrentAudioDriver() { return "dummy"; } +extern "C" const char* SDLCALL SDL_GetError() { return "test error"; } +extern "C" SDL_AudioDeviceID SDLCALL SDL_OpenAudioDevice(const char* name, int capture, const SDL_AudioSpec* desired, SDL_AudioSpec* obtained, int) { + Require(name == nullptr && capture == 0 && desired->callback == nullptr, "the main port did not open the default device"); + ++opened; + *obtained = *desired; + return device; +} +extern "C" void SDLCALL SDL_PauseAudioDevice(SDL_AudioDeviceID id, int) { Require(id == device, "wrong device paused"); } +extern "C" Uint32 SDLCALL SDL_GetQueuedAudioSize(SDL_AudioDeviceID id) { Require(id == device, "wrong queue queried"); return 0; } +extern "C" int SDLCALL SDL_QueueAudio(SDL_AudioDeviceID id, const void* data, Uint32 bytes) { + Require(id == device, "wrong queue fed"); + ++queued; + queuedBytes = bytes; + if (bytes <= suppliedBytes) Require(std::memcmp(data, supplied, bytes) == 0, "the queued audio is not the supplied samples"); + return 0; +} +extern "C" void SDLCALL SDL_ClearQueuedAudio(SDL_AudioDeviceID id) { Require(id == device, "wrong queue cleared"); } +extern "C" void SDLCALL SDL_CloseAudioDevice(SDL_AudioDeviceID id) { Require(id == device, "wrong device closed"); ++closed; } + +int main(int argc, char** argv) { + try { + Require(argc == 2, "invalid test arguments"); + const std::string_view mode = argv[1]; + if (mode == "pass_through") RunPassThrough(); + else if (mode == "wrap") RunOverflow(0x80000000u, 5, 8u * sizeof(float)); + else if (mode == "mix") RunOverflow(0x80000000u, 7, 8u * sizeof(float)); + else if (mode == "threshold") RunOverflow(0x40000000u, 0, sizeof(std::int16_t)); + else Require(false, "invalid buffer size mode"); + std::puts("audio out buffer size tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/libs/prx/libSceCesCs/Export.cpp b/core/libs/prx/libSceCesCs/Export.cpp index 25dcb94514..fe24b79623 100644 --- a/core/libs/prx/libSceCesCs/Export.cpp +++ b/core/libs/prx/libSceCesCs/Export.cpp @@ -21,10 +21,10 @@ constexpr int CES_ERROR_UNASSIGNED_CODE = static_cast(0x805C0020); constexpr int CES_ERROR_INVALID_DST_BUFFER = static_cast(0x805C0030); constexpr int CES_ERROR_DST_BUFFER_END = static_cast(0x805C0031); -constexpr uint8_t PROFILES[3] = {0, 1, 2}; -constexpr const uint8_t* CP1252_PROFILE = &PROFILES[0]; -constexpr const uint8_t* CP1251_PROFILE = &PROFILES[1]; -constexpr const uint8_t* CP1250_PROFILE = &PROFILES[2]; +constexpr uint8_t PROFILES[3][8] = {{0}, {1}, {2}}; +constexpr const uint8_t* CP1252_PROFILE = PROFILES[0]; +constexpr const uint8_t* CP1251_PROFILE = PROFILES[1]; +constexpr const uint8_t* CP1250_PROFILE = PROFILES[2]; constexpr uint16_t CP1252_HIGH[32] = { 0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008D, 0x017D, 0x008F, diff --git a/core/libs/prx/libSceCommonDialog/CommonDialog.hpp b/core/libs/prx/libSceCommonDialog/CommonDialog.hpp index f1949f6eeb..260eb371bc 100644 --- a/core/libs/prx/libSceCommonDialog/CommonDialog.hpp +++ b/core/libs/prx/libSceCommonDialog/CommonDialog.hpp @@ -248,6 +248,7 @@ inline bool ValidateCommonDialogBaseParam(const CommonDialogBaseParam& param) { } constexpr int COMMON_DIALOG_OK = 0; +constexpr int COMMON_DIALOG_ERROR_ALREADY_SYSTEM_INITIALIZED = static_cast(0x80B80002); constexpr int COMMON_DIALOG_ERROR_ALREADY_INITIALIZED = static_cast(0x80B80004); #endif diff --git a/core/libs/prx/libSceCommonDialog/Export.cpp b/core/libs/prx/libSceCommonDialog/Export.cpp index bb18755ae2..47f9a84bb1 100644 --- a/core/libs/prx/libSceCommonDialog/Export.cpp +++ b/core/libs/prx/libSceCommonDialog/Export.cpp @@ -12,7 +12,7 @@ extern "C" { int APS5_VABI sceCommonDialogInitialize(void) { bool expected = false; if (!g_initialized.compare_exchange_strong(expected, true)) { - return COMMON_DIALOG_ERROR_ALREADY_INITIALIZED; + return COMMON_DIALOG_ERROR_ALREADY_SYSTEM_INITIALIZED; } return COMMON_DIALOG_OK; } diff --git a/core/libs/prx/libSceFontFt/Export.cpp b/core/libs/prx/libSceFontFt/Export.cpp index b0a213d62f..628a572b94 100644 --- a/core/libs/prx/libSceFontFt/Export.cpp +++ b/core/libs/prx/libSceFontFt/Export.cpp @@ -78,8 +78,7 @@ int APS5_VABI sceFontFtSupportTrueType(FontLibrary library) { } int APS5_VABI sceFontFtSupportTrueTypeGx(FontLibrary library) { - NotImplemented_nid_no_patch(__func__); - return 0; + return FontFt::SupportFormats(library, {"truetype"}); } int APS5_VABI sceFontFtSupportType1(FontLibrary library) { diff --git a/core/libs/prx/libSceFreeType/CMakeLists.txt b/core/libs/prx/libSceFreeType/CMakeLists.txt index e0a3b3e116..3dd0cd443c 100644 --- a/core/libs/prx/libSceFreeType/CMakeLists.txt +++ b/core/libs/prx/libSceFreeType/CMakeLists.txt @@ -1,6 +1,7 @@ add_library(libSceFreeType SHARED EXCLUDE_FROM_ALL Export.cpp src/GuestObjects.cpp + src/Names.cpp ) target_include_directories(libSceFreeType PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceFreeType PRIVATE freetype) @@ -20,7 +21,21 @@ if(BUILD_TESTING) target_link_libraries(guest_freetype_charmap_tests PRIVATE libSceFreeType freetype libc) configure_windows_unwind(guest_freetype_charmap_tests) add_test(NAME guest_freetype_charmap COMMAND guest_freetype_charmap_tests) + add_test_executable(guest_freetype_names_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeNames.cpp) + target_include_directories(guest_freetype_names_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_names_tests PRIVATE libSceFreeType freetype libc) + configure_windows_unwind(guest_freetype_names_tests) + add_test(NAME guest_freetype_names COMMAND guest_freetype_names_tests) + add_test_executable(guest_freetype_sfnt_names_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeSfntNames.cpp) + target_include_directories(guest_freetype_sfnt_names_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_sfnt_names_tests PRIVATE libSceFreeType freetype libc) + configure_windows_unwind(guest_freetype_sfnt_names_tests) + add_test(NAME guest_freetype_sfnt_names COMMAND guest_freetype_sfnt_names_tests) if(WIN32) + set_tests_properties(guest_freetype_sfnt_names PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + set_tests_properties(guest_freetype_names PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") set_tests_properties(guest_freetype PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") set_tests_properties(guest_freetype_charmap PROPERTIES ENVIRONMENT_MODIFICATION diff --git a/core/libs/prx/libSceFreeType/Export.cpp b/core/libs/prx/libSceFreeType/Export.cpp index 0c4f8d79b1..2962f4a0b9 100644 --- a/core/libs/prx/libSceFreeType/Export.cpp +++ b/core/libs/prx/libSceFreeType/Export.cpp @@ -149,6 +149,28 @@ int APS5_VABI FT_Set_Pixel_Sizes_nid_postfix(GuestFaceRec* face, std::uint32_t p return error; } +int APS5_VABI FT_Set_Char_Size_nid_postfix(GuestFaceRec* face, std::int64_t charWidth, std::int64_t charHeight, + std::uint32_t horizontalResolution, std::uint32_t verticalResolution) { + GuestFace* owner = FaceOf(face); + if (!owner) return FT_Err_Invalid_Face_Handle; + if (!owner->host->size) return FT_Err_Invalid_Size_Handle; + if (!charWidth) charWidth = charHeight; + else if (!charHeight) charHeight = charWidth; + if (charWidth < 64) charWidth = 64; + if (charHeight < 64) charHeight = 64; + if (!horizontalResolution) horizontalResolution = verticalResolution; + else if (!verticalResolution) verticalResolution = horizontalResolution; + if (!horizontalResolution) horizontalResolution = verticalResolution = 72; + const auto maximum = std::numeric_limits::max(); + if (charWidth > (maximum - 36) / horizontalResolution || charHeight > (maximum - 36) / verticalResolution) { + throw std::runtime_error("FT_Set_Char_Size: a character size or resolution outside the host arithmetic range is not implemented"); + } + const FT_Error error = FT_Set_Char_Size(owner->host, static_cast(charWidth), static_cast(charHeight), + horizontalResolution, verticalResolution); + SyncSize(*owner); + return error; +} + std::uint32_t APS5_VABI FT_Get_Char_Index_nid_postfix(GuestFaceRec* face, std::uint64_t charcode) { const GuestFace* owner = FaceOf(face); return FT_Get_Char_Index(owner ? owner->host : nullptr, static_cast(static_cast(charcode))); @@ -205,4 +227,20 @@ std::uint64_t APS5_VABI FT_Get_Next_Char_nid_postfix(GuestFaceRec* face, std::ui return FT_Get_Next_Char(owner ? owner->host : nullptr, static_cast(static_cast(charcode)), glyphIndex); } +void APS5_VABI FT_Set_Transform_nid_postfix(GuestFaceRec* face, const GuestMatrix* matrix, const GuestVector* delta) { + GuestFace* owner = FaceOf(face); + if (!owner) return; + const auto component = [](std::int64_t value) { + if (value < std::numeric_limits::min() || value > std::numeric_limits::max()) { + throw std::runtime_error("FT_Set_Transform: a component outside the host FT_Long range is not implemented"); + } + return static_cast(value); + }; + FT_Matrix nativeMatrix{}; + FT_Vector nativeDelta{}; + if (matrix) nativeMatrix = {component(matrix->xx), component(matrix->xy), component(matrix->yx), component(matrix->yy)}; + if (delta) nativeDelta = {component(delta->x), component(delta->y)}; + FT_Set_Transform(owner->host, matrix ? &nativeMatrix : nullptr, delta ? &nativeDelta : nullptr); +} + } diff --git a/core/libs/prx/libSceFreeType/include/GuestFreeType.hpp b/core/libs/prx/libSceFreeType/include/GuestFreeType.hpp index 1a2a074378..2ee419426d 100644 --- a/core/libs/prx/libSceFreeType/include/GuestFreeType.hpp +++ b/core/libs/prx/libSceFreeType/include/GuestFreeType.hpp @@ -18,6 +18,13 @@ struct GuestVector { std::int64_t y; }; +struct GuestMatrix { + std::int64_t xx; + std::int64_t xy; + std::int64_t yx; + std::int64_t yy; +}; + struct GuestBBox { std::int64_t xMin; std::int64_t yMin; @@ -40,6 +47,15 @@ struct GuestCharMapRec { std::uint16_t encoding_id; }; +struct GuestSfntName { + std::uint16_t platform_id; + std::uint16_t encoding_id; + std::uint16_t language_id; + std::uint16_t name_id; + unsigned char* string; + std::uint32_t string_len; +}; + struct GuestSizeMetrics { std::uint16_t x_ppem; std::uint16_t y_ppem; @@ -149,9 +165,11 @@ struct GuestFaceRec { void* internal; }; +static_assert(sizeof(GuestMatrix) == 32 && offsetof(GuestMatrix, xy) == 8 && offsetof(GuestMatrix, yx) == 16 && offsetof(GuestMatrix, yy) == 24); static_assert(sizeof(GuestGeneric) == 16 && offsetof(GuestGeneric, finalizer) == 8); static_assert(sizeof(GuestBitmapSize) == 32 && offsetof(GuestBitmapSize, size) == 8); static_assert(sizeof(GuestCharMapRec) == 16 && offsetof(GuestCharMapRec, platform_id) == 12); +static_assert(sizeof(GuestSfntName) == 24 && offsetof(GuestSfntName, string) == 8 && offsetof(GuestSfntName, string_len) == 16); static_assert(sizeof(GuestSizeMetrics) == 56 && offsetof(GuestSizeMetrics, x_scale) == 8); static_assert(sizeof(GuestSizeRec) == 88 && offsetof(GuestSizeRec, metrics) == 24); static_assert(sizeof(GuestBitmap) == 40 && offsetof(GuestBitmap, buffer) == 16 && offsetof(GuestBitmap, palette) == 32); diff --git a/core/libs/prx/libSceFreeType/src/Names.cpp b/core/libs/prx/libSceFreeType/src/Names.cpp new file mode 100644 index 0000000000..23d3b8ad8f --- /dev/null +++ b/core/libs/prx/libSceFreeType/src/Names.cpp @@ -0,0 +1,55 @@ +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include FT_SFNT_NAMES_H + +#include + +using namespace FreeType; + +extern "C" { + +int APS5_VABI FT_Get_Glyph_Name_nid_postfix(GuestFaceRec* face, std::uint32_t glyphIndex, void* buffer, std::uint32_t bufferSize) { + const GuestFace* owner = FaceOf(face); + const FT_Face host = owner ? owner->host : nullptr; + if (!host || !buffer || bufferSize == 0) return FT_Get_Glyph_Name(host, glyphIndex, buffer, bufferSize); + GuestArena::HostWrite write(buffer, bufferSize); + if (static_cast(glyphIndex) >= host->num_glyphs) { + static_cast(buffer)[0] = '\0'; + return FT_Err_Invalid_Glyph_Index; + } + return FT_Get_Glyph_Name(host, glyphIndex, buffer, bufferSize); +} + +std::uint32_t APS5_VABI FT_Get_Name_Index_nid_postfix(GuestFaceRec* face, const char* glyphName) { + const GuestFace* owner = FaceOf(face); + return FT_Get_Name_Index(owner ? owner->host : nullptr, glyphName); +} + +const char* APS5_VABI FT_Get_Postscript_Name_nid_postfix(GuestFaceRec* face) { + const GuestFace* owner = FaceOf(face); + return FT_Get_Postscript_Name(owner ? owner->host : nullptr); +} + +std::uint32_t APS5_VABI FT_Get_Sfnt_Name_Count_nid_postfix(GuestFaceRec* face) { + const GuestFace* owner = FaceOf(face); + return FT_Get_Sfnt_Name_Count(owner ? owner->host : nullptr); +} + +int APS5_VABI FT_Get_Sfnt_Name_nid_postfix(GuestFaceRec* face, std::uint32_t index, GuestSfntName* name) { + const GuestFace* owner = FaceOf(face); + FT_SfntName native{}; + const FT_Error error = FT_Get_Sfnt_Name(owner ? owner->host : nullptr, index, name ? &native : nullptr); + if (!error) { + name->platform_id = native.platform_id; + name->encoding_id = native.encoding_id; + name->language_id = native.language_id; + name->name_id = native.name_id; + name->string = native.string; + name->string_len = native.string_len; + } + return error; +} + +} diff --git a/core/libs/prx/libSceGameLiveStreaming/Export.cpp b/core/libs/prx/libSceGameLiveStreaming/Export.cpp index 4fa5a57772..891a864574 100644 --- a/core/libs/prx/libSceGameLiveStreaming/Export.cpp +++ b/core/libs/prx/libSceGameLiveStreaming/Export.cpp @@ -5,6 +5,8 @@ #include "prx/libSceUserService/UserService.hpp" #include #include +#include +#include static std::mutex g_mutex; static bool g_initialized = false; @@ -271,8 +273,10 @@ int APS5_VABI sceGameLiveStreamingStopDebugBroadcast(void) { return 0; } -int APS5_VABI sceGameLiveStreamingEnableLiveStreaming(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceGameLiveStreamingEnableLiveStreaming(bool isEnableLiveStreaming) { + std::lock_guard lock(g_mutex); + if (!g_initialized) throw std::logic_error(std::string(__func__) + ": not initialized"); + (void)isEnableLiveStreaming; return 0; } diff --git a/core/libs/prx/libSceHmd/CMakeLists.txt b/core/libs/prx/libSceHmd/CMakeLists.txt index d5a15e9ca8..5bf180e478 100644 --- a/core/libs/prx/libSceHmd/CMakeLists.txt +++ b/core/libs/prx/libSceHmd/CMakeLists.txt @@ -1,11 +1,13 @@ add_library(libSceHmd SHARED EXCLUDE_FROM_ALL Export.cpp) target_include_directories(libSceHmd PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(libSceHmd PRIVATE libc) configure_windows_unwind(libSceHmd) if(BUILD_TESTING) add_test_executable(guest_hmd_tests ${LIBS_INCLUDE_DIR}/tests/GuestHmd.cpp) target_include_directories(guest_hmd_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_hmd_tests PRIVATE libSceHmd) + configure_windows_unwind(guest_hmd_tests) add_test(NAME guest_hmd COMMAND guest_hmd_tests) if(WIN32) set_tests_properties(guest_hmd PROPERTIES ENVIRONMENT_MODIFICATION diff --git a/core/libs/prx/libSceHmd/Export.cpp b/core/libs/prx/libSceHmd/Export.cpp index 3ede810dfe..d32073ea63 100644 --- a/core/libs/prx/libSceHmd/Export.cpp +++ b/core/libs/prx/libSceHmd/Export.cpp @@ -1,7 +1,12 @@ #include "Hmd.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/General.hpp" #include +#include +#include +#include +#include struct HmdDistortionCommandParameter; struct HmdWideNearDistortionCommandParameter; @@ -64,6 +69,36 @@ std::int32_t Initialize(const Hmd::InitializeParam* param) { return initialized.exchange(true) ? Hmd::AlreadyInitialized : 0; } +std::uint32_t CompiledSdkVersion() { + static const auto* processParameters = static_cast(ApplicationProcessParameters_nid_no_patch()); + std::uint32_t version; + std::memcpy(&version, processParameters + 0x10, sizeof(version)); + return version; +} + +std::int32_t CommandQueryHandle(std::int32_t handle, const char* query) { + if (handle != 0) return Hmd::InvalidHandle; + if (initialized.load()) NotImplemented_nid_no_patch((std::string(query) + " with handle 0 after sceHmdInitialize").c_str()); + if (CompiledSdkVersion() >= Hmd::StartupStateSdkVersion) NotImplemented_nid_no_patch((std::string(query) + " with handle 0 before sceHmdInitialize from SDK 4.50").c_str()); + return Hmd::NotInitialized; +} + +template +T ParameterWord(const Hmd2dVrCommandParameter* parameter, std::size_t offset) { + T value; + std::memcpy(&value, reinterpret_cast(parameter) + offset, sizeof(value)); + return value; +} + +std::int32_t Vr2dCommandArguments(const Hmd2dVrCommandParameter* parameter, const HmdCommandBuffer* commandBuffer) { + if (!parameter || !commandBuffer) return Hmd::ParameterNull; + for (const std::size_t offset : {0x00u, 0x08u, 0x20u}) { + if (ParameterWord(parameter, offset) == 0) return Hmd::ParameterNull; + } + if (ParameterWord(parameter, 0x34) != 0 || ParameterWord(parameter, 0x38) != 0) return Hmd::ParameterInvalid; + return 0; +} + } struct HmdEyeDistances { @@ -428,8 +463,7 @@ int APS5_VABI sceHmdReprojectionStopCapture(HmdCaptureInfo* info) { } bool APS5_VABI sceHmdInternalIsCommonDlgMiniAppVr2d(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return false; } int APS5_VABI sceHmdInternalMmapGetSensorCalibrationData(void* buffer, std::uint32_t size) { @@ -438,8 +472,9 @@ int APS5_VABI sceHmdInternalMmapGetSensorCalibrationData(void* buffer, std::uint } int APS5_VABI sceHmdDistortionGetCorrectionCommand(std::int32_t handle, const HmdDistortionCommandParameter* parameter, HmdCommandBuffer* commandBuffer) { - NotImplemented_nid_no_patch(__func__); - return 0; + (void)parameter; + (void)commandBuffer; + return CommandQueryHandle(handle, __func__); } int APS5_VABI sceHmdReprojectionQueryOnionBuffAlign(void) { @@ -452,13 +487,11 @@ int APS5_VABI sceHmdInternalGetDebugMode(std::uint8_t* mode) { } bool APS5_VABI sceHmdInternalIsMiniAppVr2d(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return false; } bool APS5_VABI sceHmdInternalIsCommonDlgVr2d(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return false; } int APS5_VABI sceHmdReprojectionSetOutputMinColor(float red, float green, float blue) { @@ -572,8 +605,9 @@ int APS5_VABI sceHmdInternalGetHmuSerialNumber(std::uint8_t* serialNumber) { } int APS5_VABI sceHmdDistortionGetWideNearCorrectionCommand(std::int32_t handle, const HmdWideNearDistortionCommandParameter* parameter, HmdCommandBuffer* commandBuffer) { - NotImplemented_nid_no_patch(__func__); - return 0; + (void)parameter; + (void)commandBuffer; + return CommandQueryHandle(handle, __func__); } int APS5_VABI sceHmdInternalCheckS3dPassModeAvailable(bool* available) { @@ -597,8 +631,7 @@ int APS5_VABI sceHmdReprojectionStartLiveCapture(const HmdLiveCaptureParameter* } bool APS5_VABI sceHmdInternalIsGameVr2d(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return false; } int APS5_VABI sceHmdGetAssyError(HmdAssemblyError* data) { @@ -727,8 +760,8 @@ int APS5_VABI sceHmdInternalMmapGetModeId(std::uint32_t* modeId) { } int APS5_VABI sceHmdDistortionGet2dVrCommand(std::int32_t handle, const Hmd2dVrCommandParameter* parameter, HmdCommandBuffer* commandBuffer) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (const auto result = Vr2dCommandArguments(parameter, commandBuffer); result != 0) return result; + return CommandQueryHandle(handle, __func__); } int APS5_VABI sceHmdInternalDfuSendSize(std::int32_t size) { @@ -737,8 +770,9 @@ int APS5_VABI sceHmdInternalDfuSendSize(std::int32_t size) { } int APS5_VABI sceHmdGetWideNearDistortionCorrectionCommand(std::int32_t handle, const HmdWideNearDistortionCommandParameter* parameter, HmdCommandBuffer* commandBuffer) { - NotImplemented_nid_no_patch(__func__); - return 0; + (void)parameter; + (void)commandBuffer; + return CommandQueryHandle(handle, __func__); } int APS5_VABI sceHmdReprojectionStartMultilayer2(const HmdReprojectionMultilayerParameter* layers, std::uint32_t layerCount, const HmdReprojectionSubmissionParameter* submission, std::uint64_t flipArg, const void* reserved) { @@ -747,8 +781,9 @@ int APS5_VABI sceHmdReprojectionStartMultilayer2(const HmdReprojectionMultilayer } int APS5_VABI sceHmdGetDistortionCorrectionCommand(std::int32_t handle, const HmdDistortionCommandParameter* parameter, HmdCommandBuffer* commandBuffer) { - NotImplemented_nid_no_patch(__func__); - return 0; + (void)parameter; + (void)commandBuffer; + return CommandQueryHandle(handle, __func__); } int APS5_VABI sceHmdInternalSetDeviceConnection(std::uint64_t connection) { @@ -962,8 +997,8 @@ int APS5_VABI sceHmdReprojectionQueryGarlicBuffSize(void) { } int APS5_VABI sceHmdGet2dVrCommand(std::int32_t handle, const Hmd2dVrCommandParameter* parameter, HmdCommandBuffer* commandBuffer) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (const auto result = Vr2dCommandArguments(parameter, commandBuffer); result != 0) return result; + return CommandQueryHandle(handle, __func__); } } diff --git a/core/libs/prx/libSceHmd/Hmd.hpp b/core/libs/prx/libSceHmd/Hmd.hpp index 31cf038dbb..22f812c774 100644 --- a/core/libs/prx/libSceHmd/Hmd.hpp +++ b/core/libs/prx/libSceHmd/Hmd.hpp @@ -15,6 +15,7 @@ inline constexpr std::int32_t DeviceDisconnected = static_cast(0x8 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); +inline constexpr std::uint32_t StartupStateSdkVersion = 0x04500000; enum class DeviceStatus : std::uint32_t { Ready = 0, @@ -55,6 +56,10 @@ struct HmdFieldOfView; struct HmdEyeOffset; struct HmdInertialSensorData; struct HmdAssemblyError; +struct HmdDistortionCommandParameter; +struct HmdWideNearDistortionCommandParameter; +struct Hmd2dVrCommandParameter; +struct HmdCommandBuffer; extern "C" { std::int32_t APS5_VABI sceHmdInitialize(const Hmd::InitializeParam* param); @@ -78,6 +83,16 @@ int APS5_VABI sceHmdGetFieldOfView(std::int32_t handle, HmdFieldOfView* fieldOfV int APS5_VABI sceHmdGet2DEyeOffset(std::int32_t handle, HmdEyeOffset* leftOffset, HmdEyeOffset* rightOffset); int APS5_VABI sceHmdGetInertialSensorData(std::int32_t handle, HmdInertialSensorData* data, std::uint32_t count); int APS5_VABI sceHmdGetAssyError(HmdAssemblyError* data); +int APS5_VABI sceHmdDistortionGet2dVrCommand(std::int32_t handle, const Hmd2dVrCommandParameter* parameter, HmdCommandBuffer* commandBuffer); +int APS5_VABI sceHmdDistortionGetCorrectionCommand(std::int32_t handle, const HmdDistortionCommandParameter* parameter, HmdCommandBuffer* commandBuffer); +int APS5_VABI sceHmdDistortionGetWideNearCorrectionCommand(std::int32_t handle, const HmdWideNearDistortionCommandParameter* parameter, HmdCommandBuffer* commandBuffer); +int APS5_VABI sceHmdGet2dVrCommand(std::int32_t handle, const Hmd2dVrCommandParameter* parameter, HmdCommandBuffer* commandBuffer); +int APS5_VABI sceHmdGetDistortionCorrectionCommand(std::int32_t handle, const HmdDistortionCommandParameter* parameter, HmdCommandBuffer* commandBuffer); +int APS5_VABI sceHmdGetWideNearDistortionCorrectionCommand(std::int32_t handle, const HmdWideNearDistortionCommandParameter* parameter, HmdCommandBuffer* commandBuffer); +bool APS5_VABI sceHmdInternalIsGameVr2d(void); +bool APS5_VABI sceHmdInternalIsMiniAppVr2d(void); +bool APS5_VABI sceHmdInternalIsCommonDlgVr2d(void); +bool APS5_VABI sceHmdInternalIsCommonDlgMiniAppVr2d(void); } #endif diff --git a/core/libs/prx/libSceHmd2/CMakeLists.txt b/core/libs/prx/libSceHmd2/CMakeLists.txt index fc0c32cbf4..6d7565ac4a 100644 --- a/core/libs/prx/libSceHmd2/CMakeLists.txt +++ b/core/libs/prx/libSceHmd2/CMakeLists.txt @@ -14,3 +14,15 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +if(BUILD_TESTING) + add_test_executable(guest_hmd2_gaze_tests ${LIBS_INCLUDE_DIR}/tests/GuestHmd2Gaze.cpp) + target_include_directories(guest_hmd2_gaze_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_hmd2_gaze_tests PRIVATE libSceHmd2 libc) + configure_windows_unwind(guest_hmd2_gaze_tests) + add_test(NAME guest_hmd2_gaze COMMAND guest_hmd2_gaze_tests) + if(WIN32) + set_tests_properties(guest_hmd2_gaze PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceHmd2/Export.cpp b/core/libs/prx/libSceHmd2/Export.cpp index a0dd351bdf..d744bf2741 100644 --- a/core/libs/prx/libSceHmd2/Export.cpp +++ b/core/libs/prx/libSceHmd2/Export.cpp @@ -291,14 +291,40 @@ std::int32_t APS5_VABI sceHmd2Close(std::int32_t handle) { return 0; } -int APS5_VABI sceHmd2GazeGetResult(const Hmd2::GazeResultParameter* parameter, Hmd2::GazeResult* result) { - NotImplemented_nid_no_patch(__func__); +static std::int32_t Hmd2GazeHandle(const Hmd2::GazeResultParameter* param, const void* result, std::uint32_t resultSize, const char* name) { + if (!param || !result) return Hmd2::ParameterNull; + std::uint32_t size; + std::memcpy(&size, result, sizeof(size)); + if (param->size != sizeof(Hmd2::GazeResultParameter) || size != resultSize) return Hmd2::ParameterInvalid; + std::lock_guard lock(stateMutex); + if (openHandle == 0 || param->handle != openHandle) return Hmd2::InvalidHandle; + NotImplemented_nid_no_patch(name); return 0; } -int APS5_VABI sceHmd2GazeGetResultForFoveatedRendering(const Hmd2::GazeResultParameter* parameter, Hmd2::FoveatedRenderingResult* result) { - NotImplemented_nid_no_patch(__func__); - return 0; +static void Hmd2StoreInvalidGaze(std::byte* result, std::size_t offset) { + const std::int32_t invalidIndex = -1; + const float invalidPair[2] = {-0.0f, 0.0f}; + const float invalidScalar = -1.0f; + std::memcpy(result + 4, &invalidIndex, sizeof(invalidIndex)); + std::memcpy(result + offset, invalidPair, sizeof(invalidPair)); + std::memcpy(result + offset + sizeof(invalidPair), &invalidScalar, sizeof(invalidScalar)); +} + +int APS5_VABI sceHmd2GazeGetResult(const Hmd2::GazeResultParameter* param, Hmd2::GazeResult* result) { + const std::int32_t status = Hmd2GazeHandle(param, result, 0x38, __func__); + if (status != Hmd2::InvalidHandle) return status; + auto* bytes = reinterpret_cast(result); + std::memset(bytes + 4, 0, 0x38 - 4); + Hmd2StoreInvalidGaze(bytes, 0x14); + return status; +} + +int APS5_VABI sceHmd2GazeGetResultForFoveatedRendering(const Hmd2::GazeResultParameter* param, Hmd2::FoveatedRenderingResult* result) { + const std::int32_t status = Hmd2GazeHandle(param, result, 0x18, __func__); + if (status != Hmd2::InvalidHandle) return status; + Hmd2StoreInvalidGaze(reinterpret_cast(result), 8); + return status; } std::int32_t APS5_VABI sceHmd2GetDeviceInformation(Hmd2::DeviceInformation* info) { diff --git a/core/libs/prx/libSceHttp/Export.cpp b/core/libs/prx/libSceHttp/Export.cpp index 6599dbf379..cce00df0ae 100644 --- a/core/libs/prx/libSceHttp/Export.cpp +++ b/core/libs/prx/libSceHttp/Export.cpp @@ -317,9 +317,8 @@ int APS5_VABI sceHttpSendRequest(int request_id, const void* post_data, size_t s } int APS5_VABI sceHttpSetAuthEnabled(int id, int enable) { - (void)id; - (void)enable; - return 0; + if (!HttpInitialised()) return ERROR_BEFORE_INIT; + return SetHttpOption(id, &HttpOptions::authEnabled, enable != 0) ? 0 : ERROR_INVALID_ID; } int APS5_VABI sceHttpSetAuthInfoCallback(int id, HttpAuthInfoCallback callback, void* userArg) { @@ -727,12 +726,17 @@ int APS5_VABI sceHttpAddRequestHeaderRaw(int id, const char* header, int mode) { } int APS5_VABI sceHttpRemoveRequestHeader(int id, const char* name) { - NotImplemented_nid_no_patch(__func__); + (void)id; + (void)name; return 0; } -int APS5_VABI sceHttpGetAuthEnabled(int id, int* enabled) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceHttpGetAuthEnabled(int id, int* enable) { + if (!HttpInitialised()) return ERROR_BEFORE_INIT; + if (!enable) return ERROR_INVALID_VALUE; + const auto options = FindHttpOptions(id); + if (!options) return ERROR_INVALID_ID; + *enable = options->authEnabled ? 1 : 0; return 0; } diff --git a/core/libs/prx/libSceHttp/src/Options.hpp b/core/libs/prx/libSceHttp/src/Options.hpp index beca67b395..c92168167a 100644 --- a/core/libs/prx/libSceHttp/src/Options.hpp +++ b/core/libs/prx/libSceHttp/src/Options.hpp @@ -6,6 +6,7 @@ struct HttpOptions { bool autoRedirect = true; + bool authEnabled = false; bool nonblock = false; bool acceptEncodingGzip = false; HttpEpollHandle epoll = nullptr; diff --git a/core/libs/prx/libSceHttp/src/Parse.cpp b/core/libs/prx/libSceHttp/src/Parse.cpp index 0732f62407..409e18cf1b 100644 --- a/core/libs/prx/libSceHttp/src/Parse.cpp +++ b/core/libs/prx/libSceHttp/src/Parse.cpp @@ -11,10 +11,10 @@ bool isAsciiDigit(char c) { return c >= '0' && c <= '9'; } -bool parseVersionNumber(const char* line, size_t lineLen, size_t& index, int32_t* value) { - if (index >= lineLen || !isAsciiDigit(line[index])) return false; +bool parseVersionNumber(const char* line, size_t& index, int32_t* value) { + if (!isAsciiDigit(line[index])) return false; uint32_t number = 0; - while (index < lineLen && isAsciiDigit(line[index])) { + while (isAsciiDigit(line[index])) { number = number * 10 + static_cast(line[index] - '0'); ++index; } @@ -36,11 +36,11 @@ int APS5_VABI sceHttpParseStatusLine(const char* statusLine, size_t lineLen, int if (lineLen < 8 || std::memcmp(statusLine, "HTTP/", 5) != 0) return ERROR_PARSE_HTTP_INVALID_RESPONSE; size_t index = 5; - if (!parseVersionNumber(statusLine, lineLen, index, httpMajorVer)) return ERROR_PARSE_HTTP_INVALID_RESPONSE; - if (index >= lineLen || statusLine[index] != '.') return ERROR_PARSE_HTTP_INVALID_RESPONSE; + if (!parseVersionNumber(statusLine, index, httpMajorVer)) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + if (statusLine[index] != '.') return ERROR_PARSE_HTTP_INVALID_RESPONSE; ++index; - if (!parseVersionNumber(statusLine, lineLen, index, httpMinorVer)) return ERROR_PARSE_HTTP_INVALID_RESPONSE; - if (index >= lineLen || statusLine[index] != ' ') return ERROR_PARSE_HTTP_INVALID_RESPONSE; + if (!parseVersionNumber(statusLine, index, httpMinorVer)) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + if (statusLine[index] != ' ') return ERROR_PARSE_HTTP_INVALID_RESPONSE; ++index; if (lineLen - index < 3) return ERROR_PARSE_HTTP_INVALID_RESPONSE; @@ -51,10 +51,11 @@ int APS5_VABI sceHttpParseStatusLine(const char* statusLine, size_t lineLen, int index += 3; const char* phrase = statusLine + index; - const void* lineFeed = std::memchr(phrase, '\n', lineLen - index); - if (!lineFeed) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + const size_t searched = lineLen - index + 1; + size_t length = 0; + while (length != searched && phrase[length] != '\n') ++length; + if (length == searched) return ERROR_PARSE_HTTP_INVALID_RESPONSE; - size_t length = static_cast(static_cast(lineFeed) - phrase); const size_t consumed = index + length + 1; if (consumed > static_cast(INT32_MAX)) { throw std::out_of_range("sceHttpParseStatusLine: consumed byte count exceeds int"); diff --git a/core/libs/prx/libSceHttp/src/Uri.cpp b/core/libs/prx/libSceHttp/src/Uri.cpp index b1cc8db443..2cf41f6f6b 100644 --- a/core/libs/prx/libSceHttp/src/Uri.cpp +++ b/core/libs/prx/libSceHttp/src/Uri.cpp @@ -10,6 +10,8 @@ #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libSceHttp/src/HttpErrors.hpp" +extern "C" int APS5_VABI sceHttpUriSweepPath(char* dst, const char* src, size_t srcSize); + namespace { constexpr uint32_t URI_BUILD_WITH_SCHEME = 0x01; @@ -29,6 +31,7 @@ struct UriParts { std::string_view password; std::string_view hostname; std::string path; + size_t pathSpace = 0; std::string_view query; std::string_view fragment; uint16_t port = 0; @@ -55,8 +58,8 @@ bool equalsIgnoreCase(std::string_view left, std::string_view right) { } uint16_t defaultPort(std::string_view scheme) { - if (equalsIgnoreCase(scheme, "http")) return 80; - if (equalsIgnoreCase(scheme, "https")) return 443; + if (scheme.size() >= 5 && equalsIgnoreCase(scheme.substr(0, 5), "https")) return 443; + if (scheme.size() >= 4 && equalsIgnoreCase(scheme.substr(0, 4), "http")) return 80; return 0; } @@ -64,31 +67,21 @@ bool isHostCharacter(char c) { return isAsciiAlpha(c) || isAsciiDigit(c) || c == '-' || c == '.' || c == '_'; } -std::string removeDotSegments(std::string_view input) { - std::string output; - while (!input.empty()) { - if (input.starts_with("../")) input.remove_prefix(3); - else if (input.starts_with("./")) input.remove_prefix(2); - else if (input.starts_with("/./")) input.remove_prefix(2); - else if (input == "/.") input = "/"; - else if (input.starts_with("/../") || input == "/..") { - input = input.size() == 3 ? std::string_view("/") : input.substr(3); - const size_t slash = output.rfind('/'); - output.erase(slash == std::string::npos ? 0 : slash); - } else if (input == "." || input == "..") input = {}; - else { - const size_t end = input.find('/', 1); - const size_t length = end == std::string_view::npos ? input.size() : end; - output.append(input.substr(0, length)); - input.remove_prefix(length); - } - } - return output; +bool isUserinfoCharacter(char c) { + return isAsciiAlpha(c) || isAsciiDigit(c) || std::string_view("-._~!$&'()*+,;=%:").find(c) != std::string_view::npos; +} + +std::string sweptPath(std::string_view path) { + const std::string source(path); + std::string swept(source.size() + 1, '\0'); + sceHttpUriSweepPath(swept.data(), source.c_str(), source.size() + 1); + swept.resize(std::strlen(swept.c_str())); + return swept; } size_t schemeLength(std::string_view uri) { size_t schemeEnd = 0; - if (!uri.empty() && isAsciiAlpha(uri[0])) { + if (!uri.empty() && (isAsciiAlpha(uri[0]) || uri[0] == '+' || uri[0] == '-' || uri[0] == '.')) { schemeEnd = 1; while (schemeEnd < uri.size() && (isAsciiAlpha(uri[schemeEnd]) || isAsciiDigit(uri[schemeEnd]) || uri[schemeEnd] == '+' || uri[schemeEnd] == '-' || uri[schemeEnd] == '.')) ++schemeEnd; @@ -110,49 +103,51 @@ int parseUri(std::string_view uri, UriParts& parts) { parts.opaque = !uri.starts_with("//"); if (!parts.opaque) uri.remove_prefix(2); - std::string_view authority = uri.substr(0, uri.find_first_of("/?#")); - uri.remove_prefix(authority.size()); - - const size_t at = authority.rfind('@'); - if (at != std::string_view::npos) { - const std::string_view userinfo = authority.substr(0, at); + size_t userinfoEnd = 0; + while (userinfoEnd < uri.size() && isUserinfoCharacter(uri[userinfoEnd])) ++userinfoEnd; + if (userinfoEnd < uri.size() && uri[userinfoEnd] == '@') { + const std::string_view userinfo = uri.substr(0, userinfoEnd); const size_t colon = userinfo.find(':'); parts.username = userinfo.substr(0, colon); if (colon != std::string_view::npos) parts.password = userinfo.substr(colon + 1); - authority.remove_prefix(at + 1); + uri.remove_prefix(userinfoEnd + 1); } - std::string_view port; - if (authority.starts_with('[')) { - const size_t close = authority.find(']'); + if (uri.starts_with('[')) { + const size_t close = uri.find(']'); if (close == std::string_view::npos) return ERROR_INVALID_URL; - parts.hostname = authority.substr(1, close - 1); - authority.remove_prefix(close + 1); - if (!authority.empty() && authority[0] != ':') return ERROR_INVALID_URL; - if (!authority.empty()) port = authority.substr(1); + parts.hostname = uri.substr(1, close - 1); for (const char c : parts.hostname) { if (!isHostCharacter(c) && c != ':') return ERROR_INVALID_URL; } + uri.remove_prefix(close + 1); } else { - const size_t colon = authority.find(':'); - parts.hostname = authority.substr(0, colon); - if (colon != std::string_view::npos) port = authority.substr(colon + 1); - for (const char c : parts.hostname) { - if (!isHostCharacter(c)) return ERROR_INVALID_URL; + size_t hostEnd = 0; + if (!uri.starts_with('.')) { + while (hostEnd < uri.size() && isHostCharacter(uri[hostEnd])) ++hostEnd; } + parts.hostname = uri.substr(0, hostEnd); + uri.remove_prefix(hostEnd); } - if (port.size() > 5) return ERROR_INVALID_URL; - uint32_t portValue = 0; - for (const char c : port) { - if (!isAsciiDigit(c)) return ERROR_INVALID_URL; - portValue = portValue * 10 + static_cast(c - '0'); + parts.port = defaultPort(parts.scheme); + if (uri.starts_with(':')) { + uri.remove_prefix(1); + const std::string_view port = uri.substr(0, uri.find('/')); + uri.remove_prefix(port.size()); + if (port.size() > 5) return ERROR_INVALID_URL; + uint32_t portValue = 0; + for (const char c : port) { + if (!isAsciiDigit(c)) return ERROR_INVALID_URL; + portValue = portValue * 10 + static_cast(c - '0'); + } + if (portValue > 0x10000) return ERROR_INVALID_URL; + if (!port.empty()) parts.port = static_cast(portValue); } - if (portValue > 0xFFFF) return ERROR_INVALID_URL; - parts.port = port.empty() ? defaultPort(parts.scheme) : static_cast(portValue); const std::string_view path = uri.substr(0, uri.find_first_of("?#")); - parts.path = removeDotSegments(path); + parts.path = sweptPath(path); + parts.pathSpace = path.size(); uri.remove_prefix(path.size()); if (uri.starts_with('?')) { @@ -164,8 +159,8 @@ int parseUri(std::string_view uri, UriParts& parts) { } size_t poolSize(const UriParts& parts) { - const std::string_view fields[] = {parts.scheme, parts.username, parts.password, parts.hostname, parts.path, parts.query, parts.fragment}; - size_t size = 0; + const std::string_view fields[] = {parts.scheme, parts.username, parts.password, parts.hostname, parts.query, parts.fragment}; + size_t size = parts.pathSpace + 1; for (const std::string_view field : fields) size += field.size() + 1; return size; } @@ -345,10 +340,12 @@ int APS5_VABI sceHttpUriParse(SceHttpUriElement* out, const char* src_url, void* if (const int result = parseUri(src_url, parts); result != 0) return result; const std::string_view fields[] = {parts.scheme, parts.username, parts.password, parts.hostname, parts.path, parts.query, parts.fragment}; + const size_t spaces[] = {parts.scheme.size(), parts.username.size(), parts.password.size(), parts.hostname.size(), parts.pathSpace, parts.query.size(), + parts.fragment.size()}; const size_t size = poolSize(parts); + if (write && prepare < size) return ERROR_OUT_OF_MEMORY; if (require) *require = size; if (!write) return 0; - if (prepare < size) return ERROR_OUT_OF_MEMORY; char* next = static_cast(pool); char* strings[7]; @@ -356,7 +353,7 @@ int APS5_VABI sceHttpUriParse(SceHttpUriElement* out, const char* src_url, void* strings[i] = next; if (!fields[i].empty()) std::memcpy(next, fields[i].data(), fields[i].size()); next[fields[i].size()] = '\0'; - next += fields[i].size() + 1; + next += spaces[i] + 1; } *out = {}; out->opaque = parts.opaque; @@ -376,31 +373,38 @@ int APS5_VABI sceHttpUriCopy(SceHttpUriElement* out, const SceHttpUriElement* sr const bool write = out && pool; if (!write && !require) return ERROR_INVALID_VALUE; + if (write) { + std::memset(out, 0, sizeof(*out)); + std::memset(pool, 0, prepare); + } const char* const fields[] = {src->scheme, src->hostname, src->path, src->username, src->password}; - SceHttpUriElement copy{}; - char** const slots[] = {©.scheme, ©.hostname, ©.path, ©.username, ©.password}; - if (write) std::memset(pool, 0, prepare); + const uint16_t port = src->port; + size_t lengths[5]; size_t used = 0; for (size_t i = 0; i < 5; ++i) { - const size_t length = fields[i] ? std::strlen(fields[i]) : 0; - const size_t needed = length + 1; - if (write) { - if (needed > prepare - used) { - *out = {}; - std::memset(pool, 0, prepare); - return ERROR_OUT_OF_MEMORY; - } - char* next = static_cast(pool) + used; - if (length != 0) std::memcpy(next, fields[i], length); - next[length] = '\0'; - *slots[i] = next; - } - used += needed; + lengths[i] = fields[i] ? std::strlen(fields[i]) : 0; + used += lengths[i] + 1; } - if (write) { - copy.port = src->port; - *out = copy; + if (!write) { + *require = used; + return 0; } + if (used > prepare) return ERROR_OUT_OF_MEMORY; + + char* next = static_cast(pool); + char* strings[5]; + for (size_t i = 0; i < 5; ++i) { + strings[i] = next; + if (lengths[i] != 0) std::memcpy(next, fields[i], lengths[i]); + next[lengths[i]] = '\0'; + next += lengths[i] + 1; + } + out->scheme = strings[0]; + out->hostname = strings[1]; + out->path = strings[2]; + out->username = strings[3]; + out->password = strings[4]; + out->port = port; if (require) *require = used; return 0; } diff --git a/core/libs/prx/libSceImeDialog/Export.cpp b/core/libs/prx/libSceImeDialog/Export.cpp index c9b181b025..c19acdd36a 100644 --- a/core/libs/prx/libSceImeDialog/Export.cpp +++ b/core/libs/prx/libSceImeDialog/Export.cpp @@ -17,6 +17,7 @@ constexpr std::uint32_t EndStatusUserCanceled = 1; constexpr std::uint32_t EndStatusAborted = 2; constexpr std::uint32_t MaxTextLength = 2048; constexpr int ErrorBusy = static_cast(0x80bc0001u); +constexpr int ErrorSuspending = static_cast(0x80bc0009u); constexpr int ErrorNotOpened = static_cast(0x80bc0002u); constexpr int ErrorNotInUse = static_cast(0x80bc0107u); constexpr int ErrorInvalidMaxTextLength = static_cast(0x80bc0016u); @@ -190,18 +191,20 @@ int APS5_VABI sceImeDialogForceClose(void) { } int APS5_VABI sceImeDialogGetCurrentStarState(int64_t state) { - NotImplemented_nid_no_patch(__func__); + std::lock_guard lock(g_mutex); + if (g_dialog.status == StatusNone) return ErrorNotInUse; + if (state == 0) return ErrorInvalidAddress; + *reinterpret_cast(state) = 0; return 0; } int APS5_VABI sceImeDialogInitInternal(Param* param, ExtendedParam* extended) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceImeDialogInit(param, extended); } -int APS5_VABI sceImeDialogSetPanelPosition(int32_t posx, int32_t posy) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceImeDialogSetPanelPosition(int32_t, int32_t) { + std::lock_guard lock(g_mutex); + return g_dialog.status == StatusNone ? ErrorNotInUse : ErrorSuspending; } diff --git a/core/libs/prx/libSceJemspace/Export.cpp b/core/libs/prx/libSceJemspace/Export.cpp index d81f4a5e32..5dc318d1a1 100644 --- a/core/libs/prx/libSceJemspace/Export.cpp +++ b/core/libs/prx/libSceJemspace/Export.cpp @@ -1,10 +1,58 @@ -#include -#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" extern "C" { -APS5_DUMMY_FUN +int APS5_VABI sceJemspaceCalloc() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceJemspaceCreate() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceJemspaceDestroy() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceJemspaceFree() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceJemspaceGetStats() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceJemspaceMalloc() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceJemspaceMallocUsableSize() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceJemspaceMemalign() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceJemspacePosixMemalign() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceJemspacePrintStats() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceJemspacePurge() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceJemspaceRealloc() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceJemspaceReallocalign() { + NotImplemented_nid_no_patch(__func__); +} } diff --git a/core/libs/prx/libSceJson2/Export.cpp b/core/libs/prx/libSceJson2/Export.cpp index 51c4bb6730..a84b073cd9 100644 --- a/core/libs/prx/libSceJson2/Export.cpp +++ b/core/libs/prx/libSceJson2/Export.cpp @@ -763,6 +763,11 @@ const Value* APS5_VABI _ZNK3sce4Json5Array4backEv(const Array* self) { if (self->items->empty()) throw std::out_of_range("sce::Json::Array::back: empty array"); return &self->items->back(); } +void APS5_VABI _ZN3sce4Json5Array9pop_frontEv(Array* self) { + if (self->items->empty()) throw std::out_of_range("sce::Json::Array::pop_front: empty array"); + Destroy(self->items->front()); + self->items->pop_front(); +} void APS5_VABI _ZN3sce4Json5Array8iteratorD1Ev(ArrayIterator* self) { delete self->state; self->state = nullptr; } ArrayIterator* APS5_VABI _ZN3sce4Json5Array8iteratorppEv(ArrayIterator* self) { if (self->state && self->state->position != self->state->end) ++self->state->position; diff --git a/core/libs/prx/libSceJxr/CMakeLists.txt b/core/libs/prx/libSceJxr/CMakeLists.txt new file mode 100644 index 0000000000..251402ba35 --- /dev/null +++ b/core/libs/prx/libSceJxr/CMakeLists.txt @@ -0,0 +1,15 @@ +add_library(libSceJxr SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceJxr PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceJxr) + +if(BUILD_TESTING) + add_test_executable(guest_jxr_tests ${LIBS_INCLUDE_DIR}/tests/GuestJxr.cpp) + target_include_directories(guest_jxr_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_jxr_tests PRIVATE libSceJxr libc) + configure_windows_unwind(guest_jxr_tests) + add_test(NAME guest_jxr COMMAND guest_jxr_tests) + if(WIN32) + set_tests_properties(guest_jxr PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceJxr/Export.cpp b/core/libs/prx/libSceJxr/Export.cpp index 608aedc418..de2c55aa4e 100644 --- a/core/libs/prx/libSceJxr/Export.cpp +++ b/core/libs/prx/libSceJxr/Export.cpp @@ -3,12 +3,21 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { +constexpr std::int64_t WmpErrSuccess = 0; +} + extern "C" { +void APS5_VABI free_nid_postfix(void* memory); + APS5_DUMMY_FUN -int APS5_VABI PKFree_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI PKFree_nid_postfix(void** memory) { + if (memory != nullptr) { + free_nid_postfix(*memory); + *memory = nullptr; + } + return WmpErrSuccess; } int APS5_VABI PixelFormatLookup_nid_postfix(void) { diff --git a/core/libs/prx/libSceKeyboard/Export.cpp b/core/libs/prx/libSceKeyboard/Export.cpp index c7be6fe290..9cbc7cd436 100644 --- a/core/libs/prx/libSceKeyboard/Export.cpp +++ b/core/libs/prx/libSceKeyboard/Export.cpp @@ -27,6 +27,10 @@ int APS5_VABI sceKeyboardReadState(int32_t handle, KeyboardData* data) { return Keyboard::ReadState(handle, data); } +int APS5_VABI sceKeyboardGetConnection(std::int32_t handle, bool* connected) { + return Keyboard::GetConnection(handle, connected); +} + int APS5_VABI sceKeyboardGetKey2Char(std::int32_t handle, std::int32_t arrange, std::uint8_t led, std::uint8_t modifierKey, std::uint16_t keyCode, KeyboardCharData* charData) { return Keyboard::GetKey2Char(handle, arrange, led, modifierKey, keyCode, charData); } @@ -47,8 +51,7 @@ int APS5_VABI sceKeyboardDeviceOpen(std::uint64_t deviceId, std::int32_t index, } int APS5_VABI sceKeyboardDisconnectAll(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return Keyboard::DisconnectAll(); } int APS5_VABI sceKeyboardDisconnectDevice(std::int32_t deviceId) { @@ -61,11 +64,6 @@ int APS5_VABI sceKeyboardDisconnectPort(std::int32_t first, std::int32_t second) return 0; } -int APS5_VABI sceKeyboardGetConnection(std::int32_t handle, bool* connected) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceKeyboardGetDeviceInfo(std::int32_t deviceId, KeyboardDeviceInfo* info) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceKeyboard/include/KeyboardState.hpp b/core/libs/prx/libSceKeyboard/include/KeyboardState.hpp index 11c32a5447..3552af3153 100644 --- a/core/libs/prx/libSceKeyboard/include/KeyboardState.hpp +++ b/core/libs/prx/libSceKeyboard/include/KeyboardState.hpp @@ -18,6 +18,8 @@ int Open(int userId, std::int32_t type, std::int32_t index); int Close(std::int32_t handle); int Read(std::int32_t handle, KeyboardData* data, std::int32_t num); int ReadState(std::int32_t handle, KeyboardData* data); +int GetConnection(std::int32_t handle, bool* connected); +int DisconnectAll(); int GetKey2Char(std::int32_t handle, std::int32_t arrange, std::uint32_t led, std::uint32_t modifierKey, std::uint16_t keyCode, KeyboardCharData* charData); void Publish(const KeyboardInputEvent& event); } diff --git a/core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp b/core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp index bd9cf7d8f8..954040b147 100644 --- a/core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp +++ b/core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp @@ -154,6 +154,23 @@ int ReadState(std::int32_t handle, KeyboardData* data) { return KEYBOARD_OK; } +int GetConnection(std::int32_t handle, bool* isConnected) { + std::lock_guard lock(keyboardMutex); + if (!initialized) return KEYBOARD_ERROR_NOT_INITIALIZED; + if (handle != KEYBOARD_HANDLE || !opened) return KEYBOARD_ERROR_INVALID_HANDLE; + if (isConnected == nullptr) return KEYBOARD_ERROR_INVALID_ARG; + *isConnected = connected; + return KEYBOARD_OK; +} + +int DisconnectAll() { + KeyboardInputEvent event{}; + event.connectionChange = true; + event.connected = false; + Publish(event); + return KEYBOARD_OK; +} + int GetKey2Char(std::int32_t handle, std::int32_t arrange, std::uint32_t ledState, std::uint32_t modifierKey, std::uint16_t keyCode, KeyboardCharData* charData) { { std::lock_guard lock(keyboardMutex); diff --git a/core/libs/prx/libSceKeyboard/tests/Keyboard.cpp b/core/libs/prx/libSceKeyboard/tests/Keyboard.cpp index 7c97932bff..0307b994df 100644 --- a/core/libs/prx/libSceKeyboard/tests/Keyboard.cpp +++ b/core/libs/prx/libSceKeyboard/tests/Keyboard.cpp @@ -13,6 +13,7 @@ int APS5_VABI sceKeyboardOpen(int, std::int32_t, std::int32_t, const void*); int APS5_VABI sceKeyboardClose(std::int32_t); int APS5_VABI sceKeyboardRead(std::int32_t, KeyboardData*, std::int32_t); int APS5_VABI sceKeyboardReadState(std::int32_t, KeyboardData*); +int APS5_VABI sceKeyboardGetConnection(std::int32_t, bool*); int APS5_VABI sceKeyboardGetKey2Char(std::int32_t, std::int32_t, std::uint8_t, std::uint8_t, std::uint16_t, KeyboardCharData*); int APS5_VABI sceKeyboardConnectPort(void); int APS5_VABI sceKeyboardDisconnectAll(void); @@ -44,14 +45,20 @@ static std::uint16_t Jis(std::uint32_t modifierKey, std::uint16_t keyCode, std:: int main() { KeyboardData data[16]{}; KeyboardCharData charData{}; + bool connected = false; Require(sceKeyboardOpen(1, 0, 0, nullptr) == KEYBOARD_ERROR_NOT_INITIALIZED); + Require(sceKeyboardGetConnection(KEYBOARD_HANDLE, &connected) == KEYBOARD_ERROR_NOT_INITIALIZED); Require(sceKeyboardInit() == KEYBOARD_OK); Require(sceKeyboardInit() == KEYBOARD_OK); Require(sceKeyboardOpen(1, 0, 1, nullptr) == KEYBOARD_ERROR_INVALID_ARG); Require(sceKeyboardOpen(1, 1, 0, nullptr) == KEYBOARD_ERROR_INVALID_ARG); Require(sceKeyboardGetKey2Char(KEYBOARD_HANDLE, KEYBOARD_ARRANGEMENT_101, 0, 0, 0x04, &charData) == KEYBOARD_ERROR_INVALID_HANDLE); + Require(sceKeyboardGetConnection(KEYBOARD_HANDLE, &connected) == KEYBOARD_ERROR_INVALID_HANDLE); Require(sceKeyboardOpen(1, 0, 0, nullptr) == KEYBOARD_HANDLE); Require(sceKeyboardOpen(1, 0, 0, nullptr) == KEYBOARD_ERROR_ALREADY_OPENED); + Require(sceKeyboardGetConnection(42, &connected) == KEYBOARD_ERROR_INVALID_HANDLE); + Require(sceKeyboardGetConnection(KEYBOARD_HANDLE, nullptr) == KEYBOARD_ERROR_INVALID_ARG); + Require(sceKeyboardGetConnection(KEYBOARD_HANDLE, &connected) == KEYBOARD_OK && connected); Require(sceKeyboardRead(42, data, 1) == KEYBOARD_ERROR_INVALID_HANDLE); Require(sceKeyboardRead(KEYBOARD_HANDLE, nullptr, 1) == KEYBOARD_ERROR_INVALID_ARG); Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 17) == KEYBOARD_ERROR_INVALID_ARG); @@ -96,9 +103,11 @@ int main() { window.window.event = SDL_WINDOWEVENT_CLOSE; input.HandleEvent(window, 7); Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 1 && !data[0].connected && data[0].length == 1 && data[0].key_code[0] == 0); + Require(sceKeyboardGetConnection(KEYBOARD_HANDLE, &connected) == KEYBOARD_OK && !connected); window.window.event = SDL_WINDOWEVENT_FOCUS_GAINED; input.HandleEvent(window, 7); Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 1 && data[0].connected); + Require(sceKeyboardGetConnection(KEYBOARD_HANDLE, &connected) == KEYBOARD_OK && connected); for (std::uint16_t key = 0x04; key < 0x04 + 20; ++key) { KeyboardInputEvent press{}; @@ -143,11 +152,31 @@ int main() { Require(sceKeyboardGetKey2Char(KEYBOARD_HANDLE, 2, 0, 0, 0x04, &charData) == KEYBOARD_ERROR_INVALID_ARG); Require(sceKeyboardClose(KEYBOARD_HANDLE) == KEYBOARD_OK); + Require(sceKeyboardGetConnection(KEYBOARD_HANDLE, &connected) == KEYBOARD_ERROR_INVALID_HANDLE); Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == KEYBOARD_ERROR_INVALID_HANDLE); Require(sceKeyboardReadState(KEYBOARD_HANDLE, data) == KEYBOARD_ERROR_INVALID_HANDLE); Require(sceKeyboardClose(KEYBOARD_HANDLE) == KEYBOARD_ERROR_INVALID_HANDLE); Require(sceKeyboardOpen(1, 0, 0, nullptr) == KEYBOARD_HANDLE); Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 1 && data[0].length == 1 && data[0].key_code[0] == 0); + Require(data[0].connected); + KeyboardInputEvent pressed{}; + pressed.keyCode = 0x04; + pressed.pressed = true; + KeyboardPublishInput_nid_postfix(pressed); + Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 1 && data[0].key_code[0] == 0x04); + Require(sceKeyboardDisconnectAll() == KEYBOARD_OK); + Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 1 && !data[0].connected && data[0].length == 1 && data[0].key_code[0] == 0); + Require(sceKeyboardReadState(KEYBOARD_HANDLE, data) == KEYBOARD_OK && !data[0].connected && data[0].key_code[0] == 0); + Require(sceKeyboardDisconnectAll() == KEYBOARD_OK); + Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 0); + pressed.keyCode = 0x05; + KeyboardPublishInput_nid_postfix(pressed); + Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 0); + window.window.event = SDL_WINDOWEVENT_FOCUS_LOST; + input.HandleEvent(window, 7); + window.window.event = SDL_WINDOWEVENT_FOCUS_GAINED; + input.HandleEvent(window, 7); + Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 1 && data[0].connected); Require(sceKeyboardClose(KEYBOARD_HANDLE) == KEYBOARD_OK); bool threwConnect = false; @@ -157,12 +186,4 @@ int main() { threwConnect = true; } Require(threwConnect); - - bool threwDisconnect = false; - try { - sceKeyboardDisconnectAll(); - } catch (const std::runtime_error&) { - threwDisconnect = true; - } - Require(threwDisconnect); } diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt new file mode 100644 index 0000000000..d9d266270c --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp) +target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceLibreSsl) + +if(BUILD_TESTING) + add_test_executable(guest_libressl_digest_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslDigest.cpp) + target_include_directories(guest_libressl_digest_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_digest_tests PRIVATE libSceLibreSsl libc) + configure_windows_unwind(guest_libressl_digest_tests) + add_test(NAME guest_libressl_digest COMMAND guest_libressl_digest_tests) + set_tests_properties(guest_libressl_digest PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_digest PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 93b05e0fec..670bb8911e 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -846,36 +846,6 @@ int APS5_VABI HMAC_Update_nid_postfix(void) { return 0; } -int APS5_VABI MD4_Final_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI MD4_Init_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI MD4_Update_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI MD5_Final_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI MD5_Init_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI MD5_Update_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI OBJ_cleanup_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1116,86 +1086,6 @@ int APS5_VABI RSA_verify_PKCS1_PSS_nid_postfix(void) { return 0; } -int APS5_VABI SHA1_Final_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA1_Init_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA1_Update_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA224_Final_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA224_Init_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA224_Update_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA256_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA256_Final_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA256_Init_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA256_Update_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA384_Final_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA384_Init_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA384_Update_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA512_Final_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA512_Init_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SHA512_Update_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CIPHER_get_bits_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/src/Digest.cpp b/core/libs/prx/libSceLibreSsl/src/Digest.cpp new file mode 100644 index 0000000000..e58974597e --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/Digest.cpp @@ -0,0 +1,549 @@ +#include +#include +#include +#include +#include +#include +#include "prx/libc/include/General.hpp" + +namespace { +struct Md4Context { + std::uint32_t A, B, C, D; + std::uint32_t Nl, Nh; + std::uint32_t data[16]; + std::uint32_t num; +}; + +struct Md5Context { + std::uint32_t A, B, C, D; + std::uint32_t Nl, Nh; + std::uint32_t data[16]; + std::uint32_t num; +}; + +struct Sha1Context { + std::uint32_t h[5]; + std::uint32_t Nl, Nh; + std::uint32_t data[16]; + std::uint32_t num; +}; + +struct Sha256Context { + std::uint32_t h[8]; + std::uint32_t Nl, Nh; + std::uint32_t data[16]; + std::uint32_t num, mdLength; +}; + +struct Sha512Context { + std::uint64_t h[8]; + std::uint64_t Nl, Nh; + std::uint64_t data[16]; + std::uint32_t num, mdLength; +}; + +static_assert(sizeof(Md4Context) == 92); +static_assert(sizeof(Md5Context) == 92); +static_assert(sizeof(Sha1Context) == 96); +static_assert(sizeof(Sha256Context) == 112); +static_assert(sizeof(Sha512Context) == 216); + +constexpr std::size_t Sha224Length = 28; +constexpr std::size_t Sha256Length = 32; +constexpr std::size_t Sha384Length = 48; +constexpr std::size_t Sha512Length = 64; + +std::uint32_t LoadLittle32(const unsigned char* p) { + return std::uint32_t(p[0]) | std::uint32_t(p[1]) << 8 | std::uint32_t(p[2]) << 16 | std::uint32_t(p[3]) << 24; +} + +std::uint32_t LoadBig32(const unsigned char* p) { + return std::uint32_t(p[0]) << 24 | std::uint32_t(p[1]) << 16 | std::uint32_t(p[2]) << 8 | std::uint32_t(p[3]); +} + +std::uint64_t LoadBig64(const unsigned char* p) { + return std::uint64_t(LoadBig32(p)) << 32 | LoadBig32(p + 4); +} + +void StoreLittle32(unsigned char* p, std::uint32_t value) { + for (int i = 0; i < 4; ++i) p[i] = static_cast(value >> (8 * i)); +} + +void StoreBig32(unsigned char* p, std::uint32_t value) { + for (int i = 0; i < 4; ++i) p[i] = static_cast(value >> (24 - 8 * i)); +} + +void StoreBig64(unsigned char* p, std::uint64_t value) { + StoreBig32(p, static_cast(value >> 32)); + StoreBig32(p + 4, static_cast(value)); +} + +void Md4Block(Md4Context& c, const unsigned char* block) { + std::uint32_t x[16]; + for (int i = 0; i < 16; ++i) x[i] = LoadLittle32(block + 4 * i); + std::uint32_t a = c.A, b = c.B, cc = c.C, d = c.D; + const auto f = [](std::uint32_t x1, std::uint32_t y, std::uint32_t z) { return (x1 & y) | (~x1 & z); }; + const auto g = [](std::uint32_t x1, std::uint32_t y, std::uint32_t z) { return (x1 & y) | (x1 & z) | (y & z); }; + const auto h = [](std::uint32_t x1, std::uint32_t y, std::uint32_t z) { return x1 ^ y ^ z; }; + static constexpr int shifts1[4] {3, 7, 11, 19}; + static constexpr int shifts2[4] {3, 5, 9, 13}; + static constexpr int shifts3[4] {3, 9, 11, 15}; + static constexpr int order2[16] {0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15}; + static constexpr int order3[16] {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15}; + for (int i = 0; i < 16; ++i) { + const auto t = std::rotl(a + f(b, cc, d) + x[i], shifts1[i % 4]); + a = d; d = cc; cc = b; b = t; + } + for (int i = 0; i < 16; ++i) { + const auto t = std::rotl(a + g(b, cc, d) + x[order2[i]] + 0x5a827999u, shifts2[i % 4]); + a = d; d = cc; cc = b; b = t; + } + for (int i = 0; i < 16; ++i) { + const auto t = std::rotl(a + h(b, cc, d) + x[order3[i]] + 0x6ed9eba1u, shifts3[i % 4]); + a = d; d = cc; cc = b; b = t; + } + c.A += a; c.B += b; c.C += cc; c.D += d; +} + +void Md5Block(Md5Context& c, const unsigned char* block) { + static constexpr std::uint32_t constants[64] { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391, + }; + static constexpr int shifts[16] {7, 12, 17, 22, 5, 9, 14, 20, 4, 11, 16, 23, 6, 10, 15, 21}; + std::uint32_t x[16]; + for (int i = 0; i < 16; ++i) x[i] = LoadLittle32(block + 4 * i); + std::uint32_t a = c.A, b = c.B, cc = c.C, d = c.D; + for (int i = 0; i < 64; ++i) { + std::uint32_t mix; + int index; + switch (i / 16) { + case 0: mix = (b & cc) | (~b & d); index = i; break; + case 1: mix = (d & b) | (~d & cc); index = (5 * i + 1) % 16; break; + case 2: mix = b ^ cc ^ d; index = (3 * i + 5) % 16; break; + default: mix = cc ^ (b | ~d); index = (7 * i) % 16; break; + } + const auto t = d; + d = cc; + cc = b; + b += std::rotl(a + mix + constants[i] + x[index], shifts[(i / 16) * 4 + i % 4]); + a = t; + } + c.A += a; c.B += b; c.C += cc; c.D += d; +} + +void Sha1Block(Sha1Context& c, const unsigned char* block) { + std::uint32_t w[80]; + for (int i = 0; i < 16; ++i) w[i] = LoadBig32(block + 4 * i); + for (int i = 16; i < 80; ++i) w[i] = std::rotl(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1); + std::uint32_t a = c.h[0], b = c.h[1], cc = c.h[2], d = c.h[3], e = c.h[4]; + for (int i = 0; i < 80; ++i) { + std::uint32_t f, k; + if (i < 20) { f = (b & cc) | (~b & d); k = 0x5a827999; } + else if (i < 40) { f = b ^ cc ^ d; k = 0x6ed9eba1; } + else if (i < 60) { f = (b & cc) | (b & d) | (cc & d); k = 0x8f1bbcdc; } + else { f = b ^ cc ^ d; k = 0xca62c1d6; } + const auto t = std::rotl(a, 5) + f + e + k + w[i]; + e = d; d = cc; cc = std::rotl(b, 30); b = a; a = t; + } + c.h[0] += a; c.h[1] += b; c.h[2] += cc; c.h[3] += d; c.h[4] += e; +} + +void Sha256Block(Sha256Context& c, const unsigned char* block) { + static constexpr std::uint32_t constants[64] { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, + }; + std::uint32_t w[64]; + for (int i = 0; i < 16; ++i) w[i] = LoadBig32(block + 4 * i); + for (int i = 16; i < 64; ++i) { + const auto s0 = std::rotr(w[i - 15], 7) ^ std::rotr(w[i - 15], 18) ^ (w[i - 15] >> 3); + const auto s1 = std::rotr(w[i - 2], 17) ^ std::rotr(w[i - 2], 19) ^ (w[i - 2] >> 10); + w[i] = w[i - 16] + s0 + w[i - 7] + s1; + } + std::uint32_t v[8]; + std::memcpy(v, c.h, sizeof(v)); + for (int i = 0; i < 64; ++i) { + const auto s1 = std::rotr(v[4], 6) ^ std::rotr(v[4], 11) ^ std::rotr(v[4], 25); + const auto choice = (v[4] & v[5]) ^ (~v[4] & v[6]); + const auto t1 = v[7] + s1 + choice + constants[i] + w[i]; + const auto s0 = std::rotr(v[0], 2) ^ std::rotr(v[0], 13) ^ std::rotr(v[0], 22); + const auto majority = (v[0] & v[1]) ^ (v[0] & v[2]) ^ (v[1] & v[2]); + std::memmove(v + 1, v, 7 * sizeof(v[0])); + v[4] += t1; + v[0] = t1 + s0 + majority; + } + for (int i = 0; i < 8; ++i) c.h[i] += v[i]; +} + +void Sha512Block(Sha512Context& c, const unsigned char* block) { + static constexpr std::uint64_t constants[80] { + 0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc, 0x3956c25bf348b538, + 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, 0xd807aa98a3030242, 0x12835b0145706fbe, + 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, + 0xc19bf174cf692694, 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, + 0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, 0x983e5152ee66dfab, + 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4, 0xc6e00bf33da88fc2, 0xd5a79147930aa725, + 0x06ca6351e003826f, 0x142929670a0e6e70, 0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, + 0x53380d139d95b3df, 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b, + 0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30, 0xd192e819d6ef5218, + 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, 0x19a4c116b8d2d0c8, 0x1e376c085141ab53, + 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8, 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, + 0x682e6ff3d6b2b8a3, 0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec, + 0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b, 0xca273eceea26619c, + 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, 0x06f067aa72176fba, 0x0a637dc5a2c898a6, + 0x113f9804bef90dae, 0x1b710b35131c471b, 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, + 0x431d67c49c100d4c, 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817, + }; + std::uint64_t w[80]; + for (int i = 0; i < 16; ++i) w[i] = LoadBig64(block + 8 * i); + for (int i = 16; i < 80; ++i) { + const auto s0 = std::rotr(w[i - 15], 1) ^ std::rotr(w[i - 15], 8) ^ (w[i - 15] >> 7); + const auto s1 = std::rotr(w[i - 2], 19) ^ std::rotr(w[i - 2], 61) ^ (w[i - 2] >> 6); + w[i] = w[i - 16] + s0 + w[i - 7] + s1; + } + std::uint64_t v[8]; + std::memcpy(v, c.h, sizeof(v)); + for (int i = 0; i < 80; ++i) { + const auto s1 = std::rotr(v[4], 14) ^ std::rotr(v[4], 18) ^ std::rotr(v[4], 41); + const auto choice = (v[4] & v[5]) ^ (~v[4] & v[6]); + const auto t1 = v[7] + s1 + choice + constants[i] + w[i]; + const auto s0 = std::rotr(v[0], 28) ^ std::rotr(v[0], 34) ^ std::rotr(v[0], 39); + const auto majority = (v[0] & v[1]) ^ (v[0] & v[2]) ^ (v[1] & v[2]); + std::memmove(v + 1, v, 7 * sizeof(v[0])); + v[4] += t1; + v[0] = t1 + s0 + majority; + } + for (int i = 0; i < 8; ++i) c.h[i] += v[i]; +} + +template TContext& RequireContext(TContext* context, const char* function) { + if (!context) throw std::invalid_argument(std::string(function) + ": null context"); + return *context; +} + +void RequireInput(const void* data, std::size_t length, const char* function) { + if (!data && length) throw std::invalid_argument(std::string(function) + ": null input"); +} + +void RequireDigest(const unsigned char* digest, const char* function) { + if (!digest) throw std::invalid_argument(std::string(function) + ": null digest buffer"); +} + +void RequireBuffered(std::uint32_t buffered, std::size_t block, const char* function) { + if (buffered >= block) throw std::invalid_argument(std::string(function) + ": buffered byte count is not below the block size"); +} + +template void Update32(TContext& c, const void* input, std::size_t length, TBlock block) { + constexpr std::size_t blockSize = 64; + auto* buffer = reinterpret_cast(c.data); + auto* data = static_cast(input); + const std::uint64_t bits = (std::uint64_t(c.Nh) << 32 | c.Nl) + (std::uint64_t(length) << 3); + c.Nl = static_cast(bits); + c.Nh = static_cast(bits >> 32); + if (c.num) { + const std::size_t room = blockSize - c.num; + if (length < room) { + std::memcpy(buffer + c.num, data, length); + c.num += static_cast(length); + return; + } + std::memcpy(buffer + c.num, data, room); + block(c, buffer); + data += room; + length -= room; + c.num = 0; + std::memset(buffer, 0, blockSize); + } + for (; length >= blockSize; data += blockSize, length -= blockSize) block(c, data); + if (length) { + std::memcpy(buffer, data, length); + c.num = static_cast(length); + } +} + +template void Final32(TContext& c, bool bigEndian, TBlock block) { + constexpr std::size_t blockSize = 64; + auto* buffer = reinterpret_cast(c.data); + std::size_t n = c.num; + buffer[n++] = 0x80; + if (n > blockSize - 8) { + std::memset(buffer + n, 0, blockSize - n); + n = 0; + block(c, buffer); + } + std::memset(buffer + n, 0, blockSize - 8 - n); + if (bigEndian) { + StoreBig32(buffer + 56, c.Nh); + StoreBig32(buffer + 60, c.Nl); + } else { + StoreLittle32(buffer + 56, c.Nl); + StoreLittle32(buffer + 60, c.Nh); + } + block(c, buffer); + c.num = 0; + std::memset(buffer, 0, blockSize); +} + +void Sha512Update(Sha512Context& c, const void* input, std::size_t length) { + constexpr std::size_t blockSize = 128; + auto* buffer = reinterpret_cast(c.data); + auto* data = static_cast(input); + const std::uint64_t low = c.Nl + (std::uint64_t(length) << 3); + if (low < c.Nl) ++c.Nh; + c.Nh += std::uint64_t(length) >> 61; + c.Nl = low; + if (c.num) { + const std::size_t room = blockSize - c.num; + if (length < room) { + std::memcpy(buffer + c.num, data, length); + c.num += static_cast(length); + return; + } + std::memcpy(buffer + c.num, data, room); + Sha512Block(c, buffer); + data += room; + length -= room; + c.num = 0; + } + for (; length >= blockSize; data += blockSize, length -= blockSize) Sha512Block(c, data); + if (length) { + std::memcpy(buffer, data, length); + c.num = static_cast(length); + } +} + +int Sha512Final(unsigned char* digest, Sha512Context& c) { + constexpr std::size_t blockSize = 128; + auto* buffer = reinterpret_cast(c.data); + std::size_t n = c.num; + buffer[n++] = 0x80; + if (n > blockSize - 16) { + std::memset(buffer + n, 0, blockSize - n); + n = 0; + Sha512Block(c, buffer); + } + std::memset(buffer + n, 0, blockSize - 16 - n); + StoreBig64(buffer + 112, c.Nh); + StoreBig64(buffer + 120, c.Nl); + Sha512Block(c, buffer); + if (!digest) return 0; + if (c.mdLength == Sha224Length || c.mdLength == Sha256Length) + NotImplemented_nid_no_patch("SHA512_Final: SHA-512/224 and SHA-512/256 digest lengths"); + if (c.mdLength != Sha384Length && c.mdLength != Sha512Length) return 0; + for (std::size_t i = 0; i < c.mdLength / 8; ++i) StoreBig64(digest + 8 * i, c.h[i]); + return 1; +} + +int Sha256Final(unsigned char* digest, Sha256Context& c, const char* function) { + RequireDigest(digest, function); + RequireBuffered(c.num, 64, function); + Final32(c, true, Sha256Block); + if (c.mdLength > Sha256Length) return 0; + for (std::size_t i = 0; i < c.mdLength / 4; ++i) StoreBig32(digest + 4 * i, c.h[i]); + return 1; +} +} + +extern "C" { + +int APS5_VABI MD4_Init_nid_postfix(Md4Context* context) { + auto& c = RequireContext(context, __func__); + std::memset(&c, 0, sizeof(c)); + c.A = 0x67452301; + c.B = 0xefcdab89; + c.C = 0x98badcfe; + c.D = 0x10325476; + return 1; +} + +int APS5_VABI MD4_Update_nid_postfix(Md4Context* context, const void* data, std::size_t length) { + auto& c = RequireContext(context, __func__); + if (!length) return 1; + RequireInput(data, length, __func__); + RequireBuffered(c.num, 64, __func__); + Update32(c, data, length, Md4Block); + return 1; +} + +int APS5_VABI MD4_Final_nid_postfix(unsigned char* digest, Md4Context* context) { + auto& c = RequireContext(context, __func__); + RequireDigest(digest, __func__); + RequireBuffered(c.num, 64, __func__); + Final32(c, false, Md4Block); + StoreLittle32(digest, c.A); + StoreLittle32(digest + 4, c.B); + StoreLittle32(digest + 8, c.C); + StoreLittle32(digest + 12, c.D); + return 1; +} + +int APS5_VABI MD5_Init_nid_postfix(Md5Context* context) { + auto& c = RequireContext(context, __func__); + std::memset(&c, 0, sizeof(c)); + c.A = 0x67452301; + c.B = 0xefcdab89; + c.C = 0x98badcfe; + c.D = 0x10325476; + return 1; +} + +int APS5_VABI MD5_Update_nid_postfix(Md5Context* context, const void* data, std::size_t length) { + auto& c = RequireContext(context, __func__); + if (!length) return 1; + RequireInput(data, length, __func__); + RequireBuffered(c.num, 64, __func__); + Update32(c, data, length, Md5Block); + return 1; +} + +int APS5_VABI MD5_Final_nid_postfix(unsigned char* digest, Md5Context* context) { + auto& c = RequireContext(context, __func__); + RequireDigest(digest, __func__); + RequireBuffered(c.num, 64, __func__); + Final32(c, false, Md5Block); + StoreLittle32(digest, c.A); + StoreLittle32(digest + 4, c.B); + StoreLittle32(digest + 8, c.C); + StoreLittle32(digest + 12, c.D); + return 1; +} + +int APS5_VABI SHA1_Init_nid_postfix(Sha1Context* context) { + auto& c = RequireContext(context, __func__); + std::memset(&c, 0, sizeof(c)); + c.h[0] = 0x67452301; + c.h[1] = 0xefcdab89; + c.h[2] = 0x98badcfe; + c.h[3] = 0x10325476; + c.h[4] = 0xc3d2e1f0; + return 1; +} + +int APS5_VABI SHA1_Update_nid_postfix(Sha1Context* context, const void* data, std::size_t length) { + auto& c = RequireContext(context, __func__); + if (!length) return 1; + RequireInput(data, length, __func__); + RequireBuffered(c.num, 64, __func__); + Update32(c, data, length, Sha1Block); + return 1; +} + +int APS5_VABI SHA1_Final_nid_postfix(unsigned char* digest, Sha1Context* context) { + auto& c = RequireContext(context, __func__); + RequireDigest(digest, __func__); + RequireBuffered(c.num, 64, __func__); + Final32(c, true, Sha1Block); + for (int i = 0; i < 5; ++i) StoreBig32(digest + 4 * i, c.h[i]); + return 1; +} + +int APS5_VABI SHA224_Init_nid_postfix(Sha256Context* context) { + static constexpr std::uint32_t initial[8] {0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4}; + auto& c = RequireContext(context, __func__); + std::memset(&c, 0, sizeof(c)); + std::memcpy(c.h, initial, sizeof(initial)); + c.mdLength = Sha224Length; + return 1; +} + +int APS5_VABI SHA256_Init_nid_postfix(Sha256Context* context) { + static constexpr std::uint32_t initial[8] {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19}; + auto& c = RequireContext(context, __func__); + std::memset(&c, 0, sizeof(c)); + std::memcpy(c.h, initial, sizeof(initial)); + c.mdLength = Sha256Length; + return 1; +} + +int APS5_VABI SHA256_Update_nid_postfix(Sha256Context* context, const void* data, std::size_t length) { + auto& c = RequireContext(context, __func__); + if (!length) return 1; + RequireInput(data, length, __func__); + RequireBuffered(c.num, 64, __func__); + Update32(c, data, length, Sha256Block); + return 1; +} + +int APS5_VABI SHA224_Update_nid_postfix(Sha256Context* context, const void* data, std::size_t length) { + return SHA256_Update_nid_postfix(context, data, length); +} + +int APS5_VABI SHA256_Final_nid_postfix(unsigned char* digest, Sha256Context* context) { + return Sha256Final(digest, RequireContext(context, __func__), __func__); +} + +int APS5_VABI SHA224_Final_nid_postfix(unsigned char* digest, Sha256Context* context) { + return Sha256Final(digest, RequireContext(context, __func__), __func__); +} + +unsigned char* APS5_VABI SHA256_nid_postfix(const unsigned char* data, std::size_t length, unsigned char* digest) { + RequireInput(data, length, __func__); + if (!digest) NotImplemented_nid_no_patch("SHA256: null digest buffer"); + Sha256Context c; + SHA256_Init_nid_postfix(&c); + if (length) Update32(c, data, length, Sha256Block); + Sha256Final(digest, c, __func__); + return digest; +} + +int APS5_VABI SHA384_Init_nid_postfix(Sha512Context* context) { + static constexpr std::uint64_t initial[8] { + 0xcbbb9d5dc1059ed8, 0x629a292a367cd507, 0x9159015a3070dd17, 0x152fecd8f70e5939, + 0x67332667ffc00b31, 0x8eb44a8768581511, 0xdb0c2e0d64f98fa7, 0x47b5481dbefa4fa4, + }; + auto& c = RequireContext(context, __func__); + std::memset(&c, 0, sizeof(c)); + std::memcpy(c.h, initial, sizeof(initial)); + c.mdLength = Sha384Length; + return 1; +} + +int APS5_VABI SHA512_Init_nid_postfix(Sha512Context* context) { + static constexpr std::uint64_t initial[8] { + 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179, + }; + auto& c = RequireContext(context, __func__); + std::memset(&c, 0, sizeof(c)); + std::memcpy(c.h, initial, sizeof(initial)); + c.mdLength = Sha512Length; + return 1; +} + +int APS5_VABI SHA512_Update_nid_postfix(Sha512Context* context, const void* data, std::size_t length) { + auto& c = RequireContext(context, __func__); + if (!length) return 1; + RequireInput(data, length, __func__); + RequireBuffered(c.num, 128, __func__); + Sha512Update(c, data, length); + return 1; +} + +int APS5_VABI SHA384_Update_nid_postfix(Sha512Context* context, const void* data, std::size_t length) { + return SHA512_Update_nid_postfix(context, data, length); +} + +int APS5_VABI SHA512_Final_nid_postfix(unsigned char* digest, Sha512Context* context) { + auto& c = RequireContext(context, __func__); + RequireBuffered(c.num, 128, __func__); + return Sha512Final(digest, c); +} + +int APS5_VABI SHA384_Final_nid_postfix(unsigned char* digest, Sha512Context* context) { + return SHA512_Final_nid_postfix(digest, context); +} + +} diff --git a/core/libs/prx/libSceLibreSsl3/CMakeLists.txt b/core/libs/prx/libSceLibreSsl3/CMakeLists.txt new file mode 100644 index 0000000000..1a3d213378 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl3/CMakeLists.txt @@ -0,0 +1,22 @@ +add_library(libSceLibreSsl3 SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp) +target_include_directories(libSceLibreSsl3 PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(libSceLibreSsl3 PRIVATE libSceLibreSsl) +configure_windows_unwind(libSceLibreSsl3) + +if(BUILD_TESTING) + add_test_executable(guest_crypto_memcmp_tests ${LIBS_INCLUDE_DIR}/tests/GuestCryptoMemcmp.cpp) + target_include_directories(guest_crypto_memcmp_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_crypto_memcmp_tests PRIVATE libSceLibreSsl3) + add_test(NAME guest_crypto_memcmp COMMAND guest_crypto_memcmp_tests) + set_tests_properties(guest_crypto_memcmp PROPERTIES TIMEOUT 20) + add_test_executable(guest_libressl3_digest_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSsl3Digest.cpp) + target_include_directories(guest_libressl3_digest_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl3_digest_tests PRIVATE libSceLibreSsl3 libc) + configure_windows_unwind(guest_libressl3_digest_tests) + add_test(NAME guest_libressl3_digest COMMAND guest_libressl3_digest_tests) + set_tests_properties(guest_libressl3_digest PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_crypto_memcmp guest_libressl3_digest PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceLibreSsl3/Export.cpp b/core/libs/prx/libSceLibreSsl3/Export.cpp index d3289b659b..623b05d144 100644 --- a/core/libs/prx/libSceLibreSsl3/Export.cpp +++ b/core/libs/prx/libSceLibreSsl3/Export.cpp @@ -31,9 +31,13 @@ int APS5_VABI BIO_vfree_nid_postfix(void) { return 0; } -int APS5_VABI CRYPTO_memcmp_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI CRYPTO_memcmp_nid_postfix(const void* first, const void* second, std::size_t length) { + const auto* firstBytes = static_cast(first); + const auto* secondBytes = static_cast(second); + unsigned int difference = 0; + for (std::size_t index = 0; index < length; ++index) + difference |= firstBytes[index] ^ secondBytes[index]; + return static_cast(difference); } int APS5_VABI DH_get0_key_nid_postfix(void) { @@ -216,11 +220,6 @@ int APS5_VABI EVP_sha224_nid_postfix(void) { return 0; } -int APS5_VABI MD5_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI OBJ_cmp_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -281,11 +280,6 @@ int APS5_VABI RSA_up_ref_nid_postfix(void) { return 0; } -int APS5_VABI SHA1_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_add0_chain_cert_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl3/src/Digest.cpp b/core/libs/prx/libSceLibreSsl3/src/Digest.cpp new file mode 100644 index 0000000000..373654f3d5 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl3/src/Digest.cpp @@ -0,0 +1,30 @@ +#include +#include "prx/libc/include/General.hpp" + +extern "C" { +int APS5_VABI MD5_Init_nid_postfix(void* context); +int APS5_VABI MD5_Update_nid_postfix(void* context, const void* data, std::size_t length); +int APS5_VABI MD5_Final_nid_postfix(unsigned char* digest, void* context); +int APS5_VABI SHA1_Init_nid_postfix(void* context); +int APS5_VABI SHA1_Update_nid_postfix(void* context, const void* data, std::size_t length); +int APS5_VABI SHA1_Final_nid_postfix(unsigned char* digest, void* context); + +unsigned char* APS5_VABI MD5_nid_postfix(const unsigned char* data, std::size_t length, unsigned char* digest) { + if (!digest) NotImplemented_nid_no_patch("MD5: null digest buffer"); + alignas(8) unsigned char context[92]; + MD5_Init_nid_postfix(context); + MD5_Update_nid_postfix(context, data, length); + MD5_Final_nid_postfix(digest, context); + return digest; +} + +unsigned char* APS5_VABI SHA1_nid_postfix(const unsigned char* data, std::size_t length, unsigned char* digest) { + if (!digest) NotImplemented_nid_no_patch("SHA1: null digest buffer"); + alignas(8) unsigned char context[96]; + SHA1_Init_nid_postfix(context); + SHA1_Update_nid_postfix(context, data, length); + SHA1_Final_nid_postfix(digest, context); + return digest; +} + +} diff --git a/core/libs/prx/libSceNKWebKit/Export.cpp b/core/libs/prx/libSceNKWebKit/Export.cpp index d81f4a5e32..a6caa403e8 100644 --- a/core/libs/prx/libSceNKWebKit/Export.cpp +++ b/core/libs/prx/libSceNKWebKit/Export.cpp @@ -5,6 +5,754 @@ extern "C" { -APS5_DUMMY_FUN +int APS5_VABI sceNKWebKitUnknown____URPJNZ18_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("++-URPJNZ18", sceNKWebKitUnknown____URPJNZ18_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown___67uYkhqBU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("++67uYkhqBU", sceNKWebKitUnknown___67uYkhqBU_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown___8RYr2gjrw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("++8RYr2gjrw", sceNKWebKitUnknown___8RYr2gjrw_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown___BntMcao1o_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("++BntMcao1o", sceNKWebKitUnknown___BntMcao1o_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown___R8IXi10PI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("++R8IXi10PI", sceNKWebKitUnknown___R8IXi10PI_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown___j2h_QO22w_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("++j2h+QO22w", sceNKWebKitUnknown___j2h_QO22w_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown____7XwmYLnI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+-+7XwmYLnI", sceNKWebKitUnknown____7XwmYLnI_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown___wmVyLhgm0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+-wmVyLhgm0", sceNKWebKitUnknown___wmVyLhgm0_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__0QEt8ZQWgg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+0QEt8ZQWgg", sceNKWebKitUnknown__0QEt8ZQWgg_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__0YNxcA_duk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+0YNxcA-duk", sceNKWebKitUnknown__0YNxcA_duk_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__0ZZzGYxx_I_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+0ZZzGYxx+I", sceNKWebKitUnknown__0ZZzGYxx_I_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__0rdnkSicqk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+0rdnkSicqk", sceNKWebKitUnknown__0rdnkSicqk_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__1Ex5GCP8Xs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+1Ex5GCP8Xs", sceNKWebKitUnknown__1Ex5GCP8Xs_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__1FfZ_0sxvQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+1FfZ+0sxvQ", sceNKWebKitUnknown__1FfZ_0sxvQ_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__1ObAtzCrYE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+1ObAtzCrYE", sceNKWebKitUnknown__1ObAtzCrYE_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__1n_h4FUu9E_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+1n+h4FUu9E", sceNKWebKitUnknown__1n_h4FUu9E_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__26aZpK535w_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+26aZpK535w", sceNKWebKitUnknown__26aZpK535w_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__2XuBaAXb_4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+2XuBaAXb+4", sceNKWebKitUnknown__2XuBaAXb_4_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__2rTX2KM7Ms_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+2rTX2KM7Ms", sceNKWebKitUnknown__2rTX2KM7Ms_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__31dbB3S_6Y_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+31dbB3S-6Y", sceNKWebKitUnknown__31dbB3S_6Y_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__3p6wRzzzlQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+3p6wRzzzlQ", sceNKWebKitUnknown__3p6wRzzzlQ_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__4_GNOUIBt0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+4-GNOUIBt0", sceNKWebKitUnknown__4_GNOUIBt0_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__45BiDnCTxw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+45BiDnCTxw", sceNKWebKitUnknown__45BiDnCTxw_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__4lVmXsIwqM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+4lVmXsIwqM", sceNKWebKitUnknown__4lVmXsIwqM_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__5E2YZZIstM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+5E2YZZIstM", sceNKWebKitUnknown__5E2YZZIstM_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__5V_UOk8zS4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+5V-UOk8zS4", sceNKWebKitUnknown__5V_UOk8zS4_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__63IB3UdMOY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+63IB3UdMOY", sceNKWebKitUnknown__63IB3UdMOY_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__6boXsT6XFA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+6boXsT6XFA", sceNKWebKitUnknown__6boXsT6XFA_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__79w68212vg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+79w68212vg", sceNKWebKitUnknown__79w68212vg_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__7HpqmfJz48_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+7HpqmfJz48", sceNKWebKitUnknown__7HpqmfJz48_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__8T1y8Y_lgQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+8T1y8Y-lgQ", sceNKWebKitUnknown__8T1y8Y_lgQ_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__8VuTEFp13s_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+8VuTEFp13s", sceNKWebKitUnknown__8VuTEFp13s_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__9TKACifR2g_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+9TKACifR2g", sceNKWebKitUnknown__9TKACifR2g_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__9ZBRWNf4og_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+9ZBRWNf4og", sceNKWebKitUnknown__9ZBRWNf4og_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__9us0nrDaCU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+9us0nrDaCU", sceNKWebKitUnknown__9us0nrDaCU_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__AIEKk6K05M_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+AIEKk6K05M", sceNKWebKitUnknown__AIEKk6K05M_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__Abk7CfTivU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+Abk7CfTivU", sceNKWebKitUnknown__Abk7CfTivU_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__AnsPWo12m8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+AnsPWo12m8", sceNKWebKitUnknown__AnsPWo12m8_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__BDqCDede1o_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+BDqCDede1o", sceNKWebKitUnknown__BDqCDede1o_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__BSUP6XSeAw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+BSUP6XSeAw", sceNKWebKitUnknown__BSUP6XSeAw_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__BZCJi3OIiQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+BZCJi3OIiQ", sceNKWebKitUnknown__BZCJi3OIiQ_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__ByVxWl1pW8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+ByVxWl1pW8", sceNKWebKitUnknown__ByVxWl1pW8_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__CgSI_4D534_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+CgSI-4D534", sceNKWebKitUnknown__CgSI_4D534_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__CpPA9_CKUA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+CpPA9+CKUA", sceNKWebKitUnknown__CpPA9_CKUA_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__DcUw5YwkSY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+DcUw5YwkSY", sceNKWebKitUnknown__DcUw5YwkSY_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__DpDBrSZWUc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+DpDBrSZWUc", sceNKWebKitUnknown__DpDBrSZWUc_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__E3D_W4kpl8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+E3D-W4kpl8", sceNKWebKitUnknown__E3D_W4kpl8_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__EN6MgNmVv4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+EN6MgNmVv4", sceNKWebKitUnknown__EN6MgNmVv4_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__ErgockOJwc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+ErgockOJwc", sceNKWebKitUnknown__ErgockOJwc_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__FKudG3uS8A_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+FKudG3uS8A", sceNKWebKitUnknown__FKudG3uS8A_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__FWUl_T8yG8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+FWUl-T8yG8", sceNKWebKitUnknown__FWUl_T8yG8_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__Fca5Eyr4BU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+Fca5Eyr4BU", sceNKWebKitUnknown__Fca5Eyr4BU_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__FcvpbbdrZM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+FcvpbbdrZM", sceNKWebKitUnknown__FcvpbbdrZM_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__FvUhrQaiJQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+FvUhrQaiJQ", sceNKWebKitUnknown__FvUhrQaiJQ_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__GOwQvxqztE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+GOwQvxqztE", sceNKWebKitUnknown__GOwQvxqztE_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__Gp0M0sPj9I_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+Gp0M0sPj9I", sceNKWebKitUnknown__Gp0M0sPj9I_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__H_bnpXdWtk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+H-bnpXdWtk", sceNKWebKitUnknown__H_bnpXdWtk_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__H5PTMieXNE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+H5PTMieXNE", sceNKWebKitUnknown__H5PTMieXNE_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__HuPyGuX_jY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+HuPyGuX+jY", sceNKWebKitUnknown__HuPyGuX_jY_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__IMGO_dXHis_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+IMGO+dXHis", sceNKWebKitUnknown__IMGO_dXHis_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__ImJ026_DAw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+ImJ026-DAw", sceNKWebKitUnknown__ImJ026_DAw_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__IoqHgIw0PM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+IoqHgIw0PM", sceNKWebKitUnknown__IoqHgIw0PM_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__JnngM7WyHI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+JnngM7WyHI", sceNKWebKitUnknown__JnngM7WyHI_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__Kb7j57jDzs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+Kb7j57jDzs", sceNKWebKitUnknown__Kb7j57jDzs_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__Kx_IV_21hk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+Kx-IV+21hk", sceNKWebKitUnknown__Kx_IV_21hk_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__LUabdo811U_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+LUabdo811U", sceNKWebKitUnknown__LUabdo811U_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__LiOmqon2ZI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+LiOmqon2ZI", sceNKWebKitUnknown__LiOmqon2ZI_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__LoAenYdWlM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+LoAenYdWlM", sceNKWebKitUnknown__LoAenYdWlM_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__LrmM3KN3N8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+LrmM3KN3N8", sceNKWebKitUnknown__LrmM3KN3N8_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__MzGFuNN348_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+MzGFuNN348", sceNKWebKitUnknown__MzGFuNN348_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__N_GwpCV4yk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+N-GwpCV4yk", sceNKWebKitUnknown__N_GwpCV4yk_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__N6UhRaLrJE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+N6UhRaLrJE", sceNKWebKitUnknown__N6UhRaLrJE_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__NGrXdCa0q8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+NGrXdCa0q8", sceNKWebKitUnknown__NGrXdCa0q8_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__NiLTGGKF_E_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+NiLTGGKF+E", sceNKWebKitUnknown__NiLTGGKF_E_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__Nqfy8HVG84_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+Nqfy8HVG84", sceNKWebKitUnknown__Nqfy8HVG84_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__NuhuZH75ak_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+NuhuZH75ak", sceNKWebKitUnknown__NuhuZH75ak_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__OCxbaKiBUo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+OCxbaKiBUo", sceNKWebKitUnknown__OCxbaKiBUo_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__OOjOkuWL6g_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+OOjOkuWL6g", sceNKWebKitUnknown__OOjOkuWL6g_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__OkTn3_PBxM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+OkTn3+PBxM", sceNKWebKitUnknown__OkTn3_PBxM_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__PDx3sfB_l4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+PDx3sfB-l4", sceNKWebKitUnknown__PDx3sfB_l4_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__POsWAWjXo8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+POsWAWjXo8", sceNKWebKitUnknown__POsWAWjXo8_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__PeIrQMgYpw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+PeIrQMgYpw", sceNKWebKitUnknown__PeIrQMgYpw_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__Q7xLvpc0PA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+Q7xLvpc0PA", sceNKWebKitUnknown__Q7xLvpc0PA_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__QL3zQ6ZNy4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+QL3zQ6ZNy4", sceNKWebKitUnknown__QL3zQ6ZNy4_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__QqzEnc5c80_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+QqzEnc5c80", sceNKWebKitUnknown__QqzEnc5c80_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__R0GXLrsPM0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+R0GXLrsPM0", sceNKWebKitUnknown__R0GXLrsPM0_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__R2eXISeSvw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+R2eXISeSvw", sceNKWebKitUnknown__R2eXISeSvw_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__RkwSDm5kGU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+RkwSDm5kGU", sceNKWebKitUnknown__RkwSDm5kGU_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__RtBREW92M0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+RtBREW92M0", sceNKWebKitUnknown__RtBREW92M0_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__S72K_fa2_8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+S72K+fa2-8", sceNKWebKitUnknown__S72K_fa2_8_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__S7ftAAWhoQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+S7ftAAWhoQ", sceNKWebKitUnknown__S7ftAAWhoQ_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__SHzgUiuQDE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+SHzgUiuQDE", sceNKWebKitUnknown__SHzgUiuQDE_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__Sgy63JgEgQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+Sgy63JgEgQ", sceNKWebKitUnknown__Sgy63JgEgQ_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__Sozphy1Wa8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+Sozphy1Wa8", sceNKWebKitUnknown__Sozphy1Wa8_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__TPntL1OK80_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+TPntL1OK80", sceNKWebKitUnknown__TPntL1OK80_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__TT_AplhQWA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+TT+AplhQWA", sceNKWebKitUnknown__TT_AplhQWA_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__TjNmuhxdBQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+TjNmuhxdBQ", sceNKWebKitUnknown__TjNmuhxdBQ_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__U_nxwdrnDY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+U-nxwdrnDY", sceNKWebKitUnknown__U_nxwdrnDY_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__UKI_MUuiCU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+UKI-MUuiCU", sceNKWebKitUnknown__UKI_MUuiCU_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__VvktcfIouQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+VvktcfIouQ", sceNKWebKitUnknown__VvktcfIouQ_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__WS2yRv5aBI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+WS2yRv5aBI", sceNKWebKitUnknown__WS2yRv5aBI_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__XZfnHByVO0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+XZfnHByVO0", sceNKWebKitUnknown__XZfnHByVO0_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__YBfWz5udq4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+YBfWz5udq4", sceNKWebKitUnknown__YBfWz5udq4_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__YPwmEWTZwE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+YPwmEWTZwE", sceNKWebKitUnknown__YPwmEWTZwE_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__YYEOsi3IMk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+YYEOsi3IMk", sceNKWebKitUnknown__YYEOsi3IMk_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__YyqQTUIQJQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+YyqQTUIQJQ", sceNKWebKitUnknown__YyqQTUIQJQ_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__ZgGbFykhls_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+ZgGbFykhls", sceNKWebKitUnknown__ZgGbFykhls_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__ZwjLJ74Y38_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+ZwjLJ74Y38", sceNKWebKitUnknown__ZwjLJ74Y38_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__b1xwU7aUwU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+b1xwU7aUwU", sceNKWebKitUnknown__b1xwU7aUwU_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__b5TfVQ3_4s_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+b5TfVQ3-4s", sceNKWebKitUnknown__b5TfVQ3_4s_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__bDb7iby5NI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+bDb7iby5NI", sceNKWebKitUnknown__bDb7iby5NI_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__baqz7ahOng_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+baqz7ahOng", sceNKWebKitUnknown__baqz7ahOng_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__bvtn3pCtek_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+bvtn3pCtek", sceNKWebKitUnknown__bvtn3pCtek_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__byORTSkWQo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+byORTSkWQo", sceNKWebKitUnknown__byORTSkWQo_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__dAwquupevQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+dAwquupevQ", sceNKWebKitUnknown__dAwquupevQ_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__dTlroLZao8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+dTlroLZao8", sceNKWebKitUnknown__dTlroLZao8_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__eVN0UdRT8c_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+eVN0UdRT8c", sceNKWebKitUnknown__eVN0UdRT8c_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__fTKZiGqo8E_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+fTKZiGqo8E", sceNKWebKitUnknown__fTKZiGqo8E_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__feeFP8Y2_c_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+feeFP8Y2+c", sceNKWebKitUnknown__feeFP8Y2_c_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__iLBOBrdx5A_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+iLBOBrdx5A", sceNKWebKitUnknown__iLBOBrdx5A_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__iLdIVuhDhY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+iLdIVuhDhY", sceNKWebKitUnknown__iLdIVuhDhY_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__jG4wqqA8yg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+jG4wqqA8yg", sceNKWebKitUnknown__jG4wqqA8yg_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__jLvgfDA97c_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+jLvgfDA97c", sceNKWebKitUnknown__jLvgfDA97c_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__ji7IBGwnCM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+ji7IBGwnCM", sceNKWebKitUnknown__ji7IBGwnCM_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__k3JGU_UUXo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+k3JGU-UUXo", sceNKWebKitUnknown__k3JGU_UUXo_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__kX_CdbguB0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+kX-CdbguB0", sceNKWebKitUnknown__kX_CdbguB0_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__keSg8ay8cE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+keSg8ay8cE", sceNKWebKitUnknown__keSg8ay8cE_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__lI9zCCS_HQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+lI9zCCS-HQ", sceNKWebKitUnknown__lI9zCCS_HQ_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__lwLqZJLo3g_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+lwLqZJLo3g", sceNKWebKitUnknown__lwLqZJLo3g_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__mS029ncrbo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+mS029ncrbo", sceNKWebKitUnknown__mS029ncrbo_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__mYy22Ucg7Y_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+mYy22Ucg7Y", sceNKWebKitUnknown__mYy22Ucg7Y_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__nHWcLWOmb4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+nHWcLWOmb4", sceNKWebKitUnknown__nHWcLWOmb4_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__nSg5kHtXkg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+nSg5kHtXkg", sceNKWebKitUnknown__nSg5kHtXkg_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__nc3IouJMEA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+nc3IouJMEA", sceNKWebKitUnknown__nc3IouJMEA_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__ndmq8LhAjA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+ndmq8LhAjA", sceNKWebKitUnknown__ndmq8LhAjA_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__nrEMsyYHiw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+nrEMsyYHiw", sceNKWebKitUnknown__nrEMsyYHiw_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__oJHrfcyJJM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+oJHrfcyJJM", sceNKWebKitUnknown__oJHrfcyJJM_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__ovAafdG0O8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+ovAafdG0O8", sceNKWebKitUnknown__ovAafdG0O8_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__pWGccNXto8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+pWGccNXto8", sceNKWebKitUnknown__pWGccNXto8_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__q0GELad_mY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+q0GELad-mY", sceNKWebKitUnknown__q0GELad_mY_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__qXuNlEFSRQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+qXuNlEFSRQ", sceNKWebKitUnknown__qXuNlEFSRQ_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__qsTbJFS86A_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+qsTbJFS86A", sceNKWebKitUnknown__qsTbJFS86A_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__r_TJDA2yIM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+r+TJDA2yIM", sceNKWebKitUnknown__r_TJDA2yIM_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__r0UH41I1dI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+r0UH41I1dI", sceNKWebKitUnknown__r0UH41I1dI_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__r8OGkuL4dc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+r8OGkuL4dc", sceNKWebKitUnknown__r8OGkuL4dc_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__rFA8ZR_qaI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+rFA8ZR+qaI", sceNKWebKitUnknown__rFA8ZR_qaI_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__rPvFDsPbXM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+rPvFDsPbXM", sceNKWebKitUnknown__rPvFDsPbXM_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__sUvE9LF80k_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+sUvE9LF80k", sceNKWebKitUnknown__sUvE9LF80k_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__suuwfNT5Oc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+suuwfNT5Oc", sceNKWebKitUnknown__suuwfNT5Oc_nid_no_patch); + +int APS5_VABI sceNKWebKitUnknown__t_MaTLADe4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+t-MaTLADe4", sceNKWebKitUnknown__t_MaTLADe4_nid_no_patch); } diff --git a/core/libs/prx/libSceNetCtl/Export.cpp b/core/libs/prx/libSceNetCtl/Export.cpp index 519bcf4ce1..54c9d941fe 100644 --- a/core/libs/prx/libSceNetCtl/Export.cpp +++ b/core/libs/prx/libSceNetCtl/Export.cpp @@ -131,7 +131,7 @@ static bool HasIpv4Callback() { return false; } -static bool HostHasAddress(int family) { +static bool QueryHostHasAddress(int family) { #ifdef _WIN32 static const int socket_status = [] { WSADATA data{}; @@ -188,6 +188,74 @@ struct HostIpv4Interface { std::vector dns; }; +#ifdef _WIN32 +namespace { + +struct HostAddressCache { + std::mutex lock; + bool registered = false; + bool tried = false; + std::atomic generation{1}; + std::uint64_t seen[3]{}; + bool value[3]{}; + std::uint64_t interfaceSeen = 0; + bool interfaceFound = false; + HostIpv4Interface interfaceValue; +}; + +HostAddressCache& AddressCache() { + static HostAddressCache cache; + return cache; +} + +void CALLBACK HostAddressChanged(PVOID, PMIB_UNICASTIPADDRESS_ROW, MIB_NOTIFICATION_TYPE) { + AddressCache().generation.fetch_add(1, std::memory_order_acq_rel); +} + +void CALLBACK HostInterfaceChanged(PVOID, PMIB_IPINTERFACE_ROW, MIB_NOTIFICATION_TYPE) { + AddressCache().generation.fetch_add(1, std::memory_order_acq_rel); +} + +void CALLBACK HostRouteChanged(PVOID, PMIB_IPFORWARD_ROW2, MIB_NOTIFICATION_TYPE) { + AddressCache().generation.fetch_add(1, std::memory_order_acq_rel); +} + +void WatchHostAddresses(HostAddressCache& cache) { + if (cache.tried) return; + cache.tried = true; + HANDLE addresses = nullptr; + HANDLE interfaces = nullptr; + HANDLE routes = nullptr; + cache.registered = NotifyUnicastIpAddressChange(AF_UNSPEC, HostAddressChanged, nullptr, FALSE, &addresses) == NO_ERROR && + NotifyIpInterfaceChange(AF_UNSPEC, HostInterfaceChanged, nullptr, FALSE, &interfaces) == NO_ERROR && + NotifyRouteChange2(AF_UNSPEC, HostRouteChanged, nullptr, FALSE, &routes) == NO_ERROR; + if (!cache.registered) { + if (addresses != nullptr) CancelMibChangeNotify2(addresses); + if (interfaces != nullptr) CancelMibChangeNotify2(interfaces); + if (routes != nullptr) CancelMibChangeNotify2(routes); + } +} + +} +#endif + +static bool HostHasAddress(int family) { +#ifdef _WIN32 + auto& cache = AddressCache(); + const int slot = family == AF_INET ? 0 : family == AF_INET6 ? 1 : 2; + std::lock_guard guard(cache.lock); + WatchHostAddresses(cache); + const auto generation = cache.generation.load(std::memory_order_acquire); + if (cache.registered && cache.seen[slot] == generation) return cache.value[slot]; + const bool value = QueryHostHasAddress(family); + cache.value[slot] = value; + cache.seen[slot] = generation; + return value; +#else + return QueryHostHasAddress(family); +#endif +} + static bool UsableIpv4(in_addr address) { const auto host = ntohl(address.s_addr); return host != 0 && (host >> 24) != 127; @@ -310,7 +378,7 @@ static std::vector HostDnsServers() { } #endif -static bool FindHostIpv4Interface(HostIpv4Interface& found) { +static bool QueryHostIpv4Interface(HostIpv4Interface& found) { #ifdef _WIN32 ULONG size = 0; const ULONG flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_INCLUDE_GATEWAYS; @@ -371,6 +439,25 @@ static bool FindHostIpv4Interface(HostIpv4Interface& found) { #endif } +static bool FindHostIpv4Interface(HostIpv4Interface& found) { +#ifdef _WIN32 + auto& cache = AddressCache(); + std::lock_guard guard(cache.lock); + WatchHostAddresses(cache); + const auto generation = cache.generation.load(std::memory_order_acquire); + if (!cache.registered || cache.interfaceSeen != generation) { + HostIpv4Interface value; + cache.interfaceFound = QueryHostIpv4Interface(value); + cache.interfaceValue = std::move(value); + cache.interfaceSeen = generation; + } + if (cache.interfaceFound) found = cache.interfaceValue; + return cache.interfaceFound; +#else + return QueryHostIpv4Interface(found); +#endif +} + extern "C" std::size_t NetCtlHostDnsServers_nid_no_patch(std::uint32_t* servers, std::size_t capacity) { HostIpv4Interface host; if (!FindHostIpv4Interface(host)) return 0; diff --git a/core/libs/prx/libSceNgs2.native/include/Ngs2Types.hpp b/core/libs/prx/libSceNgs2.native/include/Ngs2Types.hpp index d113635624..f2d40adab9 100644 --- a/core/libs/prx/libSceNgs2.native/include/Ngs2Types.hpp +++ b/core/libs/prx/libSceNgs2.native/include/Ngs2Types.hpp @@ -21,6 +21,13 @@ static constexpr int SCE_NGS2_ERROR_INVALID_VOICE_HANDLE = static_cast(0x80 static constexpr int SCE_NGS2_ERROR_NULL_PARAM_LIST = static_cast(0x804A8360); static constexpr int SCE_NGS2_ERROR_INVALID_GEOM_LISTENER_ADDRESS = static_cast(0x804A8461); static constexpr int SCE_NGS2_ERROR_INVALID_GEOM_FLAG = static_cast(0x804A8463); +static constexpr int SCE_NGS2_ERROR_INVALID_OPTION_SIZE = static_cast(0x804A8013); +static constexpr int SCE_NGS2_ERROR_INVALID_NUM_CHANNELS = static_cast(0x804A8052); +static constexpr int SCE_NGS2_ERROR_INVALID_PITCH_SHIFT_OPTION = static_cast(0x804A8098); +static constexpr int SCE_NGS2_ERROR_INVALID_REVERB_OPTION = static_cast(0x804A8099); +static constexpr int SCE_NGS2_ERROR_INVALID_STATE_SIZE = static_cast(0x804A8302); +static constexpr int SCE_NGS2_ERROR_INVALID_BUFFER_INDEX = static_cast(0x804A8306); +static constexpr int SCE_NGS2_ERROR_INVALID_NUM_BUFFERS = static_cast(0x804A830B); static constexpr std::uint32_t SCE_NGS2_RACK_ID_SAMPLER = 0x1000; static constexpr std::uint32_t SCE_NGS2_RACK_ID_SUBMIXER = 0x2000; @@ -71,14 +78,25 @@ static constexpr std::uint32_t SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_EXIT_LOOP = 0 static constexpr std::uint32_t SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_PITCH = 0x40010005; static constexpr std::uint32_t SCE_NGS2_CUSTOM_SUBMIXER_VOICE_PARAM_SETUP = 0x40020000; static constexpr std::uint32_t SCE_NGS2_CUSTOM_VOICE_PARAM_FILTER = 0x40001300; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_VOICE_PARAM_GAIN = 0x40001500; static constexpr std::uint32_t SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX = 0x40001d00; static constexpr std::uint32_t SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX2 = 0x40001f00; static constexpr std::uint32_t SCE_NGS2_CUSTOM_MAX_MODULES = 24; static constexpr std::uint32_t SCE_NGS2_CUSTOM_MAX_PORTS = 16; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_MAX_BUFFERS = 4; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_SUBMIXER_MAX_CHANNELS = 37; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_SUBMIXER_MAX_INPUTS = 2; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_REVERB_OPTION_SIZE = 12; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_PITCH_SHIFT_OPTION_SIZE = 8; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_PITCH_SHIFT_STATE_SIZE = 8; static constexpr std::uint32_t SCE_NGS2_CUSTOM_MODULE_ID_FILTER = 0x13; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_MODULE_ID_GAIN = 0x15; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_MODULE_ID_REVERB = 0x1b; static constexpr std::uint32_t SCE_NGS2_CUSTOM_MODULE_ID_USER_FX = 0x1d; static constexpr std::uint32_t SCE_NGS2_CUSTOM_MODULE_ID_USER_FX2 = 0x1f; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_MODULE_ID_PITCH_SHIFT = 0x20; +static constexpr std::uint32_t SCE_NGS2_CUSTOM_MODULE_ID_CONVOLUTION = 0x23; static constexpr std::uint32_t SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE = 1; static constexpr std::uint32_t SCE_NGS2_WAVEFORM_BLOCKS_FLAG_APPEND = 2; @@ -527,6 +545,12 @@ struct Ngs2CustomVoiceUserFx2Param { }; static_assert(sizeof(Ngs2CustomVoiceUserFx2Param) == 24); +struct Ngs2CustomVoiceGainParam { + Ngs2VoiceParamHeader header; + float gain[SCE_NGS2_CUSTOM_SUBMIXER_MAX_CHANNELS]; +}; +static_assert(sizeof(Ngs2CustomVoiceGainParam) == 156); + struct Ngs2CustomVoiceFilterParam { Ngs2VoiceParamHeader header; std::uint32_t type; diff --git a/core/libs/prx/libSceNgs2.native/src/Atrac9.cpp b/core/libs/prx/libSceNgs2.native/src/Atrac9.cpp index 6c08a090c1..9357c6a942 100644 --- a/core/libs/prx/libSceNgs2.native/src/Atrac9.cpp +++ b/core/libs/prx/libSceNgs2.native/src/Atrac9.cpp @@ -26,6 +26,7 @@ static constexpr std::uint32_t MAX_SAMPLE_RATE = 192000; static constexpr std::uint32_t WAVEFORM_TYPE_PCM_FIRST = 0x10; static constexpr std::uint32_t WAVEFORM_TYPE_PCM_LAST = 0x1b; static constexpr std::uint32_t WAVEFORM_TYPE_VAG = 0x1c; +static constexpr std::uint32_t VAG_FRAME_SAMPLES = 28; static constexpr std::uint32_t PCM_BYTES_PER_SAMPLE[] = {1, 1, 2, 2, 3, 3, 4, 4, 4, 4, 8, 8}; static bool IsPcmType(std::uint32_t type) { @@ -43,6 +44,18 @@ static Ngs2WaveformBlock PcmBlock(std::uint32_t type, std::uint32_t channels, st return block; } +static Ngs2WaveformBlock VagBlock(std::uint32_t channels, std::uint32_t pos, std::uint32_t count) { + const std::uint32_t frameBytes = 16u * channels; + const std::uint32_t first = pos / VAG_FRAME_SAMPLES; + const std::uint32_t past = (pos + count + VAG_FRAME_SAMPLES - 1u) / VAG_FRAME_SAMPLES; + const auto offset = static_cast(static_cast(first) * frameBytes); + Ngs2WaveformBlock block{}; + block.data_offset = offset; + block.data_size = static_cast(past * frameBytes - offset); + block.num_samples = (past - first) * VAG_FRAME_SAMPLES; + return block; +} + void Ngs2Atrac9DecoderDeleter::operator()(void* handle) const { Atrac9ReleaseHandle(handle); } @@ -432,7 +445,10 @@ int APS5_VABI sceNgs2CalcWaveformBlock(const Ngs2WaveformFormat* format, uint32_ *block = PcmBlock(type, format->num_channels, sample_pos, num_samples); return SCE_NGS2_OK; } - if (type == WAVEFORM_TYPE_VAG) throw std::runtime_error("NGS2: waveform blocks of type " + Ngs2Hex(type) + " are not implemented"); + if (type == WAVEFORM_TYPE_VAG) { + *block = VagBlock(format->num_channels, sample_pos, num_samples); + return SCE_NGS2_OK; + } if (type != SCE_NGS2_WAVEFORM_TYPE_ATRAC9) return SCE_NGS2_ERROR_UNKNOWN_WAVEFORM_FORMAT; if (format->num_channels == 0 || format->num_channels > NGS2_MAX_CHANNELS || format->sample_rate == 0 || format->sample_rate > MAX_SAMPLE_RATE) { return SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT; @@ -450,6 +466,28 @@ int APS5_VABI sceNgs2CalcWaveformBlock(const Ngs2WaveformFormat* format, uint32_ return SCE_NGS2_OK; } +int APS5_VABI sceNgs2GetWaveformFrameInfo(const Ngs2WaveformFormat* format, uint32_t* out_frame_size, uint32_t* out_num_frame_samples, uint32_t* out_units_per_frame, uint32_t* out_num_delay_samples) { + if (out_frame_size == nullptr || out_num_frame_samples == nullptr || out_units_per_frame == nullptr || out_num_delay_samples == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; + if (format == nullptr || format->num_channels == 0 || format->num_channels > NGS2_MAX_CHANNELS || format->sample_rate == 0 || format->sample_rate > MAX_SAMPLE_RATE) { + return SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT; + } + if (format->waveform_type == SCE_NGS2_WAVEFORM_TYPE_PCM_I16L) { + *out_frame_size = format->num_channels * sizeof(std::int16_t); + *out_num_frame_samples = 1; + *out_units_per_frame = 1; + *out_num_delay_samples = 0; + return SCE_NGS2_OK; + } + if (format->waveform_type != SCE_NGS2_WAVEFORM_TYPE_ATRAC9) throw std::runtime_error("NGS2: the frame info of waveform type " + Ngs2Hex(format->waveform_type) + " is not implemented"); + Atrac9CodecInfo codec{}; + if (!MatchingCodecInfo(*format, codec)) return SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT; + *out_frame_size = static_cast(codec.superframeSize); + *out_num_frame_samples = static_cast(codec.frameSamples * codec.framesInSuperframe); + *out_units_per_frame = static_cast(codec.framesInSuperframe); + *out_num_delay_samples = static_cast(codec.frameSamples); + return SCE_NGS2_OK; +} + } #pragma GCC visibility pop diff --git a/core/libs/prx/libSceNgs2.native/src/Custom.cpp b/core/libs/prx/libSceNgs2.native/src/Custom.cpp index d3e586a4d9..aaad9fdb18 100644 --- a/core/libs/prx/libSceNgs2.native/src/Custom.cpp +++ b/core/libs/prx/libSceNgs2.native/src/Custom.cpp @@ -12,28 +12,75 @@ static constexpr std::uint32_t USER_FX2_FLAG_FIRST_PROCESS = 1; static constexpr std::uint32_t USER_FX2_FLAG_PARAM_CHANGED = 2; -void Ngs2CheckCustomRack(const Ngs2CustomRackOption& option) { - if (option.num_buffers != 1) throw std::runtime_error("NGS2: custom racks with " + std::to_string(option.num_buffers) + " buffers are not implemented"); +static const char* BuiltInModuleName(std::uint32_t moduleId) { + switch (moduleId) { + case SCE_NGS2_CUSTOM_MODULE_ID_GAIN: return "Gain"; + case SCE_NGS2_CUSTOM_MODULE_ID_REVERB: return "Reverb"; + case SCE_NGS2_CUSTOM_MODULE_ID_PITCH_SHIFT: return "PitchShift"; + case SCE_NGS2_CUSTOM_MODULE_ID_CONVOLUTION: return "Convolution"; + default: return nullptr; + } +} + +static std::uint32_t OptionWord(const Ngs2CustomModuleOption& option) { + std::uint32_t word = 0; + std::memcpy(&word, reinterpret_cast(&option) + sizeof(option), sizeof(word)); + return word; +} + +static int CheckBuiltInModule(const Ngs2CustomRackModuleInfo& info) { + const auto stateSizeIs = [&info](std::uint32_t size) { return info.state_size == 0 || info.state_size == size; }; + if (info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_GAIN) { + if (!stateSizeIs(0)) return SCE_NGS2_ERROR_INVALID_STATE_SIZE; + } else if (info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_REVERB) { + if (!stateSizeIs(0)) return SCE_NGS2_ERROR_INVALID_STATE_SIZE; + if (info.option != nullptr && info.option->size != SCE_NGS2_CUSTOM_REVERB_OPTION_SIZE) return SCE_NGS2_ERROR_INVALID_OPTION_SIZE; + if (info.option != nullptr && OptionWord(*info.option) > 1) return SCE_NGS2_ERROR_INVALID_REVERB_OPTION; + } else if (info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_PITCH_SHIFT) { + if (!stateSizeIs(SCE_NGS2_CUSTOM_PITCH_SHIFT_STATE_SIZE)) return SCE_NGS2_ERROR_INVALID_STATE_SIZE; + if (info.option != nullptr && info.option->size == SCE_NGS2_CUSTOM_PITCH_SHIFT_OPTION_SIZE && OptionWord(*info.option) != 0) { + return SCE_NGS2_ERROR_INVALID_PITCH_SHIFT_OPTION; + } + } + return SCE_NGS2_OK; +} + +int Ngs2CheckCustomRack(std::uint32_t rackId, const Ngs2CustomRackOption& option, std::uint32_t maxInputs) { + if (option.num_buffers == 0 || option.num_buffers > SCE_NGS2_CUSTOM_MAX_BUFFERS || option.num_buffers < maxInputs) { + return SCE_NGS2_ERROR_INVALID_NUM_BUFFERS; + } if (option.num_modules > SCE_NGS2_CUSTOM_MAX_MODULES || option.rack_option.max_ports > SCE_NGS2_CUSTOM_MAX_PORTS) APS5_INVALID_ARG_EX; for (std::uint32_t m = 0; m < option.num_modules; m++) { const auto& info = option.module[m]; if (info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_USER_FX2) { if (info.option == nullptr || info.option->size != sizeof(Ngs2CustomUserFx2ModuleOption)) APS5_INVALID_ARG_EX; - if (info.source_buffer_id != 0 || info.dest_buffer_id != 0) APS5_INVALID_ARG_EX; if (reinterpret_cast(info.option)->process_handler == nullptr) APS5_INVALID_ARG_EX; } else if (info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_FILTER) { if (info.option != nullptr || info.state_size != 0 || info.state_offset != 0) APS5_INVALID_ARG_EX; - if (info.source_buffer_id != 0 || info.extra_buffer_id != 0 || info.dest_buffer_id != 0) APS5_INVALID_ARG_EX; } else if (info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_USER_FX) { - if (info.source_buffer_id != 0 || info.dest_buffer_id != 0) APS5_INVALID_ARG_EX; if (info.option != nullptr && info.option->size < sizeof(Ngs2CustomModuleOption)) APS5_INVALID_ARG_EX; - } else { + } else if (BuiltInModuleName(info.module_id) == nullptr) { throw std::runtime_error("NGS2: custom module id " + Ngs2Hex(info.module_id) + " is not implemented"); } + if (info.source_buffer_id >= option.num_buffers || info.extra_buffer_id >= option.num_buffers || info.dest_buffer_id >= option.num_buffers) { + return SCE_NGS2_ERROR_INVALID_BUFFER_INDEX; + } + const int result = CheckBuiltInModule(info); + if (result != SCE_NGS2_OK) return result; + } + if (option.num_buffers != 1) throw std::runtime_error("NGS2: custom racks with " + std::to_string(option.num_buffers) + " buffers are not implemented"); + for (std::uint32_t m = 0; m < option.num_modules; m++) { + const auto moduleId = option.module[m].module_id; + if (moduleId == SCE_NGS2_CUSTOM_MODULE_ID_GAIN && option.module[m].option != nullptr) throw std::runtime_error("NGS2: an option for the custom Gain module is not implemented"); + if (moduleId == SCE_NGS2_CUSTOM_MODULE_ID_GAIN && rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) continue; + if (moduleId == SCE_NGS2_CUSTOM_MODULE_ID_GAIN) throw std::runtime_error("NGS2: the custom Gain module on a custom sampler rack is not implemented"); + const char* name = BuiltInModuleName(moduleId); + if (name != nullptr) throw std::runtime_error(std::string("NGS2: the custom ") + name + " module is not implemented"); } for (std::uint32_t p = 0; p < option.rack_option.max_ports; p++) { if (option.port[p].source_buffer_id != 0) APS5_INVALID_ARG_EX; } + return SCE_NGS2_OK; } static Ngs2UserFx2SetupContext UserFxContext(Ngs2Rack& rack, std::size_t module, std::uint32_t voiceIndex) { @@ -49,6 +96,7 @@ void Ngs2SetupUserFx(Ngs2Rack& rack, const Ngs2CustomRackOption& option) { for (auto& voice : rack.voices) { voice.userFx.resize(option.num_modules); voice.customUserFx.resize(option.num_modules); + voice.customGain.assign(option.num_modules, {}); } for (std::uint32_t m = 0; m < option.num_modules; m++) { rack.customModuleIds[m] = option.module[m].module_id; @@ -116,6 +164,26 @@ static void SetCustomUserFxParam(Ngs2Voice& voice, std::uint32_t index, const Ng fx.flags = 1; } +static void SetGainParam(Ngs2Voice& voice, std::uint32_t index, const Ngs2CustomVoiceGainParam& param) { + if (index >= voice.customGain.size() || voice.rack->customModuleIds[index] != SCE_NGS2_CUSTOM_MODULE_ID_GAIN) APS5_INVALID_ARG_EX; + for (const float gain : param.gain) { + if (!(gain >= -32.0f && gain <= 32.0f)) throw std::runtime_error("NGS2: a custom Gain level outside -32 to 32 is not implemented"); + } + std::copy(std::begin(param.gain), std::end(param.gain), voice.customGain[index].target.begin()); +} + +static void ProcessGain(Ngs2Voice& voice, Ngs2VoiceCustomGain& gain, const std::vector& channels, std::uint32_t grain) { + if (voice.hasSamples) { + for (std::uint32_t c = 0; c < voice.channels; c++) { + const float from = gain.current[c]; + const float step = (gain.target[c] - from) / static_cast(grain); + for (std::uint32_t i = 0; i < grain; i++) channels[c][i] *= from + step * static_cast(i + 1); + } + voice.hasSamples = std::any_of(voice.samples.begin(), voice.samples.end(), [](float sample) { return sample != 0.0f; }); + } + gain.current = gain.target; +} + void Ngs2ApplyCustomParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param) { const auto rackId = voice.rack->rackId; if (param.id == SCE_NGS2_CUSTOM_SUBMIXER_VOICE_PARAM_SETUP) { @@ -148,6 +216,11 @@ void Ngs2ApplyCustomParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param) { Ngs2SetFilter(voice, filter); return; } + if ((param.id & ~0x1fu) == SCE_NGS2_CUSTOM_VOICE_PARAM_GAIN) { + if (rackId != SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) throw std::invalid_argument("NGS2: voice param " + Ngs2Hex(param.id) + " belongs to another rack"); + SetGainParam(voice, param.id & 0x1f, ParamAs(param)); + return; + } if ((param.id & ~0x1fu) == SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX2) { if (rackId != SCE_NGS2_RACK_ID_CUSTOM_SAMPLER && rackId != SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) { throw std::invalid_argument("NGS2: voice param " + Ngs2Hex(param.id) + " belongs to another rack"); @@ -169,7 +242,9 @@ void Ngs2ProcessUserFx(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t samp std::vector channels(voice.channels); for (std::uint32_t c = 0; c < voice.channels; c++) channels[c] = voice.samples.data() + static_cast(c) * grain; for (std::size_t m = 0; m < voice.rack->customModuleIds.size(); m++) { - if (voice.rack->customModuleIds[m] == SCE_NGS2_CUSTOM_MODULE_ID_USER_FX2) { + if (voice.rack->customModuleIds[m] == SCE_NGS2_CUSTOM_MODULE_ID_GAIN) { + ProcessGain(voice, voice.customGain[m], channels, grain); + } else if (voice.rack->customModuleIds[m] == SCE_NGS2_CUSTOM_MODULE_ID_USER_FX2) { if (!voice.hasSamples) continue; auto& fx = voice.userFx[m]; const auto& module = voice.rack->userFx[m]; diff --git a/core/libs/prx/libSceNgs2.native/src/Ngs2Internal.hpp b/core/libs/prx/libSceNgs2.native/src/Ngs2Internal.hpp index adc9f3d665..a0e886d230 100644 --- a/core/libs/prx/libSceNgs2.native/src/Ngs2Internal.hpp +++ b/core/libs/prx/libSceNgs2.native/src/Ngs2Internal.hpp @@ -108,6 +108,11 @@ struct Ngs2VoiceCustomUserFx { std::uint32_t flags = 0; }; +struct Ngs2VoiceCustomGain { + std::array current{}; + std::array target{}; +}; + struct Ngs2Voice { Ngs2Rack* rack = nullptr; Ngs2PlayState state = Ngs2PlayState::Empty; @@ -136,6 +141,7 @@ struct Ngs2Voice { float lfeLevel = 1.0f; std::vector userFx; std::vector customUserFx; + std::vector customGain; Ngs2UserFxProcessHandler userFxHandler = nullptr; std::array userFxData{}; std::uint32_t userFxFlags = 0; @@ -229,7 +235,7 @@ std::size_t Ngs2Atrac9BlockBytes(const Ngs2Voice& voice, const Ngs2WaveformBlock void Ngs2RestartAtrac9(Ngs2Voice& voice); void Ngs2ConsumeAtrac9(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t systemRate); bool Ngs2FinishBlock(Ngs2Voice& voice); -void Ngs2CheckCustomRack(const Ngs2CustomRackOption& option); +int Ngs2CheckCustomRack(std::uint32_t rackId, const Ngs2CustomRackOption& option, std::uint32_t maxInputs); void Ngs2SetupUserFx(Ngs2Rack& rack, const Ngs2CustomRackOption& option); void Ngs2CleanupUserFx(Ngs2Rack& rack); void Ngs2ApplyCustomParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param); diff --git a/core/libs/prx/libSceNgs2.native/src/Rack.cpp b/core/libs/prx/libSceNgs2.native/src/Rack.cpp index 47897d3215..f5c85495eb 100644 --- a/core/libs/prx/libSceNgs2.native/src/Rack.cpp +++ b/core/libs/prx/libSceNgs2.native/src/Rack.cpp @@ -87,22 +87,35 @@ static std::uint32_t RackMaxChannels(std::uint32_t rackId, const RackOptions& op return NGS2_MAX_CHANNELS; } -static RackOptions CheckedRackOption(std::uint32_t rackId, const Ngs2RackOption* option) { +static int CheckedRackOption(std::uint32_t rackId, const Ngs2RackOption* option, RackOptions& options) { const auto size = RackOptionSize(rackId); - if (option == nullptr) return DefaultRackOption(rackId); + if (option == nullptr) { + options = DefaultRackOption(rackId); + return SCE_NGS2_OK; + } if (option->size != size) throw std::invalid_argument("NGS2: unexpected rack option size " + std::to_string(option->size)); - RackOptions options{}; + options = {}; std::memcpy(&options, option, size); const auto maxChannels = RackMaxChannels(rackId, options); - if (options.common.max_voices == 0 || maxChannels == 0 || maxChannels > NGS2_MAX_CHANNELS) { + if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER && maxChannels > SCE_NGS2_CUSTOM_SUBMIXER_MAX_CHANNELS) return SCE_NGS2_ERROR_INVALID_NUM_CHANNELS; + if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER && (options.customSubmixer.max_inputs == 0 || options.customSubmixer.max_inputs > SCE_NGS2_CUSTOM_SUBMIXER_MAX_INPUTS)) { + throw std::runtime_error("NGS2: custom submixer racks with " + std::to_string(options.customSubmixer.max_inputs) + " inputs are not implemented"); + } + if (options.common.max_voices == 0 || maxChannels == 0) throw std::invalid_argument("NGS2: invalid rack voice or channel count"); + int result = SCE_NGS2_OK; + if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SAMPLER) result = Ngs2CheckCustomRack(rackId, options.customSampler.custom_rack_option, 0); + if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) result = Ngs2CheckCustomRack(rackId, options.customSubmixer.custom_rack_option, options.customSubmixer.max_inputs); + if (result != SCE_NGS2_OK) return result; + if (maxChannels > NGS2_MAX_CHANNELS) { + if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) { + throw std::runtime_error("NGS2: custom submixer racks with " + std::to_string(maxChannels) + " channels are not implemented"); + } throw std::invalid_argument("NGS2: invalid rack voice or channel count"); } - if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SAMPLER) Ngs2CheckCustomRack(options.customSampler.custom_rack_option); - if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) Ngs2CheckCustomRack(options.customSubmixer.custom_rack_option); if (rackId == SCE_NGS2_RACK_ID_REVERB && options.reverb.reverb_size != 1) { throw std::runtime_error("NGS2: reverb rack size " + std::to_string(options.reverb.reverb_size) + " is not implemented"); } - return options; + return SCE_NGS2_OK; } static int CreateRack(Ngs2Handle systemHandle, std::uint32_t rackId, const RackOptions& options, const Ngs2ContextBufferInfo& bufferInfo, @@ -165,7 +178,9 @@ extern "C" { int APS5_VABI sceNgs2RackQueryBufferSize(uint32_t rack_id, const Ngs2RackOption* option, Ngs2ContextBufferInfo* buffer_info) { if (buffer_info == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; - CheckedRackOption(rack_id, option); + RackOptions options{}; + const int result = CheckedRackOption(rack_id, option, options); + if (result != SCE_NGS2_OK) throw std::invalid_argument("NGS2: invalid rack option, rejected at creation with " + Ngs2Hex(static_cast(result))); *buffer_info = {}; buffer_info->host_buffer_size = sizeof(Ngs2Rack); return SCE_NGS2_OK; @@ -173,16 +188,20 @@ int APS5_VABI sceNgs2RackQueryBufferSize(uint32_t rack_id, const Ngs2RackOption* int APS5_VABI sceNgs2RackCreate(uintptr_t system_handle, uint32_t rack_id, const Ngs2RackOption* option, const Ngs2ContextBufferInfo* buffer_info, uintptr_t* handle) { if (handle == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; - const auto options = CheckedRackOption(rack_id, option); if (buffer_info == nullptr) APS5_INVALID_ARG_EX; + RackOptions options{}; + const int result = CheckedRackOption(rack_id, option, options); + if (result != SCE_NGS2_OK) return result; const Ngs2Lock lock; return CreateRack(system_handle, rack_id, options, *buffer_info, {}, handle); } int APS5_VABI sceNgs2RackCreateWithAllocator(uintptr_t system_handle, uint32_t rack_id, const Ngs2RackOption* option, const Ngs2BufferAllocator* allocator, uintptr_t* handle) { if (handle == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; - const auto options = CheckedRackOption(rack_id, option); if (allocator == nullptr || allocator->alloc_handler == nullptr || allocator->free_handler == nullptr) APS5_INVALID_ARG_EX; + RackOptions options{}; + const int checked = CheckedRackOption(rack_id, option, options); + if (checked != SCE_NGS2_OK) return checked; const Ngs2Lock lock; if (Ngs2FindSystem(system_handle) == nullptr) return SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE; Ngs2ContextBufferInfo bufferInfo{}; diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2Atrac9.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2Atrac9.cpp index eed5eaf1f1..bbbebab2e4 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2Atrac9.cpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2Atrac9.cpp @@ -195,6 +195,45 @@ static void TestCalcBlock() { Require(sceNgs2CalcWaveformBlock(&silent, 0, 1, &block) == SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT); } +static void TestFrameInfo() { + const Ngs2WaveformFormat supported[] = { + {SCE_NGS2_WAVEFORM_TYPE_ATRAC9, 1, 48000, Config, 0, 0}, + {SCE_NGS2_WAVEFORM_TYPE_ATRAC9, 1, 48000, Config, 7, 9}, + }; + const Ngs2WaveformFormat rejected[] = { + {SCE_NGS2_WAVEFORM_TYPE_ATRAC9, 2, 48000, Config, 0, 0}, + {SCE_NGS2_WAVEFORM_TYPE_ATRAC9, 0, 48000, Config, 0, 0}, + {SCE_NGS2_WAVEFORM_TYPE_ATRAC9, 9, 48000, Config, 0, 0}, + {SCE_NGS2_WAVEFORM_TYPE_ATRAC9, 1, 44100, Config, 0, 0}, + {SCE_NGS2_WAVEFORM_TYPE_ATRAC9, 1, 0, Config, 0, 0}, + {SCE_NGS2_WAVEFORM_TYPE_ATRAC9, 1, 192001, Config, 0, 0}, + {SCE_NGS2_WAVEFORM_TYPE_ATRAC9, 1, 48000, 0, 0, 0}, + }; + for (const auto& format : supported) { + Ngs2WaveformBlock block{}; + Require(sceNgs2CalcWaveformBlock(&format, 0, SuperframeSamples, &block) == SCE_NGS2_OK && block.data_size == SuperframeBytes); + std::uint32_t values[4] = {0xa1, 0xa2, 0xa3, 0xa4}; + Require(sceNgs2GetWaveformFrameInfo(&format, &values[0], &values[1], &values[2], &values[3]) == SCE_NGS2_OK); + Require(values[0] == SuperframeBytes && values[1] == SuperframeSamples && values[2] == 4 && values[3] == SuperframeSamples / 4); + } + for (const auto& format : rejected) { + Ngs2WaveformBlock block{}; + const int calc = sceNgs2CalcWaveformBlock(&format, 0, SuperframeSamples, &block); + Require(calc == SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT); + std::uint32_t values[4] = {0xa1, 0xa2, 0xa3, 0xa4}; + Require(sceNgs2GetWaveformFrameInfo(&format, &values[0], &values[1], &values[2], &values[3]) == calc); + Require(values[0] == 0xa1 && values[1] == 0xa2 && values[2] == 0xa3 && values[3] == 0xa4); + } + std::uint32_t values[4] = {0xa1, 0xa2, 0xa3, 0xa4}; + Require(sceNgs2GetWaveformFrameInfo(nullptr, &values[0], &values[1], &values[2], &values[3]) == SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT); + Require(sceNgs2GetWaveformFrameInfo(&supported[0], nullptr, &values[1], &values[2], &values[3]) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); + Require(sceNgs2GetWaveformFrameInfo(&supported[0], &values[0], nullptr, &values[2], &values[3]) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); + Require(sceNgs2GetWaveformFrameInfo(&supported[0], &values[0], &values[1], nullptr, &values[3]) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); + Require(sceNgs2GetWaveformFrameInfo(&supported[0], &values[0], &values[1], &values[2], nullptr) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); + Require(sceNgs2GetWaveformFrameInfo(nullptr, nullptr, nullptr, nullptr, nullptr) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); + Require(values[0] == 0xa1 && values[1] == 0xa2 && values[2] == 0xa3 && values[3] == 0xa4); +} + static void RequirePcmBlock(std::uint32_t type, std::uint32_t channels, std::uint32_t pos, std::uint32_t count, std::uint64_t offset, std::uint64_t size) { const Ngs2WaveformFormat format{type, channels, 48000, 0, 0, 0}; Ngs2WaveformBlock block; @@ -229,6 +268,26 @@ static void TestCalcPcmBlock() { Require(sceNgs2CalcWaveformBlock(nullptr, 0, 1, &block) == SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT && block.data_size == 0 && block.num_samples == 0); } +static void RequireVagBlock(std::uint32_t channels, std::uint32_t pos, std::uint32_t count, std::uint64_t offset, std::uint64_t size, std::uint32_t samples) { + const Ngs2WaveformFormat format{0x1c, channels, 48000, 0, 0, 0}; + Ngs2WaveformBlock block; + std::memset(&block, 0xab, sizeof(block)); + Require(sceNgs2CalcWaveformBlock(&format, pos, count, &block) == SCE_NGS2_OK); + Require(block.data_offset == offset && block.data_size == size && block.num_samples == samples); + Require(block.num_repeats == 0 && block.num_skip_samples == 0 && block.reserved == 0 && block.user_data == 0); +} + +static void TestCalcVagBlock() { + RequireVagBlock(1, 0, 28, 0, 16, 28); + RequireVagBlock(1, 27, 2, 0, 32, 56); + RequireVagBlock(1, 28, 28, 16, 16, 28); + RequireVagBlock(2, 30, 1, 32, 32, 28); + RequireVagBlock(6, 56, 57, 192, 288, 84); + RequireVagBlock(1, 0, 0, 0, 0, 0); + RequireVagBlock(1, 5, 0, 0, 16, 28); + RequireVagBlock(0, 100, 50, 0, 0, 84); +} + static void Set32(std::vector& bytes, std::size_t offset, std::uint32_t value) { for (int i = 0; i < 4; ++i) bytes[offset + i] = static_cast(value >> (i * 8)); } @@ -538,7 +597,9 @@ int main() { TestParseLoops(); TestMalformedLoops(); TestCalcBlock(); + TestFrameInfo(); TestCalcPcmBlock(); + TestCalcVagBlock(); TestCorruptSuperframeAtPageEnd(); TestStreamPartitions(); TestRefill(reference); diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp index 379c277110..5b6acd7ad2 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp @@ -356,6 +356,153 @@ static void TestCustomFilter() { } +struct ModuleOptionWords { + std::uint32_t size; + std::uint32_t words[15]; +}; + +static int allocCalls = 0; + +static std::int32_t APS5_VABI CountAlloc(Ngs2ContextBufferInfo*) { + allocCalls++; + return SCE_NGS2_OK; +} + +static std::int32_t APS5_VABI CountFree(Ngs2ContextBufferInfo*) { + return SCE_NGS2_OK; +} + +static Ngs2CustomSubmixerRackOption BuiltInRackOption(const Ngs2CustomUserFx2ModuleOption& userFx2, std::uint32_t moduleId, + const ModuleOptionWords* moduleOption, std::uint32_t stateSize) { + auto option = RackOption(&userFx2, 1); + auto& module = option.custom_rack_option.module[0]; + module.module_id = moduleId; + module.option = reinterpret_cast(moduleOption); + module.state_size = stateSize; + return option; +} + +static void TestCustomRackChecks() { + calls = {}; + const auto system = CreateSystem(); + const auto userFx2 = Module(Gain); + const auto good = RackOption(&userFx2, 1); + Ngs2ContextBufferInfo query{}; + Require(sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER, &good.custom_rack_option.rack_option, &query) == SCE_NGS2_OK); + const auto info = Buffer(query); + const auto create = [&](const Ngs2CustomSubmixerRackOption& option) { + uintptr_t rack = 0; + const int result = sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER, &option.custom_rack_option.rack_option, &info, &rack); + Require(rack == 0); + return result; + }; + + auto option = good; + option.custom_rack_option.num_buffers = 0; + Require(create(option) == SCE_NGS2_ERROR_INVALID_NUM_BUFFERS); + Require(CreateThrows(option)); + option.custom_rack_option.num_buffers = SCE_NGS2_CUSTOM_MAX_BUFFERS + 1; + Require(create(option) == SCE_NGS2_ERROR_INVALID_NUM_BUFFERS); + option = good; + option.max_inputs = 2; + Require(create(option) == SCE_NGS2_ERROR_INVALID_NUM_BUFFERS); + option.custom_rack_option.num_buffers = 2; + Require(CreateThrows(option)); + option.custom_rack_option.num_buffers = SCE_NGS2_CUSTOM_MAX_BUFFERS; + option.custom_rack_option.module[0].dest_buffer_id = SCE_NGS2_CUSTOM_MAX_BUFFERS - 1; + Require(CreateThrows(option)); + option.custom_rack_option.module[0].dest_buffer_id = SCE_NGS2_CUSTOM_MAX_BUFFERS; + Require(create(option) == SCE_NGS2_ERROR_INVALID_BUFFER_INDEX); + option = good; + option.max_inputs = 0; + Require(CreateThrows(option)); + option.max_inputs = SCE_NGS2_CUSTOM_SUBMIXER_MAX_INPUTS + 1; + Require(CreateThrows(option)); + + option = good; + option.custom_rack_option.module[0].source_buffer_id = 1; + Require(create(option) == SCE_NGS2_ERROR_INVALID_BUFFER_INDEX); + option = good; + option.custom_rack_option.module[0].extra_buffer_id = 1; + Require(create(option) == SCE_NGS2_ERROR_INVALID_BUFFER_INDEX); + + option = good; + option.max_channels = SCE_NGS2_CUSTOM_SUBMIXER_MAX_CHANNELS + 1; + Require(create(option) == SCE_NGS2_ERROR_INVALID_NUM_CHANNELS); + option.max_channels = 16; + Require(CreateThrows(option)); + option.max_channels = SCE_NGS2_CUSTOM_SUBMIXER_MAX_CHANNELS; + Require(CreateThrows(option)); + + Require(!CreateThrows(BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_GAIN, nullptr, 0))); + Require(create(BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_GAIN, nullptr, 4)) == SCE_NGS2_ERROR_INVALID_STATE_SIZE); + const ModuleOptionWords gainOption{sizeof(Ngs2CustomModuleOption), {}}; + Require(CreateThrows(BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_GAIN, &gainOption, 0))); + + const ModuleOptionWords reverb{SCE_NGS2_CUSTOM_REVERB_OPTION_SIZE, {1}}; + const ModuleOptionWords reverbOff{SCE_NGS2_CUSTOM_REVERB_OPTION_SIZE, {0}}; + const ModuleOptionWords reverbSize{16, {1}}; + const ModuleOptionWords reverbWord{SCE_NGS2_CUSTOM_REVERB_OPTION_SIZE, {2}}; + Require(CreateThrows(BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_REVERB, &reverb, 0))); + Require(CreateThrows(BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_REVERB, &reverbOff, 0))); + Require(create(BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_REVERB, &reverbSize, 0)) == SCE_NGS2_ERROR_INVALID_OPTION_SIZE); + Require(create(BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_REVERB, &reverbWord, 0)) == SCE_NGS2_ERROR_INVALID_REVERB_OPTION); + Require(create(BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_REVERB, &reverb, 4)) == SCE_NGS2_ERROR_INVALID_STATE_SIZE); + + const ModuleOptionWords pitchShift{SCE_NGS2_CUSTOM_PITCH_SHIFT_OPTION_SIZE, {0}}; + const ModuleOptionWords pitchShiftWord{SCE_NGS2_CUSTOM_PITCH_SHIFT_OPTION_SIZE, {1}}; + const auto pitchShiftState = SCE_NGS2_CUSTOM_PITCH_SHIFT_STATE_SIZE; + Require(CreateThrows(BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_PITCH_SHIFT, &pitchShift, pitchShiftState))); + Require(CreateThrows(BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_PITCH_SHIFT, &pitchShift, 0))); + Require(create(BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_PITCH_SHIFT, &pitchShift, 4)) == SCE_NGS2_ERROR_INVALID_STATE_SIZE); + Require(create(BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_PITCH_SHIFT, &pitchShiftWord, pitchShiftState)) == SCE_NGS2_ERROR_INVALID_PITCH_SHIFT_OPTION); + + const ModuleOptionWords convolution{64, {}}; + Require(CreateThrows(BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_CONVOLUTION, &convolution, 64))); + option = BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_CONVOLUTION, &convolution, 64); + option.custom_rack_option.module[0].dest_buffer_id = 1; + Require(create(option) == SCE_NGS2_ERROR_INVALID_BUFFER_INDEX); + + option = BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_GAIN, nullptr, 0); + option.custom_rack_option.num_modules = 2; + option.custom_rack_option.module[1] = good.custom_rack_option.module[0]; + option.custom_rack_option.module[1].dest_buffer_id = 1; + Require(create(option) == SCE_NGS2_ERROR_INVALID_BUFFER_INDEX); + Require(CreateThrows(BuiltInRackOption(userFx2, 0x19, nullptr, 0))); + + option = good; + option.custom_rack_option.num_buffers = 0; + bool rejected = false; + uintptr_t rack = 0; + try { + sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER, &option.custom_rack_option.rack_option, nullptr, &rack); + } catch (const std::invalid_argument&) { + rejected = true; + } + Require(rejected); + const Ngs2BufferAllocator allocator{CountAlloc, CountFree, 0}; + Require(sceNgs2RackCreateWithAllocator(system, SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER, &option.custom_rack_option.rack_option, &allocator, &rack) == SCE_NGS2_ERROR_INVALID_NUM_BUFFERS); + Require(rack == 0 && allocCalls == 0 && calls.setupVoices.empty()); + + Ngs2CustomSamplerRackOption sampler{}; + sampler.custom_rack_option.rack_option.size = sizeof(sampler); + sampler.custom_rack_option.rack_option.max_grain_samples = 512; + sampler.custom_rack_option.rack_option.max_voices = 1; + sampler.custom_rack_option.num_modules = 1; + sampler.custom_rack_option.module[0].module_id = SCE_NGS2_CUSTOM_MODULE_ID_FILTER; + sampler.max_channel_works = 1; + Require(sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &sampler.custom_rack_option.rack_option, &info, &rack) == SCE_NGS2_ERROR_INVALID_NUM_BUFFERS); + sampler.custom_rack_option.num_buffers = 1; + sampler.custom_rack_option.module[0].extra_buffer_id = 1; + Require(sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &sampler.custom_rack_option.rack_option, &info, &rack) == SCE_NGS2_ERROR_INVALID_BUFFER_INDEX); + Require(rack == 0); + sampler.custom_rack_option.module[0].extra_buffer_id = 0; + Require(sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &sampler.custom_rack_option.rack_option, &info, &rack) == SCE_NGS2_OK); + sampler.custom_rack_option.module[0].module_id = SCE_NGS2_CUSTOM_MODULE_ID_GAIN; + Require(CreateSamplerThrows(sampler)); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} + struct LegacyUserFxState { std::uint32_t calls = 0; std::uint32_t flags = 0; @@ -467,11 +614,62 @@ static void TestCustomLegacyUserFx() { Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); } +static void TestCustomGain() { + const auto system = CreateSystem(); + const auto master = Mastering(system, 2); + const auto userFx2 = Module(Gain); + const auto rack = CustomRack(system, BuiltInRackOption(userFx2, SCE_NGS2_CUSTOM_MODULE_ID_GAIN, nullptr, 0)); + const auto custom = Voice(rack); + Control(custom, SCE_NGS2_CUSTOM_SUBMIXER_VOICE_PARAM_SETUP, Ngs2CustomSubmixerVoiceSetupParam{{}, 2, 2, 0, 0}); + Patch(custom, master); + Event(custom, SCE_NGS2_VOICE_EVENT_PLAY); + std::vector pcm(Grain * 2 * 8, 16384); + const auto sampler = Sampler(system, pcm); + Patch(sampler, custom); + Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY); + + auto out = Render(system); + for (std::uint32_t i = 0; i < Grain; i++) Require(out[i * 2] == 0.0f && out[i * 2 + 1] == 0.0f); + + Ngs2CustomVoiceGainParam gain{}; + gain.gain[0] = 2.0f; + gain.gain[1] = -0.5f; + Control(custom, SCE_NGS2_CUSTOM_VOICE_PARAM_GAIN | 0, gain); + out = Render(system); + for (std::uint32_t i = 0; i < Grain; i++) { + Require(out[i * 2] == 0.5f * 2.0f * static_cast(i + 1) / Grain); + Require(out[i * 2 + 1] == 0.5f * -0.5f * static_cast(i + 1) / Grain); + } + out = Render(system); + for (std::uint32_t i = 0; i < Grain; i++) Require(out[i * 2] == 1.0f && out[i * 2 + 1] == -0.25f); + + gain.gain[0] = 1.0f; + Control(custom, SCE_NGS2_CUSTOM_VOICE_PARAM_GAIN | 0, gain); + out = Render(system); + for (std::uint32_t i = 0; i < Grain; i++) Require(out[i * 2] == 0.5f * (2.0f - static_cast(i + 1) / Grain)); + + auto outside = gain; + outside.gain[2] = 33.0f; + Require(ControlThrows(custom, SCE_NGS2_CUSTOM_VOICE_PARAM_GAIN | 0, outside)); + Require(ControlThrows(custom, SCE_NGS2_CUSTOM_VOICE_PARAM_GAIN | 1, gain)); + Require(ControlThrows(sampler, SCE_NGS2_CUSTOM_VOICE_PARAM_GAIN | 0, gain)); + struct ShortGainParam { + Ngs2VoiceParamHeader header; + float gain[2]; + }; + Require(ControlThrows(custom, SCE_NGS2_CUSTOM_VOICE_PARAM_GAIN | 0, ShortGainParam{})); + + Require(sceNgs2RackDestroy(rack, nullptr) == SCE_NGS2_OK); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} + int main() { TestUserFxChain(); TestRackOptions(); + TestCustomRackChecks(); TestCustomSampler(); TestCustomFilter(); TestCustomLegacyUserFx(); + TestCustomGain(); return 0; } diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2Parse.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2Parse.cpp index a2545f52e6..6a0c74f5b3 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2Parse.cpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2Parse.cpp @@ -113,9 +113,59 @@ static void TestParseFileBank() { std::filesystem::remove(path); } +static void TestFrameInfoPcm() { + for (std::uint32_t channels = 1; channels <= 8; channels++) { + for (const std::uint32_t rate : {1u, 22050u, 48000u, 192000u}) { + const Ngs2WaveformFormat format{SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, channels, rate, 0, 0, 0}; + std::uint32_t frameSize = 0xa1, frameSamples = 0xa2, units = 0xa3, delay = 0xa4; + Require(sceNgs2GetWaveformFrameInfo(&format, &frameSize, &frameSamples, &units, &delay) == SCE_NGS2_OK); + Require(frameSize == channels * 2 && frameSamples == 1 && units == 1 && delay == 0); + const auto file = PcmFile(channels, rate, 16, 10); + Ngs2WaveformInfo info{}; + Require(sceNgs2ParseWaveformData(file.data(), file.size(), &info) == SCE_NGS2_OK); + Require(info.audio_frame_size == frameSize && info.num_audio_frame_samples == frameSamples); + Require(info.num_audio_unit_per_frame == units && info.num_delay_samples == delay); + } + } + const Ngs2WaveformFormat ignored{SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 2, 48000, 0x12345678, 3, 5}; + std::uint32_t frameSize = 0xa1, frameSamples = 0xa2, units = 0xa3, delay = 0xa4; + Require(sceNgs2GetWaveformFrameInfo(&ignored, &frameSize, &frameSamples, &units, &delay) == SCE_NGS2_OK); + Require(frameSize == 4 && frameSamples == 1 && units == 1 && delay == 0); +} + +static void TestFrameInfoErrors() { + const Ngs2WaveformFormat pcm{SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 2, 48000, 0, 0, 0}; + std::uint32_t values[4] = {0xa1, 0xa2, 0xa3, 0xa4}; + const auto untouched = [&] { return values[0] == 0xa1 && values[1] == 0xa2 && values[2] == 0xa3 && values[3] == 0xa4; }; + Require(sceNgs2GetWaveformFrameInfo(&pcm, nullptr, &values[1], &values[2], &values[3]) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); + Require(sceNgs2GetWaveformFrameInfo(&pcm, &values[0], nullptr, &values[2], &values[3]) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); + Require(sceNgs2GetWaveformFrameInfo(&pcm, &values[0], &values[1], nullptr, &values[3]) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); + Require(sceNgs2GetWaveformFrameInfo(&pcm, &values[0], &values[1], &values[2], nullptr) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); + Require(sceNgs2GetWaveformFrameInfo(nullptr, nullptr, nullptr, nullptr, nullptr) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); + Require(sceNgs2GetWaveformFrameInfo(nullptr, &values[0], &values[1], &values[2], &values[3]) == SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT); + Require(untouched()); + const Ngs2WaveformFormat invalid[] = { + {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 0, 48000, 0, 0, 0}, + {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 9, 48000, 0, 0, 0}, + {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 2, 0, 0, 0, 0}, + {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 2, 192001, 0, 0, 0}, + {0x99, 0, 48000, 0, 0, 0}, + {SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 2, 0, 0, 0, 0}, + }; + for (const auto& format : invalid) { + Require(sceNgs2GetWaveformFrameInfo(&format, &values[0], &values[1], &values[2], &values[3]) == SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT); + Require(untouched()); + } + for (const std::uint32_t type : {0u, 0x10u, 0x11u, 0x13u, 0x14u, 0x16u, SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 0x19u, 0x41u, 0xffffffffu}) { + Require(FrameInfoThrows({type, 2, 48000, 0, 0, 0})); + } +} + int main() { TestParsePcm(); TestParseFile(); TestParseFileBank(); + TestFrameInfoPcm(); + TestFrameInfoErrors(); return 0; } diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2Test.hpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2Test.hpp index 95252adde2..4768cc7cb3 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2Test.hpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2Test.hpp @@ -6,12 +6,14 @@ #include #include #include +#include #include extern "C" { int APS5_VABI sceNgs2ParseWaveformData(const void*, size_t, Ngs2WaveformInfo*); int APS5_VABI sceNgs2ParseWaveformFile(const char*, uint32_t, Ngs2WaveformInfo*); int APS5_VABI sceNgs2CalcWaveformBlock(const Ngs2WaveformFormat*, uint32_t, uint32_t, Ngs2WaveformBlock*); +int APS5_VABI sceNgs2GetWaveformFrameInfo(const Ngs2WaveformFormat*, uint32_t*, uint32_t*, uint32_t*, uint32_t*); int APS5_VABI sceNgs2SystemResetOption(Ngs2SystemOption*); int APS5_VABI sceNgs2SystemQueryBufferSize(const Ngs2SystemOption*, Ngs2ContextBufferInfo*); int APS5_VABI sceNgs2SystemCreate(const Ngs2SystemOption*, const Ngs2ContextBufferInfo*, uintptr_t*); @@ -60,6 +62,18 @@ inline void Check(bool value, int line) { } #define Require(value) Check((value), __LINE__) +inline bool FrameInfoThrows(const Ngs2WaveformFormat& format) { + std::uint32_t values[4] = {0xa1, 0xa2, 0xa3, 0xa4}; + bool threw = false; + try { + sceNgs2GetWaveformFrameInfo(&format, &values[0], &values[1], &values[2], &values[3]); + } catch (const std::exception&) { + threw = true; + } + Require(values[0] == 0xa1 && values[1] == 0xa2 && values[2] == 0xa3 && values[3] == 0xa4); + return threw; +} + inline constexpr std::uint32_t Grain = 8; inline std::vector> buffers; diff --git a/core/libs/prx/libSceNgs2/PendingExports.cpp b/core/libs/prx/libSceNgs2/PendingExports.cpp index 79edf98b0c..d930fb00c1 100644 --- a/core/libs/prx/libSceNgs2/PendingExports.cpp +++ b/core/libs/prx/libSceNgs2/PendingExports.cpp @@ -21,10 +21,6 @@ int APS5_VABI sceNgs2FftQuerySize() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNgs2GetWaveformFrameInfo(void* format, std::uint32_t* outFrameSize, std::uint32_t* outNumFrameSamples, std::uint32_t* outUnitsPerFrame, std::uint32_t* outNumDelaySamples) { - NotImplemented_nid_no_patch(__func__); - return 0; -} int APS5_VABI sceNgs2ParseWaveformUser(std::uintptr_t handler, std::uintptr_t userData, void* outInfo) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceNpCppWebApi/Export.cpp b/core/libs/prx/libSceNpCppWebApi/Export.cpp index 6238d389f8..5c53984a9f 100644 --- a/core/libs/prx/libSceNpCppWebApi/Export.cpp +++ b/core/libs/prx/libSceNpCppWebApi/Export.cpp @@ -1890,4 +1890,471 @@ std::uint64_t APS5_VABI _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V15Owner12getAc NotImplemented_nid_no_patch(__func__); return 0; } + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi10getFriendsEiRKNS4_21ParameterToGetFriendsERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_18GetFriendsResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriends10initializeEPNS1_6Common10LibContextEPKc() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriends9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriendsC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriendsD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi17getBasicPresencesEiRKNS4_28ParameterToGetBasicPresencesERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_25GetBasicPresencesResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresences10initializeEPNS1_6Common10LibContextEPKc() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresences9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresencesC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresencesD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V118GetFriendsResponse10getFriendsEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V124GetBlockingUsersResponse9getBlocksEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V125GetBasicPresencesResponse17getBasicPresencesEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi16getBlockingUsersEiRKNS4_27ParameterToGetBlockingUsersERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_24GetBlockingUsersResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsers10initializeEPNS1_6Common10LibContextE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsers9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsersC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsersD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi15filterProfanityEiRKNS4_26ParameterToFilterProfanityERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_23FilterProfanityResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi26ParameterToFilterProfanity10initializeEPNS1_6Common10LibContextEPKcSA_NS6_12IntrusivePtrINS3_19WebApiFilterRequestEEE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi26ParameterToFilterProfanity9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi26ParameterToFilterProfanityC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi26ParameterToFilterProfanityD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi15ProfanityFilter2V226WebApiFilterRequestFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_19WebApiFilterRequestEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi33getCommunicationRestrictionStatusEiRKNS4_44ParameterToGetCommunicationRestrictionStatusERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_38CommunicationRestrictionStatusResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi44ParameterToGetCommunicationRestrictionStatus10initializeEPNS1_6Common10LibContextEm() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi44ParameterToGetCommunicationRestrictionStatus9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi44ParameterToGetCommunicationRestrictionStatusC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi44ParameterToGetCommunicationRestrictionStatusD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter2V219WebApiFilterRequestEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, ReferenceCountOffset); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter2V219WebApiFilterRequestEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter2V219WebApiFilterRequestEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V113BasicPresenceEEEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, VectorReferenceCountOffset, __func__); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEED2Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorImED2Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBase14getHeaderValueEPKcRNS2_12IntrusivePtrINS2_6StringEEE() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE3endEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE5beginEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorImE3endEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorImE5beginEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEEppEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorImEppEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence12getAccountIdEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence12getInContextEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence15getOnlineStatusEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi15ProfanityFilter2V223FilterProfanityResponse10getMessageEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponse13getRestrictedEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEE3getEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEE3getEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V113BasicPresenceEEEEEEdeEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V113BasicPresenceEEEEEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEEdeEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEEptEv(const IntrusivePtr* self) { + return self->object; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE4sizeEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorImE4sizeEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEEdeEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEEneERKS9_() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorImEdeEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorImEneERKS4_() { + NotImplemented_nid_no_patch(__func__); + return 0; +} } diff --git a/core/libs/prx/libSceNpEntitlementAccess/Export.cpp b/core/libs/prx/libSceNpEntitlementAccess/Export.cpp index 717d50a1ed..683dde7c84 100644 --- a/core/libs/prx/libSceNpEntitlementAccess/Export.cpp +++ b/core/libs/prx/libSceNpEntitlementAccess/Export.cpp @@ -23,8 +23,14 @@ static constexpr uint32_t SKU_FLAG_FULL = 3; namespace { +#ifdef _WIN32 +const wchar_t* ConfiguredEntitlements() { return _wgetenv(L"ANYPS5_ENTITLEMENTS"); } +#else +const char* ConfiguredEntitlements() { return std::getenv("ANYPS5_ENTITLEMENTS"); } +#endif + std::filesystem::path EntitlementsPath() { - if (const char* configured = std::getenv("ANYPS5_ENTITLEMENTS"); configured != nullptr && configured[0] != '\0') return configured; + if (const auto* configured = ConfiguredEntitlements(); configured != nullptr && configured[0] != 0) return configured; #ifdef _WIN32 wchar_t module[MAX_PATH]; const auto length = GetModuleFileNameW(nullptr, module, MAX_PATH); @@ -50,7 +56,7 @@ const std::vector& OwnedAddons() { const auto path = EntitlementsPath(); std::ifstream file(path); if (!file) { - if (std::getenv("ANYPS5_ENTITLEMENTS") != nullptr || std::filesystem::exists(path)) throw std::runtime_error("NpEntitlementAccess: cannot read " + path.string()); + if (ConfiguredEntitlements() != nullptr || std::filesystem::exists(path)) throw std::runtime_error("NpEntitlementAccess: cannot read " + path.string()); return addons; } std::string line; diff --git a/core/libs/prx/libSceNpManager/Export.cpp b/core/libs/prx/libSceNpManager/Export.cpp index 93a1095e2e..15fd862b97 100644 --- a/core/libs/prx/libSceNpManager/Export.cpp +++ b/core/libs/prx/libSceNpManager/Export.cpp @@ -20,6 +20,7 @@ static constexpr int SCE_NP_ERROR_CALLBACK_MAX = static_cast(0x8055001D); static constexpr uint32_t NP_STATE_SIGNED_OUT = 1; static constexpr int NP_POLL_ASYNC_FINISHED = 0; static constexpr uint32_t NP_REACHABILITY_STATE_UNAVAILABLE = 0; +static constexpr uint32_t NP_GAME_PRESENCE_STATUS_OFFLINE = 0; static std::atomic g_nextRequest{1}; @@ -37,6 +38,12 @@ int AddRequest() { return reqId; } +int SignedOutRequest(int reqId) { + if (reqId == 0) return SCE_NP_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(requestMutex); + return liveRequests.count(reqId) ? SCE_NP_ERROR_SIGNED_OUT : SCE_NP_ERROR_REQUEST_NOT_FOUND; +} + using NpStateCallbackA = void (APS5_VABI *)(int userId, uint32_t state, void* userdata); using NpStateCallback = void (APS5_VABI *)(int userId, uint32_t state, NpId* npId, void* userdata); @@ -253,29 +260,31 @@ int APS5_VABI sceNpAsmTerminate(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNpCheckNpAvailabilityA(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpCheckNpAvailabilityA(int reqId, int userId) { + if (userId == USER_SERVICE_USER_ID_INVALID) return SCE_NP_ERROR_INVALID_ARGUMENT; + return SignedOutRequest(reqId); +} +int APS5_VABI sceNpGetAccountDateOfBirthA(int userId, void* dateOfBirth) { + if (!dateOfBirth || userId == USER_SERVICE_USER_ID_INVALID) return SCE_NP_ERROR_INVALID_ARGUMENT; + return SCE_NP_ERROR_SIGNED_OUT; +} +int APS5_VABI sceNpGetAccountLanguageA(int reqId, int userId, void* language) { + if (!language || userId == USER_SERVICE_USER_ID_INVALID) return SCE_NP_ERROR_INVALID_ARGUMENT; + return SignedOutRequest(reqId); +} +int APS5_VABI sceNpGetGamePresenceStatus(const NpOnlineId* onlineId, uint32_t* status) { + if (!onlineId || !status) return SCE_NP_ERROR_INVALID_ARGUMENT; + *status = NP_GAME_PRESENCE_STATUS_OFFLINE; return 0; } -int APS5_VABI sceNpGetAccountDateOfBirthA(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpGetGamePresenceStatusA(int userId, uint32_t* status) { + if (!status || userId == USER_SERVICE_USER_ID_INVALID) return SCE_NP_ERROR_INVALID_ARGUMENT; + *status = NP_GAME_PRESENCE_STATUS_OFFLINE; return 0; } -int APS5_VABI sceNpGetAccountLanguageA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} -int APS5_VABI sceNpGetGamePresenceStatus(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} -int APS5_VABI sceNpGetGamePresenceStatusA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} -int APS5_VABI sceNpGetParentalControlInfoA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpGetParentalControlInfoA(int reqId, int userId, int8_t* age, void* info) { + if (!age || !info || userId == USER_SERVICE_USER_ID_INVALID) return SCE_NP_ERROR_INVALID_ARGUMENT; + return SignedOutRequest(reqId); } int APS5_VABI sceNpIdMapperAbortRequest(void) { NotImplemented_nid_no_patch(__func__); diff --git a/core/libs/prx/libSceNpParty/Export.cpp b/core/libs/prx/libSceNpParty/Export.cpp index 67fdadc679..305da3f6cc 100644 --- a/core/libs/prx/libSceNpParty/Export.cpp +++ b/core/libs/prx/libSceNpParty/Export.cpp @@ -1,3 +1,4 @@ +#include #include #include #include "SceTypes.hpp" @@ -5,9 +6,19 @@ namespace { +constexpr int NP_PARTY_ERROR_ALREADY_INITIALIZED = static_cast(0x80552502u); +constexpr int NP_PARTY_ERROR_NOT_INITIALIZED = static_cast(0x80552503u); constexpr int NP_PARTY_ERROR_INVALID_ARGUMENT = static_cast(0x80552504u); +constexpr int NP_PARTY_ERROR_NOT_IN_PARTY = static_cast(0x80552506u); constexpr std::uint16_t NP_PARTY_STATE_NOT_IN_PARTY = 2; +std::atomic g_initialized{false}; + +int notInParty(const void* out) { + if (!out) return NP_PARTY_ERROR_INVALID_ARGUMENT; + return NP_PARTY_ERROR_NOT_IN_PARTY; +} + } extern "C" { @@ -17,8 +28,9 @@ int APS5_VABI sceNpPartyGetState(std::uint16_t* state) { *state = NP_PARTY_STATE_NOT_IN_PARTY; return 0; } + int APS5_VABI sceNpPartyCheckCallback(void) { - NotImplemented_nid_no_patch(__func__); + if (!g_initialized) return NP_PARTY_ERROR_NOT_INITIALIZED; return 0; } @@ -32,9 +44,8 @@ int APS5_VABI sceNpPartyCreateA(void) { return 0; } -int APS5_VABI sceNpPartyGetId(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpPartyGetId(void* id) { + return notInParty(id); } int APS5_VABI sceNpPartyGetMemberInfo(void) { @@ -42,19 +53,16 @@ int APS5_VABI sceNpPartyGetMemberInfo(void) { return 0; } -int APS5_VABI sceNpPartyGetMemberInfoA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpPartyGetMemberInfoA(std::uint16_t, void* memberInfo) { + return notInParty(memberInfo); } -int APS5_VABI sceNpPartyGetMemberSessionInfo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpPartyGetMemberSessionInfo(std::uint16_t, void* memberGameSessionInfo) { + return notInParty(memberGameSessionInfo); } -int APS5_VABI sceNpPartyGetMemberVoiceInfo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpPartyGetMemberVoiceInfo(std::uint16_t, std::uint16_t, std::uint8_t* memberVoiceState) { + return notInParty(memberVoiceState); } int APS5_VABI sceNpPartyGetMembers(void) { @@ -62,9 +70,8 @@ int APS5_VABI sceNpPartyGetMembers(void) { return 0; } -int APS5_VABI sceNpPartyGetMembersA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpPartyGetMembersA(void* memberList) { + return notInParty(memberList); } int APS5_VABI sceNpPartyGetStateAsUser(void) { @@ -72,9 +79,8 @@ int APS5_VABI sceNpPartyGetStateAsUser(void) { return 0; } -int APS5_VABI sceNpPartyGetStateAsUserA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpPartyGetStateAsUserA(std::int32_t, std::uint16_t* state) { + return sceNpPartyGetState(state); } int APS5_VABI sceNpPartyGetVoiceChatPriority(void) { @@ -82,8 +88,10 @@ int APS5_VABI sceNpPartyGetVoiceChatPriority(void) { return 0; } -int APS5_VABI sceNpPartyInitialize(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpPartyInitialize(const void* param) { + if (!param) return NP_PARTY_ERROR_INVALID_ARGUMENT; + bool expected = false; + if (!g_initialized.compare_exchange_strong(expected, true)) return NP_PARTY_ERROR_ALREADY_INITIALIZED; return 0; } @@ -102,8 +110,9 @@ int APS5_VABI sceNpPartyRegisterHandler(void) { return 0; } -int APS5_VABI sceNpPartyRegisterHandlerA(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpPartyRegisterHandlerA(const void* handlers, void*) { + if (!g_initialized) return NP_PARTY_ERROR_NOT_INITIALIZED; + if (!handlers) return NP_PARTY_ERROR_INVALID_ARGUMENT; return 0; } @@ -112,9 +121,8 @@ int APS5_VABI sceNpPartyRegisterPrivateHandler(void) { return 0; } -int APS5_VABI sceNpPartySendBinaryMessage(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpPartySendBinaryMessage(const void* sendParams) { + return notInParty(sendParams); } int APS5_VABI sceNpPartySetVoiceChatPriority(void) { @@ -133,7 +141,7 @@ int APS5_VABI sceNpPartyShowInvitationListA(void) { } int APS5_VABI sceNpPartyTerminate(void) { - NotImplemented_nid_no_patch(__func__); + if (!g_initialized.exchange(false)) return NP_PARTY_ERROR_NOT_INITIALIZED; return 0; } diff --git a/core/libs/prx/libSceNpTus/Export.cpp b/core/libs/prx/libSceNpTus/Export.cpp index 21e0e3e7fa..da02e06801 100644 --- a/core/libs/prx/libSceNpTus/Export.cpp +++ b/core/libs/prx/libSceNpTus/Export.cpp @@ -88,6 +88,16 @@ int Complete(int reqId, int asyncResult) { if (const int error = WaitOffline(reqId, &result)) return error; return result; } + +constexpr std::uint32_t INVALID_SERVICE_LABEL = 0xFFFFFFFF; + +int CreateTitleContext() { + std::lock_guard lock(tusMutex); + if (titleContexts.size() >= MAX_TITLE_CONTEXTS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS; + const int contextId = nextTitleContext++; + titleContexts.insert(contextId); + return contextId; +} } extern "C" { @@ -95,11 +105,7 @@ extern "C" { int APS5_VABI sceNpTusCreateNpTitleCtxA(std::uint32_t serviceLabel, int userId) { (void)serviceLabel; (void)userId; - std::lock_guard lock(tusMutex); - if (titleContexts.size() >= MAX_TITLE_CONTEXTS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS; - const int contextId = nextTitleContext++; - titleContexts.insert(contextId); - return contextId; + return CreateTitleContext(); } int APS5_VABI sceNpTusCreateRequest(int titleCtxId) { @@ -177,14 +183,14 @@ int APS5_VABI sceNpTusSetThreadParam(void) { return 0; } -int APS5_VABI sceNpTssCreateNpTitleCtx(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpTssCreateNpTitleCtx(std::uint32_t serviceLabel, const NpId* npId) { + if (!npId) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (serviceLabel == INVALID_SERVICE_LABEL) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return CreateTitleContext(); } -int APS5_VABI sceNpTssCreateNpTitleCtxA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpTssCreateNpTitleCtxA(std::uint32_t serviceLabel, int userId) { + return sceNpTusCreateNpTitleCtxA(serviceLabel, userId); } int APS5_VABI sceNpTusCreateTitleCtx(void) { diff --git a/core/libs/prx/libScePad/Export.cpp b/core/libs/prx/libScePad/Export.cpp index 232ba2f6d3..057d434611 100644 --- a/core/libs/prx/libScePad/Export.cpp +++ b/core/libs/prx/libScePad/Export.cpp @@ -923,18 +923,18 @@ bool APS5_VABI scePadIsValidHandle(std::int32_t handle, std::int32_t portType) { } bool APS5_VABI scePadIsDS4Connected(std::int32_t handle) { - NotImplemented_nid_no_patch(__func__); - return 0; + (void)handle; + return false; } bool APS5_VABI scePadIsBlasterConnected(std::int32_t handle) { - NotImplemented_nid_no_patch(__func__); - return 0; + (void)handle; + return false; } bool APS5_VABI scePadIsMoveConnected(std::int32_t handle) { - NotImplemented_nid_no_patch(__func__); - return 0; + (void)handle; + return false; } int APS5_VABI scePadGetBluetoothAddress(std::int32_t handle, std::uint64_t* address) { diff --git a/core/libs/prx/libScePosixForWebKit/Export.cpp b/core/libs/prx/libScePosixForWebKit/Export.cpp index ca47a2ecf7..c02c94fd7a 100644 --- a/core/libs/prx/libScePosixForWebKit/Export.cpp +++ b/core/libs/prx/libScePosixForWebKit/Export.cpp @@ -1,3 +1,4 @@ +#include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/HostEntropy.hpp" #include "prx/libc/include/General.hpp" @@ -7,6 +8,15 @@ #include #include +extern "C" { +int APS5_VABI access_nid_postfix(const char*, int); +int APS5_VABI clock_gettime_nid_postfix(int, KernelTimespec*); +char* APS5_VABI getcwd_nid_postfix(char*, std::size_t); +char* APS5_VABI getenv_nid_postfix(const char*); +int APS5_VABI lstat_nid_postfix(const char*, FileStat*); +void* APS5_VABI mmap_nid_postfix(void*, std::size_t, int, int, int, std::int64_t) noexcept; +} + namespace { constexpr int FnmNomatch = 1; @@ -181,14 +191,12 @@ std::uint32_t APS5_VABI arc4random_nid_postfix() { return value; } -int APS5_VABI __wrap_access() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI __wrap_access(const char* path, int mode) { + return access_nid_postfix(path, mode); } -int APS5_VABI __wrap_clock_gettime() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI __wrap_clock_gettime(int clockId, KernelTimespec* tp) { + return clock_gettime_nid_postfix(clockId, tp); } int APS5_VABI __wrap_fchown() { @@ -196,14 +204,12 @@ int APS5_VABI __wrap_fchown() { return 0; } -int APS5_VABI __wrap_getcwd() { - NotImplemented_nid_no_patch(__func__); - return 0; +char* APS5_VABI __wrap_getcwd(char* buffer, std::size_t size) { + return getcwd_nid_postfix(buffer, size); } -int APS5_VABI __wrap_getenv() { - NotImplemented_nid_no_patch(__func__); - return 0; +char* APS5_VABI __wrap_getenv(const char* name) { + return getenv_nid_postfix(name); } int APS5_VABI __wrap_link() { @@ -211,14 +217,12 @@ int APS5_VABI __wrap_link() { return 0; } -int APS5_VABI __wrap_lstat() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI __wrap_lstat(const char* path, FileStat* sb) { + return lstat_nid_postfix(path, sb); } -int APS5_VABI __wrap_mmap() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI __wrap_mmap(void* address, std::size_t length, int protection, int flags, int descriptor, std::int64_t offset) { + return mmap_nid_postfix(address, length, protection, flags, descriptor, offset); } int APS5_VABI __wrap_readlink() { diff --git a/core/libs/prx/libSceRnpsAppMgr/Export.cpp b/core/libs/prx/libSceRnpsAppMgr/Export.cpp index d81f4a5e32..a2fdcbc15d 100644 --- a/core/libs/prx/libSceRnpsAppMgr/Export.cpp +++ b/core/libs/prx/libSceRnpsAppMgr/Export.cpp @@ -1,10 +1,143 @@ -#include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" extern "C" { -APS5_DUMMY_FUN +int APS5_VABI sceRnpsAppMgrBlockAppInstall() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrCreateUpdateRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrCreateUrlRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrDestroyBundle() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrDestroyUpdateRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrFinishUrlRequest() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrGetAppInfo() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrGetAppInfoDebugString() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrGetAppInfoList() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrGetEventForShellUI() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrGetUpdateProgress() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrHasEventForShellUI() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrInitialize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrPushEventForShellUI() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrRecoverUfsImage() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrRegisterShellUI() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrRemoveApp() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrRemoveAppOnSystemShutdown() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrRemoveUfsImageOnSystemShutdown() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrRequestUpdateCheck() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrSetCrlUrl() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrSetEventFlagForClients() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrSetUpdateUrl() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrStartService() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrTerminate() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrUnblockAppInstall() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRnpsAppMgrUnregisterShellUI() { + NotImplemented_nid_no_patch(__func__); + return 0; +} } diff --git a/core/libs/prx/libSceScreenShot/CMakeLists.txt b/core/libs/prx/libSceScreenShot/CMakeLists.txt new file mode 100644 index 0000000000..0300d678fa --- /dev/null +++ b/core/libs/prx/libSceScreenShot/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(libSceScreenShot SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceScreenShot PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceScreenShot) + +if(BUILD_TESTING) + add_test_executable(guest_screenshot_tests ${LIBS_INCLUDE_DIR}/tests/GuestScreenShot.cpp) + target_include_directories(guest_screenshot_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_screenshot_tests PRIVATE libSceScreenShot libkernel libc) + configure_windows_unwind(guest_screenshot_tests) + add_test(NAME guest_screenshot COMMAND guest_screenshot_tests small) + add_test(NAME guest_screenshot_large_overlay COMMAND guest_screenshot_tests large) + if(WIN32) + set_tests_properties(guest_screenshot guest_screenshot_large_overlay PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceScreenShot/Export.cpp b/core/libs/prx/libSceScreenShot/Export.cpp index 3f898a1f74..dbc4cf09af 100644 --- a/core/libs/prx/libSceScreenShot/Export.cpp +++ b/core/libs/prx/libSceScreenShot/Export.cpp @@ -1,35 +1,89 @@ +#include #include #include +#include #include "SceTypes.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libkernel/File/include/File.hpp" + +extern "C" int APS5_VABI sceKernelFstat(int d, FileStat* sb); + +namespace { + +constexpr int ErrorArgument = static_cast(0x80BE0001); +constexpr int ErrorOverlayFile = static_cast(0x80BE0003); +constexpr int ErrorOverlaySize = static_cast(0x80BE000E); +constexpr std::uint32_t LargeOverlaySdkVersion = 0x3500000; +constexpr std::size_t MaxOverlayPath = 0x400; +constexpr std::uint16_t FileTypeMask = 0xF000; +constexpr std::uint16_t DirectoryType = 0x4000; + +std::atomic disabled{false}; + +std::uint32_t CompiledSdkVersion() { + static const auto* processParameters = static_cast(ApplicationProcessParameters_nid_no_patch()); + std::uint32_t version; + std::memcpy(&version, processParameters + 0x10, sizeof(version)); + return version; +} + +int SetOverlayImage(const char* path, int offsetX, int offsetY) { + const bool large = CompiledSdkVersion() >= LargeOverlaySdkVersion; + const int maxX = large ? 3839 : 1919; + const int maxY = large ? 2159 : 1079; + const std::int64_t maxSize = large ? 8 * 1024 * 1024 : 2 * 1024 * 1024; + if (!path || offsetX < 0 || offsetX > maxX || offsetY < 0 || offsetY > maxY) return ErrorArgument; + if (strnlen(path, MaxOverlayPath) >= MaxOverlayPath) return ErrorArgument; + const int descriptor = sceKernelOpen(path, 0, 0); + if (descriptor < 0) return ErrorOverlayFile; + FileStat stat{}; + int result = ErrorOverlayFile; + if (sceKernelFstat(descriptor, &stat) == 0 && (stat.st_mode & FileTypeMask) != DirectoryType) { + result = stat.st_size > 0 && stat.st_size <= maxSize ? 0 : ErrorOverlaySize; + } + sceKernelClose(descriptor); + return result; +} + +} extern "C" { -int APS5_VABI sceScreenShotSetOverlayImage(const char* path, int x, int y) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceScreenShotSetOverlayImage(const char* path, int offsetX, int offsetY) { + return SetOverlayImage(path, offsetX, offsetY); } -int APS5_VABI sceScreenShotSetOverlayImageWithOrigin(const char* path, int x, int y, int origin) { - NotImplemented_nid_no_patch(__func__); - return 0; -} -int APS5_VABI sceScreenShotCapture() { - NotImplemented_nid_no_patch(__func__); - return 0; + +int APS5_VABI sceScreenShotSetOverlayImageWithOrigin(const char* path, int offsetX, int offsetY, int origin) { + if (origin < 1 || origin > 9) return ErrorArgument; + return SetOverlayImage(path, offsetX, offsetY); } + int APS5_VABI sceScreenShotDisable() { - NotImplemented_nid_no_patch(__func__); - return 0; -} -int APS5_VABI sceScreenShotDisableNotification() { - NotImplemented_nid_no_patch(__func__); + disabled = true; return 0; } + int APS5_VABI sceScreenShotEnable() { - NotImplemented_nid_no_patch(__func__); + disabled = false; return 0; } + +int APS5_VABI sceScreenShotIsDisabled(int id, int* isDisabled) { + if (id == -1 || !isDisabled) return ErrorArgument; + *isDisabled = disabled ? 1 : 0; + return 0; +} + +int APS5_VABI sceScreenShotDisableNotification() { + return 0; +} + int APS5_VABI sceScreenShotEnableNotification() { + return 0; +} + +int APS5_VABI sceScreenShotCapture() { NotImplemented_nid_no_patch(__func__); return 0; } @@ -41,10 +95,6 @@ int APS5_VABI sceScreenShotGetDrcParam() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceScreenShotIsDisabled() { - NotImplemented_nid_no_patch(__func__); - return 0; -} int APS5_VABI sceScreenShotIsVshScreenCaptureDisabled() { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceShareUtility/Export.cpp b/core/libs/prx/libSceShareUtility/Export.cpp index fb34206c21..b8d35a3625 100644 --- a/core/libs/prx/libSceShareUtility/Export.cpp +++ b/core/libs/prx/libSceShareUtility/Export.cpp @@ -5,6 +5,114 @@ extern "C" { -APS5_DUMMY_FUN +int APS5_VABI sceShareUtilityAdvanceFromFileWithContentIdList() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceFromFileWithTitleIdList() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceGetServiceInfo() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceInitialize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceQueryServiceInfo() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceQueryServiceInfoA() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceResetPlayingContent() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceSetPlayingContent() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceSetUploadContentData() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceTerminate() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceValidateContents() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceWakeUpShareMenu() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceWakeUpShareMenuBroadcast() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceWakeUpShareMenuDefault() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityAdvanceWakeUpShareMenuForShareContent() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityInitialize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityInitializeEx2() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityOpenShareMenu() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityOpenShareMenuBroadcast() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityOpenShareMenuDefault() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityOpenShareMenuForShareContent() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceShareUtilityTerminate() { + NotImplemented_nid_no_patch(__func__); + return 0; +} } diff --git a/core/libs/prx/libSceSsl/Export.cpp b/core/libs/prx/libSceSsl/Export.cpp index bfc982dcdc..ceb7654ba3 100644 --- a/core/libs/prx/libSceSsl/Export.cpp +++ b/core/libs/prx/libSceSsl/Export.cpp @@ -1337,8 +1337,7 @@ int APS5_VABI sceSslLoadRootCACert() { } int APS5_VABI sceSslRecv(int sslConnectionId, void* buf, std::size_t fulllen) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceSslRead(sslConnectionId, buf, fulllen, 0); } int APS5_VABI sceSslReuseConnection(int sslConnectionId, int sockId) { @@ -1347,8 +1346,7 @@ int APS5_VABI sceSslReuseConnection(int sslConnectionId, int sockId) { } int APS5_VABI sceSslSend(int sslConnectionId, void* msg, std::size_t fulllen) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceSslWrite(sslConnectionId, msg, fulllen, 0); } int APS5_VABI sceSslSetSslVersion() { diff --git a/core/libs/prx/libSceSystemLogger2/Export.cpp b/core/libs/prx/libSceSystemLogger2/Export.cpp index d81f4a5e32..82a408b187 100644 --- a/core/libs/prx/libSceSystemLogger2/Export.cpp +++ b/core/libs/prx/libSceSystemLogger2/Export.cpp @@ -1,10 +1,33 @@ -#include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" extern "C" { -APS5_DUMMY_FUN +int APS5_VABI sceSystemLogger2GetTimestamp() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSystemLogger2Initialize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSystemLogger2SetEventAsync() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSystemLogger2SetEventAsyncEx() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSystemLogger2Terminate() { + NotImplemented_nid_no_patch(__func__); + return 0; +} } diff --git a/core/libs/prx/libSceSystemService/CMakeLists.txt b/core/libs/prx/libSceSystemService/CMakeLists.txt index 3542172d1b..20a06f0cd9 100644 --- a/core/libs/prx/libSceSystemService/CMakeLists.txt +++ b/core/libs/prx/libSceSystemService/CMakeLists.txt @@ -22,10 +22,14 @@ if(BUILD_TESTING) configure_windows_unwind(guest_loadexec_host) find_package(Python3 COMPONENTS Interpreter QUIET) if(Python3_FOUND) + set(loadexecLibraries $ $ $ $) + if(APS5_ENABLE_TRACY) + list(APPEND loadexecLibraries $) + endif() add_test(NAME loadexec_arguments COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/core/relinker/relinker/tests/test_loadexec_arguments.py $ $ - $ $ $ $ ${CMAKE_CXX_COMPILER}) + ${loadexecLibraries} ${CMAKE_CXX_COMPILER}) set_tests_properties(loadexec_arguments PROPERTIES TIMEOUT 30) add_asan_test_options(loadexec_arguments verify_asan_link_order=0) endif() diff --git a/core/libs/prx/libSceUlt/Export.cpp b/core/libs/prx/libSceUlt/Export.cpp index 63344b81f8..f876cfba21 100644 --- a/core/libs/prx/libSceUlt/Export.cpp +++ b/core/libs/prx/libSceUlt/Export.cpp @@ -236,6 +236,15 @@ int APS5_VABI sceUltUlthreadOptParamInitialize(UltOptParam* optParam, std::uint3 return InitializeOptParam(optParam); } +int APS5_VABI sceUltConditionVariableOptParamInitialize(UltOptParam* optParam, std::uint32_t buildVersion) { + (void)buildVersion; + return InitializeOptParam(optParam); +} + +int APS5_VABI _sceUltConditionVariableOptParamInitialize(UltOptParam* optParam, std::uint32_t buildVersion) { + return sceUltConditionVariableOptParamInitialize(optParam, buildVersion); +} + int APS5_VABI sceUltMutexCreate(void* mutex, const char* name, void* waitingQueueResourcePool, const UltMutexOptParam* optParam, std::uint32_t buildVersion) { (void)name; (void)buildVersion; @@ -369,7 +378,9 @@ int APS5_VABI sceUltMutexUnlock(void* mutex) { return ULT_OK; } -int APS5_VABI _sceUltConditionVariableCreate(void* condition, const char*, void* mutex, const void* optParam, std::uint32_t) { +int APS5_VABI sceUltConditionVariableCreate(void* condition, const char* name, void* mutex, const void* optParam, std::uint32_t buildVersion) { + (void)name; + (void)buildVersion; if (condition == nullptr || mutex == nullptr) return ULT_ERROR_NULL; if (optParam != nullptr) throw std::runtime_error("ULT condition variable options are not implemented"); std::lock_guard lock(gMutex); @@ -385,6 +396,10 @@ int APS5_VABI _sceUltConditionVariableCreate(void* condition, const char*, void* return ULT_OK; } +int APS5_VABI _sceUltConditionVariableCreate(void* condition, const char* name, void* mutex, const void* optParam, std::uint32_t buildVersion) { + return sceUltConditionVariableCreate(condition, name, mutex, optParam, buildVersion); +} + int APS5_VABI sceUltConditionVariableDestroy(void* condition) { std::lock_guard lock(gMutex); const auto it = gConditions.find(condition); @@ -1081,20 +1096,5 @@ int APS5_VABI sceUltUlthreadFactoryInitialize() { return 0; } -int APS5_VABI sceUltConditionVariableCreate() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceUltConditionVariableOptParamInitialize() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - - -int APS5_VABI _sceUltConditionVariableOptParamInitialize() { - NotImplemented_nid_no_patch(__func__); - return 0; -} } diff --git a/core/libs/prx/libSceVdecCore/Export.cpp b/core/libs/prx/libSceVdecCore/Export.cpp index fb34206c21..343d1ec6a6 100644 --- a/core/libs/prx/libSceVdecCore/Export.cpp +++ b/core/libs/prx/libSceVdecCore/Export.cpp @@ -5,6 +5,104 @@ extern "C" { -APS5_DUMMY_FUN +int APS5_VABI sceVdecCoreAnalyzeStream() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreCreateDecoder() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreDeleteDecoder() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreFinalizeComputeResource() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreFlushDecodeOutput() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreGetDecodeOutput() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreInitializeComputeResource() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreMapMemoryBlock() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreQueryComputeResourceInfo() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreQueryFrameBufferInfo() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreQueryInstanceSize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreResetDecoder() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreSetDecodeInput() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreSetDecodeOutputSw() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreSyncDecode() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreSyncDecodeOutputSw() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreSyncDecodeWptr() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreTrySyncDecode() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreTrySyncDecodeOutputSw() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVdecCoreTrySyncDecodeWptr() { + NotImplemented_nid_no_patch(__func__); + return 0; +} } diff --git a/core/libs/prx/libSceVdecsw/Export.cpp b/core/libs/prx/libSceVdecsw/Export.cpp index 4c70113346..d6fa384070 100644 --- a/core/libs/prx/libSceVdecsw/Export.cpp +++ b/core/libs/prx/libSceVdecsw/Export.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" @@ -19,6 +20,8 @@ constexpr int VdecswErrorInputQueueEmpty = static_cast(0x81510116u); constexpr int VdecswErrorDecodePending = static_cast(0x81510117u); constexpr std::uint32_t CodecVp9 = 2382845; constexpr std::size_t MaxQueuedPictures = 4; +constexpr std::uint32_t CodecAvc = 1; +constexpr std::uint32_t CodecHevc = 974921; struct InputData { std::uint64_t thisSize; @@ -99,10 +102,55 @@ struct AvcPictureInfo { std::uint8_t vui[44]; }; +struct HevcPictureInfo { + std::uint64_t thisSize; + bool isValid; + std::uint64_t ptsData; + std::uint64_t dtsData; + std::uint64_t attachedData; + std::uint32_t picWidthInLumaSamples; + std::uint32_t picHeightInLumaSamples; + std::uint8_t bitDepthLumaMinus8; + std::uint8_t bitDepthChromaMinus8; + std::uint8_t timingInfoPresentFlag; + std::uint32_t numUnitsInTick; + std::uint32_t timeScale; + std::uint32_t aspectRatioInfoPresentFlag; + std::uint8_t aspectRatioIdc; + std::uint16_t sarWidth; + std::uint16_t sarHeight; + std::uint8_t videoSignal[8]; + std::uint32_t picStruct; + std::uint32_t sourceScanType; + std::uint32_t duplicateFlag; + std::uint32_t conformanceWindowFlag; + std::uint32_t confWinLeftOffset; + std::uint32_t confWinRightOffset; + std::uint32_t confWinTopOffset; + std::uint32_t confWinBottomOffset; + std::uint32_t defaultDisplayWindow[5]; + std::uint8_t chromaLocation[4]; + std::uint8_t presentFlags[11]; + std::uint8_t idrPictureFlag; + std::uint8_t irapPictureFlag; + std::uint8_t generalProfileSpace; + std::uint8_t generalTierFlag; + std::uint8_t generalProfileIdc; + std::uint8_t generalSourceFlags[3]; + std::uint8_t generalLevelIdc; + std::uint8_t subLayer[9]; + std::uint8_t frameCroppingFlag; + std::uint32_t frameCropLeftOffset; + std::uint32_t frameCropRightOffset; + std::uint32_t frameCropTopOffset; + std::uint32_t frameCropBottomOffset; +}; + static_assert(sizeof(InputData) == 0x30 && sizeof(InputResult) == 0x18 && sizeof(FrameBuffer) == 0x18); static_assert(sizeof(Vp9PictureInfo) == 0x58 && offsetof(Vp9PictureInfo, ptsData) == 0x10 && offsetof(Vp9PictureInfo, profile) == 0x28 && offsetof(Vp9PictureInfo, bitDepth) == 0x38 && offsetof(Vp9PictureInfo, colorSpace) == 0x3c && offsetof(Vp9PictureInfo, renderHeight) == 0x50); static_assert(sizeof(OutputInfo) == 0x38 && offsetof(OutputInfo, isLastFrame) == 9 && offsetof(OutputInfo, pictureCount) == 0xb && offsetof(OutputInfo, isDiscardedFrame) == 0x1c); static_assert(sizeof(AvcPictureInfo) == 0x78 && offsetof(AvcPictureInfo, picWidthInMbsMinus1) == 0x2c && offsetof(AvcPictureInfo, frameCropBottomOffset) == 0x44); +static_assert(sizeof(HevcPictureInfo) == 0xb8 && offsetof(HevcPictureInfo, picWidthInLumaSamples) == 0x28 && offsetof(HevcPictureInfo, sarWidth) == 0x42 && offsetof(HevcPictureInfo, picStruct) == 0x50 && offsetof(HevcPictureInfo, conformanceWindowFlag) == 0x5c && offsetof(HevcPictureInfo, idrPictureFlag) == 0x93 && offsetof(HevcPictureInfo, generalProfileIdc) == 0x97 && offsetof(HevcPictureInfo, generalLevelIdc) == 0x9b && offsetof(HevcPictureInfo, frameCroppingFlag) == 0xa5 && offsetof(HevcPictureInfo, frameCropBottomOffset) == 0xb4); struct Videodec2FrameBuffer { std::uint64_t thisSize; @@ -126,19 +174,6 @@ struct Videodec2OutputInfo { std::uint32_t framePitchInBytes; }; -struct Videodec2AvcPictureInfo { - std::uint64_t thisSize; - bool isValid; - std::uint64_t ptsData; - std::uint64_t dtsData; - std::uint64_t attachedData; - std::uint8_t idrPictureFlag; - std::uint8_t profileIdc; - std::uint8_t levelIdc; - std::uint32_t picWidthInLumaSamples; - std::uint32_t picHeightInLumaSamples; -}; - struct Videodec2Crop { std::uint32_t left; std::uint32_t right; @@ -146,6 +181,18 @@ struct Videodec2Crop { std::uint32_t bottom; }; +struct Videodec2PictureHeader { + std::uint64_t pts; + std::uint64_t dts; + std::uint64_t attached; + bool keyFrame; + bool hevcIdr; + std::uint32_t profile; + std::uint32_t level; + Videodec2Crop crop; + std::uint8_t bitDepthMinus8; +}; + struct DecoderMemoryInfo { std::uint64_t thisSize; std::uint64_t cpuMemorySize; @@ -171,8 +218,7 @@ int APS5_VABI sceVideodec2Decode_nid_postfix(std::uint64_t handle, const InputDa int APS5_VABI sceVideodec2Flush_nid_postfix(std::uint64_t handle, Videodec2FrameBuffer* frame, Videodec2OutputInfo* output); int APS5_VABI sceVideodec2GetVp9PictureInfo_nid_postfix(const Videodec2OutputInfo* output, Vp9PictureInfo* info); int APS5_VABI sceVideodec2Reset_nid_postfix(std::uint64_t handle); -int APS5_VABI sceVideodec2GetAvcPictureInfo_nid_postfix(const Videodec2OutputInfo* output, Videodec2AvcPictureInfo* first, Videodec2AvcPictureInfo* second); -void Videodec2PictureCrop_nid_no_patch(std::uint64_t handle, const void* frameBuffer, Videodec2Crop* crop); +void Videodec2PictureHeader_nid_no_patch(std::uint64_t handle, const void* frameBuffer, Videodec2PictureHeader* header); } namespace { @@ -187,10 +233,12 @@ struct ConsumedInput { std::uint32_t frames; }; +using PictureInfo = std::variant; + struct Picture { std::vector pixels; Videodec2OutputInfo output; - AvcPictureInfo info; + PictureInfo info; }; struct Decoder { @@ -200,7 +248,7 @@ struct Decoder { std::deque consumed; std::deque outputs; std::deque pictures; - std::map delivered; + std::map delivered; bool finalized = false; bool drained = false; std::map deliveredVp9; @@ -216,27 +264,54 @@ std::shared_ptr findDecoder(std::uint64_t handle) { return found->second; } -AvcPictureInfo pictureInfo(std::uint64_t handle, const Videodec2OutputInfo& output) { - Videodec2AvcPictureInfo source{sizeof(Videodec2AvcPictureInfo)}; - sceVideodec2GetAvcPictureInfo_nid_postfix(&output, &source, nullptr); - if (!source.isValid) throw std::runtime_error("Vdecsw: decoded picture has no picture info"); - Videodec2Crop crop{}; - Videodec2PictureCrop_nid_no_patch(handle, output.frameBuffer, &crop); +PictureInfo pictureInfo(std::uint64_t handle, const Videodec2OutputInfo& output) { + Videodec2PictureHeader header{}; + Videodec2PictureHeader_nid_no_patch(handle, output.frameBuffer, &header); + const auto& crop = header.crop; + const bool cropped = crop.left != 0 || crop.right != 0 || crop.top != 0 || crop.bottom != 0; + if (output.codecType == CodecHevc) { + HevcPictureInfo info{}; + info.thisSize = sizeof(HevcPictureInfo); + info.isValid = true; + info.ptsData = header.pts; + info.dtsData = header.dts; + info.attachedData = header.attached; + info.picWidthInLumaSamples = output.frameWidth; + info.picHeightInLumaSamples = output.frameHeight; + info.bitDepthLumaMinus8 = header.bitDepthMinus8; + info.bitDepthChromaMinus8 = header.bitDepthMinus8; + info.conformanceWindowFlag = cropped; + info.confWinLeftOffset = crop.left / 2u; + info.confWinRightOffset = crop.right / 2u; + info.confWinTopOffset = crop.top / 2u; + info.confWinBottomOffset = crop.bottom / 2u; + info.idrPictureFlag = header.hevcIdr; + info.irapPictureFlag = header.keyFrame; + info.generalProfileIdc = static_cast(header.profile); + info.generalLevelIdc = static_cast(header.level); + info.frameCroppingFlag = cropped; + info.frameCropLeftOffset = info.confWinLeftOffset; + info.frameCropRightOffset = info.confWinRightOffset; + info.frameCropTopOffset = info.confWinTopOffset; + info.frameCropBottomOffset = info.confWinBottomOffset; + return info; + } + if (output.codecType != CodecAvc) throw std::runtime_error("Vdecsw: picture info is only implemented for H.264 and HEVC"); const auto widthInMbs = (output.frameWidth + 15u) / 16u; const auto heightInMbs = (output.frameHeight + 15u) / 16u; AvcPictureInfo info{}; info.thisSize = sizeof(AvcPictureInfo); info.isValid = true; - info.ptsData = source.ptsData; - info.dtsData = source.dtsData; - info.attachedData = source.attachedData; - info.idrPictureFlag = source.idrPictureFlag; - info.profileIdc = source.profileIdc; - info.levelIdc = source.levelIdc; + info.ptsData = header.pts; + info.dtsData = header.dts; + info.attachedData = header.attached; + info.idrPictureFlag = header.keyFrame; + info.profileIdc = static_cast(header.profile); + info.levelIdc = static_cast(header.level); info.picWidthInMbsMinus1 = widthInMbs - 1u; info.picHeightInMapUnitsMinus1 = heightInMbs - 1u; info.frameMbsOnlyFlag = 1; - info.frameCroppingFlag = crop.left != 0 || crop.right != 0 || crop.top != 0 || crop.bottom != 0; + info.frameCroppingFlag = cropped; info.frameCropLeftOffset = crop.left / 2u; info.frameCropRightOffset = crop.right / 2u; info.frameCropTopOffset = crop.top / 2u; @@ -251,6 +326,29 @@ Vp9PictureInfo vp9PictureInfo(const Videodec2OutputInfo& output) { return info; } +template +int deliveredPictureInfo(const OutputInfo* output, TInfo* info) { + if (output == nullptr || info == nullptr || info->thisSize < sizeof(std::uint64_t)) throw std::runtime_error("Vdecsw: invalid argument pointer"); + if (output->pictureCount > 1) throw std::runtime_error("Vdecsw: second field picture info is not implemented"); + if (output->pictureCount == 0) return 0; + std::vector> all; + { + std::lock_guard lock(decodersMutex); + for (const auto& [handle, decoder] : decoders) all.push_back(decoder); + } + for (const auto& decoder : all) { + std::lock_guard lock(decoder->mutex); + const auto found = decoder->delivered.find(output->frameBuffer); + if (found == decoder->delivered.end()) continue; + const auto* source = std::get_if(&found->second); + if (source == nullptr) throw std::runtime_error("Vdecsw: picture info requested for another codec"); + const auto bytes = std::min(info->thisSize, sizeof(TInfo)) - sizeof(std::uint64_t); + std::memcpy(reinterpret_cast(info) + sizeof(std::uint64_t), reinterpret_cast(source) + sizeof(std::uint64_t), bytes); + return 0; + } + throw std::runtime_error("Vdecsw: picture info requested for a frame buffer no decoder returned"); +} + void advance(std::uint64_t handle, Decoder& decoder) { while (decoder.pictures.size() < MaxQueuedPictures) { std::vector pixels(decoder.frameBytes); @@ -270,7 +368,7 @@ void advance(std::uint64_t handle, Decoder& decoder) { return; } if (output.isValid) { - const auto info = output.codecType == CodecVp9 ? AvcPictureInfo{} : pictureInfo(handle, output); + const auto info = output.codecType == CodecVp9 ? PictureInfo{} : pictureInfo(handle, output); decoder.pictures.push_back({std::move(pixels), output, info}); } } @@ -414,31 +512,12 @@ int APS5_VABI sceVdecswGetPictureInfo(const OutputInfo* output, void* first, voi } int APS5_VABI sceVdecswGetAvcPictureInfo(const OutputInfo* output, AvcPictureInfo* first, AvcPictureInfo* second) { - if (output == nullptr || first == nullptr || first->thisSize < sizeof(std::uint64_t)) throw std::runtime_error("Vdecsw: invalid argument pointer"); - if (output->pictureCount > 1) throw std::runtime_error("Vdecsw: second field picture info is not implemented"); - if (output->pictureCount == 0) return 0; static_cast(second); - std::vector> all; - { - std::lock_guard lock(decodersMutex); - for (const auto& [handle, decoder] : decoders) all.push_back(decoder); - } - for (const auto& decoder : all) { - std::lock_guard lock(decoder->mutex); - const auto found = decoder->delivered.find(output->frameBuffer); - if (found == decoder->delivered.end()) continue; - const auto bytes = std::min(first->thisSize, sizeof(AvcPictureInfo)) - sizeof(std::uint64_t); - std::memcpy(reinterpret_cast(first) + sizeof(std::uint64_t), reinterpret_cast(&found->second) + sizeof(std::uint64_t), bytes); - return 0; - } - throw std::runtime_error("Vdecsw: picture info requested for a frame buffer no decoder returned"); + return deliveredPictureInfo(output, first); } -int APS5_VABI sceVdecswGetHevcPictureInfo(const OutputInfo* output, void* picture) { - (void)output; - (void)picture; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVdecswGetHevcPictureInfo(const OutputInfo* output, HevcPictureInfo* picture) { + return deliveredPictureInfo(output, picture); } int APS5_VABI sceVdecswGetVp9PictureInfo(const OutputInfo* output, Vp9PictureInfo* picture) { diff --git a/core/libs/prx/libSceVenc/Export.cpp b/core/libs/prx/libSceVenc/Export.cpp index fb34206c21..8d8de5aa65 100644 --- a/core/libs/prx/libSceVenc/Export.cpp +++ b/core/libs/prx/libSceVenc/Export.cpp @@ -5,6 +5,74 @@ extern "C" { -APS5_DUMMY_FUN +int APS5_VABI sceVencCreateEncoder() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVencDeleteEncoder() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVencGetConfig() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVencMapMemory() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVencQueryHeader() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVencQueryMemorySize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVencQueryPreset() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVencSetConfig() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVencSetDecodedFrame() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVencSetPictureConfig() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVencSetRateControlConfig() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVencSetReferenceFrameInvalidationConfig() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVencStartEncode() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVencStopEncode() { + NotImplemented_nid_no_patch(__func__); + return 0; +} } diff --git a/core/libs/prx/libSceVideoOut/tests/InputMapping.cpp b/core/libs/prx/libSceVideoOut/tests/InputMapping.cpp index 3497443fcc..6b0c4348ed 100644 --- a/core/libs/prx/libSceVideoOut/tests/InputMapping.cpp +++ b/core/libs/prx/libSceVideoOut/tests/InputMapping.cpp @@ -18,6 +18,14 @@ void require(bool condition, const char* message) { std::filesystem::path root; +void setConfigPath(const std::filesystem::path& path) { +#ifdef _WIN32 + require(_wputenv_s(L"ANYPS5_INPUT_CONFIG", path.wstring().c_str()) == 0, "set ANYPS5_INPUT_CONFIG"); +#else + require(::setenv("ANYPS5_INPUT_CONFIG", path.string().c_str(), 1) == 0, "set ANYPS5_INPUT_CONFIG"); +#endif +} + void useConfig(const std::string& text) { const auto path = root / "anyps5-input.ini"; { @@ -25,11 +33,7 @@ void useConfig(const std::string& text) { file << text; require(file.good(), "write the input mapping"); } -#ifdef _WIN32 - require(_putenv_s("ANYPS5_INPUT_CONFIG", path.string().c_str()) == 0, "set ANYPS5_INPUT_CONFIG"); -#else - require(::setenv("ANYPS5_INPUT_CONFIG", path.string().c_str(), 1) == 0, "set ANYPS5_INPUT_CONFIG"); -#endif + setConfigPath(path); } bool rejects(const std::string& text) { @@ -140,14 +144,6 @@ void mouseLookReject() { require(rejects("MouseDecayMs = 20\nmousedecayms = 30\n"), "a mouse setting given twice is rejected"); } -void setConfigPath(const std::filesystem::path& path) { -#ifdef _WIN32 - require(_wputenv_s(L"ANYPS5_INPUT_CONFIG", path.wstring().c_str()) == 0, "set ANYPS5_INPUT_CONFIG"); -#else - require(::setenv("ANYPS5_INPUT_CONFIG", path.string().c_str(), 1) == 0, "set ANYPS5_INPUT_CONFIG"); -#endif -} - void nonAsciiPaths() { const std::u8string name = u8"anyps5-input-\u00fc-\u65e5\u672c-" + std::u8string(root.filename().u8string()); const auto directory = root / std::filesystem::path(name); diff --git a/core/libs/prx/libSceVideodec/Export.cpp b/core/libs/prx/libSceVideodec/Export.cpp index 56919ce362..56ae352fcf 100644 --- a/core/libs/prx/libSceVideodec/Export.cpp +++ b/core/libs/prx/libSceVideodec/Export.cpp @@ -168,6 +168,10 @@ struct Decoder2Crop { }; static_assert(sizeof(ConfigInfo) == 40 && sizeof(ResourceInfo) == 56 && sizeof(Ctrl) == 24); +static_assert(offsetof(ConfigInfo, maxFrameWidth) == 20 && offsetof(ConfigInfo, videodecFlags) == 32); +static_assert(offsetof(ResourceInfo, cpuGpuMemorySize) == 24 && offsetof(ResourceInfo, maxFrameBufferSize) == 40); +static_assert(offsetof(ResourceInfo, frameBufferAlignment) == 48); +static_assert(sizeof(Decoder2ConfigInfo) == 40 && sizeof(Decoder2MemoryInfo) == 72); static_assert(sizeof(FrameBuffer) == 24 && sizeof(InputData) == 48 && sizeof(PictureInfo) == 112); static_assert(sizeof(CodecInfo) == 64 && offsetof(PictureInfo, codec) == 48); @@ -212,6 +216,20 @@ struct Geometry { Decoder2Crop crop{}; }; +Decoder2ConfigInfo MapConfig(const ConfigInfo& config) { + Decoder2ConfigInfo decoderConfig{}; + decoderConfig.thisSize = sizeof(Decoder2ConfigInfo); + decoderConfig.resourceType = 1; + decoderConfig.codecType = CodecAvc; + decoderConfig.profile = config.profile; + decoderConfig.maxLevel = config.maxLevel; + decoderConfig.maxFrameWidth = config.maxFrameWidth; + decoderConfig.maxFrameHeight = config.maxFrameHeight; + decoderConfig.maxDpbFrameCount = config.maxDpbFrameCount; + decoderConfig.decodePipelineDepth = 1; + return decoderConfig; +} + Geometry Measure(const Decoder2OutputInfo& output, const Decoder2Crop& crop) { Geometry geometry; geometry.width = output.frameWidth; @@ -266,16 +284,7 @@ extern "C" { int APS5_VABI sceVideodecCreateDecoder(const ConfigInfo* config, const ResourceInfo* resource, Ctrl* ctrl) { if (config == nullptr || resource == nullptr || ctrl == nullptr) return VideodecErrorArgumentPointer; if (config->thisSize < sizeof(ConfigInfo) || resource->thisSize < sizeof(ResourceInfo)) return VideodecErrorStructSize; - Decoder2ConfigInfo decoderConfig{}; - decoderConfig.thisSize = sizeof(Decoder2ConfigInfo); - decoderConfig.resourceType = 1; - decoderConfig.codecType = CodecAvc; - decoderConfig.profile = config->profile; - decoderConfig.maxLevel = config->maxLevel; - decoderConfig.maxFrameWidth = config->maxFrameWidth; - decoderConfig.maxFrameHeight = config->maxFrameHeight; - decoderConfig.maxDpbFrameCount = config->maxDpbFrameCount; - decoderConfig.decodePipelineDepth = 1; + const auto decoderConfig = MapConfig(*config); Decoder2MemoryInfo decoderMemory{}; decoderMemory.thisSize = sizeof(Decoder2MemoryInfo); decoderMemory.cpuMemorySize = resource->cpuMemorySize; @@ -407,8 +416,21 @@ int APS5_VABI sceVideodecMapMemory(void) { return 0; } -int APS5_VABI sceVideodecQueryResourceInfo(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVideodecQueryResourceInfo(const ConfigInfo* config, ResourceInfo* resource) { + if (config == nullptr || resource == nullptr) return VideodecErrorArgumentPointer; + if (config->thisSize < sizeof(ConfigInfo) || resource->thisSize < sizeof(ResourceInfo)) return VideodecErrorStructSize; + const auto decoderConfig = MapConfig(*config); + Decoder2MemoryInfo decoderMemory{}; + decoderMemory.thisSize = sizeof(Decoder2MemoryInfo); + try { + sceVideodec2QueryDecoderMemoryInfo_nid_postfix(&decoderConfig, &decoderMemory); + } catch (const std::exception&) { + return VideodecErrorConfigInfo; + } + resource->cpuMemorySize = decoderMemory.cpuMemorySize; + resource->cpuGpuMemorySize = decoderMemory.cpuGpuMemorySize; + resource->maxFrameBufferSize = decoderMemory.maxFrameBufferSize; + resource->frameBufferAlignment = decoderMemory.frameBufferAlignment; return 0; } diff --git a/core/libs/prx/libSceVideodec2/Export.cpp b/core/libs/prx/libSceVideodec2/Export.cpp index 2d51f7cb70..583b18c0ff 100644 --- a/core/libs/prx/libSceVideodec2/Export.cpp +++ b/core/libs/prx/libSceVideodec2/Export.cpp @@ -283,6 +283,20 @@ struct Picture { bool idr = false; Vp9PictureInfo vp9{}; Crop crop{}; + bool hevcIdr = false; + std::uint8_t bitDepthMinus8 = 0; +}; + +struct PictureHeader { + std::uint64_t pts; + std::uint64_t dts; + std::uint64_t attached; + bool keyFrame; + bool hevcIdr; + std::uint32_t profile; + std::uint32_t level; + Crop crop; + std::uint8_t bitDepthMinus8; }; struct FrameDeleter { @@ -437,6 +451,16 @@ bool CarriesAvcSlice(const std::vector& unit) { return false; } +bool StartsHevcIdr(const std::vector& unit) { + for (std::size_t index = 0; index + 3 < unit.size(); ++index) { + if (unit[index] != 0 || unit[index + 1] != 0 || unit[index + 2] != 1) continue; + const auto type = (unit[index + 3] >> 1u) & 0x3fu; + if (type < 32) return type == 19 || type == 20; + index += 2; + } + return false; +} + void Receive(Decoder& decoder) { for (;;) { FramePointer frame(av_frame_alloc()); @@ -526,6 +550,7 @@ void EmitDecoded(std::uint64_t handle, Decoder& decoder, FrameBuffer* frame, Out if (decoder.context->profile > 0) decoder.profile = static_cast(decoder.context->profile) & 0xffu; if (decoder.context->level > 0) decoder.level = static_cast(decoder.context->level); if (!WriteNv12(decoder, *picture, static_cast(frame->frameBuffer), frame->frameBufferSize)) throw std::runtime_error("Videodec2: NV12 conversion failed"); + timestamps.bitDepthMinus8 = decoder.sampleBytes == 2 ? 2 : 0; const auto pitch = PitchBytes(decoder.width, decoder.sampleBytes); frame->isAccepted = true; output->isValid = true; @@ -664,7 +689,9 @@ int APS5_VABI sceVideodec2Decode_nid_postfix(std::uint64_t handle, const InputDa std::memcpy(packet->data + decoder.parameterSets.size(), unit->data(), unit->size()); decoder.parameterSets.clear(); packet->pts = decoder.nextKey; - decoder.inputs.emplace(decoder.nextKey++, picture); + auto tagged = picture; + tagged.hevcIdr = decoder.codec == CodecHevc && StartsHevcIdr(*unit); + decoder.inputs.emplace(decoder.nextKey++, tagged); Send(decoder, packet); EmitDecoded(handle, decoder, frame, output); return 0; @@ -776,15 +803,23 @@ int APS5_VABI sceVideodec2GetVp9PictureInfo_nid_postfix(const OutputInfo* output return 0; } -void Videodec2PictureCrop_nid_no_patch(std::uint64_t handle, const void* frameBuffer, Crop* crop) { - if (!crop) throw std::runtime_error("Videodec2: invalid argument pointer"); +void Videodec2PictureHeader_nid_no_patch(std::uint64_t handle, const void* frameBuffer, PictureHeader* header) { + if (!header) throw std::runtime_error("Videodec2: invalid argument pointer"); const auto found = FindDecoder(handle); if (!found) throw std::runtime_error("Videodec2: invalid decoder handle"); auto& decoder = *found; std::lock_guard guard(decoder.mutex); const auto picture = decoder.pictures.find(frameBuffer); if (picture == decoder.pictures.end()) throw std::runtime_error("Videodec2: unknown frame buffer"); - *crop = picture->second.crop; + const auto& stored = picture->second; + *header = {stored.pts, stored.dts, stored.attached, stored.idr, stored.hevcIdr, decoder.profile, decoder.level, stored.crop, stored.bitDepthMinus8}; +} + +void Videodec2PictureCrop_nid_no_patch(std::uint64_t handle, const void* frameBuffer, Crop* crop) { + if (!crop) throw std::runtime_error("Videodec2: invalid argument pointer"); + PictureHeader header{}; + Videodec2PictureHeader_nid_no_patch(handle, frameBuffer, &header); + *crop = header.crop; } } diff --git a/core/libs/prx/libSceVoice/Export.cpp b/core/libs/prx/libSceVoice/Export.cpp index 707d112ea4..3357ee67ea 100644 --- a/core/libs/prx/libSceVoice/Export.cpp +++ b/core/libs/prx/libSceVoice/Export.cpp @@ -26,6 +26,9 @@ constexpr auto NotInitialized = static_cast(0x804e0801u); constexpr auto InvalidPort = static_cast(0x804e0804u); constexpr auto InvalidArgument = static_cast(0x804e0805u); constexpr auto PortNotFound = static_cast(0x804e0807u); +constexpr std::int32_t ThreadPriorityHighest = 256; +constexpr std::int32_t ThreadPriorityLowest = 767; +constexpr std::uint64_t ThreadAffinityAll = 0x1fff; struct Port { std::int32_t type; @@ -42,6 +45,7 @@ struct Voice { bool initialized = false; bool started = false; std::int32_t appType = 0; + bool mutedAll = false; std::deque freePorts; std::map ports; std::set> connections; @@ -56,6 +60,10 @@ Voice& State() { throw std::logic_error(std::string(function) + ": " + why); } +bool IsThreadSetting(std::int32_t priority, std::uint64_t affinity) { + return priority >= ThreadPriorityHighest && priority <= ThreadPriorityLowest && affinity != 0 && (affinity & ~ThreadAffinityAll) == 0; +} + Port& RequirePort(Voice& voice, std::uint32_t id, const char* function) { if (!voice.initialized) Fail(function, "library not initialized"); const auto found = voice.ports.find(id); @@ -65,6 +73,21 @@ Port& RequirePort(Voice& voice, std::uint32_t id, const char* function) { bool IsInput(std::int32_t type) { return type == PortInDevice || type == PortInPcm || type == PortInVoice; } +bool VoiceFrame(std::int32_t bitrate, std::uint16_t& frameSize, std::uint16_t& frameDuration) { + switch (bitrate) { + case 3850: frameSize = 20; frameDuration = 40; return true; + case 4650: frameSize = 24; frameDuration = 40; return true; + case 5700: frameSize = 15; frameDuration = 20; return true; + case 7300: frameSize = 19; frameDuration = 20; return true; + case 11800: frameSize = 15; frameDuration = 10; return true; + case 12200: frameSize = 16; frameDuration = 10; return true; + case 14400: frameSize = 27; frameDuration = 15; return true; + case 16000: frameSize = 20; frameDuration = 10; return true; + case 22533: frameSize = 43; frameDuration = 15; return true; + default: return false; + } +} + } extern "C" { @@ -90,7 +113,10 @@ int APS5_VABI sceVoiceSetThreadsParams(const VoiceThreadsParams* params) { if (voice.started) return static_cast(0x804e080bu); if (params->mode == 0) return 0; if (params->mode != 1) return InvalidArgument; - Fail(__func__, "worker priority and affinity are not implemented"); + if (!IsThreadSetting(params->firstPriority, params->firstAffinity) || !IsThreadSetting(params->secondPriority, params->secondAffinity) || !IsThreadSetting(params->thirdPriority, params->thirdAffinity)) { + Fail(__func__, "the result for a priority outside 256-767 or an affinity mask outside 0x1fff is unknown"); + } + return 0; } int APS5_VABI sceVoiceStart(const VoiceStartParam* param) { @@ -121,6 +147,7 @@ int APS5_VABI sceVoiceEnd_nid_postfix(void) { voice.connections.clear(); voice.started = false; voice.appType = 0; + voice.mutedAll = false; voice.initialized = false; return 0; } @@ -138,18 +165,7 @@ int APS5_VABI sceVoiceCreatePort(std::uint32_t* portId, const VoicePortParam* pa if (type == PortInPcm || type == PortOutPcm) { if (param->pcmaudio.sample_rate != 16000 || param->pcmaudio.data_type != 0) return InvalidArgument; } else if (type == PortInVoice || type == PortOutVoice) { - switch (param->voice.bitrate) { - case 3850: frameSize = 20; frameDuration = 40; break; - case 4650: frameSize = 24; frameDuration = 40; break; - case 5700: frameSize = 15; frameDuration = 20; break; - case 7300: frameSize = 19; frameDuration = 20; break; - case 11800: frameSize = 15; frameDuration = 10; break; - case 12200: frameSize = 16; frameDuration = 10; break; - case 14400: frameSize = 27; frameDuration = 15; break; - case 16000: frameSize = 20; frameDuration = 10; break; - case 22533: frameSize = 43; frameDuration = 15; break; - default: return InvalidArgument; - } + if (!VoiceFrame(param->voice.bitrate, frameSize, frameDuration)) return InvalidArgument; } constexpr auto resourceFull = static_cast(0x804e0808u); if (voice.freePorts.empty()) return resourceFull; @@ -338,7 +354,7 @@ int APS5_VABI sceVoiceGetMuteFlag(std::uint32_t portId, bool* muted) { if (!muted) return InvalidArgument; const auto found = voice.ports.find(portId); if (found == voice.ports.end()) return PortNotFound; - *muted = found->second.muted; + *muted = found->second.muted || voice.mutedAll; return 0; } @@ -411,9 +427,8 @@ int APS5_VABI sceVoiceDisableChat(void) { Fail(__func__, "audio chat backend is not implemented"); } -int APS5_VABI sceVoiceInitHQ(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVoiceInitHQ(const VoiceInitParam* param, std::int32_t version) { + return sceVoiceInit(param, version); } int APS5_VABI sceVoiceGetMemorySize(void) { @@ -441,23 +456,55 @@ int APS5_VABI sceVoiceVADPVTAdjustment(void) { return 0; } -int APS5_VABI sceVoiceVADSetVersion(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVoiceVADSetVersion(std::int32_t version) { + return version == 0 || version == 1 ? 0 : InvalidArgument; +} + +int APS5_VABI sceVoiceSetMuteFlagAll(bool muted) { + auto& voice = State(); + std::lock_guard lock(voice.mutex); + if (!voice.initialized) return NotInitialized; + voice.mutedAll = muted; return 0; } -int APS5_VABI sceVoiceSetMuteFlagAll(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVoiceSetBitRate(std::uint32_t portId, std::uint32_t bitrate) { + auto& voice = State(); + std::lock_guard lock(voice.mutex); + if (!voice.initialized) return NotInitialized; + if (portId > 127) return InvalidPort; + const auto found = voice.ports.find(portId); + if (found == voice.ports.end()) return PortNotFound; + auto& port = found->second; + if (port.type != PortInVoice && port.type != PortOutVoice) Fail(__func__, "port does not carry encoded voice"); + std::uint16_t frameSize = 0; + std::uint16_t frameDuration = 0; + if (bitrate > INT32_MAX || !VoiceFrame(static_cast(bitrate), frameSize, frameDuration)) Fail(__func__, "unsupported bitrate " + std::to_string(bitrate)); + port.bitrate = bitrate; + port.frameSize = frameSize; return 0; } -int APS5_VABI sceVoiceSetBitRate(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceUpdatePort(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVoiceUpdatePort(std::uint32_t portId, const VoicePortParam* param) { + auto& voice = State(); + std::lock_guard lock(voice.mutex); + if (!voice.initialized) return NotInitialized; + if (portId > 127) return InvalidPort; + if (!param) Fail(__func__, "null port parameters"); + const auto found = voice.ports.find(portId); + if (found == voice.ports.end()) return PortNotFound; + auto& port = found->second; + if (param->port_type != port.type) Fail(__func__, "port type " + std::to_string(param->port_type) + " differs from the port's " + std::to_string(port.type)); + std::uint16_t frameSize = port.frameSize; + std::uint16_t frameDuration = 0; + const bool voicePort = port.type == PortInVoice || port.type == PortOutVoice; + if (voicePort && !VoiceFrame(param->voice.bitrate, frameSize, frameDuration)) Fail(__func__, "unsupported bitrate " + std::to_string(param->voice.bitrate)); + port.muted = param->mute != 0; + port.volume = std::isfinite(param->volume) ? param->volume : 0.0f; + if (voicePort) { + port.bitrate = static_cast(param->voice.bitrate); + port.frameSize = frameSize; + } return 0; } diff --git a/core/libs/prx/libSceVorbisDec/CMakeLists.txt b/core/libs/prx/libSceVorbisDec/CMakeLists.txt new file mode 100644 index 0000000000..0da3f0a43e --- /dev/null +++ b/core/libs/prx/libSceVorbisDec/CMakeLists.txt @@ -0,0 +1,77 @@ +enable_language(C) + +set(OGG_DIR ${CMAKE_SOURCE_DIR}/3rdparty/ogg) +set(VORBIS_DIR ${CMAKE_SOURCE_DIR}/3rdparty/vorbis) +set(OGG_GENERATED_DIR ${CMAKE_CURRENT_BINARY_DIR}/ogg/include) + +include(CheckIncludeFiles) +check_include_files(inttypes.h INCLUDE_INTTYPES_H) +check_include_files(stdint.h INCLUDE_STDINT_H) +check_include_files(sys/types.h INCLUDE_SYS_TYPES_H) +set(SIZE16 int16_t) +set(USIZE16 uint16_t) +set(SIZE32 int32_t) +set(USIZE32 uint32_t) +set(SIZE64 int64_t) +set(USIZE64 uint64_t) +configure_file(${OGG_DIR}/include/ogg/config_types.h.in ${OGG_GENERATED_DIR}/ogg/config_types.h @ONLY) + +add_library(ogg STATIC EXCLUDE_FROM_ALL + ${OGG_DIR}/src/bitwise.c + ${OGG_DIR}/src/framing.c +) +target_include_directories(ogg PUBLIC ${OGG_DIR}/include ${OGG_GENERATED_DIR}) + +add_library(vorbis STATIC EXCLUDE_FROM_ALL + ${VORBIS_DIR}/lib/analysis.c + ${VORBIS_DIR}/lib/bitrate.c + ${VORBIS_DIR}/lib/block.c + ${VORBIS_DIR}/lib/codebook.c + ${VORBIS_DIR}/lib/envelope.c + ${VORBIS_DIR}/lib/floor0.c + ${VORBIS_DIR}/lib/floor1.c + ${VORBIS_DIR}/lib/info.c + ${VORBIS_DIR}/lib/lookup.c + ${VORBIS_DIR}/lib/lpc.c + ${VORBIS_DIR}/lib/lsp.c + ${VORBIS_DIR}/lib/mapping0.c + ${VORBIS_DIR}/lib/mdct.c + ${VORBIS_DIR}/lib/psy.c + ${VORBIS_DIR}/lib/registry.c + ${VORBIS_DIR}/lib/res0.c + ${VORBIS_DIR}/lib/sharedbook.c + ${VORBIS_DIR}/lib/smallft.c + ${VORBIS_DIR}/lib/synthesis.c + ${VORBIS_DIR}/lib/window.c +) +target_include_directories(vorbis PUBLIC ${VORBIS_DIR}/include PRIVATE ${VORBIS_DIR}/lib) +target_link_libraries(vorbis PUBLIC ogg) +if(NOT WIN32) + target_link_libraries(vorbis PRIVATE m) +endif() + +add_library(vorbisfile STATIC EXCLUDE_FROM_ALL ${VORBIS_DIR}/lib/vorbisfile.c) +target_include_directories(vorbisfile PRIVATE ${VORBIS_DIR}/lib) +target_link_libraries(vorbisfile PUBLIC vorbis) +set_target_properties(ogg vorbis vorbisfile PROPERTIES POSITION_INDEPENDENT_CODE ON C_VISIBILITY_PRESET hidden) +if(MINGW) + foreach(library IN ITEMS ogg vorbis vorbisfile) + target_compile_options(${library} PRIVATE -fexceptions) + endforeach() +endif() + +add_library(libSceVorbisDec SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceVorbisDec PRIVATE ${LIBS_INCLUDE_DIR}) +target_compile_definitions(libSceVorbisDec PRIVATE OV_EXCLUDE_STATIC_CALLBACKS) +target_link_libraries(libSceVorbisDec PRIVATE vorbisfile vorbis ogg) +if(MINGW OR CMAKE_SYSTEM_NAME STREQUAL "Linux") + target_link_options(libSceVorbisDec PRIVATE LINKER:--exclude-libs,ALL) +endif() +configure_windows_unwind(libSceVorbisDec) + +if(BUILD_TESTING) + add_library(vorbisenc STATIC EXCLUDE_FROM_ALL ${VORBIS_DIR}/lib/vorbisenc.c) + target_include_directories(vorbisenc PRIVATE ${VORBIS_DIR}/lib) + target_link_libraries(vorbisenc PUBLIC vorbis) + set_target_properties(vorbisenc PROPERTIES POSITION_INDEPENDENT_CODE ON C_VISIBILITY_PRESET hidden) +endif() diff --git a/core/libs/prx/libSceVorbisDec/Export.cpp b/core/libs/prx/libSceVorbisDec/Export.cpp index 44ae4fcc5b..b6b60cadf5 100644 --- a/core/libs/prx/libSceVorbisDec/Export.cpp +++ b/core/libs/prx/libSceVorbisDec/Export.cpp @@ -1,33 +1,202 @@ +#include "prx/libSceVorbisDec/include/GuestVorbisFile.hpp" +#include "prx/libc/include/General.hpp" + +#include + #include #include -#include "SceTypes.hpp" -#include "prx/libc/include/General.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace VorbisDec; + +static_assert(sizeof(vorbis_comment) == sizeof(GuestComment)); +static_assert(offsetof(vorbis_comment, user_comments) == offsetof(GuestComment, user_comments)); +static_assert(offsetof(vorbis_comment, comment_lengths) == offsetof(GuestComment, comment_lengths)); +static_assert(offsetof(vorbis_comment, comments) == offsetof(GuestComment, comments)); +static_assert(offsetof(vorbis_comment, vendor) == offsetof(GuestComment, vendor)); +static_assert(SEEK_SET == 0 && SEEK_CUR == 1 && SEEK_END == 2); + +namespace { + +struct Handle { + OggVorbis_File file{}; + void* datasource = nullptr; + GuestCallbacks callbacks{}; + std::vector infos; + bool tellOutOfRange = false; + + ~Handle() { + file.datasource = nullptr; + ov_clear(&file); + } +}; + +struct Table { + std::mutex mutex; + std::unordered_map> handles; +}; + +Table& Handles() { + static auto* table = new Table; + return *table; +} + +Handle* Find(const void* vf) { + auto& table = Handles(); + const std::lock_guard lock(table.mutex); + const auto found = table.handles.find(vf); + return found == table.handles.end() ? nullptr : found->second.get(); +} + +std::unique_ptr Take(const void* vf) { + auto& table = Handles(); + const std::lock_guard lock(table.mutex); + const auto found = table.handles.find(vf); + if (found == table.handles.end()) return nullptr; + auto handle = std::move(found->second); + table.handles.erase(found); + return handle; +} + +void Store(const void* vf, std::unique_ptr handle) { + auto& table = Handles(); + const std::lock_guard lock(table.mutex); + table.handles[vf] = std::move(handle); +} + +void SyncInfos(Handle& handle) { + for (std::size_t link = 0; link < handle.infos.size(); ++link) { + const vorbis_info& info = handle.file.vi[link]; + handle.infos[link] = {info.version, info.channels, info.rate, info.bitrate_upper, info.bitrate_nominal, info.bitrate_lower, info.bitrate_window, info.codec_setup}; + } +} + +void RequireFile(const void* vf, const char* function) { + if (!vf) throw std::invalid_argument(std::string(function) + ": a null OggVorbis_File is not implemented"); +} + +template +auto WithHandle(const void* vf, const char* function, TCall call) { + RequireFile(vf, function); + if (Handle* handle = Find(vf)) { + const auto result = call(*handle); + SyncInfos(*handle); + return result; + } + Handle closed; + return call(closed); +} + +std::size_t ReadSource(void* buffer, std::size_t size, std::size_t count, void* source) { + const auto* handle = static_cast(source); + return handle->callbacks.read_func(buffer, size, count, handle->datasource); +} + +int SeekSource(void* source, ogg_int64_t offset, int whence) { + const auto* handle = static_cast(source); + return handle->callbacks.seek_func(handle->datasource, offset, whence); +} + +int CloseSource(void* source) { + const auto* handle = static_cast(source); + return handle->callbacks.close_func(handle->datasource); +} + +long TellSource(void* source) { + auto* handle = static_cast(source); + const std::int64_t position = handle->callbacks.tell_func(handle->datasource); + if (position < std::numeric_limits::min() || position > std::numeric_limits::max()) { + handle->tellOutOfRange = true; + return -1; + } + return static_cast(position); +} + +struct FilterContext { + GuestFilter filter; + void* param; +}; + +void RunFilter(float** pcm, long channels, long samples, void* context) { + const auto* filter = static_cast(context); + filter->filter(pcm, channels, samples, filter->param); +} + +} extern "C" { -int APS5_VABI sceVorbisDecBitrate(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVorbisDecOpenCallbacks(void* datasource, void* vf, const char* initial, std::int64_t ibytes, GuestCallbacks callbacks) { + RequireFile(vf, __func__); + if (initial && (ibytes < std::numeric_limits::min() || ibytes > std::numeric_limits::max())) { + throw std::runtime_error(std::string(__func__) + ": an initial byte count outside the host long range is not implemented"); + } + Take(vf).reset(); + auto handle = std::make_unique(); + handle->datasource = datasource; + handle->callbacks = callbacks; + const ov_callbacks host{ + callbacks.read_func ? ReadSource : nullptr, + callbacks.seek_func ? SeekSource : nullptr, + callbacks.close_func ? CloseSource : nullptr, + callbacks.tell_func ? TellSource : nullptr, + }; + const int result = ov_open_callbacks(datasource ? handle.get() : nullptr, &handle->file, initial, initial ? static_cast(ibytes) : 0, host); + if (handle->tellOutOfRange) { + throw std::runtime_error(std::string(__func__) + ": a stream length outside the host long range is not implemented"); + } + if (result != 0) return result; + handle->infos.resize(static_cast(handle->file.links)); + SyncInfos(*handle); + Store(vf, std::move(handle)); return 0; } -int APS5_VABI sceVorbisDecBitrateInstant(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVorbisDecClear(void* vf) { + const std::unique_ptr handle = Take(vf); + if (!handle) { + Handle closed; + return ov_clear(&closed.file); + } + void* const source = handle->file.datasource; + handle->file.datasource = nullptr; + const int result = ov_clear(&handle->file); + if (source && handle->callbacks.close_func) handle->callbacks.close_func(handle->datasource); + return result; } -int APS5_VABI sceVorbisDecClear(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI sceVorbisDecBitrate(void* vf, int link) { + return WithHandle(vf, __func__, [&](Handle& handle) -> std::int64_t { return ov_bitrate(&handle.file, link); }); } -int APS5_VABI sceVorbisDecComment(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI sceVorbisDecBitrateInstant(void* vf) { + return WithHandle(vf, __func__, [](Handle& handle) -> std::int64_t { return ov_bitrate_instant(&handle.file); }); } -int APS5_VABI sceVorbisDecCrossLap(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +GuestComment* APS5_VABI sceVorbisDecComment(void* vf, int link) { + return WithHandle(vf, __func__, [&](Handle& handle) { return reinterpret_cast(ov_comment(&handle.file, link)); }); +} + +int APS5_VABI sceVorbisDecCrossLap(void* first, void* second) { + if (first != second) RequireFile(first, __func__); + Handle* from = Find(first); + if (first != second && from) RequireFile(second, __func__); + Handle* to = second == first ? from : Find(second); + Handle closedFrom; + Handle closedTo; + OggVorbis_File* fromFile = from ? &from->file : &closedFrom.file; + OggVorbis_File* toFile = second == first ? fromFile : to ? &to->file : &closedTo.file; + const int result = ov_crosslap(fromFile, toFile); + if (from) SyncInfos(*from); + if (to) SyncInfos(*to); + return result; } int APS5_VABI sceVorbisDecGetDecodePriority(void) { @@ -40,99 +209,84 @@ int APS5_VABI sceVorbisDecGetLastDecodeError(void) { return 0; } -int APS5_VABI sceVorbisDecHalfrate(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVorbisDecHalfrate(void* vf, int flag) { + return WithHandle(vf, __func__, [&](Handle& handle) { return ov_halfrate(&handle.file, flag); }); } -int APS5_VABI sceVorbisDecHalfrateP(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVorbisDecHalfrateP(void* vf) { + return WithHandle(vf, __func__, [](Handle& handle) { return ov_halfrate_p(&handle.file); }); } -int APS5_VABI sceVorbisDecInfo(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +GuestInfo* APS5_VABI sceVorbisDecInfo(void* vf, int link) { + return WithHandle(vf, __func__, [&](Handle& handle) -> GuestInfo* { + const vorbis_info* info = ov_info(&handle.file, link); + return info ? &handle.infos.at(static_cast(info - handle.file.vi)) : nullptr; + }); } -int APS5_VABI sceVorbisDecOpenCallbacks(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVorbisDecPcmSeek(void* vf, std::int64_t position) { + return WithHandle(vf, __func__, [&](Handle& handle) { return ov_pcm_seek(&handle.file, position); }); } -int APS5_VABI sceVorbisDecPcmSeek(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVorbisDecPcmSeekLap(void* vf, std::int64_t position) { + return WithHandle(vf, __func__, [&](Handle& handle) { return ov_pcm_seek_lap(&handle.file, position); }); } -int APS5_VABI sceVorbisDecPcmSeekLap(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVorbisDecPcmSeekPage(void* vf, std::int64_t position) { + return WithHandle(vf, __func__, [&](Handle& handle) { return ov_pcm_seek_page(&handle.file, position); }); } -int APS5_VABI sceVorbisDecPcmSeekPage(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVorbisDecPcmSeekPageLap(void* vf, std::int64_t position) { + return WithHandle(vf, __func__, [&](Handle& handle) { return ov_pcm_seek_page_lap(&handle.file, position); }); } -int APS5_VABI sceVorbisDecPcmSeekPageLap(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI sceVorbisDecPcmTell(void* vf) { + return WithHandle(vf, __func__, [](Handle& handle) -> std::int64_t { return ov_pcm_tell(&handle.file); }); } -int APS5_VABI sceVorbisDecPcmTell(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI sceVorbisDecPcmTotal(void* vf, int link) { + return WithHandle(vf, __func__, [&](Handle& handle) -> std::int64_t { return ov_pcm_total(&handle.file, link); }); } -int APS5_VABI sceVorbisDecPcmTotal(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVorbisDecRawSeek(void* vf, std::int64_t position) { + return WithHandle(vf, __func__, [&](Handle& handle) { return ov_raw_seek(&handle.file, position); }); } -int APS5_VABI sceVorbisDecRawSeek(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVorbisDecRawSeekLap(void* vf, std::int64_t position) { + return WithHandle(vf, __func__, [&](Handle& handle) { return ov_raw_seek_lap(&handle.file, position); }); } -int APS5_VABI sceVorbisDecRawSeekLap(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI sceVorbisDecRawTell(void* vf) { + return WithHandle(vf, __func__, [](Handle& handle) -> std::int64_t { return ov_raw_tell(&handle.file); }); } -int APS5_VABI sceVorbisDecRawTell(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI sceVorbisDecRawTotal(void* vf, int link) { + return WithHandle(vf, __func__, [&](Handle& handle) -> std::int64_t { return ov_raw_total(&handle.file, link); }); } -int APS5_VABI sceVorbisDecRawTotal(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI sceVorbisDecRead(void* vf, char* buffer, int length, int bigEndian, int word, int isSigned, int* bitstream) { + return WithHandle(vf, __func__, [&](Handle& handle) -> std::int64_t { + return ov_read(&handle.file, buffer, length, bigEndian, word, isSigned, bitstream); + }); } -int APS5_VABI sceVorbisDecRead(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI sceVorbisDecReadFilter(void* vf, char* buffer, int length, int bigEndian, int word, int isSigned, int* bitstream, GuestFilter filter, void* filterParam) { + FilterContext context{filter, filterParam}; + return WithHandle(vf, __func__, [&](Handle& handle) -> std::int64_t { + return ov_read_filter(&handle.file, buffer, length, bigEndian, word, isSigned, bitstream, filter ? RunFilter : nullptr, &context); + }); } -int APS5_VABI sceVorbisDecReadFilter(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI sceVorbisDecReadFloat(void* vf, float*** pcmChannels, int samples, int* bitstream) { + return WithHandle(vf, __func__, [&](Handle& handle) -> std::int64_t { return ov_read_float(&handle.file, pcmChannels, samples, bitstream); }); } -int APS5_VABI sceVorbisDecReadFloat(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI sceVorbisDecSeekable(void* vf) { + return WithHandle(vf, __func__, [](Handle& handle) -> std::int64_t { return ov_seekable(&handle.file); }); } -int APS5_VABI sceVorbisDecSeekable(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVorbisDecSerialNumber(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI sceVorbisDecSerialNumber(void* vf, int link) { + return WithHandle(vf, __func__, [&](Handle& handle) -> std::int64_t { return ov_serialnumber(&handle.file, link); }); } int APS5_VABI sceVorbisDecSetDecodePriority(void) { @@ -140,39 +294,32 @@ int APS5_VABI sceVorbisDecSetDecodePriority(void) { return 0; } -int APS5_VABI sceVorbisDecStreams(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int64_t APS5_VABI sceVorbisDecStreams(void* vf) { + return WithHandle(vf, __func__, [](Handle& handle) -> std::int64_t { return ov_streams(&handle.file); }); } -int APS5_VABI sceVorbisDecTimeSeek(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVorbisDecTimeSeek(void* vf, double position) { + return WithHandle(vf, __func__, [&](Handle& handle) { return ov_time_seek(&handle.file, position); }); } -int APS5_VABI sceVorbisDecTimeSeekLap(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVorbisDecTimeSeekLap(void* vf, double position) { + return WithHandle(vf, __func__, [&](Handle& handle) { return ov_time_seek_lap(&handle.file, position); }); } -int APS5_VABI sceVorbisDecTimeSeekPage(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVorbisDecTimeSeekPage(void* vf, double position) { + return WithHandle(vf, __func__, [&](Handle& handle) { return ov_time_seek_page(&handle.file, position); }); } -int APS5_VABI sceVorbisDecTimeSeekPageLap(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVorbisDecTimeSeekPageLap(void* vf, double position) { + return WithHandle(vf, __func__, [&](Handle& handle) { return ov_time_seek_page_lap(&handle.file, position); }); } -int APS5_VABI sceVorbisDecTimeTell(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +double APS5_VABI sceVorbisDecTimeTell(void* vf) { + return WithHandle(vf, __func__, [](Handle& handle) { return ov_time_tell(&handle.file); }); } -int APS5_VABI sceVorbisDecTimeTotal(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +double APS5_VABI sceVorbisDecTimeTotal(void* vf, int link) { + return WithHandle(vf, __func__, [&](Handle& handle) { return ov_time_total(&handle.file, link); }); } } diff --git a/core/libs/prx/libSceVorbisDec/include/GuestVorbisFile.hpp b/core/libs/prx/libSceVorbisDec/include/GuestVorbisFile.hpp new file mode 100644 index 0000000000..9da5f3e1d5 --- /dev/null +++ b/core/libs/prx/libSceVorbisDec/include/GuestVorbisFile.hpp @@ -0,0 +1,46 @@ +#ifndef CORE_LIBS_PRX_LIBSCEVORBISDEC_INCLUDE_GUESTVORBISFILE_HPP +#define CORE_LIBS_PRX_LIBSCEVORBISDEC_INCLUDE_GUESTVORBISFILE_HPP + +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include + +namespace VorbisDec { + +struct GuestCallbacks { + std::size_t (APS5_VABI *read_func)(void*, std::size_t, std::size_t, void*); + int (APS5_VABI *seek_func)(void*, std::int64_t, int); + int (APS5_VABI *close_func)(void*); + std::int64_t (APS5_VABI *tell_func)(void*); +}; + +struct GuestInfo { + std::int32_t version; + std::int32_t channels; + std::int64_t rate; + std::int64_t bitrate_upper; + std::int64_t bitrate_nominal; + std::int64_t bitrate_lower; + std::int64_t bitrate_window; + void* codec_setup; +}; + +struct GuestComment { + char** user_comments; + std::int32_t* comment_lengths; + std::int32_t comments; + char* vendor; +}; + +using GuestFilter = void (APS5_VABI *)(float**, std::int64_t, std::int64_t, void*); + +static_assert(sizeof(GuestCallbacks) == 32); +static_assert(offsetof(GuestInfo, rate) == 8 && offsetof(GuestInfo, bitrate_window) == 40 && offsetof(GuestInfo, codec_setup) == 48); +static_assert(sizeof(GuestInfo) == 56); +static_assert(offsetof(GuestComment, comment_lengths) == 8 && offsetof(GuestComment, comments) == 16 && offsetof(GuestComment, vendor) == 24); +static_assert(sizeof(GuestComment) == 32); + +} + +#endif diff --git a/core/libs/prx/libSceWebBrowserDialog/CMakeLists.txt b/core/libs/prx/libSceWebBrowserDialog/CMakeLists.txt index 2c498e5df1..641f60463e 100644 --- a/core/libs/prx/libSceWebBrowserDialog/CMakeLists.txt +++ b/core/libs/prx/libSceWebBrowserDialog/CMakeLists.txt @@ -12,3 +12,14 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +if(BUILD_TESTING) + add_test_executable(guest_web_browser_dialog_cookies_tests ${LIBS_INCLUDE_DIR}/tests/GuestWebBrowserDialogCookies.cpp) + target_include_directories(guest_web_browser_dialog_cookies_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_web_browser_dialog_cookies_tests PRIVATE libSceWebBrowserDialog) + add_test(NAME guest_web_browser_dialog_cookies COMMAND guest_web_browser_dialog_cookies_tests) + if(WIN32) + set_tests_properties(guest_web_browser_dialog_cookies PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceWebBrowserDialog/Export.cpp b/core/libs/prx/libSceWebBrowserDialog/Export.cpp index 8c720fbab1..3b8080508c 100644 --- a/core/libs/prx/libSceWebBrowserDialog/Export.cpp +++ b/core/libs/prx/libSceWebBrowserDialog/Export.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "SceTypes.hpp" #include "prx/libSceCommonDialog/CommonDialog.hpp" #include "prx/libc/include/General.hpp" @@ -24,6 +25,7 @@ static_assert(sizeof(WebBrowserDialogResetCookieParam) == 264); namespace { std::atomic g_status{0}; +std::atomic g_open{false}; constexpr int COMMON_DIALOG_STATUS_NONE = 0; constexpr int COMMON_DIALOG_STATUS_RUNNING = 2; @@ -31,10 +33,23 @@ constexpr int COMMON_DIALOG_STATUS_FINISHED = 3; constexpr int COMMON_DIALOG_RESULT_USER_CANCELED = 1; constexpr int COMMON_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x80B80003u); constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80005u); +constexpr int COMMON_DIALOG_ERROR_INVALID_STATE = static_cast(0x80B80006u); constexpr int COMMON_DIALOG_ERROR_BUSY = static_cast(0x80B80007u); constexpr int COMMON_DIALOG_ERROR_NOT_RUNNING = static_cast(0x80B8000Bu); constexpr int COMMON_DIALOG_ERROR_PARAMETER = static_cast(0x80B8000Au); constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast(0x80B8000Du); + +bool ReservedClear(const std::uint8_t (&reserved)[256]) { + for (const auto byte : reserved) + if (byte != 0) return false; + return true; +} + +bool LengthWithin(const char* text, std::size_t limit) { + if (!text) return false; + const auto length = strnlen(text, limit); + return length != 0 && length < limit; +} } extern "C" { @@ -47,14 +62,21 @@ int APS5_VABI sceWebBrowserDialogInitialize(void) { int APS5_VABI sceWebBrowserDialogTerminate(void) { int expected = 1; - if (g_status.compare_exchange_strong(expected, COMMON_DIALOG_STATUS_NONE)) return 0; + if (g_status.compare_exchange_strong(expected, COMMON_DIALOG_STATUS_NONE)) { + g_open = false; + return 0; + } expected = COMMON_DIALOG_STATUS_FINISHED; - if (g_status.compare_exchange_strong(expected, COMMON_DIALOG_STATUS_NONE)) return 0; + if (g_status.compare_exchange_strong(expected, COMMON_DIALOG_STATUS_NONE)) { + g_open = false; + return 0; + } if (g_status.load() == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; return COMMON_DIALOG_ERROR_BUSY; } int APS5_VABI sceWebBrowserDialogClose(void) { + g_open = false; const int status = g_status.load(); if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; if (status != COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_NOT_RUNNING; @@ -88,16 +110,24 @@ int APS5_VABI sceWebBrowserDialogOpen(const void* param) { if (status == COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_BUSY; if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; g_status = COMMON_DIALOG_STATUS_FINISHED; + g_open = true; return 0; } int APS5_VABI sceWebBrowserDialogUpdateStatus(void) { - return g_status.load(); + const int status = g_status.load(); + if (status == COMMON_DIALOG_STATUS_FINISHED) g_open = false; + return status; } int APS5_VABI sceWebBrowserDialogSetCookie(const WebBrowserDialogCookieParam* param) { - NotImplemented_nid_no_patch(__func__); + if (g_status.load() == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (g_open.load()) return COMMON_DIALOG_ERROR_INVALID_STATE; + if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + if (param->size != sizeof(WebBrowserDialogCookieParam) || !LengthWithin(param->address, 0x800) || !LengthWithin(param->cookie, 0x1000) || + !ReservedClear(param->reserved)) + return COMMON_DIALOG_ERROR_PARAMETER; return 0; } @@ -118,11 +148,15 @@ int APS5_VABI sceWebBrowserDialogOpenForPredeterminedContent(const void* param, if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; if (status == COMMON_DIALOG_STATUS_RUNNING) return COMMON_DIALOG_ERROR_BUSY; g_status = COMMON_DIALOG_STATUS_FINISHED; + g_open = true; return 0; } int APS5_VABI sceWebBrowserDialogResetCookie(const WebBrowserDialogResetCookieParam* param) { - NotImplemented_nid_no_patch(__func__); + if (g_status.load() == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; + if (g_open.load()) return COMMON_DIALOG_ERROR_INVALID_STATE; + if (param->size != sizeof(WebBrowserDialogResetCookieParam) || !ReservedClear(param->reserved)) return COMMON_DIALOG_ERROR_PARAMETER; return 0; } diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 3289c98e99..1b9c5d5348 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -1033,7 +1033,7 @@ if(BUILD_TESTING) target_link_libraries(guest_raise_exception_tests PRIVATE libkernel libc) configure_windows_unwind(guest_raise_exception_tests) add_test(NAME guest_raise_exception COMMAND guest_raise_exception_tests) - set_tests_properties(guest_raise_exception PROPERTIES TIMEOUT 30) + set_tests_properties(guest_raise_exception PROPERTIES TIMEOUT 60) add_asan_test_options(guest_raise_exception detect_stack_use_after_return=0) if(WIN32) set_tests_properties(guest_raise_exception PROPERTIES ENVIRONMENT_MODIFICATION diff --git a/core/libs/prx/libkernel/Equeue/src/FileEvent.cpp b/core/libs/prx/libkernel/Equeue/src/FileEvent.cpp index 04fe98539f..cd24ff30d0 100644 --- a/core/libs/prx/libkernel/Equeue/src/FileEvent.cpp +++ b/core/libs/prx/libkernel/Equeue/src/FileEvent.cpp @@ -165,6 +165,16 @@ struct Snapshot { }; bool TakeSnapshot(HANDLE handle, Snapshot& snapshot) { + std::wstring path(MAX_PATH, L'\0'); + auto length = ::GetFinalPathNameByHandleW(handle, path.data(), static_cast(path.size()), FILE_NAME_NORMALIZED); + if (length >= path.size()) { + path.resize(length); + length = ::GetFinalPathNameByHandleW(handle, path.data(), length, FILE_NAME_NORMALIZED); + } + if (length != 0 && length < path.size()) { + path.resize(length); + snapshot.path = std::move(path); + } FILE_BASIC_INFO basic{}; FILE_STANDARD_INFO standard{}; if (!::GetFileInformationByHandleEx(handle, FileBasicInfo, &basic, sizeof(basic)) || @@ -178,16 +188,6 @@ bool TakeSnapshot(HANDLE handle, Snapshot& snapshot) { snapshot.creation = basic.CreationTime.QuadPart; snapshot.lastWrite = basic.LastWriteTime.QuadPart; snapshot.change = basic.ChangeTime.QuadPart; - std::wstring path(MAX_PATH, L'\0'); - auto length = ::GetFinalPathNameByHandleW(handle, path.data(), static_cast(path.size()), FILE_NAME_NORMALIZED); - if (length >= path.size()) { - path.resize(length); - length = ::GetFinalPathNameByHandleW(handle, path.data(), length, FILE_NAME_NORMALIZED); - } - if (length != 0 && length < path.size()) { - path.resize(length); - snapshot.path = std::move(path); - } return true; } diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index fa57cf492d..af3a52401b 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -498,6 +498,14 @@ static std::int64_t PosixResult(std::int64_t result) { return result < 0 ? PosixFailure(static_cast(result) & 0xffff) : result; } +static int DeviceFilesystem(KernelStatfs* fs) { + fs->f_bsize = 512; + fs->f_iosize = 512; + fs->f_blocks = 2; + fs->f_namemax = 255; + return 0; +} + #ifdef _WIN32 static int NativeFstatfs(int descriptor, KernelStatfs* fs) { if (!File::IsDirectoryDescriptor(descriptor)) { @@ -505,6 +513,7 @@ static int NativeFstatfs(int descriptor, KernelStatfs* fs) { if (handle == INVALID_HANDLE_VALUE) return GUEST_EBADF; const DWORD type = ::GetFileType(handle); if (type == FILE_TYPE_PIPE) return GUEST_EINVAL; + if (type == FILE_TYPE_CHAR) return DeviceFilesystem(fs); if (type != FILE_TYPE_DISK) throw std::runtime_error("_fstatfs: descriptor " + std::to_string(descriptor) + " is not a file on a Windows volume"); } @@ -552,6 +561,7 @@ static int NativeFstatfs(int descriptor, KernelStatfs* fs) { if (::fstat(descriptor, &file) != 0) return SceErrorFromErrno(errno) & 0xffff; if (S_ISSOCK(file.st_mode) && (::fcntl(descriptor, F_GETFL) & O_PATH) == 0) return GUEST_EINVAL; if (NativeIsPipe(descriptor)) return GUEST_EINVAL; + if (S_ISCHR(file.st_mode)) return DeviceFilesystem(fs); struct statvfs host{}; if (::fstatvfs(descriptor, &host) != 0) return SceErrorFromErrno(errno) & 0xffff; if (host.f_flag & ST_RDONLY) fs->f_flags |= KERNEL_MNT_RDONLY; @@ -883,7 +893,7 @@ int APS5_VABI flock_nid_postfix(int d, int operation) { } int APS5_VABI _fstatfs_nid_postfix(int d, KernelStatfs* buf) { - if (GuestSockets::IsOpen(d)) return PosixFailure(GUEST_EINVAL); + if (GuestSockets::IsOpen(d) || IsKqueueDescriptor(d)) return PosixFailure(GUEST_EINVAL); if (d < 0) return PosixFailure(GUEST_EBADF); File::RefuseArchivedFile(d, "fstatfs"); KernelStatfs result{}; diff --git a/core/libs/prx/libkernel/Pthread/src/Thread.cpp b/core/libs/prx/libkernel/Pthread/src/Thread.cpp index 31451006c2..aa76de407d 100644 --- a/core/libs/prx/libkernel/Pthread/src/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Thread.cpp @@ -74,9 +74,9 @@ static GuestThreadState& CurrentGuestState() { return state; #endif } -static std::mutex stackLock; -static std::map> liveStacks; -static std::map protectedStackPages; +static std::mutex& stackLock = *new std::mutex(); +static auto& liveStacks = *new std::map>(); +static auto& protectedStackPages = *new std::map(); #if defined(__linux__) struct GuestStackMapping { std::uintptr_t mappingEnd; @@ -84,7 +84,7 @@ struct GuestStackMapping { std::uintptr_t stackEnd; }; static std::mutex guestStackMappingsLock; -static std::map guestStackMappings; +static auto& guestStackMappings = *new std::map(); static void RegisterGuestStackMapping(PthreadPrivate* thread) { const auto mappingStart = reinterpret_cast(thread->guestStack); diff --git a/core/libs/prx/libkernel/System/src/Process.cpp b/core/libs/prx/libkernel/System/src/Process.cpp index 348464a177..3ea5497d80 100644 --- a/core/libs/prx/libkernel/System/src/Process.cpp +++ b/core/libs/prx/libkernel/System/src/Process.cpp @@ -8,6 +8,12 @@ #else #include #include +#ifdef __linux__ +#include +#elif defined(__APPLE__) +#include +#include +#endif #endif #if defined(__linux__) && defined(__x86_64__) #include @@ -18,6 +24,7 @@ #include "prx/libc/include/General.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/HostEntropy.hpp" +#include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/Shutdown.hpp" #include "prx/libkernel/AppMetadata/include/AppMetadata.hpp" #include "prx/libkernel/DirectMemory/DirectMemory.hpp" @@ -46,6 +53,7 @@ extern "C" int* APS5_VABI __error_nid_postfix(); extern "C" int APS5_VABI stat_nid_postfix(const char* path, FileStat* sb); +extern "C" int APS5_VABI sceKernelVirtualQuery(const void* addr, int flags, VirtualQueryInfo* info, std::uint64_t info_size); namespace { @@ -59,6 +67,86 @@ std::mutex supplementaryGroupsMutex; std::vector supplementaryGroups{0}; constexpr unsigned freebsdWaitOptions = 0x8000003Fu; +bool StoreGuestId(std::uint32_t* output) { + if (output == nullptr) return true; + const auto address = reinterpret_cast(output); + if (address > std::numeric_limits::max() - sizeof(*output)) return false; + const auto end = address + sizeof(*output); + for (auto cursor = address; cursor < end;) { + VirtualQueryInfo info{}; + if (sceKernelVirtualQuery(reinterpret_cast(cursor), 0, &info, sizeof(info)) != 0 || + info.end <= cursor || (info.protection & 2) == 0) + return false; + cursor = std::min(end, info.end); + } + const GuestArena::HostWrite guard(output, sizeof(*output)); + if (!guard.Open()) return false; + constexpr std::uint32_t zero = 0; +#ifdef _WIN32 + SIZE_T written = 0; + return WriteProcessMemory(GetCurrentProcess(), output, &zero, sizeof(zero), &written) != 0 && written == sizeof(zero); +#elif defined(__linux__) + iovec local{const_cast(&zero), sizeof(zero)}; + iovec remote{output, sizeof(zero)}; + return ::process_vm_writev(::getpid(), &local, 1, &remote, 1, 0) == static_cast(sizeof(zero)); +#elif defined(__APPLE__) + return ::mach_vm_write(::mach_task_self(), reinterpret_cast(output), + reinterpret_cast(const_cast(&zero)), sizeof(zero)) == KERN_SUCCESS; +#else + *output = zero; + return true; +#endif +} + +bool GuestRange(const void* pointer, std::size_t bytes) { + const auto start = reinterpret_cast(pointer); + return start != 0 && bytes <= std::numeric_limits::max() - start; +} + +bool ReadGuest(void* destination, const void* source, std::size_t bytes) { + if (bytes == 0) return true; + if (!GuestRange(source, bytes)) return false; + GuestArena::GuestArenaPrepareHostRead_nid_no_patch(source, bytes); +#ifdef _WIN32 + SIZE_T copied = 0; + return ReadProcessMemory(GetCurrentProcess(), source, destination, bytes, &copied) != 0 && copied == bytes; +#elif defined(__linux__) + iovec local{destination, bytes}; + iovec remote{const_cast(source), bytes}; + const auto copied = ::process_vm_readv(::getpid(), &local, 1, &remote, 1, 0); + if (copied < 0 && errno != EFAULT) throw std::runtime_error("credentials: process_vm_readv failed with errno " + std::to_string(errno)); + return copied >= 0 && static_cast(copied) == bytes; +#elif defined(__APPLE__) + mach_vm_size_t copied = 0; + return ::mach_vm_read_overwrite(::mach_task_self(), reinterpret_cast(source), bytes, + reinterpret_cast(destination), &copied) == KERN_SUCCESS && copied == bytes; +#else + throw std::runtime_error("credentials: checked guest reads are not implemented on this host"); +#endif +} + +bool WriteGuest(void* destination, const void* source, std::size_t bytes) { + if (bytes == 0) return true; + if (!GuestRange(destination, bytes)) return false; + const GuestArena::HostWrite guard(destination, bytes); + if (!guard.Open()) return false; +#ifdef _WIN32 + SIZE_T copied = 0; + return WriteProcessMemory(GetCurrentProcess(), destination, source, bytes, &copied) != 0 && copied == bytes; +#elif defined(__linux__) + iovec local{const_cast(source), bytes}; + iovec remote{destination, bytes}; + const auto copied = ::process_vm_writev(::getpid(), &local, 1, &remote, 1, 0); + if (copied < 0 && errno != EFAULT) throw std::runtime_error("credentials: process_vm_writev failed with errno " + std::to_string(errno)); + return copied >= 0 && static_cast(copied) == bytes; +#elif defined(__APPLE__) + return ::mach_vm_write(::mach_task_self(), reinterpret_cast(destination), + reinterpret_cast(source), static_cast(bytes)) == KERN_SUCCESS; +#else + throw std::runtime_error("credentials: checked guest writes are not implemented on this host"); +#endif +} + std::atomic gpoBits{0}; constexpr std::array openPsId{'A', 'n', 'y', 'P', 'S', '5', 'O', 'p', 'e', 'n', 'P', 's', 'I', 'd', 0, 1}; @@ -314,6 +402,22 @@ int APS5_VABI issetugid_nid_postfix(void) { return 0; } +int APS5_VABI getresuid_nid_postfix(std::uint32_t* ruid, std::uint32_t* euid, std::uint32_t* suid) { + if (!StoreGuestId(ruid) || !StoreGuestId(euid) || !StoreGuestId(suid)) { + *__error_nid_postfix() = errnoBadAddress; + return -1; + } + return 0; +} + +int APS5_VABI getresgid_nid_postfix(std::uint32_t* rgid, std::uint32_t* egid, std::uint32_t* sgid) { + if (!StoreGuestId(rgid) || !StoreGuestId(egid) || !StoreGuestId(sgid)) { + *__error_nid_postfix() = errnoBadAddress; + return -1; + } + return 0; +} + void APS5_VABI exit_nid_postfix(int code) { LibcExit_nid_no_patch(code); } @@ -770,6 +874,28 @@ int APS5_VABI setregid_nid_postfix(std::uint32_t rgid, std::uint32_t egid) { return 0; } +int APS5_VABI setresuid_nid_postfix(std::uint32_t ruid, std::uint32_t euid, std::uint32_t suid) { + constexpr std::uint32_t unchanged = 0xffffffffu; + if (ruid != 0 && ruid != unchanged) + throw std::runtime_error("setresuid: unsupported real user id " + std::to_string(ruid)); + if (euid != 0 && euid != unchanged) + throw std::runtime_error("setresuid: unsupported effective user id " + std::to_string(euid)); + if (suid != 0 && suid != unchanged) + throw std::runtime_error("setresuid: unsupported saved user id " + std::to_string(suid)); + return 0; +} + +int APS5_VABI setresgid_nid_postfix(std::uint32_t rgid, std::uint32_t egid, std::uint32_t sgid) { + constexpr std::uint32_t unchanged = 0xffffffffu; + if (rgid != 0 && rgid != unchanged) + throw std::runtime_error("setresgid: unsupported real group id " + std::to_string(rgid)); + if (egid != 0 && egid != unchanged) + throw std::runtime_error("setresgid: unsupported effective group id " + std::to_string(egid)); + if (sgid != 0 && sgid != unchanged) + throw std::runtime_error("setresgid: unsupported saved group id " + std::to_string(sgid)); + return 0; +} + int APS5_VABI setgroups_nid_postfix(int count, const std::uint32_t* groups) { if (count < 0 || count > freebsdGroupsMax) { *__error_nid_postfix() = errnoInvalidArgument; @@ -779,14 +905,22 @@ int APS5_VABI setgroups_nid_postfix(int count, const std::uint32_t* groups) { *__error_nid_postfix() = errnoBadAddress; return -1; } - if (count > 0 && groups[0] != 0) - throw std::runtime_error("setgroups: unsupported effective group id " + std::to_string(groups[0])); + std::vector requested; + if (count > 0) { + requested.resize(static_cast(count)); + if (!ReadGuest(requested.data(), groups, requested.size() * sizeof(requested[0]))) { + *__error_nid_postfix() = errnoBadAddress; + return -1; + } + } + if (count > 0 && requested[0] != 0) + throw std::runtime_error("setgroups: unsupported effective group id " + std::to_string(requested[0])); std::lock_guard lock(supplementaryGroupsMutex); if (count == 0) { supplementaryGroups.resize(1); return 0; } - supplementaryGroups.assign(groups, groups + count); + supplementaryGroups.swap(requested); return 0; } @@ -802,7 +936,10 @@ int APS5_VABI getgroups_nid_postfix(int capacity, std::uint32_t* groups) { *__error_nid_postfix() = errnoBadAddress; return -1; } - std::copy(supplementaryGroups.begin(), supplementaryGroups.end(), groups); + if (!WriteGuest(groups, supplementaryGroups.data(), supplementaryGroups.size() * sizeof(supplementaryGroups[0]))) { + *__error_nid_postfix() = errnoBadAddress; + return -1; + } return count; } diff --git a/core/libs/prx/libkernel/System/src/Signals.cpp b/core/libs/prx/libkernel/System/src/Signals.cpp index 773a688b23..da332603c9 100644 --- a/core/libs/prx/libkernel/System/src/Signals.cpp +++ b/core/libs/prx/libkernel/System/src/Signals.cpp @@ -32,6 +32,8 @@ struct GuestSigaction { }; static_assert(sizeof(GuestSigaction) == 32 && offsetof(GuestSigaction, flags) == 8 && offsetof(GuestSigaction, mask) == 12); +extern "C" GuestSignalSet _sigintr_nid_postfix{}; + namespace { using GuestHandler = void (APS5_VABI *)(int); constexpr int MaxSignal = 128; @@ -483,7 +485,7 @@ GuestHandler APS5_VABI signal_nid_postfix(int guest, GuestHandler handler) { const auto invalid = reinterpret_cast(static_cast(-1)); if (guest < 1 || guest > MaxSignal || ((guest == GuestSigkill || guest == GuestSigstop) && handler != nullptr)) { *__error_nid_postfix() = 22; return invalid; } std::lock_guard lock(registration); - const GuestSigaction action{reinterpret_cast(handler), SaRestart, {}}; + const GuestSigaction action{reinterpret_cast(handler), SignalInSet(_sigintr_nid_postfix, guest) ? 0 : SaRestart, {}}; if (!Install(guest, action)) { *__error_nid_postfix() = 22; return invalid; } const auto previous = reinterpret_cast(dispositions[guest].handler); dispositions[guest] = action; diff --git a/core/libs/prx/libkernel_sys/Export.Discovery.cpp b/core/libs/prx/libkernel_sys/Export.Discovery.cpp index 702eff446b..28f66fa504 100644 --- a/core/libs/prx/libkernel_sys/Export.Discovery.cpp +++ b/core/libs/prx/libkernel_sys/Export.Discovery.cpp @@ -98,11 +98,6 @@ int APS5_VABI fchownat_nid_postfix(void) { return 0; } -int APS5_VABI fstat_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI futimesat_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -313,11 +308,6 @@ int APS5_VABI lchflags_nid_postfix(void) { return 0; } -int APS5_VABI lchmod_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI lchown_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -933,11 +923,6 @@ int APS5_VABI sceKernelGetChildApp(void) { return 0; } -int APS5_VABI sceKernelGetCompiledSdkVersion(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceKernelGetCompiledSdkVersionByPath(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index ce8d061708..9ece898a95 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -19,6 +19,8 @@ #endif #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" +#include "prx/libkernel/KernelErrors.hpp" #include "prx/libc/include/FilesystemError.hpp" #include "prx/libkernel/File/include/DirectoryDescriptor.hpp" #include "prx/libkernel/File/include/DescriptorControl.hpp" @@ -255,6 +257,13 @@ int RenamePath(const std::filesystem::path& source, const std::filesystem::path& extern "C" { +int APS5_VABI sceKernelGetCompiledSdkVersion(std::int32_t* version) { + if (version == nullptr) return SCE_KERNEL_ERROR_EINVAL; + static const auto* parameters = static_cast(ApplicationProcessParameters_nid_no_patch()); + std::memcpy(version, parameters + 0x10, sizeof(*version)); + return 0; +} + int APS5_VABI fork_nid_postfix(void) { NotImplemented_nid_no_patch("fork: child processes"); return -1; @@ -274,6 +283,12 @@ int APS5_VABI fstatfs_nid_postfix(int d, void* buf) { return _fstatfs_nid_postfix(d, buf); } +std::int64_t APS5_VABI _fstat_nid_postfix(int d, FileStat* sb); + +int APS5_VABI fstat_nid_postfix(int d, FileStat* sb) { + return static_cast(_fstat_nid_postfix(d, sb)); +} + std::int64_t APS5_VABI readlink_nid_postfix(const char* path, char* buffer, std::size_t size) { if (size > static_cast(std::numeric_limits::max())) return Fail(GuestEinval); if (path == nullptr) return Fail(GuestEfault); @@ -569,6 +584,23 @@ int APS5_VABI fchmodat_nid_postfix(int fd, const char* path, int mode, int flag) return 0; } +int APS5_VABI lchmod_nid_postfix(const char* path, std::uint16_t mode) { +#ifndef __APPLE__ + std::filesystem::path native; + if (path != nullptr && *path != '\0' && ResolvePathError_nid_no_patch(path, native) == 0) { +#ifdef _WIN32 + const DWORD attributes = GetFileAttributesW(native.wstring().c_str()); + const bool symbolic = attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; +#else + std::error_code status; + const bool symbolic = std::filesystem::is_symlink(std::filesystem::symlink_status(native, status)); +#endif + if (symbolic) NotImplemented_nid_no_patch("lchmod: the mode of a symbolic link on a host that cannot change it"); + } +#endif + return fchmodat_nid_postfix(GuestAtFdcwd, path, mode, GuestAtSymlinkNofollow); +} + int APS5_VABI fchown_nid_postfix(int fd, std::uint32_t uid, std::uint32_t gid) { constexpr std::uint32_t Unchanged = 0xFFFFFFFFu; if (uid != 0 && uid != Unchanged) NotImplemented_nid_no_patch("fchown: an owner other than the guest user"); diff --git a/core/libs/tests/GuestAjmWVorbis.cpp b/core/libs/tests/GuestAjmWVorbis.cpp new file mode 100644 index 0000000000..4344e98e00 --- /dev/null +++ b/core/libs/tests/GuestAjmWVorbis.cpp @@ -0,0 +1,107 @@ +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include +#include +#include +#include +#include + +struct HeaderPacketParam { + std::uint64_t size; + std::uint32_t channels; + std::uint32_t sampleRate; + std::int32_t bytesPerSecond; + std::int32_t blockSize0Log2; + std::int32_t blockSize1Log2; + std::uint32_t reserved; + const std::uint8_t* setupPacket; + std::uint64_t setupPacketSize; + std::uint8_t* output; + std::uint64_t outputSize; + std::uint64_t writtenSize; +}; +static_assert(sizeof(HeaderPacketParam) == 0x48); + +extern "C" int APS5_VABI sceAjmDecWVorbisCreateHeaderPacket(HeaderPacketParam* param); + +namespace { + +constexpr int InvalidParameter = static_cast(0x8A6E0001); +constexpr int InvalidSize = static_cast(0x8A6E0002); +constexpr int OutputTooSmall = static_cast(0x8A6E0003); +constexpr int InvalidSetup = static_cast(0x8A6E0004); + +void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "%s\n", message); + std::abort(); +} + +constexpr std::array Setup {5, 'v', 'o', 'r', 'b', 'i', 's', 9, 0, 0xAA, 0xBB, 0xCC}; + +HeaderPacketParam Valid(std::uint8_t* output, std::size_t outputSize) { + return {sizeof(HeaderPacketParam), 2, 48000, 16000, 8, 11, 0, Setup.data(), Setup.size(), output, outputSize, 0}; +} + +int Create(HeaderPacketParam param) { return sceAjmDecWVorbisCreateHeaderPacket(¶m); } + +void TestValidation() { + std::array output {}; + Require(sceAjmDecWVorbisCreateHeaderPacket(nullptr) == InvalidParameter, "a null parameter is refused"); + auto param = Valid(output.data(), output.size()); + param.size = 0x40; + Require(Create(param) == InvalidSize, "a wrong structure size is refused"); + param = Valid(output.data(), output.size()); + param.bytesPerSecond = -1; + Require(Create(param) == InvalidParameter, "a negative byte rate is refused"); + param = Valid(output.data(), output.size()); + param.blockSize0Log2 = 5; + Require(Create(param) == InvalidParameter, "a block size below 64 is refused"); + param.blockSize0Log2 = 38; + param.blockSize1Log2 = 38; + Require(Create(param) == 0, "block size exponents are taken modulo 32 as the console's shift does"); + param = Valid(output.data(), output.size()); + param.blockSize1Log2 = 7; + Require(Create(param) == InvalidParameter, "block size 1 below block size 0 is refused"); + param.blockSize1Log2 = 14; + Require(Create(param) == InvalidParameter, "a block size above 8192 is refused"); + param = Valid(output.data(), output.size()); + param.setupPacket = nullptr; + param.setupPacketSize = 0; + Require(Create(param) == InvalidParameter, "a null setup packet is refused before its size"); + param = Valid(output.data(), output.size()); + param.setupPacketSize = 8; + Require(Create(param) == InvalidSetup, "a setup packet below 9 bytes is refused"); + param = Valid(nullptr, 0); + Require(Create(param) == InvalidParameter, "a null output is refused before its size"); + param = Valid(output.data(), 9); + Require(Create(param) == OutputTooSmall, "an output below 10 bytes is refused"); + param = Valid(output.data(), 56 + Setup.size() - 1); + Require(Create(param) == OutputTooSmall, "an output that cannot hold the setup packet is refused"); +} + +void TestPacket() { + std::array output {}; + output.fill(0xEE); + auto param = Valid(output.data(), output.size()); + Require(sceAjmDecWVorbisCreateHeaderPacket(¶m) == 0, "a valid header packet is built"); + Require(param.writtenSize == 56 + Setup.size(), "the written size covers the table, both headers and the setup packet"); + constexpr std::uint8_t Expected[] { + 0, 0, 30, 0, 16, 0, 9, 0, 3, 0, + 1, 'v', 'o', 'r', 'b', 'i', 's', 0, 0, 0, 0, 2, 0x80, 0xBB, 0, 0, 0, 0, 0, 0, 0x00, 0xF4, 0x01, 0x00, 0, 0, 0, 0, 0xB8, 1, + 3, 'v', 'o', 'r', 'b', 'i', 's', 0, 0, 0, 0, 0, 0, 0, 0, 1, + }; + static_assert(sizeof(Expected) == 56); + Require(std::memcmp(output.data(), Expected, sizeof(Expected)) == 0, "the table, identification and comment headers match"); + Require(std::memcmp(output.data() + 56, Setup.data(), Setup.size()) == 0, "the setup packet follows unchanged"); + Require(output[56 + Setup.size()] == 0xEE, "nothing is written past the packet"); +} + +} + +int main() { + TestValidation(); + TestPacket(); + std::puts("guest ajm wvorbis header packet: PASS"); +} diff --git a/core/libs/tests/GuestAmpr.cpp b/core/libs/tests/GuestAmpr.cpp index f6017f152f..b17776bcd9 100644 --- a/core/libs/tests/GuestAmpr.cpp +++ b/core/libs/tests/GuestAmpr.cpp @@ -37,6 +37,10 @@ int APS5_VABI sceAmprCommandBufferSetMarkerWithColor(Apr::CommandBufferObject*, std::uint64_t APS5_VABI sceAmprMeasureCommandSizePushMarker(const char*); std::uint64_t APS5_VABI sceAmprMeasureCommandSizePushMarkerWithColor(const char*, std::uint32_t); std::uint64_t APS5_VABI sceAmprMeasureCommandSizePopMarker(); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWriteAddressOnCompletion(); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWriteCounterOnCompletion(); +std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWriteKernelEventQueueOnCompletion(); +void* APS5_VABI sceAmprCommandBufferGetBufferBaseAddress(const Apr::CommandBufferObject*); std::uint64_t APS5_VABI sceAmprMeasureCommandSizeSetMarker(const char*); std::uint64_t APS5_VABI sceAmprMeasureCommandSizeSetMarkerWithColor(const char*, std::uint32_t); int APS5_VABI sceKernelAprSubmitCommandBuffer(const Apr::CommandBufferObject*, std::uint32_t); @@ -582,6 +586,10 @@ void TestVersionedCounters() { void TestClearBuffer() { Recorder recorder; + Require(sceAmprMeasureCommandSizeWriteAddressOnCompletion() == sizeof(Apr::WriteAddressCommand)); + Require(sceAmprMeasureCommandSizeWriteCounterOnCompletion() == sizeof(Apr::WriteCounterCommand)); + Require(sceAmprMeasureCommandSizeWriteKernelEventQueueOnCompletion() == sizeof(Apr::WriteKernelEventQueueCommand)); + Require(sceAmprCommandBufferGetBufferBaseAddress(&recorder.buffer) == recorder.buffer.base); Require(sceAmprCommandBufferNop(&recorder.buffer, 1) == 0); Require(recorder.Offset() != 0 && recorder.Commands() == 1); Require(sceAmprCommandBufferClearBuffer(&recorder.buffer) == recorder.memory.data()); diff --git a/core/libs/tests/GuestAudio3dSpeakerArray.cpp b/core/libs/tests/GuestAudio3dSpeakerArray.cpp new file mode 100644 index 0000000000..7ef0c9942e --- /dev/null +++ b/core/libs/tests/GuestAudio3dSpeakerArray.cpp @@ -0,0 +1,75 @@ +#include "SceTypes.hpp" +#include +#include +#include +#include +#include + +struct Audio3dSpeakerArrayParameters { + const AudioOut2Position* positions; + std::uint32_t numSpeakers; + std::uint8_t is3d; + void* memory; + std::size_t memorySize; +}; + +extern "C" { +size_t APS5_VABI sceAudio3dGetSpeakerArrayMemorySize(std::uint32_t num_speakers, std::uint8_t is_3d); +int APS5_VABI sceAudio3dCreateSpeakerArray(AudioOut2SpeakerArrayHandle* handle, const Audio3dSpeakerArrayParameters* parameters); +int APS5_VABI sceAudio3dDeleteSpeakerArray(AudioOut2SpeakerArrayHandle handle); +int APS5_VABI sceAudio3dGetSpeakerArrayMixCoefficients(AudioOut2SpeakerArrayHandle handle, AudioOut2Position pos, float spread, float* coefficients, std::uint32_t num_coefficients); +int APS5_VABI sceAudio3dGetSpeakerArrayMixCoefficients2(AudioOut2SpeakerArrayHandle handle, AudioOut2Position pos, float spread, float* coefficients, std::uint32_t num_coefficients, std::uint8_t height_aware, float downmix_spread_radius); +size_t APS5_VABI sceAudioOut2GetSpeakerArrayMemorySize(std::uint32_t num_speakers, std::uint8_t is_3d, std::uint8_t is_ambisonics); +} + +namespace { + +constexpr int INVALID_PARAMETER = static_cast(0x80EA0004); + +void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "%s\n", message); + std::abort(); +} + +template +void RequireThrows(F f, const char* message) { + bool threw = false; + try { + f(); + } catch (const std::exception&) { + threw = true; + } + Require(threw, message); +} + +} + +int main() { + Require(sceAudio3dGetSpeakerArrayMemorySize(4, 1) == sceAudioOut2GetSpeakerArrayMemorySize(4, 1, 0), "memory size forwards to AudioOut2 without ambisonics"); + + const AudioOut2Position positions[4] = {{1, 1, 0}, {1, -1, 0}, {-1, 1, 0}, {-1, -1, 0}}; + alignas(16) unsigned char memory[1024] = {}; + const std::size_t size = sceAudio3dGetSpeakerArrayMemorySize(4, 0); + Require(size <= sizeof(memory), "speaker array fits the test buffer"); + const Audio3dSpeakerArrayParameters parameters{positions, 4, 0, memory, size}; + + AudioOut2SpeakerArrayHandle handle = nullptr; + Require(sceAudio3dCreateSpeakerArray(nullptr, ¶meters) == INVALID_PARAMETER, "null handle is an invalid parameter"); + Require(sceAudio3dCreateSpeakerArray(&handle, nullptr) == INVALID_PARAMETER, "null parameters are an invalid parameter"); + Require(handle == nullptr, "a rejected create leaves the handle"); + Require(sceAudio3dCreateSpeakerArray(&handle, ¶meters) == 0, "create succeeds without ambisonics parameters"); + Require(handle != nullptr, "create stores the handle"); + + float coefficients[4] = {-1, -1, -1, -1}; + Require(sceAudio3dGetSpeakerArrayMixCoefficients(handle, AudioOut2Position{1, 0, 0}, 0.0f, coefficients, 4) == 0, "mix coefficients succeed"); + Require(coefficients[2] == 0.0f && coefficients[3] == 0.0f, "mix coefficients are written"); + float coefficients2[4] = {-1, -1, -1, -1}; + Require(sceAudio3dGetSpeakerArrayMixCoefficients2(handle, AudioOut2Position{1, 0, 0}, 0.0f, coefficients2, 4, 1, 0.5f) == 0, "mix coefficients 2 succeed"); + Require(coefficients2[3] == 0.0f, "mix coefficients 2 are written"); + RequireThrows([&] { sceAudio3dGetSpeakerArrayMixCoefficients(handle, AudioOut2Position{1, 0, 0}, 0.0f, coefficients, 3); }, "a coefficient count that differs from the array is rejected"); + + Require(sceAudio3dDeleteSpeakerArray(handle) == 0, "delete succeeds"); + RequireThrows([&] { sceAudio3dDeleteSpeakerArray(handle); }, "a deleted array is no longer valid"); + return 0; +} diff --git a/core/libs/tests/GuestAudioIn.cpp b/core/libs/tests/GuestAudioIn.cpp index 61b528535b..15caefd1ec 100644 --- a/core/libs/tests/GuestAudioIn.cpp +++ b/core/libs/tests/GuestAudioIn.cpp @@ -118,7 +118,10 @@ void TestValidation() { port = sceAudioInAsyncOpen(user, 0, 0, 128, 48000, 0x10); gain = 0.5f; Require(port > 0 && sceAudioInGetGain(port, &gain) == 0 && gain == 1.0f && sceAudioInGetRerouteCount(port) == 0 && sceAudioInClose(port) == 0); - Require(ReportsNotImplemented("sceAudioInIsPadEmbeddedMic", [] { sceAudioInIsPadEmbeddedMic(1); })); + Require(sceAudioInIsPadEmbeddedMic(1) == static_cast(0x80260101)); + const int mic = sceAudioInOpen(user, 0, 0, 256, 48000, 0x10); + Require(mic > 0 && sceAudioInIsPadEmbeddedMic(mic) == 0 && sceAudioInClose(mic) == 0); + Require(sceAudioInIsPadEmbeddedMic(mic) == static_cast(0x80260101)); } void InputSound(int handle, std::vector& block, int samples = 256) { diff --git a/core/libs/tests/GuestCommonDialog.cpp b/core/libs/tests/GuestCommonDialog.cpp index 1638caf9ec..e5b9cd993b 100644 --- a/core/libs/tests/GuestCommonDialog.cpp +++ b/core/libs/tests/GuestCommonDialog.cpp @@ -13,12 +13,15 @@ void Require(bool value) { if (!value) std::abort(); } +constexpr int kErrAlreadySystemInitialized = static_cast(0x80B80002); + } int main() { Require(!sceCommonDialogIsUsed()); Require(sceCommonDialogInitialize() == COMMON_DIALOG_OK); Require(!sceCommonDialogIsUsed()); - Require(sceCommonDialogInitialize() == COMMON_DIALOG_ERROR_ALREADY_INITIALIZED); + Require(sceCommonDialogInitialize() == kErrAlreadySystemInitialized); + Require(sceCommonDialogInitialize() == kErrAlreadySystemInitialized); Require(!sceCommonDialogIsUsed()); } diff --git a/core/libs/tests/GuestContentSearch.cpp b/core/libs/tests/GuestContentSearch.cpp index e9facbccc1..5676436c77 100644 --- a/core/libs/tests/GuestContentSearch.cpp +++ b/core/libs/tests/GuestContentSearch.cpp @@ -15,12 +15,34 @@ int APS5_VABI sceContentSearchOpenMetadataByContentId(std::int64_t contentId, st int APS5_VABI sceContentSearchCloseMetadata(std::int32_t metadataId); int APS5_VABI sceContentSearchGetMetadataFieldInfo(std::int32_t metadataId, const char* field, std::int32_t* metadataType, std::int32_t* size); int APS5_VABI sceContentSearchGetMetadataValue(std::int32_t metadataId, const char* field, void* value); +int APS5_VABI sceContentBinderEnd(void); +int APS5_VABI sceContentBinderGetNumOfDifference(void); +int APS5_VABI sceContentBinderInitialize(void); +int APS5_VABI sceContentBinderIsUpdated(void); +int APS5_VABI sceContentBinderReset(void); +int APS5_VABI sceContentBinderSearchDifference(void); +int APS5_VABI sceContentBinderStart(void); +int APS5_VABI sceContentBinderTerminate(void); +int APS5_VABI sceContentSearchGetApplicationLastUpdateId(void); +int APS5_VABI sceContentSearchGetMyApplicationIndex(void); +int APS5_VABI sceContentSearchGetNumOfApplication(void); +int APS5_VABI sceContentSearchSearchApplication(void); } namespace { void Require(bool value) { if (!value) std::abort(); } +template +bool ThrowsRuntimeError(TAction action) { + try { + action(); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + template bool Throws(TAction action) { try { @@ -136,4 +158,18 @@ int main() { Require(sceContentSearchTerm() == 0); Require(sceContentSearchGetContentLastUpdateId(&updateId) == ErrorNotInitialized); Require(updateId == -1); + constexpr int binderResult = static_cast(0x809D000F); + Require(sceContentBinderInitialize() == binderResult); + Require(sceContentBinderStart() == binderResult); + Require(sceContentBinderReset() == binderResult); + Require(sceContentBinderIsUpdated() == binderResult); + Require(sceContentBinderGetNumOfDifference() == binderResult); + Require(sceContentBinderSearchDifference() == binderResult); + Require(sceContentBinderEnd() == binderResult); + Require(sceContentBinderTerminate() == binderResult); + + Require(ThrowsRuntimeError([] { sceContentSearchGetApplicationLastUpdateId(); })); + Require(ThrowsRuntimeError([] { sceContentSearchGetMyApplicationIndex(); })); + Require(ThrowsRuntimeError([] { sceContentSearchGetNumOfApplication(); })); + Require(ThrowsRuntimeError([] { sceContentSearchSearchApplication(); })); } diff --git a/core/libs/tests/GuestCredentials.cpp b/core/libs/tests/GuestCredentials.cpp index 887f69c727..ff69af902b 100644 --- a/core/libs/tests/GuestCredentials.cpp +++ b/core/libs/tests/GuestCredentials.cpp @@ -11,6 +11,8 @@ int APS5_VABI setuid_nid_postfix(std::uint32_t); int APS5_VABI setgid_nid_postfix(std::uint32_t); int APS5_VABI setreuid_nid_postfix(std::uint32_t, std::uint32_t); int APS5_VABI setregid_nid_postfix(std::uint32_t, std::uint32_t); +int APS5_VABI setresuid_nid_postfix(std::uint32_t, std::uint32_t, std::uint32_t); +int APS5_VABI setresgid_nid_postfix(std::uint32_t, std::uint32_t, std::uint32_t); int APS5_VABI setgroups_nid_postfix(int, const std::uint32_t*); int APS5_VABI getgroups_nid_postfix(int, std::uint32_t*); int APS5_VABI getuid_nid_postfix(void); @@ -18,6 +20,8 @@ int APS5_VABI geteuid_nid_postfix(void); int APS5_VABI getgid_nid_postfix(void); int APS5_VABI getegid_nid_postfix(void); int APS5_VABI issetugid_nid_postfix(void); +int APS5_VABI getresuid_nid_postfix(std::uint32_t*, std::uint32_t*, std::uint32_t*); +int APS5_VABI getresgid_nid_postfix(std::uint32_t*, std::uint32_t*, std::uint32_t*); int* APS5_VABI __error_nid_postfix(); } @@ -43,6 +47,17 @@ static bool ThrowsRe(SetReId function, std::uint32_t r, std::uint32_t e) { return false; } +using SetResId = int (APS5_VABI *)(std::uint32_t, std::uint32_t, std::uint32_t); + +static bool ThrowsRes(SetResId function, std::uint32_t r, std::uint32_t e, std::uint32_t s) { + try { + function(r, e, s); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + constexpr int GuestEfault = 14; constexpr int GuestEinval = 22; constexpr std::uint32_t Unchanged = 0xffffffffu; @@ -68,6 +83,32 @@ int main() { Require(Throws(setgid_nid_postfix, Unchanged)); Require(getuid_nid_postfix() == 0 && geteuid_nid_postfix() == 0); Require(getgid_nid_postfix() == 0 && getegid_nid_postfix() == 0 && issetugid_nid_postfix() == 0); + + std::uint32_t ruid = 99, euid = 99, suid = 99; + *__error_nid_postfix() = 13; + Require(getresuid_nid_postfix(&ruid, &euid, &suid) == 0); + Require(ruid == 0 && euid == 0 && suid == 0); + Require(*__error_nid_postfix() == 13); + ruid = 99; + euid = 99; + suid = 99; + Require(getresuid_nid_postfix(&ruid, nullptr, nullptr) == 0 && ruid == 0); + Require(getresuid_nid_postfix(nullptr, &euid, nullptr) == 0 && euid == 0); + Require(getresuid_nid_postfix(nullptr, nullptr, &suid) == 0 && suid == 0); + Require(getresuid_nid_postfix(nullptr, nullptr, nullptr) == 0); + + std::uint32_t rgid = 99, egid = 99, sgid = 99; + *__error_nid_postfix() = 13; + Require(getresgid_nid_postfix(&rgid, &egid, &sgid) == 0); + Require(rgid == 0 && egid == 0 && sgid == 0); + Require(*__error_nid_postfix() == 13); + rgid = 99; + egid = 99; + sgid = 99; + Require(getresgid_nid_postfix(&rgid, nullptr, nullptr) == 0 && rgid == 0); + Require(getresgid_nid_postfix(nullptr, &egid, nullptr) == 0 && egid == 0); + Require(getresgid_nid_postfix(nullptr, nullptr, &sgid) == 0 && sgid == 0); + Require(getresgid_nid_postfix(nullptr, nullptr, nullptr) == 0); Require(setreuid_nid_postfix(Unchanged, Unchanged) == 0); Require(setreuid_nid_postfix(0, Unchanged) == 0); Require(setreuid_nid_postfix(Unchanged, 0) == 0); @@ -82,10 +123,44 @@ int main() { Require(ThrowsRe(setregid_nid_postfix, 1000, 0)); Require(ThrowsRe(setregid_nid_postfix, 0, 1000)); + Require(setresuid_nid_postfix(Unchanged, Unchanged, Unchanged) == 0); + Require(setresuid_nid_postfix(0, Unchanged, Unchanged) == 0); + Require(setresuid_nid_postfix(Unchanged, 0, Unchanged) == 0); + Require(setresuid_nid_postfix(Unchanged, Unchanged, 0) == 0); + Require(setresuid_nid_postfix(0, 0, 0) == 0); + Require(ThrowsRes(setresuid_nid_postfix, 1000, 0, 0)); + Require(ThrowsRes(setresuid_nid_postfix, 0, 1000, 0)); + Require(ThrowsRes(setresuid_nid_postfix, 0, 0, 1000)); + Require(ThrowsRes(setresuid_nid_postfix, 1000, Unchanged, Unchanged)); + Require(ThrowsRes(setresuid_nid_postfix, Unchanged, 1000, Unchanged)); + Require(ThrowsRes(setresuid_nid_postfix, Unchanged, Unchanged, 1000)); + + Require(setresgid_nid_postfix(Unchanged, Unchanged, Unchanged) == 0); + Require(setresgid_nid_postfix(0, Unchanged, Unchanged) == 0); + Require(setresgid_nid_postfix(Unchanged, 0, Unchanged) == 0); + Require(setresgid_nid_postfix(Unchanged, Unchanged, 0) == 0); + Require(setresgid_nid_postfix(0, 0, 0) == 0); + Require(ThrowsRes(setresgid_nid_postfix, 1000, 0, 0)); + Require(ThrowsRes(setresgid_nid_postfix, 0, 1000, 0)); + Require(ThrowsRes(setresgid_nid_postfix, 0, 0, 1000)); + Require(ThrowsRes(setresgid_nid_postfix, 1000, Unchanged, Unchanged)); + Require(ThrowsRes(setresgid_nid_postfix, Unchanged, 1000, Unchanged)); + Require(ThrowsRes(setresgid_nid_postfix, Unchanged, Unchanged, 1000)); + std::array groups{}; Require(getgroups_nid_postfix(0, nullptr) == 1); Require(getgroups_nid_postfix(static_cast(groups.size()), groups.data()) == 1 && groups[0] == 0); Require(groups[0] == static_cast(getegid_nid_postfix())); + *__error_nid_postfix() = 13; + RequireFailure(setgroups_nid_postfix(1, reinterpret_cast(1)), GuestEfault); + *__error_nid_postfix() = 13; + RequireFailure(getgroups_nid_postfix(1, reinterpret_cast(1)), GuestEfault); + Require(getgroups_nid_postfix(0, nullptr) == 1); + + *__error_nid_postfix() = 13; + RequireFailure(getresuid_nid_postfix(reinterpret_cast(1), nullptr, nullptr), GuestEfault); + *__error_nid_postfix() = 13; + RequireFailure(getresgid_nid_postfix(nullptr, reinterpret_cast(1), nullptr), GuestEfault); const std::array wanted{0, 5, 7}; Require(setgroups_nid_postfix(static_cast(wanted.size()), wanted.data()) == 0); diff --git a/core/libs/tests/GuestCryptoMemcmp.cpp b/core/libs/tests/GuestCryptoMemcmp.cpp new file mode 100644 index 0000000000..578b4a4645 --- /dev/null +++ b/core/libs/tests/GuestCryptoMemcmp.cpp @@ -0,0 +1,125 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else +#include +#include +#endif + +extern "C" int APS5_VABI CRYPTO_memcmp_nid_postfix(const void*, const void*, std::size_t); + +namespace { + +void Require(bool value) { + if (!value) { + std::fprintf(stderr, "CRYPTO_memcmp check failed\n"); + std::abort(); + } +} + +class GuardedPage { +public: + explicit GuardedPage(bool different) { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + pageSize = info.dwPageSize; + memory = static_cast(VirtualAlloc(nullptr, pageSize * 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + Require(memory != nullptr); + DWORD previous; + Require(VirtualProtect(memory + pageSize, pageSize, PAGE_NOACCESS, &previous) != 0); +#else + const long nativePageSize = sysconf(_SC_PAGESIZE); + Require(nativePageSize > 0); + pageSize = static_cast(nativePageSize); + void* allocation = mmap(nullptr, pageSize * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(allocation != MAP_FAILED); + memory = static_cast(allocation); + Require(mprotect(memory + pageSize, pageSize, PROT_NONE) == 0); +#endif + std::memset(memory, 0xA5, pageSize); + if (different) memory[pageSize - 1] ^= 0xFF; +#ifdef _WIN32 + Require(VirtualProtect(memory, pageSize, PAGE_READONLY, &previous) != 0); +#else + Require(mprotect(memory, pageSize, PROT_READ) == 0); +#endif + } + + GuardedPage(const GuardedPage&) = delete; + GuardedPage& operator=(const GuardedPage&) = delete; + + ~GuardedPage() { +#ifdef _WIN32 + VirtualFree(memory, 0, MEM_RELEASE); +#else + munmap(memory, pageSize * 2); +#endif + } + + const unsigned char* Tail(std::size_t length) const { + Require(length <= pageSize); + return memory + pageSize - length; + } + +private: + unsigned char* memory = nullptr; + std::size_t pageSize = 0; +}; + +} + +int main() { + Require(CRYPTO_memcmp_nid_postfix(nullptr, nullptr, 0) == 0); + unsigned char byte = 0xA5; + Require(CRYPTO_memcmp_nid_postfix(nullptr, &byte, 0) == 0); + Require(CRYPTO_memcmp_nid_postfix(&byte, nullptr, 0) == 0); + for (unsigned int left = 0; left < 256; ++left) { + for (unsigned int right = 0; right < 256; ++right) { + const unsigned char a = static_cast(left); + const unsigned char b = static_cast(right); + Require((CRYPTO_memcmp_nid_postfix(&a, &b, 1) == 0) == (left == right)); + } + } + std::array first; + for (std::size_t index = 0; index < first.size(); ++index) first[index] = static_cast(index * 37); + auto second = first; + const auto original = first; + Require(CRYPTO_memcmp_nid_postfix(first.data(), first.data(), first.size()) == 0); + Require(CRYPTO_memcmp_nid_postfix(first.data() + 1, second.data() + 1, 65) == 0); + for (std::size_t index = 1; index <= 65; ++index) { + second[index] ^= 0x80; + const auto changed = second; + Require(CRYPTO_memcmp_nid_postfix(first.data() + 1, second.data() + 1, 65) != 0); + Require(CRYPTO_memcmp_nid_postfix(second.data() + 1, first.data() + 1, 65) != 0); + Require(first == original && second == changed); + second[index] ^= 0x80; + } + second[32] ^= 0x80; + Require(CRYPTO_memcmp_nid_postfix(first.data(), second.data(), 32) == 0); + Require(CRYPTO_memcmp_nid_postfix(first.data(), second.data(), 33) != 0); + second = first; + second[1] ^= 0x80; + second[65] ^= 0x80; + Require(CRYPTO_memcmp_nid_postfix(first.data(), second.data(), first.size()) != 0); + first.fill(0xCC); + Require(CRYPTO_memcmp_nid_postfix(first.data(), first.data() + 1, first.size() - 1) == 0); + first[32] = 0xCD; + const auto overlap = first; + Require(CRYPTO_memcmp_nid_postfix(first.data(), first.data() + 1, first.size() - 1) != 0); + Require(first == overlap); + const GuardedPage equal(false); + const GuardedPage other(false); + const GuardedPage different(true); + for (const std::size_t length : {0, 1, 2, 7, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129}) { + Require(CRYPTO_memcmp_nid_postfix(equal.Tail(length), other.Tail(length), length) == 0); + Require((CRYPTO_memcmp_nid_postfix(equal.Tail(length), different.Tail(length), length) == 0) == (length == 0)); + } +} diff --git a/core/libs/tests/GuestEntitlements.cpp b/core/libs/tests/GuestEntitlements.cpp new file mode 100644 index 0000000000..868c1f6e7f --- /dev/null +++ b/core/libs/tests/GuestEntitlements.cpp @@ -0,0 +1,67 @@ +#include "SceTypes.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +int APS5_VABI sceNpEntitlementAccessGetAddcontEntitlementInfo(std::uint32_t, const NpUnifiedEntitlementLabel*, NpEntitlementAccessAddcontEntitlementInfo*); +int APS5_VABI sceNpEntitlementAccessGetAddcontEntitlementInfoList(std::uint32_t, NpEntitlementAccessAddcontEntitlementInfo*, std::uint32_t, std::uint32_t*); +} + +static void Require(bool value) { if (!value) std::abort(); } + +static void SetEntitlements(const std::filesystem::path& file) { +#ifdef _WIN32 + Require(_wputenv_s(L"ANYPS5_ENTITLEMENTS", file.wstring().c_str()) == 0); +#else + Require(::setenv("ANYPS5_ENTITLEMENTS", file.c_str(), 1) == 0); +#endif +} + +int main(int argc, char** argv) { + const auto root = std::filesystem::temp_directory_path() / ("anyps5_guest_entitlements-" + std::to_string(std::random_device{}())); + const auto directory = root / std::filesystem::path(u8"dlc-a\u00e7\u00e3o-\u65e5\u672c"); + const auto file = directory / "anyps5-entitlements.ini"; + if (argc == 2 && std::string_view(argv[1]) == "missing") { + SetEntitlements(file); + std::uint32_t hits = 0; + bool rejected = false; + try { + sceNpEntitlementAccessGetAddcontEntitlementInfoList(0, nullptr, 0, &hits); + } catch (const std::runtime_error& error) { + rejected = std::string_view(error.what()).starts_with("NpEntitlementAccess: cannot read "); + } + Require(rejected); + std::puts("Guest entitlements rejected a missing configured file"); + return 0; + } + std::filesystem::create_directories(directory); + { + std::ofstream ini(file, std::ios::binary); + ini << "# owned add-ons\nFIRSTADDON0000\n SECONDADDON ; second\n"; + Require(static_cast(ini)); + } + SetEntitlements(file); + NpEntitlementAccessAddcontEntitlementInfo list[3]{}; + std::uint32_t hits = 0; + Require(sceNpEntitlementAccessGetAddcontEntitlementInfoList(0, list, 3, &hits) == 0); + Require(hits == 2); + Require(std::strcmp(list[0].entitlement_label.data, "FIRSTADDON0000") == 0); + Require(std::strcmp(list[1].entitlement_label.data, "SECONDADDON") == 0); + NpUnifiedEntitlementLabel label{}; + std::memcpy(label.data, "SECONDADDON", 11); + NpEntitlementAccessAddcontEntitlementInfo info{}; + Require(sceNpEntitlementAccessGetAddcontEntitlementInfo(0, &label, &info) == 0); + Require(std::strcmp(info.entitlement_label.data, "SECONDADDON") == 0 && info.package_type == 3 && info.download_status == 4); + std::filesystem::remove_all(root); + std::puts("Guest entitlements passed"); + return 0; +} diff --git a/core/libs/tests/GuestErrorDialog.cpp b/core/libs/tests/GuestErrorDialog.cpp index 106819b6a8..51779896a1 100644 --- a/core/libs/tests/GuestErrorDialog.cpp +++ b/core/libs/tests/GuestErrorDialog.cpp @@ -9,6 +9,8 @@ int APS5_VABI sceErrorDialogUpdateStatus(void); int APS5_VABI sceErrorDialogGetStatus(void); int APS5_VABI sceErrorDialogClose(void); int APS5_VABI sceErrorDialogTerminate(void); +int APS5_VABI sceErrorDialogOpenDetail(void); +int APS5_VABI sceErrorDialogOpenWithReport(void); } namespace { @@ -20,6 +22,7 @@ constexpr int kStatusFinished = 3; constexpr int kErrNotInitialized = static_cast(0x80ED0001); constexpr int kErrAlreadyInitialized = static_cast(0x80ED0002); constexpr int kErrParam = static_cast(0x80ED0003); +constexpr int kErrUnexpectedFatal = static_cast(0x80ED0004); constexpr int kErrInvalidState = static_cast(0x80ED0005); struct ErrorDialogParam { @@ -68,4 +71,6 @@ int main() { Require(sceErrorDialogGetStatus() == 0); Require(sceErrorDialogUpdateStatus() == 0); Require(sceErrorDialogClose() == kErrInvalidState); + Require(sceErrorDialogOpenDetail() == kErrUnexpectedFatal); + Require(sceErrorDialogOpenWithReport() == kErrUnexpectedFatal); } diff --git a/core/libs/tests/GuestFont.cpp b/core/libs/tests/GuestFont.cpp index f052a81a9b..5fc1104e79 100644 --- a/core/libs/tests/GuestFont.cpp +++ b/core/libs/tests/GuestFont.cpp @@ -72,6 +72,7 @@ int APS5_VABI sceFontFtSupportOpenTypeTtf(FontLibrary library); int APS5_VABI sceFontFtSupportPcf(FontLibrary library); int APS5_VABI sceFontFtSupportPfr(FontLibrary library); int APS5_VABI sceFontFtSupportTrueType(FontLibrary library); +int APS5_VABI sceFontFtSupportTrueTypeGx(FontLibrary library); int APS5_VABI sceFontFtSupportType1(FontLibrary library); int APS5_VABI sceFontFtSupportType42(FontLibrary library); int APS5_VABI sceFontFtSupportWinFonts(FontLibrary library); @@ -222,10 +223,10 @@ int main() { FontLibrary library = nullptr; Require(sceFontCreateLibrary(&memory, nullptr, &library) == SCE_FONT_ERROR_INVALID_PARAMETER && library == nullptr); Require(sceFontCreateLibrary(&memory, sceFontSelectLibraryFt(0), &library) == SCE_FONT_OK && library != nullptr); - Require(sceFontFtSupportBdf(library) == 0 && sceFontFtSupportCid(library) == 0 && sceFontFtSupportOpenType(library) == 0 && sceFontFtSupportOpenTypeOtf(library) == 0 && sceFontFtSupportOpenTypeTtf(library) == 0 && sceFontFtSupportPcf(library) == 0 && sceFontFtSupportPfr(library) == 0 && sceFontFtSupportTrueType(library) == 0 && sceFontFtSupportType1(library) == 0 && sceFontFtSupportType42(library) == 0 && sceFontFtSupportWinFonts(library) == 0); + Require(sceFontFtSupportBdf(library) == 0 && sceFontFtSupportCid(library) == 0 && sceFontFtSupportOpenType(library) == 0 && sceFontFtSupportOpenTypeOtf(library) == 0 && sceFontFtSupportOpenTypeTtf(library) == 0 && sceFontFtSupportPcf(library) == 0 && sceFontFtSupportPfr(library) == 0 && sceFontFtSupportTrueType(library) == 0 && sceFontFtSupportTrueTypeGx(library) == 0 && sceFontFtSupportType1(library) == 0 && sceFontFtSupportType42(library) == 0 && sceFontFtSupportWinFonts(library) == 0); const auto* nativeLibrary = static_cast(library); const auto savedLock = nativeLibrary->lock_word; - for (auto support : {sceFontFtSupportBdf, sceFontFtSupportCid, sceFontFtSupportOpenType, sceFontFtSupportOpenTypeOtf, sceFontFtSupportOpenTypeTtf, sceFontFtSupportPcf, sceFontFtSupportPfr, sceFontFtSupportTrueType, sceFontFtSupportType1, sceFontFtSupportType42, sceFontFtSupportWinFonts}) { + for (auto support : {sceFontFtSupportBdf, sceFontFtSupportCid, sceFontFtSupportOpenType, sceFontFtSupportOpenTypeOtf, sceFontFtSupportOpenTypeTtf, sceFontFtSupportPcf, sceFontFtSupportPfr, sceFontFtSupportTrueType, sceFontFtSupportTrueTypeGx, sceFontFtSupportType1, sceFontFtSupportType42, sceFontFtSupportWinFonts}) { Font::FontLibNative invalidLibrary{}; Require(support(nullptr) == SCE_FONT_ERROR_INVALID_LIBRARY); Require(support(&invalidLibrary) == SCE_FONT_ERROR_INVALID_LIBRARY); diff --git a/core/libs/tests/GuestFreeType.cpp b/core/libs/tests/GuestFreeType.cpp index 8e5c7e7d0f..330a0a9d46 100644 --- a/core/libs/tests/GuestFreeType.cpp +++ b/core/libs/tests/GuestFreeType.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -23,6 +24,7 @@ using namespace FreeType; extern "C" { +void APS5_VABI FT_Set_Transform_nid_postfix(GuestFaceRec*, const GuestMatrix*, const GuestVector*); int APS5_VABI FT_Init_FreeType_nid_postfix(void**); int APS5_VABI FT_Done_FreeType_nid_postfix(void*); int APS5_VABI FT_New_Face_nid_postfix(void*, const char*, std::int64_t, GuestFaceRec**); @@ -33,6 +35,7 @@ std::uint32_t APS5_VABI FT_Get_Char_Index_nid_postfix(GuestFaceRec*, std::uint64 int APS5_VABI FT_Load_Glyph_nid_postfix(GuestFaceRec*, std::uint32_t, std::int32_t); int APS5_VABI FT_Load_Char_nid_postfix(GuestFaceRec*, std::uint64_t, std::int32_t); int APS5_VABI FT_Render_Glyph_nid_postfix(GuestGlyphSlotRec*, std::int32_t); +int APS5_VABI FT_Set_Char_Size_nid_postfix(GuestFaceRec*, std::int64_t, std::int64_t, std::uint32_t, std::uint32_t); } static void Check(bool value, int line) { @@ -89,6 +92,15 @@ static void SlotMatches(const GuestGlyphSlotRec* slot, FT_GlyphSlot host) { Require(slot->lsb_delta == host->lsb_delta && slot->rsb_delta == host->rsb_delta && slot->control_len == host->control_len); } +static void SizeMatches(const GuestFaceRec* face, FT_Face host) { + const auto& actual = face->size->metrics; + const auto& expected = host->size->metrics; + Require(actual.x_ppem == expected.x_ppem && actual.y_ppem == expected.y_ppem); + Require(actual.x_scale == expected.x_scale && actual.y_scale == expected.y_scale); + Require(actual.ascender == expected.ascender && actual.descender == expected.descender); + Require(actual.height == expected.height && actual.max_advance == expected.max_advance); +} + struct Finalizers { std::vector calls; }; @@ -141,6 +153,93 @@ static void SetFinalizers(GuestFaceRec* face, Finalizers& events) { face->size->generic = {&events, GuestSizeFinalizer}; } +static void CheckTransforms(const std::vector& font) { + FT_Library native = nullptr; + FT_Face host = nullptr; + FT_Face nativeOther = nullptr; + void* library = nullptr; + GuestFaceRec* face = nullptr; + GuestFaceRec* other = nullptr; + Require(FT_Init_FreeType(&native) == 0 && FT_Init_FreeType_nid_postfix(&library) == 0); + Require(FT_New_Memory_Face(native, font.data(), static_cast(font.size()), 0, &host) == 0); + Require(FT_New_Memory_Face(native, font.data(), static_cast(font.size()), 0, &nativeOther) == 0); + Require(FT_New_Memory_Face_nid_postfix(library, font.data(), font.size(), 0, &face) == 0); + Require(FT_New_Memory_Face_nid_postfix(library, font.data(), font.size(), 0, &other) == 0); + Require(FT_Set_Pixel_Sizes(host, 0, 100) == 0 && FT_Set_Pixel_Sizes_nid_postfix(face, 0, 100) == 0); + Require(FT_Set_Pixel_Sizes(nativeOther, 0, 100) == 0 && FT_Set_Pixel_Sizes_nid_postfix(other, 0, 100) == 0); + const std::array matrices{ + GuestMatrix{65536, 0, 0, 65536}, GuestMatrix{0, -65536, 65536, 0}, GuestMatrix{0, 65536, -65536, 0}, + GuestMatrix{-65536, 0, 0, 65536}, GuestMatrix{131072, 0, 0, 32768}, GuestMatrix{65536, 16384, -8192, 65536}, + GuestMatrix{0, 0, 0, 0} + }; + const std::array deltas{GuestVector{0, 0}, GuestVector{64, -128}, GuestVector{31, -63}, GuestVector{-96, 192}}; + std::size_t comparisons = 0; + for (std::size_t m = 0; m <= matrices.size(); ++m) { + for (std::size_t d = 0; d <= deltas.size(); ++d) { + GuestMatrix input = m < matrices.size() ? matrices[m] : matrices[0]; + GuestVector delta = d < deltas.size() ? deltas[d] : deltas[0]; + FT_Matrix matrix{static_cast(input.xx), static_cast(input.xy), static_cast(input.yx), static_cast(input.yy)}; + FT_Vector translation{static_cast(delta.x), static_cast(delta.y)}; + std::array savedSlot{}; + std::array savedSize{}; + std::memcpy(savedSlot.data(), face->glyph, savedSlot.size()); + std::memcpy(savedSize.data(), face->size, savedSize.size()); + FT_Set_Transform_nid_postfix(face, m < matrices.size() ? &input : nullptr, d < deltas.size() ? &delta : nullptr); + FT_Set_Transform(host, m < matrices.size() ? &matrix : nullptr, d < deltas.size() ? &translation : nullptr); + Require(std::memcmp(savedSlot.data(), face->glyph, savedSlot.size()) == 0); + Require(std::memcmp(savedSize.data(), face->size, savedSize.size()) == 0); + Require(input.xx == matrix.xx && input.xy == matrix.xy && input.yx == matrix.yx && input.yy == matrix.yy); + Require(delta.x == translation.x && delta.y == translation.y); + input = matrices[0]; + delta = deltas[0]; + for (const auto flags : std::array{FT_LOAD_DEFAULT, FT_LOAD_IGNORE_TRANSFORM, FT_LOAD_RENDER}) { + for (const std::uint32_t glyph : {0u, 1u}) { + Require(FT_Load_Glyph_nid_postfix(face, glyph, flags) == FT_Load_Glyph(host, glyph, flags)); + SlotMatches(face->glyph, host->glyph); + if (face->glyph->format == FT_GLYPH_FORMAT_OUTLINE) { + Require(FT_Render_Glyph_nid_postfix(face->glyph, FT_RENDER_MODE_NORMAL) == FT_Render_Glyph(host->glyph, FT_RENDER_MODE_NORMAL)); + SlotMatches(face->glyph, host->glyph); + } + ++comparisons; + } + } + Require(FT_Load_Glyph_nid_postfix(other, 1, FT_LOAD_DEFAULT) == FT_Load_Glyph(nativeOther, 1, FT_LOAD_DEFAULT)); + SlotMatches(other->glyph, nativeOther->glyph); + } + } + GuestMatrix rejected{65536, 0, 0, 65536}; + GuestVector rejectedDelta{}; + const auto loadUnchanged = [&] { + Require(FT_Load_Glyph_nid_postfix(face, 1, FT_LOAD_DEFAULT) == FT_Load_Glyph(host, 1, FT_LOAD_DEFAULT)); + SlotMatches(face->glyph, host->glyph); + }; + if constexpr (sizeof(FT_Long) < sizeof(std::int64_t)) { + for (const auto value : std::array{-0x80000001ll, 0x80000000ll, 0x100000000ll, + std::numeric_limits::min(), std::numeric_limits::max()}) { + for (auto member : {&GuestMatrix::xx, &GuestMatrix::xy, &GuestMatrix::yx, &GuestMatrix::yy}) { + rejected = matrices[0]; + rejected.*member = value; + Require(ThrowsWith([&] { FT_Set_Transform_nid_postfix(face, &rejected, nullptr); }, + "FT_Set_Transform: a component outside the host FT_Long range is not implemented")); + loadUnchanged(); + } + for (auto member : {&GuestVector::x, &GuestVector::y}) { + rejectedDelta = {}; + rejectedDelta.*member = value; + Require(ThrowsWith([&] { FT_Set_Transform_nid_postfix(face, &matrices[1], &rejectedDelta); }, + "FT_Set_Transform: a component outside the host FT_Long range is not implemented")); + loadUnchanged(); + } + } + } + rejected.xx = std::numeric_limits::max(); + rejectedDelta.x = std::numeric_limits::min(); + FT_Set_Transform_nid_postfix(nullptr, &rejected, &rejectedDelta); + FT_Set_Transform_nid_postfix(nullptr, nullptr, nullptr); + Require(FT_Done_FreeType_nid_postfix(library) == 0 && FT_Done_FreeType(native) == 0); + std::printf("FT_Set_Transform: %zu native glyph and bitmap comparisons\n", comparisons); +} + int main() { const auto root = std::filesystem::temp_directory_path() / ("anyps5_guest_freetype-" + std::to_string(std::random_device{}())); std::filesystem::create_directories(root / "app0"); @@ -191,6 +290,8 @@ int main() { Require(FT_New_Memory_Face_nid_postfix(library, font.data(), fontSize, -1, &probe) == 0 && probe != nullptr); Require(probe->num_faces == nativeProbe->num_faces && probe->face_index == nativeProbe->face_index); Require((probe->glyph == nullptr) == (nativeProbe->glyph == nullptr) && (probe->size == nullptr) == (nativeProbe->size == nullptr)); + Require(FT_Set_Char_Size_nid_postfix(probe, std::numeric_limits::max(), 0, 0, 0) == + FT_Set_Char_Size(nativeProbe, 64, 64, 72, 72)); Finalizers nativeProbeEvents; Finalizers guestProbeEvents; nativeProbe->generic = {&nativeProbeEvents, NativeFaceFinalizer}; @@ -302,6 +403,64 @@ int main() { slot->outline.points = original; Require(FT_Render_Glyph_nid_postfix(slot, FT_RENDER_MODE_NORMAL) == 0 && slot->bitmap_left == 10); + { + struct Request { + std::int64_t width; + std::int64_t height; + std::uint32_t horizontal; + std::uint32_t vertical; + }; + const std::array requests{ + Request{0, 0, 0, 0}, Request{0, 12 * 64, 0, 0}, Request{12 * 64, 0, 0, 0}, + Request{12 * 64, 18 * 64, 96, 144}, Request{12 * 64 + 32, 12 * 64 + 32, 72, 72}, + Request{1, 63, 72, 72}, Request{-1, -100, 0, 0}, Request{0, -100, 96, 0}, + Request{12 * 64, 18 * 64, 0, 144}, Request{12 * 64, 18 * 64, 96, 0}, + Request{65535ll * 64, 64, 72, 72}, Request{65536ll * 64, 64, 72, 72}, + Request{64, 65536ll * 64, 72, 72}, + Request{18 * 64, 12 * 64, 72, 72} + }; + std::size_t successes = 0; + std::size_t failures = 0; + for (const auto& request : requests) { + std::array previousSlot{}; + std::memcpy(previousSlot.data(), face->glyph, previousSlot.size()); + const auto expected = FT_Set_Char_Size(host, static_cast(request.width), static_cast(request.height), + request.horizontal, request.vertical); + Require(FT_Set_Char_Size_nid_postfix(face, request.width, request.height, request.horizontal, request.vertical) == expected); + Require(std::memcmp(previousSlot.data(), face->glyph, previousSlot.size()) == 0); + expected == 0 ? ++successes : ++failures; + SizeMatches(face, host); + Require(FT_Load_Char_nid_postfix(face, 'A', FT_LOAD_DEFAULT) == FT_Load_Char(host, 'A', FT_LOAD_DEFAULT)); + SlotMatches(face->glyph, host->glyph); + } + Require(successes != 0 && failures != 0); + for (const auto value : std::array{std::numeric_limits::min(), -0x80000001ll}) { + Require(FT_Set_Char_Size_nid_postfix(face, value, 0, 0, 0) == FT_Set_Char_Size(host, 64, 64, 0, 0)); + SizeMatches(face, host); + Require(FT_Set_Char_Size_nid_postfix(face, 0, value, 0, 0) == 0); + SizeMatches(face, host); + } + for (const auto value : {std::numeric_limits::min(), std::numeric_limits::max()}) { + Require(FT_Set_Char_Size_nid_postfix(nullptr, value, value, 0, 0) == FT_Err_Invalid_Face_Handle); + } + if constexpr (sizeof(FT_F26Dot6) < sizeof(std::int64_t)) { + for (const auto value : std::array{0x7FFFFFFFll, 0x80000000ll, 0x100000040ll, std::numeric_limits::max()}) { + for (const bool width : {false, true}) { + Require(ThrowsWith([&] { FT_Set_Char_Size_nid_postfix(face, width ? value : 64, width ? 64 : value, 72, 72); }, + "FT_Set_Char_Size: a character size or resolution outside the host arithmetic range is not implemented")); + SizeMatches(face, host); + } + } + Require(ThrowsWith([&] { FT_Set_Char_Size_nid_postfix(face, 64, 64, std::numeric_limits::max(), 72); }, + "FT_Set_Char_Size: a character size or resolution outside the host arithmetic range is not implemented")); + SizeMatches(face, host); + } + Require(FT_Set_Char_Size_nid_postfix(memoryFace, 0, 48 * 64, 0, 144) == 0); + Require(memoryFace->size->metrics.y_ppem == 96 && face->size->metrics.y_ppem == 1); + Require(FT_Set_Pixel_Sizes_nid_postfix(face, 0, 100) == 0 && FT_Set_Pixel_Sizes(host, 0, 100) == 0); + std::printf("FT_Set_Char_Size: %zu native request comparisons, %zu successes, %zu errors\n", requests.size(), successes, failures); + } + Finalizers nativeDone; Finalizers guestDone; SetFinalizers(host, nativeDone); @@ -339,6 +498,7 @@ int main() { void* empty = nullptr; Require(FT_Init_FreeType_nid_postfix(&empty) == 0 && FT_Done_FreeType_nid_postfix(empty) == 0); + CheckTransforms(font); std::filesystem::current_path(previous); std::error_code ignored; std::filesystem::remove_all(root, ignored); diff --git a/core/libs/tests/GuestFreeTypeNames.cpp b/core/libs/tests/GuestFreeTypeNames.cpp new file mode 100644 index 0000000000..a6aa3b7673 --- /dev/null +++ b/core/libs/tests/GuestFreeTypeNames.cpp @@ -0,0 +1,128 @@ +#include "FontFixture.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace FreeType; + +extern "C" { +int APS5_VABI FT_Get_Glyph_Name_nid_postfix(GuestFaceRec*, std::uint32_t, void*, std::uint32_t); +std::uint32_t APS5_VABI FT_Get_Name_Index_nid_postfix(GuestFaceRec*, const char*); +const char* APS5_VABI FT_Get_Postscript_Name_nid_postfix(GuestFaceRec*); +} + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "FreeType names check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +static std::vector NamedFont(const std::string& postscript, const std::string& first, const std::string& second) { + std::vector post(32, 0); + post[1] = 2; + for (const int value : {3, 0, 258, 259}) FontFixture::Put16(post, value); + for (const auto* name : {&first, &second}) { + post.push_back(static_cast(name->size())); + post.insert(post.end(), name->begin(), name->end()); + } + auto names = FontFixture::Words({0, 1, 18, 3, 1, 0x0409, 6, static_cast(postscript.size() * 2), 0}); + for (const unsigned char ch : postscript) FontFixture::Put16(names, ch); + return FontFixture::BuildFont(3, {{"post", post}, {"name", names}}); +} + +struct FacePair { + std::vector bytes; + GuestFace guest; + FT_Face oracle = nullptr; + + FacePair(GuestLibrary& library, FT_Library nativeLibrary, std::vector font) : bytes(std::move(font)) { + guest.library = &library; + Require(FT_New_Memory_Face(library.host, bytes.data(), static_cast(bytes.size()), 0, &guest.host) == 0); + Require(FT_New_Memory_Face(nativeLibrary, bytes.data(), static_cast(bytes.size()), 0, &oracle) == 0); + SyncFace(guest); + } + + ~FacePair() { + Require(FT_Done_Face(guest.host) == 0); + Require(FT_Done_Face(oracle) == 0); + } +}; + +static void CompareGlyph(GuestFaceRec* guest, FT_Face native, std::uint32_t index, std::uint32_t size) { + std::array expected; + expected.fill('?'); + auto actual = expected; + const FT_Error error = FT_Get_Glyph_Name(native, index, expected.data() + 3, size); + Require(FT_Get_Glyph_Name_nid_postfix(guest, index, actual.data() + 3, size) == error); + Require(actual == expected); +} + +static void CheckFace(FacePair& face, bool names) { + Require((FT_HAS_GLYPH_NAMES(face.oracle) != 0) == names); + for (const std::uint32_t index : {0u, 1u, 2u, 3u, 0x7FFFFFFFu}) { + for (const std::uint32_t size : {0u, 1u, 2u, 5u, 24u}) CompareGlyph(&face.guest.rec, face.oracle, index, size); + Require(FT_Get_Glyph_Name_nid_postfix(&face.guest.rec, index, nullptr, 24) == FT_Get_Glyph_Name(face.oracle, index, nullptr, 24)); + } + for (const std::uint32_t index : {0x80000000u, 0xFFFFFFFFu}) { + for (const std::uint32_t size : {0u, 1u, 24u}) { + std::array expected; + expected.fill('?'); + auto actual = expected; + if (size) expected[3] = '\0'; + Require(FT_Get_Glyph_Name_nid_postfix(&face.guest.rec, index, actual.data() + 3, size) == + (size ? FT_Err_Invalid_Glyph_Index : FT_Err_Invalid_Argument)); + Require(actual == expected); + } + } + for (const char* name : {".notdef", "alpha.custom", "beta", "gamma.custom", "delta", "ALPHA.CUSTOM", "missing", ""}) { + Require(FT_Get_Name_Index_nid_postfix(&face.guest.rec, name) == FT_Get_Name_Index(face.oracle, name)); + } + Require(FT_Get_Name_Index_nid_postfix(&face.guest.rec, nullptr) == 0); + const char* actual = FT_Get_Postscript_Name_nid_postfix(&face.guest.rec); + const char* expected = FT_Get_Postscript_Name(face.oracle); + Require((actual == nullptr) == (expected == nullptr)); + Require(!actual || std::strcmp(actual, expected) == 0); + Require(actual == FT_Get_Postscript_Name(face.guest.host)); +} + +int main() { + GuestLibrary library{}; + FT_Library nativeLibrary = nullptr; + Require(FT_Init_FreeType(&library.host) == 0); + Require(FT_Init_FreeType(&nativeLibrary) == 0); + { + FacePair first(library, nativeLibrary, NamedFont("NameFixture-A", "alpha.custom", "beta")); + FacePair second(library, nativeLibrary, NamedFont("NameFixture-B", "gamma.custom", "delta")); + std::vector post(32, 0); + post[1] = 3; + FacePair unnamed(library, nativeLibrary, FontFixture::BuildFont(3, {{"post", post}})); + CheckFace(first, true); + Require(FT_Get_Name_Index_nid_postfix(&first.guest.rec, "alpha.custom") == 1); + Require(FT_Get_Name_Index_nid_postfix(&first.guest.rec, "beta") == 2); + const char* retained = FT_Get_Postscript_Name_nid_postfix(&first.guest.rec); + Require(retained && std::strcmp(retained, "NameFixture-A") == 0); + CheckFace(second, true); + Require(FT_Get_Name_Index_nid_postfix(&second.guest.rec, "gamma.custom") == 1); + Require(FT_Get_Name_Index_nid_postfix(&second.guest.rec, "delta") == 2); + CheckFace(unnamed, false); + Require(FT_Get_Postscript_Name_nid_postfix(&unnamed.guest.rec) == nullptr); + Require(FT_Get_Postscript_Name_nid_postfix(&first.guest.rec) == retained && std::strcmp(retained, "NameFixture-A") == 0); + for (const std::uint32_t size : {0u, 1u, 24u}) CompareGlyph(nullptr, nullptr, 0, size); + Require(FT_Get_Glyph_Name_nid_postfix(nullptr, 0, nullptr, 0) == FT_Err_Invalid_Face_Handle); + Require(FT_Get_Name_Index_nid_postfix(nullptr, "alpha.custom") == 0); + Require(FT_Get_Name_Index_nid_postfix(nullptr, nullptr) == 0); + Require(FT_Get_Postscript_Name_nid_postfix(nullptr) == nullptr); + } + Require(FT_Done_FreeType(library.host) == 0); + Require(FT_Done_FreeType(nativeLibrary) == 0); +} diff --git a/core/libs/tests/GuestFreeTypeSfntNames.cpp b/core/libs/tests/GuestFreeTypeSfntNames.cpp new file mode 100644 index 0000000000..12affd6cbd --- /dev/null +++ b/core/libs/tests/GuestFreeTypeSfntNames.cpp @@ -0,0 +1,144 @@ +#include "FontFixture.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include FT_SFNT_NAMES_H + +#include +#include +#include +#include +#include +#include +#include + +using namespace FreeType; + +extern "C" { +std::uint32_t APS5_VABI FT_Get_Sfnt_Name_Count_nid_postfix(GuestFaceRec*); +int APS5_VABI FT_Get_Sfnt_Name_nid_postfix(GuestFaceRec*, std::uint32_t, GuestSfntName*); +} + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "FreeType SFNT names check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +struct NameRecord { + std::uint16_t platform; + std::uint16_t encoding; + std::uint16_t language; + std::uint16_t name; + std::vector bytes; +}; + +static std::vector NamedFont(const std::vector& records) { + auto table = FontFixture::Words({0, static_cast(records.size()), 6 + 12 * static_cast(records.size())}); + int offset = 0; + for (const auto& record : records) { + for (const int value : {static_cast(record.platform), static_cast(record.encoding), static_cast(record.language), + static_cast(record.name), static_cast(record.bytes.size()), offset}) FontFixture::Put16(table, value); + offset += static_cast(record.bytes.size()); + } + for (const auto& record : records) table.insert(table.end(), record.bytes.begin(), record.bytes.end()); + return FontFixture::BuildFont(3, {{"name", table}}); +} + +struct FacePair { + std::vector bytes; + GuestFace guest; + FT_Face oracle = nullptr; + + FacePair(GuestLibrary& library, FT_Library nativeLibrary, std::vector font) : bytes(std::move(font)) { + guest.library = &library; + Require(FT_New_Memory_Face(library.host, bytes.data(), static_cast(bytes.size()), 0, &guest.host) == 0); + Require(FT_New_Memory_Face(nativeLibrary, bytes.data(), static_cast(bytes.size()), 0, &oracle) == 0); + SyncFace(guest); + } + + ~FacePair() { + Require(FT_Done_Face(guest.host) == 0); + Require(FT_Done_Face(oracle) == 0); + } +}; + +static GuestSfntName Query(FacePair& face, std::uint32_t index, const NameRecord& record) { + struct GuardedName { + std::array before; + GuestSfntName value; + std::array after; + } actual; + std::memset(&actual, 0xA5, sizeof(actual)); + FT_SfntName expected{}; + Require(FT_Get_Sfnt_Name(face.oracle, index, &expected) == 0); + Require(FT_Get_Sfnt_Name_nid_postfix(&face.guest.rec, index, &actual.value) == 0); + for (const auto byte : actual.before) Require(byte == 0xA5); + for (const auto byte : actual.after) Require(byte == 0xA5); + const auto& name = actual.value; + Require(name.platform_id == expected.platform_id && name.platform_id == record.platform); + Require(name.encoding_id == expected.encoding_id && name.encoding_id == record.encoding); + Require(name.language_id == expected.language_id && name.language_id == record.language); + Require(name.name_id == expected.name_id && name.name_id == record.name); + Require(name.string_len == expected.string_len && name.string_len == record.bytes.size()); + Require(std::memcmp(name.string, expected.string, name.string_len) == 0); + Require(std::memcmp(name.string, record.bytes.data(), name.string_len) == 0); + FT_SfntName native{}; + Require(FT_Get_Sfnt_Name(face.guest.host, index, &native) == 0 && name.string == native.string); + return name; +} + +static void InvalidQuery(GuestFaceRec* guest, FT_Face native, std::uint32_t index) { + GuestSfntName actual; + std::memset(&actual, 0xA5, sizeof(actual)); + std::array before; + std::memcpy(before.data(), &actual, sizeof(actual)); + FT_SfntName expected{}; + const FT_Error error = FT_Get_Sfnt_Name(native, index, &expected); + Require(error != 0); + Require(FT_Get_Sfnt_Name_nid_postfix(guest, index, &actual) == error); + Require(std::memcmp(&actual, before.data(), sizeof(actual)) == 0); + Require(FT_Get_Sfnt_Name_nid_postfix(guest, index, nullptr) == FT_Get_Sfnt_Name(native, index, nullptr)); +} + +int main() { + GuestLibrary library{}; + FT_Library nativeLibrary = nullptr; + Require(FT_Init_FreeType(&library.host) == 0); + Require(FT_Init_FreeType(&nativeLibrary) == 0); + { + const std::vector records = { + {1, 0, 0, 1, {'F', 'a', 'm', 'i', 'l', 'y'}}, + {3, 1, 0x0409, 6, {0, 'S', 0, 'f', 0, 'n', 0, 't'}}, + {3, 10, 0x0411, 256, {0, 'A', 0, 0, 0, 'B'}} + }; + FacePair first(library, nativeLibrary, NamedFont(records)); + FacePair empty(library, nativeLibrary, FontFixture::BuildFont(3, {})); + Require(FT_Get_Sfnt_Name_Count_nid_postfix(&first.guest.rec) == records.size()); + Require(FT_Get_Sfnt_Name_Count_nid_postfix(&first.guest.rec) == FT_Get_Sfnt_Name_Count(first.oracle)); + std::vector retained; + for (std::uint32_t index = 0; index < records.size(); ++index) retained.push_back(Query(first, index, records[index])); + { + const std::vector other = {{3, 1, 0x0409, 6, {0, 'O', 0, 't', 0, 'h', 0, 'e', 0, 'r'}}}; + FacePair second(library, nativeLibrary, NamedFont(other)); + Require(FT_Get_Sfnt_Name_Count_nid_postfix(&second.guest.rec) == other.size()); + Query(second, 0, other[0]); + } + for (std::uint32_t index = 0; index < records.size(); ++index) { + Require(Query(first, index, records[index]).string == retained[index].string); + Require(std::memcmp(retained[index].string, records[index].bytes.data(), retained[index].string_len) == 0); + Require(FT_Get_Sfnt_Name_nid_postfix(&first.guest.rec, index, nullptr) == FT_Get_Sfnt_Name(first.oracle, index, nullptr)); + } + Require(FT_Get_Sfnt_Name_Count_nid_postfix(&empty.guest.rec) == 0); + Require(FT_Get_Sfnt_Name_Count_nid_postfix(nullptr) == 0); + for (const std::uint32_t index : {0u, 3u, 0x80000000u, 0xFFFFFFFFu}) { + if (index >= records.size()) InvalidQuery(&first.guest.rec, first.oracle, index); + InvalidQuery(&empty.guest.rec, empty.oracle, index); + InvalidQuery(nullptr, nullptr, index); + } + } + Require(FT_Done_FreeType(library.host) == 0); + Require(FT_Done_FreeType(nativeLibrary) == 0); +} diff --git a/core/libs/tests/GuestFstatFault.cpp b/core/libs/tests/GuestFstatFault.cpp index 4f0aa66bb9..06524f7df8 100644 --- a/core/libs/tests/GuestFstatFault.cpp +++ b/core/libs/tests/GuestFstatFault.cpp @@ -60,10 +60,8 @@ int main() { Require(close_nid_postfix(sockets[0]) == 0); Require(close_nid_postfix(sockets[1]) == 0); Require(close_nid_postfix(file) == 0); -#ifndef _WIN32 RequireFailure(file, GuestEbadf); RequireFailure(-1, GuestEbadf); -#endif std::filesystem::remove_all(root); } diff --git a/core/libs/tests/GuestFstatfs.cpp b/core/libs/tests/GuestFstatfs.cpp index 0c86546ccb..1a647f6094 100644 --- a/core/libs/tests/GuestFstatfs.cpp +++ b/core/libs/tests/GuestFstatfs.cpp @@ -42,6 +42,7 @@ int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t); int APS5_VABI close_nid_postfix(int); int APS5_VABI pipe_nid_postfix(int*); int APS5_VABI socketpair_nid_postfix(int, int, int, int*); +int APS5_VABI kqueue_nid_postfix(); int* APS5_VABI __error_nid_postfix(); } @@ -83,6 +84,19 @@ static void RequireVolume(int descriptor, const std::filesystem::path& path) { Require(fs.f_fstypename[0] == '\0' && fs.f_mntfromname[0] == '\0' && fs.f_mntonname[0] == '\0'); } +static void RequireDeviceFilesystem(int descriptor) { + GuestStatfs fs; + std::memset(&fs, 0x5a, sizeof(fs)); + Require(_fstatfs_nid_postfix(descriptor, &fs) == 0); + GuestStatfs expected{}; + expected.f_version = StatfsVersion; + expected.f_bsize = 512; + expected.f_iosize = 512; + expected.f_blocks = 2; + expected.f_namemax = 255; + Require(std::memcmp(&fs, &expected, sizeof(fs)) == 0); +} + int main() { const auto root = std::filesystem::temp_directory_path() / ("anyps5-fstatfs-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); @@ -122,11 +136,19 @@ int main() { Require(close_nid_postfix(sockets[1]) == 0); RequireFailure(sockets[0], GuestEbadf); + const int random = sceKernelOpen("/dev/urandom", SCE_KERNEL_O_RDONLY, 0); + Require(random >= 0); + RequireDeviceFilesystem(random); + Require(close_nid_postfix(random) == 0); + + const int kqueue = kqueue_nid_postfix(); + Require(kqueue >= 0); + RequireFailure(kqueue, GuestEinval); + Require(close_nid_postfix(kqueue) == 0); + RequireFailure(-1, GuestEbadf); Require(close_nid_postfix(file) == 0); -#ifndef _WIN32 RequireFailure(file, GuestEbadf); -#endif std::filesystem::remove_all(root); } diff --git a/core/libs/tests/GuestGameLiveStreaming.cpp b/core/libs/tests/GuestGameLiveStreaming.cpp index fed883108f..22950d430e 100644 --- a/core/libs/tests/GuestGameLiveStreaming.cpp +++ b/core/libs/tests/GuestGameLiveStreaming.cpp @@ -25,6 +25,7 @@ int APS5_VABI sceGameLiveStreamingTerminate(void); int APS5_VABI sceGameLiveStreamingGetCurrentStatus2(GameLiveStreamingStatus2* status); struct GameLiveStreamingProgramInfo; int APS5_VABI sceGameLiveStreamingGetProgramInfo(GameLiveStreamingProgramInfo*); +int APS5_VABI sceGameLiveStreamingEnableLiveStreaming(bool isEnableLiveStreaming); } namespace { @@ -33,6 +34,15 @@ void Require(bool value) { if (!value) std::abort(); } constexpr std::int32_t kInvalidUserId = -1; +bool EnableThrowsNotInitialized(bool isEnableLiveStreaming) { + try { + sceGameLiveStreamingEnableLiveStreaming(isEnableLiveStreaming); + } catch (const std::logic_error&) { + return true; + } + return false; +} + } int main() { @@ -41,8 +51,10 @@ int main() { constexpr int notInitialized = static_cast(0x80A00004); Require(sceGameLiveStreamingTerminate() == notInitialized); Require(sceGameLiveStreamingGetCurrentStatus2(nullptr) == notInitialized); + Require(EnableThrowsNotInitialized(true)); for (const std::size_t size : {0u, 1u, 0x3fffu, 0x4001u, 0x100000u}) Require(sceGameLiveStreamingInitialize(size) == invalidArgument); Require(sceGameLiveStreamingInitialize(0x4000) == 0); + Require(sceGameLiveStreamingEnableLiveStreaming(true) == 0); Require(sceGameLiveStreamingInitialize(0) == invalidArgument); Require(sceGameLiveStreamingInitialize(0x4000) == alreadyInitialized); Require(sceGameLiveStreamingGetCurrentStatus2(nullptr) == invalidArgument); @@ -71,7 +83,9 @@ int main() { Require(programInfo[i] == (i < 0x134C ? 0 : 0x7f)); } + Require(sceGameLiveStreamingEnableLiveStreaming(false) == 0); Require(sceGameLiveStreamingTerminate() == 0); + Require(EnableThrowsNotInitialized(false)); std::memset(&status, 0x7f, sizeof(status)); Require(sceGameLiveStreamingGetCurrentStatus2(&status) == notInitialized); for (const auto value : std::as_bytes(std::span(&status, 1))) Require(value == std::byte{0x7f}); diff --git a/core/libs/tests/GuestHmd.cpp b/core/libs/tests/GuestHmd.cpp index 230395afb9..257e9f381c 100644 --- a/core/libs/tests/GuestHmd.cpp +++ b/core/libs/tests/GuestHmd.cpp @@ -1,12 +1,22 @@ +#include "ProcessParametersFixture.hpp" #include "prx/libSceHmd/Hmd.hpp" +#include +#include +#include #include #include #include #include +#include namespace { +constexpr std::uint32_t LastSdkBeforeStartupState = 0x044fffff; +constexpr std::uint32_t FirstSdkWithStartupState = 0x04500000; + +alignas(16) std::array process{}; + void Require(bool condition, const char* message) { if (!condition) { std::fprintf(stderr, "HMD: %s\n", message); @@ -14,12 +24,28 @@ void Require(bool condition, const char* message) { } } +void InstallProcessParameters() { + const std::uint64_t size = process.size(); + const std::uint32_t magic = 0x4942524f; + std::memcpy(process.data(), &size, sizeof(size)); + std::memcpy(process.data() + 8, &magic, sizeof(magic)); + Require(ProcessParametersFixture::Install(process.data(), process.size()), "process parameters were not installed"); +} + +void SetCompiledSdkVersion(std::uint32_t version) { + std::memcpy(process.data() + 0x10, &version, sizeof(version)); +} + 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"); + Require(!sceHmdInternalIsGameVr2d(), "game reported in VR2D without a headset"); + Require(!sceHmdInternalIsMiniAppVr2d(), "mini app reported in VR2D without a headset"); + Require(!sceHmdInternalIsCommonDlgVr2d(), "common dialog reported in VR2D without a headset"); + Require(!sceHmdInternalIsCommonDlgMiniAppVr2d(), "common dialog mini app reported in VR2D without a headset"); } void CheckBufferQueries() { @@ -53,6 +79,77 @@ void CheckHandleQueries(std::int32_t expected) { for (const auto byte : sensor) Require(byte == 0x5a, "a refused sensor query wrote its output"); } +template +bool Unimplemented(Call call) { + try { + static_cast(call()); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +void CheckCommandQueries(bool handleZeroMeasured) { + unsigned char zeroed[64] = {}; + unsigned char vr2dBytes[64] = {}; + unsigned char commands[16]; + std::memset(commands, 0x5a, sizeof(commands)); + for (const std::size_t offset : {0x00u, 0x08u, 0x20u}) std::memset(vr2dBytes + offset, 0x11, 8); + const auto* distortion = reinterpret_cast(zeroed); + const auto* wideNear = reinterpret_cast(zeroed); + const auto* zeroedVr2d = reinterpret_cast(zeroed); + const auto* vr2d = reinterpret_cast(vr2dBytes); + auto* commandBuffer = reinterpret_cast(commands); + const auto vr2dWith = [&](std::size_t offset, unsigned char value, std::size_t bytes, auto check) { + unsigned char changed[64]; + std::memcpy(changed, vr2dBytes, sizeof(changed)); + std::memset(changed + offset, value, bytes); + check(reinterpret_cast(changed)); + }; + for (const auto handle : {-1, 1, 0x0f000000}) { + Require(sceHmdGetDistortionCorrectionCommand(handle, distortion, commandBuffer) == Hmd::InvalidHandle, "distortion command accepted a handle sceHmdOpen never issued"); + Require(sceHmdDistortionGetCorrectionCommand(handle, distortion, commandBuffer) == Hmd::InvalidHandle, "aliased distortion command accepted a handle sceHmdOpen never issued"); + Require(sceHmdGetWideNearDistortionCorrectionCommand(handle, wideNear, commandBuffer) == Hmd::InvalidHandle, "wide-near distortion command accepted a handle sceHmdOpen never issued"); + Require(sceHmdDistortionGetWideNearCorrectionCommand(handle, wideNear, commandBuffer) == Hmd::InvalidHandle, "aliased wide-near distortion command accepted a handle sceHmdOpen never issued"); + Require(sceHmdGet2dVrCommand(handle, vr2d, commandBuffer) == Hmd::InvalidHandle, "2D VR command accepted a handle sceHmdOpen never issued"); + Require(sceHmdDistortionGet2dVrCommand(handle, vr2d, commandBuffer) == Hmd::InvalidHandle, "aliased 2D VR command accepted a handle sceHmdOpen never issued"); + } + for (const auto handle : {0, -1, 1, 0x0f000000}) { + for (const auto query : {sceHmdGet2dVrCommand, sceHmdDistortionGet2dVrCommand}) { + Require(query(handle, zeroedVr2d, commandBuffer) == Hmd::ParameterNull, "2D VR command took a parameter with null pointers"); + Require(query(handle, nullptr, commandBuffer) == Hmd::ParameterNull, "2D VR command took a null parameter"); + Require(query(handle, vr2d, nullptr) == Hmd::ParameterNull, "2D VR command took a null command buffer"); + for (const std::size_t offset : {0x00u, 0x08u, 0x20u}) { + vr2dWith(offset, 0, 8, [&](const Hmd2dVrCommandParameter* changed) { + Require(query(handle, changed, commandBuffer) == Hmd::ParameterNull, "2D VR command took a null pointer in its parameter"); + }); + } + vr2dWith(0x34, 0x01, 4, [&](const Hmd2dVrCommandParameter* changed) { + Require(query(handle, changed, commandBuffer) == Hmd::ParameterInvalid, "2D VR command took a non-zero field at 0x34"); + }); + vr2dWith(0x38, 0x01, 8, [&](const Hmd2dVrCommandParameter* changed) { + Require(query(handle, changed, commandBuffer) == Hmd::ParameterInvalid, "2D VR command took a non-zero field at 0x38"); + }); + } + } + if (!handleZeroMeasured) { + Require(Unimplemented([&] { return sceHmdGetDistortionCorrectionCommand(0, distortion, commandBuffer); }), "distortion command answered handle 0 where its answer is not measured"); + Require(Unimplemented([&] { return sceHmdDistortionGetCorrectionCommand(0, distortion, commandBuffer); }), "aliased distortion command answered handle 0 where its answer is not measured"); + Require(Unimplemented([&] { return sceHmdGetWideNearDistortionCorrectionCommand(0, wideNear, commandBuffer); }), "wide-near distortion command answered handle 0 where its answer is not measured"); + Require(Unimplemented([&] { return sceHmdDistortionGetWideNearCorrectionCommand(0, wideNear, commandBuffer); }), "aliased wide-near distortion command answered handle 0 where its answer is not measured"); + Require(Unimplemented([&] { return sceHmdGet2dVrCommand(0, vr2d, commandBuffer); }), "2D VR command answered handle 0 where its answer is not measured"); + Require(Unimplemented([&] { return sceHmdDistortionGet2dVrCommand(0, vr2d, commandBuffer); }), "aliased 2D VR command answered handle 0 where its answer is not measured"); + } else { + Require(sceHmdGetDistortionCorrectionCommand(0, distortion, commandBuffer) == Hmd::NotInitialized, "distortion command before initialize"); + Require(sceHmdDistortionGetCorrectionCommand(0, distortion, commandBuffer) == Hmd::NotInitialized, "aliased distortion command before initialize"); + Require(sceHmdGetWideNearDistortionCorrectionCommand(0, wideNear, commandBuffer) == Hmd::NotInitialized, "wide-near distortion command before initialize"); + Require(sceHmdDistortionGetWideNearCorrectionCommand(0, wideNear, commandBuffer) == Hmd::NotInitialized, "aliased wide-near distortion command before initialize"); + Require(sceHmdGet2dVrCommand(0, vr2d, commandBuffer) == Hmd::NotInitialized, "2D VR command before initialize"); + Require(sceHmdDistortionGet2dVrCommand(0, vr2d, commandBuffer) == Hmd::NotInitialized, "aliased 2D VR command before initialize"); + } + for (const auto byte : commands) Require(byte == 0x5a, "a refused command query wrote its output"); +} + void CheckAssyError(std::int32_t expected) { unsigned char data[32]; std::memset(data, 0x5a, sizeof(data)); @@ -69,6 +166,7 @@ int main() { std::memset(&info, 0xa5, sizeof(info)); const auto untouched = info; + InstallProcessParameters(); CheckDisconnected(); CheckBufferQueries(); Require(sceHmdTerminate() == Hmd::NotInitialized, "terminate before initialize"); @@ -78,6 +176,10 @@ int main() { Require(sceHmdGetDeviceInformation(&info) == Hmd::NotInitialized, "query before initialize"); Require(std::memcmp(&info, &untouched, sizeof(info)) == 0, "failed query modified output"); CheckHandleQueries(Hmd::NotInitialized); + SetCompiledSdkVersion(LastSdkBeforeStartupState); + CheckCommandQueries(true); + SetCompiledSdkVersion(FirstSdkWithStartupState); + CheckCommandQueries(false); CheckAssyError(Hmd::NotInitialized); Require(sceHmdGetDeviceInformationByHandle(0, &info) == Hmd::NotInitialized, "handle query before initialize"); @@ -116,6 +218,7 @@ int main() { Require(std::memcmp(&info, &expected, sizeof(info)) == 0, "invalid handle modified output"); } CheckHandleQueries(Hmd::InvalidHandle); + CheckCommandQueries(false); CheckAssyError(Hmd::DeviceDisconnected); Require(sceHmdGetDeviceInformationByHandle(0, nullptr) == Hmd::InvalidHandle, "null query accepted invalid handle"); diff --git a/core/libs/tests/GuestHmd2Gaze.cpp b/core/libs/tests/GuestHmd2Gaze.cpp new file mode 100644 index 0000000000..e3cce92206 --- /dev/null +++ b/core/libs/tests/GuestHmd2Gaze.cpp @@ -0,0 +1,106 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +struct GazeParam { + std::uint32_t size; + std::int32_t handle; + std::uint64_t reserved; +}; + +extern "C" { +std::int32_t APS5_VABI sceHmd2Initialize(const void* param); +std::int32_t APS5_VABI sceHmd2GazeGetResult(const GazeParam* param, void* result); +std::int32_t APS5_VABI sceHmd2GazeGetResultForFoveatedRendering(const GazeParam* param, void* result); +} + +namespace { + +constexpr std::int32_t InvalidHandle = static_cast(0x8a720003); +constexpr std::int32_t ParameterNull = static_cast(0x8a720008); +constexpr std::int32_t ParameterInvalid = static_cast(0x8a720009); + +void Require(bool value, const char* what) { + if (value) return; + std::fprintf(stderr, "FAIL: %s\n", what); + std::abort(); +} + +template +T Read(const unsigned char* bytes, std::size_t offset) { + T value; + std::memcpy(&value, bytes + offset, sizeof(value)); + return value; +} + +bool NegativeZero(float value) { + return value == 0.0f && std::signbit(value); +} + +void CheckGazeResult() { + GazeParam param {sizeof(GazeParam), 0x0e010000, 0}; + unsigned char result[0x38]; + std::memset(result, 0x5a, sizeof(result)); + const std::uint32_t size = 0x38; + std::memcpy(result, &size, sizeof(size)); + + Require(sceHmd2GazeGetResult(nullptr, result) == ParameterNull, "null param"); + Require(sceHmd2GazeGetResult(¶m, nullptr) == ParameterNull, "null result"); + GazeParam wrongParam = param; + wrongParam.size = 0x18; + Require(sceHmd2GazeGetResult(&wrongParam, result) == ParameterInvalid, "param size"); + const std::uint32_t wrongSize = 0x18; + std::memcpy(result, &wrongSize, sizeof(wrongSize)); + Require(sceHmd2GazeGetResult(¶m, result) == ParameterInvalid, "result size"); + Require(Read(result, 4) == 0x5a5a5a5a, "result untouched after a size error"); + + std::memcpy(result, &size, sizeof(size)); + Require(sceHmd2GazeGetResult(¶m, result) == InvalidHandle, "unopened handle"); + Require(Read(result, 0) == 0x38, "size kept"); + Require(Read(result, 4) == -1, "+4 is -1"); + for (const std::size_t offset : {0x8u, 0xcu, 0x10u, 0x20u, 0x24u, 0x28u, 0x2cu, 0x30u, 0x34u}) + Require(Read(result, offset) == 0, "cleared word"); + Require(NegativeZero(Read(result, 0x14)), "+0x14 is -0.0"); + Require(Read(result, 0x18) == 0, "+0x18 is 0.0"); + Require(Read(result, 0x1c) == -1.0f, "+0x1c is -1.0"); + + GazeParam zero {sizeof(GazeParam), 0, 0}; + Require(sceHmd2GazeGetResult(&zero, result) == InvalidHandle, "handle 0"); +} + +void CheckFoveatedResult() { + GazeParam param {sizeof(GazeParam), 0x0e010000, 0}; + unsigned char result[0x18]; + std::memset(result, 0x5a, sizeof(result)); + const std::uint32_t size = 0x18; + std::memcpy(result, &size, sizeof(size)); + + Require(sceHmd2GazeGetResultForFoveatedRendering(nullptr, result) == ParameterNull, "foveated null param"); + Require(sceHmd2GazeGetResultForFoveatedRendering(¶m, nullptr) == ParameterNull, "foveated null result"); + unsigned char large[0x38] = {}; + const std::uint32_t largeSize = 0x38; + std::memcpy(large, &largeSize, sizeof(largeSize)); + Require(sceHmd2GazeGetResultForFoveatedRendering(¶m, large) == ParameterInvalid, "foveated result size"); + + Require(sceHmd2GazeGetResultForFoveatedRendering(¶m, result) == InvalidHandle, "foveated unopened handle"); + Require(Read(result, 0) == 0x18, "foveated size kept"); + Require(Read(result, 4) == -1, "foveated +4 is -1"); + Require(NegativeZero(Read(result, 8)), "foveated +8 is -0.0"); + Require(Read(result, 0xc) == 0, "foveated +0xc is 0.0"); + Require(Read(result, 0x10) == -1.0f, "foveated +0x10 is -1.0"); + Require(Read(result, 0x14) == 0x5a5a5a5a, "foveated +0x14 untouched"); +} + +} + +int main() { + Require(sceHmd2Initialize(nullptr) != 0, "initialize reports no headset"); + CheckGazeResult(); + CheckFoveatedResult(); + std::puts("guest Hmd2 gaze results without a headset: PASS"); + return 0; +} diff --git a/core/libs/tests/GuestHttp.cpp b/core/libs/tests/GuestHttp.cpp index b70b78bba2..9ecb47dd94 100644 --- a/core/libs/tests/GuestHttp.cpp +++ b/core/libs/tests/GuestHttp.cpp @@ -63,15 +63,174 @@ static void CheckUriParseCharacters() { Require(Equal(element.scheme, "aZ09+.-") && element.port == 0); for (unsigned value = 128; value <= 255; ++value) { const std::string byte(1, static_cast(value)); - const std::string urls[] = { - "http://" + byte + "/", "http://[" + byte + "]/", byte + "://host/", - "a" + byte + "://host/", "http://host:" + byte + "/" - }; - for (const auto& url : urls) { + for (const auto& url : {"http://[" + byte + "]/", "http://host:" + byte + "/"}) { std::size_t required = 0; Require(sceHttpUriParse(nullptr, url.c_str(), nullptr, &required, 0) == static_cast(0x80433060)); Require(sceHttpUriParse(&element, url.c_str(), pool, nullptr, sizeof(pool)) == static_cast(0x80433060)); } + Require(sceHttpUriParse(&element, ("http://" + byte + "/").c_str(), pool, nullptr, sizeof(pool)) == 0); + Require(Equal(element.hostname, "") && element.path == byte + "/"); + Require(sceHttpUriParse(&element, (byte + "://host/").c_str(), pool, nullptr, sizeof(pool)) == 0); + Require(element.opaque && Equal(element.scheme, "") && Equal(element.hostname, "") && element.path == byte + "://host/"); + Require(sceHttpUriParse(&element, ("a" + byte + "://host/").c_str(), pool, nullptr, sizeof(pool)) == 0); + Require(Equal(element.scheme, "") && Equal(element.hostname, "a") && element.path == byte + "://host/"); + } +} + +struct UriParseCase { + const char* url; + int opaque; + std::uint16_t port; + const char* scheme; + const char* username; + const char* password; + const char* hostname; + const char* path; + const char* query; + const char* fragment; + std::size_t pathSpace = 0; +}; + +static void CheckUriParseAsMeasured() { + constexpr UriParseCase cases[] = { + {"http://bad host/", 0, 80, "http", "", "", "bad", " host/", "", ""}, + {"http://ex%41mple.com/", 0, 80, "http", "", "", "ex", "%41mple.com/", "", ""}, + {"http://h\t/", 0, 80, "http", "", "", "h", "\t/", "", ""}, + {"not a url", 1, 0, "", "", "", "not", " a url", "", ""}, + {"example.com/a", 1, 0, "", "", "", "example.com", "/a", "", ""}, + {"mailto:x", 1, 0, "mailto", "", "", "x", "", "", ""}, + {"abc:def", 1, 0, "abc", "", "", "def", "", "", ""}, + {"http:", 1, 80, "http", "", "", "", "", "", ""}, + {"https:h", 1, 443, "https", "", "", "h", "", "", ""}, + {"//host/x", 0, 0, "", "", "", "host", "/x", "", ""}, + {"", 1, 0, "", "", "", "", "", "", ""}, + {"?q", 1, 0, "", "", "", "", "", "?q", ""}, + {"http://host:65536/", 0, 0, "http", "", "", "host", "/", "", ""}, + {"http://h:65535", 0, 65535, "http", "", "", "h", "", "", ""}, + {"http://h:0080/", 0, 80, "http", "", "", "h", "/", "", ""}, + {"http://h:00000/", 0, 0, "http", "", "", "h", "/", "", ""}, + {"http://host:/", 0, 80, "http", "", "", "host", "/", "", ""}, + {"http://[::1]x/", 0, 80, "http", "", "", "::1", "x/", "", ""}, + {"http://[::1]]/", 0, 80, "http", "", "", "::1", "]/", "", ""}, + {"http://[::1]:/", 0, 80, "http", "", "", "::1", "/", "", ""}, + {"http://[]/", 0, 80, "http", "", "", "", "/", "", ""}, + {"http://[.]/", 0, 80, "http", "", "", ".", "/", "", ""}, + {"http://u@h@i/", 0, 80, "http", "u", "", "h", "@i/", "", ""}, + {"http://u s@h/", 0, 80, "http", "", "", "u", " s@h/", "", ""}, + {"http://u:p:q@h/", 0, 80, "http", "u", "p:q", "h", "/", "", ""}, + {"http://u%41@h/", 0, 80, "http", "u%41", "", "h", "/", "", ""}, + {"http://h@/", 0, 80, "http", "h", "", "", "/", "", ""}, + {"http://[::1]@h/", 0, 80, "http", "", "", "::1", "@h/", "", ""}, + {"http://u@[::1]:8/", 0, 8, "http", "u", "", "::1", "/", "", ""}, + {".@h/", 1, 0, "", ".", "", "h", "/", "", ""}, + {"http://.a/", 0, 80, "http", "", "", "", ".a/", "", ""}, + {"http://-a/", 0, 80, "http", "", "", "-a", "/", "", ""}, + {"http://a..b/", 0, 80, "http", "", "", "a..b", "/", "", ""}, + {"a.", 1, 0, "", "", "", "a.", "", "", ""}, + {"http://h/a/..", 0, 80, "http", "", "", "h", "/a/..", "", ""}, + {"http://h/a/.", 0, 80, "http", "", "", "h", "/a/.", "", ""}, + {"http://h/a/b/../../..", 0, 80, "http", "", "", "h", "/..", "", "", 13}, + {"http://h/../../a", 0, 80, "http", "", "", "h", "/a", "", "", 8}, + {"http://h/a//b/../c", 0, 80, "http", "", "", "h", "/a//c", "", "", 10}, + {"http://h/a/.../../b", 0, 80, "http", "", "", "h", "/a/b", "", "", 11}, + {"http://h/./a/../b/", 0, 80, "http", "", "", "h", "/b/", "", "", 10}, + {"./a", 1, 0, "", "", "", "", "./a", "", ""}, + {"a/../b", 1, 0, "", "", "", "a", "/b", "", "", 5}, + {"1x://h/", 1, 0, "", "", "", "1x", "//h/", "", ""}, + {"-x://h/", 0, 0, "-x", "", "", "h", "/", "", ""}, + {"s_x://h/p", 1, 0, "", "", "", "s_x", "//h/p", "", ""}, + {"s:x://h/p", 1, 0, "s", "", "", "x", "//h/p", "", ""}, + {"s@x://h/p", 1, 0, "", "s", "", "x", "//h/p", "", ""}, + {"httpx://h/", 0, 80, "httpx", "", "", "h", "/", "", ""}, + {"httpsx://h/", 0, 443, "httpsx", "", "", "h", "/", "", ""}, + {"HTTPx://h/", 0, 80, "HTTPx", "", "", "h", "/", "", ""}, + {"htt://h/", 0, 0, "htt", "", "", "h", "/", "", ""}, + {"ws://h/", 0, 0, "ws", "", "", "h", "/", "", ""}, + {"https://h:80/", 0, 80, "https", "", "", "h", "/", "", ""}, + {"http://h/p?q#f#g", 0, 80, "http", "", "", "h", "/p", "?q", "#f#g"}, + {"http://h/p#f?q", 0, 80, "http", "", "", "h", "/p", "", "#f?q"}, + {"http://h?", 0, 80, "http", "", "", "h", "", "?", ""}, + }; + char pool[256]; + for (const auto& row : cases) { + SceHttpUriElement element{}; + std::size_t required = 0; + Require(sceHttpUriParse(nullptr, row.url, nullptr, &required, 0) == 0); + const std::size_t size = std::strlen(row.scheme) + std::strlen(row.username) + std::strlen(row.password) + std::strlen(row.hostname) + + (row.pathSpace != 0 ? row.pathSpace : std::strlen(row.path)) + std::strlen(row.query) + std::strlen(row.fragment) + 7; + Require(required == size); + required = 77; + Require(sceHttpUriParse(&element, row.url, pool, &required, size - 1) == static_cast(0x80431022) && required == 77); + Require(sceHttpUriParse(&element, row.url, pool, &required, sizeof(pool)) == 0 && required == size); + Require(element.opaque == row.opaque && element.port == row.port); + Require(Equal(element.scheme, row.scheme) && Equal(element.username, row.username) && Equal(element.password, row.password)); + Require(Equal(element.hostname, row.hostname) && Equal(element.path, row.path)); + Require(Equal(element.query, row.query) && Equal(element.fragment, row.fragment)); + } + for (const char* url : {"http://[::1/", "http://[a b]/", "http://[a%b]/", "http://[a@b]/", "http://host:80a/", "http://h:65537/", "http://h:99999/", + "http://h:000080/", "http://h:80?q", "http://h:80#f", "http://h:80 /", "http://h: 80/", "http://h:+80/", "http://h:-1/", + "http://h:80:81/", "http://[::1]:8a/", "http://a:b/p"}) { + std::size_t required = 77; + Require(sceHttpUriParse(nullptr, url, nullptr, &required, 0) == static_cast(0x80433060) && required == 77); + } +} + +struct UriMergeCase { + const char* base; + const char* relative; + std::size_t sizeOnly; + const char* merged; +}; + +static void CheckUriMergeAsMeasured() { + constexpr UriMergeCase cases[] = { + {"http://user:pw@example.com:8080/a/b/c?q=1#f", "?x", 0x84, "http://user:pw@example.com:8080/a/b/?x"}, + {"http://user:pw@example.com:8080/a/b/c?q=1#f", "#y", 0x84, "http://user:pw@example.com:8080/a/b/#y"}, + {"http://user:pw@example.com:8080/a/b/c?q=1#f", "../../../../d", 0x9a, "http://user:pw@example.com:8080/a/b/../../../../d"}, + {"http://user:pw@example.com:8080/a/b/c?q=1#f", "http:d", 0x8c, "http://user:pw@example.com:8080/a/b/http:d"}, + {"http://user:pw@example.com:8080/a/b/c?q=1#f", "//other.com/x", 0x9a, "//other.com/x"}, + {"http://example.com", "../d", 0x44, "http://example.com/../d"}, + {"http://example.com/a/./b/../c", "d", 0x5f, "http://example.com/a/d"}, + {"not a url", "d", 0x26, "not a url/d"}, + {"example.com/a", "b", 0x32, "example.com/b"}, + {"http://[::1]:80/a/b", "c", 0x3c, "http://::1/a/c"}, + {"http://ex ample.com/a", "c", 0x47, "http://ex ample.com/c"}, + {"http://example.com/a", "//bad host/x", 0x5a, "//bad host/x"}, + {"//host/a/b", "c", 0x27, "//host/a/c"}, + {"/a/b", "c", 0x17, "/a/c"}, + {"a/b", "c", 0x14, "a/c"}, + {"", "c", 0xb, "/c"}, + {"", "", 0x9, "/"}, + {"mailto:x@y", "c", 0x27, "mailto:x@y/c"}, + {"http://h", "?q", 0x22, "http://h/?q"}, + {"https://h:443/a", "c", 0x31, "https://h/c"}, + {"http://h:0/a", "c", 0x2a, "http://h/c"}, + {"http://h:65536/a", "c", 0x32, "http://h/c"}, + {"http://:p@h/a", "c", 0x2d, "http://:p@h/c"}, + {"http://bad host/", "./x", 0x3c, "http://bad host/./x"}, + {"http://h/a", "http://bad host/", 0x44, "http://bad host/"}, + {"http://h/a", "HTTPS://Other.com/", 0x48, "HTTPS://Other.com/"}, + {"http://h/a", "//o", 0x2a, "//o"}, + {"http://h/a", "x:y", 0x2a, "http://h/x:y"}, + {"http://h/a", "d e", 0x2a, "http://h/d e"}, + {"http://h/a", "..", 0x28, "http://h/.."}, + {"http://h/a/./b/../c", "../d", 0x47, "http://h/a/../d"}, + {"http://h/a?q#f", "", 0x30, "http://h/"}, + {"not a url", "//x/y", 0x2e, "//x/y"}, + }; + char merged[512]; + for (const auto& row : cases) { + std::size_t required = 0; + Require(sceHttpUriMerge(nullptr, row.base, row.relative, &required, 0, 0) == 0 && required == row.sizeOnly); + std::memset(merged, 'Z', sizeof(merged)); + required = 77; + Require(sceHttpUriMerge(merged, row.base, row.relative, &required, row.sizeOnly - 1, 0) == static_cast(0x80431022)); + Require(merged[0] == 'Z'); + Require(sceHttpUriMerge(merged, row.base, row.relative, &required, sizeof(merged), 0) == 0 && Equal(merged, row.merged)); + } + for (const char* base : {"http://[::1/a", "http://h:80a/a"}) { + std::size_t required = 77; + Require(sceHttpUriMerge(merged, base, "c", &required, sizeof(merged), 0) == static_cast(0x80433060) && required == 77); } } @@ -96,6 +255,163 @@ static void CheckUriEscapeBytes() { } } +struct StatusLineCase { + bool tildeFill; + const char* line; + std::size_t length; + int result; + std::int32_t major; + std::int32_t minor; + std::int32_t code; + long reasonOffset; + std::size_t phraseLength; +}; + +static void CheckStatusLineAsMeasured() { + constexpr int parseInvalidResponse = static_cast(0x80432060); + constexpr StatusLineCase cases[] = { + {false, "HTTP/1.1 200 OK\r\n", 17, 17, 1, 1, 200, 12, 0x3}, + {false, "HTTP/1.0 404 Not Found\n", 23, 23, 1, 0, 404, 12, 0xa}, + {false, "HTTP/1.1 200 OK", 15, parseInvalidResponse, 1, 1, 200, -1, 0xabcd}, + {false, "HTTP/1.1 200\r\n", 14, 14, 1, 1, 200, 12, 0x0}, + {false, "HTTP/1.1 200 \r\n", 15, 15, 1, 1, 200, 12, 0x1}, + {false, "HTTP/2 200 OK\r\n", 15, parseInvalidResponse, 2, 0, -7, -1, 0xabcd}, + {false, "HTTP/10.20 999 X\r\n", 18, 18, 10, 20, 999, 14, 0x2}, + {false, "HTTP/1.1 20 OK\r\n", 16, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {false, "HTTP/1.1 2000 OK\r\n", 18, 18, 1, 1, 200, 12, 0x4}, + {false, "HTTP/1.1 abc OK\r\n", 17, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {false, "http/1.1 200 OK\r\n", 17, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {false, "HTTP/1.1 200 OK\r\n", 18, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {false, "HTTP/1.1\t200 OK\r\n", 17, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {false, "HTTP/1.1 200OK\r\n", 16, 16, 1, 1, 200, 12, 0x2}, + {false, "ICY 200 OK\r\n", 12, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {false, "HTTP/1.1 200 OK\r", 16, parseInvalidResponse, 1, 1, 200, -1, 0xabcd}, + {false, "HTTP/1.1 200 OK\r\nX: y", 21, 17, 1, 1, 200, 12, 0x3}, + {false, "HTTP/1.1 200 OK\r\r\n", 18, 18, 1, 1, 200, 12, 0x4}, + {false, "HTTP/.1 200 OK\r\n", 16, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {false, "HTTP/1. 200 OK\r\n", 16, parseInvalidResponse, 1, 0, -7, -1, 0xabcd}, + {false, "HTTP/4294967297.1 200 OK\n", 25, 25, 1, 1, 200, 21, 0x3}, + {false, "HTTP/1.1 -12 OK\n", 16, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {false, " HTTP/1.1 200 OK\n", 17, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {false, "HTTP/1.1 200 A\nB\n", 17, 15, 1, 1, 200, 12, 0x2}, + {false, "HTTP/1.1 200\n", 13, 13, 1, 1, 200, 12, 0x0}, + {false, "HTTP/1.1 200 OK\n", 15, 16, 1, 1, 200, 12, 0x3}, + {false, "HTTP/1.1 200 OK\n", 12, parseInvalidResponse, 1, 1, 200, -1, 0xabcd}, + {false, "HTTP/1.1 200 OK\n", 11, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {false, "HTTP/1.1 200 OK\n", 8, 16, 1, 1, 200, 12, 0x3}, + {false, "HTTP/1.1 200 OK\n", 7, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {false, "HTTP/1.1 200 OK\n", 0, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 0, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 1, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 2, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 3, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 4, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 5, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 6, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 7, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 8, 16, 1, 1, 200, 12, 0x3}, + {true, "HTTP/1.1 200 OK\n", 9, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 10, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 11, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 12, parseInvalidResponse, 1, 1, 200, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 13, parseInvalidResponse, 1, 1, 200, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 14, parseInvalidResponse, 1, 1, 200, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK\n", 15, 16, 1, 1, 200, 12, 0x3}, + {true, "HTTP/1.1 200 OK\n", 16, 16, 1, 1, 200, 12, 0x3}, + {true, "HTTP/10.20 999 X\n", 0, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/10.20 999 X\n", 1, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/10.20 999 X\n", 2, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/10.20 999 X\n", 3, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/10.20 999 X\n", 4, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/10.20 999 X\n", 5, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/10.20 999 X\n", 6, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/10.20 999 X\n", 7, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/10.20 999 X\n", 8, 17, 10, 20, 999, 14, 0x2}, + {true, "HTTP/10.20 999 X\n", 9, 17, 10, 20, 999, 14, 0x2}, + {true, "HTTP/10.20 999 X\n", 10, 17, 10, 20, 999, 14, 0x2}, + {true, "HTTP/10.20 999 X\n", 11, parseInvalidResponse, 10, 20, -7, -1, 0xabcd}, + {true, "HTTP/10.20 999 X\n", 12, parseInvalidResponse, 10, 20, -7, -1, 0xabcd}, + {true, "HTTP/10.20 999 X\n", 13, parseInvalidResponse, 10, 20, -7, -1, 0xabcd}, + {true, "HTTP/10.20 999 X\n", 14, parseInvalidResponse, 10, 20, 999, -1, 0xabcd}, + {true, "HTTP/10.20 999 X\n", 15, parseInvalidResponse, 10, 20, 999, -1, 0xabcd}, + {true, "HTTP/10.20 999 X\n", 16, 17, 10, 20, 999, 14, 0x2}, + {true, "HTTP/10.20 999 X\n", 17, 17, 10, 20, 999, 14, 0x2}, + {true, "HTTP/1.12 200 OK\n", 0, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 1, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 2, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 3, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 4, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 5, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 6, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 7, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 8, 17, 1, 12, 200, 13, 0x3}, + {true, "HTTP/1.12 200 OK\n", 9, 17, 1, 12, 200, 13, 0x3}, + {true, "HTTP/1.12 200 OK\n", 10, parseInvalidResponse, 1, 12, -7, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 11, parseInvalidResponse, 1, 12, -7, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 12, parseInvalidResponse, 1, 12, -7, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 13, parseInvalidResponse, 1, 12, 200, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 14, parseInvalidResponse, 1, 12, 200, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 15, parseInvalidResponse, 1, 12, 200, -1, 0xabcd}, + {true, "HTTP/1.12 200 OK\n", 16, 17, 1, 12, 200, 13, 0x3}, + {true, "HTTP/1.12 200 OK\n", 17, 17, 1, 12, 200, 13, 0x3}, + {true, "HTTP/12.1 200 OK\n", 0, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 1, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 2, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 3, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 4, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 5, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 6, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 7, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 8, 17, 12, 1, 200, 13, 0x3}, + {true, "HTTP/12.1 200 OK\n", 9, 17, 12, 1, 200, 13, 0x3}, + {true, "HTTP/12.1 200 OK\n", 10, parseInvalidResponse, 12, 1, -7, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 11, parseInvalidResponse, 12, 1, -7, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 12, parseInvalidResponse, 12, 1, -7, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 13, parseInvalidResponse, 12, 1, 200, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 14, parseInvalidResponse, 12, 1, 200, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 15, parseInvalidResponse, 12, 1, 200, -1, 0xabcd}, + {true, "HTTP/12.1 200 OK\n", 16, 17, 12, 1, 200, 13, 0x3}, + {true, "HTTP/12.1 200 OK\n", 17, 17, 12, 1, 200, 13, 0x3}, + {true, "HTTP/1.1 200\n", 0, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200\n", 1, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200\n", 2, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200\n", 3, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200\n", 4, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200\n", 5, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200\n", 6, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200\n", 7, parseInvalidResponse, 0, 0, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200\n", 8, 13, 1, 1, 200, 12, 0x0}, + {true, "HTTP/1.1 200\n", 9, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200\n", 10, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200\n", 11, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200\n", 12, 13, 1, 1, 200, 12, 0x0}, + {true, "HTTP/1.1 200\n", 13, 13, 1, 1, 200, 12, 0x0}, + {true, "HTTP/1.1 200 OK", 15, parseInvalidResponse, 1, 1, 200, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK", 8, 255, 1, 1, 200, 12, 0xf2}, + {true, "HTTP/1.1 200 OK", 9, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK", 12, parseInvalidResponse, 1, 1, 200, -1, 0xabcd}, + {true, "HTTP/1.1 200 OK", 13, parseInvalidResponse, 1, 1, 200, -1, 0xabcd}, + {true, "HTTP/1.1 2", 8, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {true, "HTTP/1.1 20", 8, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + {true, "HTTP/1.1", 8, parseInvalidResponse, 1, 1, -7, -1, 0xabcd}, + }; + static char buffer[256]; + for (const auto& row : cases) { + std::memset(buffer, row.tildeFill ? '~' : '\0', sizeof(buffer)); + std::memcpy(buffer, row.line, std::strlen(row.line)); + if (row.tildeFill) buffer[sizeof(buffer) - 2] = '\n'; + buffer[sizeof(buffer) - 1] = '\0'; + std::int32_t major = -7; + std::int32_t minor = -7; + std::int32_t code = -7; + const char* reason = nullptr; + std::size_t phraseLength = 0xabcd; + Require(sceHttpParseStatusLine(buffer, row.length, &major, &minor, &code, &reason, &phraseLength) == row.result); + Require(major == row.major && minor == row.minor && code == row.code && phraseLength == row.phraseLength); + Require(row.reasonOffset < 0 ? reason == nullptr : reason == buffer + row.reasonOffset); + } +} + static int APS5_VABI AuthInfo(int, int, const char*, char*, char*, int, std::uint8_t**, std::uint64_t*, int*, void*) { std::abort(); } static void APS5_VABI RequestStatusCallback(int, int, void* argument) { ++*static_cast(argument); @@ -140,7 +456,7 @@ int main() { const char* url = "https://user:secret@example.com:8443/a/./b/../c?x=1&y=2#top"; std::size_t required = 0; Require(sceHttpUriParse(nullptr, url, nullptr, &required, 0) == 0); - Require(required == 6 + 5 + 7 + 12 + 5 + 9 + 5); + Require(required == 6 + 5 + 7 + 12 + 12 + 9 + 5); char pool[256]; SceHttpUriElement element; @@ -150,6 +466,7 @@ int main() { Require(Equal(element.scheme, "https") && Equal(element.username, "user") && Equal(element.password, "secret")); Require(Equal(element.hostname, "example.com") && element.port == 8443); Require(Equal(element.path, "/a/c") && Equal(element.query, "?x=1&y=2") && Equal(element.fragment, "#top")); + Require(element.path == pool + 30 && element.query == pool + 42 && element.fragment == pool + 51); char built[256]; Require(sceHttpUriBuild(nullptr, &required, 0, &element, buildAll) == 0); @@ -196,10 +513,11 @@ int main() { Require(sceHttpUriParse(&element, nullptr, pool, nullptr, sizeof(pool)) == invalidUrl); Require(sceHttpUriParse(nullptr, url, nullptr, nullptr, 0) == invalidValue); - Require(sceHttpUriParse(nullptr, "http://host:65536/", nullptr, &required, 0) == invalidUrl); + Require(sceHttpUriParse(nullptr, "http://host:65537/", nullptr, &required, 0) == invalidUrl); Require(sceHttpUriParse(nullptr, "http://host:80a/", nullptr, &required, 0) == invalidUrl); Require(sceHttpUriParse(nullptr, "http://[::1/", nullptr, &required, 0) == invalidUrl); - Require(sceHttpUriParse(nullptr, "http://bad host/", nullptr, &required, 0) == invalidUrl); + Require(sceHttpUriParse(nullptr, "http://host:80?q", nullptr, &required, 0) == invalidUrl); + CheckUriParseAsMeasured(); Require(sceHttpUriBuild(built, nullptr, sizeof(built), nullptr, buildAll) == invalidUrl); Require(sceHttpUriBuild(nullptr, nullptr, 0, &element, buildAll) == invalidValue); @@ -346,9 +664,10 @@ int main() { Require(sceHttpUriMerge(merged, base, nullptr, &required, sizeof(merged), 0) == invalidValue); Require(sceHttpUriMerge(merged, base, "./x", &required, sizeof(merged), 1) == invalidValue); Require(sceHttpUriMerge(nullptr, nullptr, nullptr, &required, 0, 1) == invalidValue); - Require(sceHttpUriMerge(merged, "http://bad host/", "./x", &required, sizeof(merged), 0) == invalidUrl); - Require(sceHttpUriMerge(merged, base, "http://bad host/", &required, sizeof(merged), 0) == invalidUrl); + Require(sceHttpUriMerge(merged, "http://[::1/", "./x", &required, sizeof(merged), 0) == invalidUrl); + Require(sceHttpUriMerge(merged, "http://h:80a/a", "c", &required, sizeof(merged), 0) == invalidUrl); Require(required == 123); + CheckUriMergeAsMeasured(); constexpr int beforeInit = static_cast(0x80431001); constexpr int aborted = static_cast(0x80431080); @@ -509,8 +828,9 @@ int main() { Require(parse("HTTP/1.1 2x0 OK\n", 16) == parseInvalidResponse); Require(parse("HTTP/1.1 20", 11) == parseInvalidResponse); Require(parse(response, 15) == parseInvalidResponse); - Require(parse(response, 16) == parseInvalidResponse); Require(phrase == nullptr && phraseLength == 0); + Require(parse(response, 16) == 17 && phrase == response + 12 && phraseLength == 3); + CheckStatusLineAsMeasured(); auto sweep = [](const char* src, const char* expected) { char swept[128]; std::memset(swept, 'x', sizeof(swept)); @@ -562,5 +882,89 @@ int main() { Require(sceHttpUriCopy(nullptr, &source, nullptr, 0, nullptr) == static_cast(0x804311FE)); SceHttpUriElement empty{}; Require(sceHttpUriCopy(©, &empty, pool, sizeof(pool), &needed) == 0 && needed == 5 && copy.scheme != nullptr && copy.scheme[0] == 0); + Require(copy.scheme == pool && copy.hostname == pool + 1 && copy.path == pool + 2 && copy.username == pool + 3 && copy.password == pool + 4); + + auto allBytes = [](const void* data, std::size_t size, unsigned char value) { + const auto* bytes = static_cast(data); + for (std::size_t i = 0; i < size; ++i) { + if (bytes[i] != value) return false; + } + return true; + }; + std::memset(pool, '~', sizeof(pool)); + std::memset(©, 0x5a, sizeof(copy)); + Require(sceHttpUriCopy(©, &source, pool, 64, &needed) == 0 && needed == 30); + Require(copy.scheme == pool && copy.hostname == pool + 5 && copy.path == pool + 17 && copy.username == pool + 22 && copy.password == pool + 27); + Require(allBytes(pool + 30, 34, 0) && pool[64] == '~' && copy.query == nullptr && copy.fragment == nullptr); + Require(allBytes(reinterpret_cast(©) + 4, 4, 0) && allBytes(copy.reserved, sizeof(copy.reserved), 0)); + + std::memset(pool, '~', sizeof(pool)); + std::memset(©, 0x5a, sizeof(copy)); + needed = 0xabcd; + Require(sceHttpUriCopy(©, &source, pool, 29, &needed) == static_cast(0x80431022) && needed == 0xabcd); + Require(allBytes(©, sizeof(copy), 0) && allBytes(pool, 29, 0) && pool[29] == '~'); + std::memset(pool, '~', sizeof(pool)); + Require(sceHttpUriCopy(©, &empty, pool, 0, &needed) == static_cast(0x80431022) && pool[0] == '~' && needed == 0xabcd); + Require(sceHttpUriCopy(©, &source, pool, 4, nullptr) == static_cast(0x80431022)); + + std::memset(pool, '~', sizeof(pool)); + Require(sceHttpUriCopy(nullptr, &source, pool, sizeof(pool), &needed) == 0 && needed == 30 && pool[0] == '~'); + Require(sceHttpUriCopy(nullptr, &source, pool, sizeof(pool), nullptr) == static_cast(0x804311FE)); + std::memset(©, 0x5a, sizeof(copy)); + Require(sceHttpUriCopy(©, &source, nullptr, 64, &needed) == 0 && needed == 30 && allBytes(©, sizeof(copy), 0x5a)); + Require(sceHttpUriCopy(©, &source, nullptr, 64, nullptr) == static_cast(0x804311FE) && allBytes(©, sizeof(copy), 0x5a)); + needed = 0xabcd; + Require(sceHttpUriCopy(©, nullptr, pool, sizeof(pool), &needed) == static_cast(0x80433060) && needed == 0xabcd && allBytes(©, sizeof(copy), 0x5a)); + Require(sceHttpUriCopy(nullptr, nullptr, nullptr, 0, nullptr) == static_cast(0x80433060)); + + char scheme[] = "SCH"; + char user[] = "usr"; + char pass[] = "pwd"; + char host[] = "hostname"; + char path[] = "/p/q"; + char query[] = "?query"; + char fragment[] = "#frag"; + char blank[] = ""; + SceHttpUriElement manual{}; + manual.opaque = 7; + manual.scheme = scheme; + manual.username = user; + manual.password = pass; + manual.hostname = host; + manual.path = path; + manual.query = query; + manual.fragment = fragment; + manual.port = 1234; + std::memset(manual.reserved, 0x11, sizeof(manual.reserved)); + Require(sceHttpUriCopy(©, &manual, pool, sizeof(pool), &needed) == 0 && needed == 26 && copy.opaque == 0 && copy.port == 1234); + Require(Equal(copy.scheme, "SCH") && copy.hostname == pool + 4 && Equal(copy.hostname, "hostname") && copy.path == pool + 13 && Equal(copy.path, "/p/q")); + Require(copy.username == pool + 18 && Equal(copy.username, "usr") && copy.password == pool + 22 && Equal(copy.password, "pwd")); + Require(copy.query == nullptr && copy.fragment == nullptr && allBytes(copy.reserved, sizeof(copy.reserved), 0)); + + SceHttpUriElement mixed{}; + mixed.hostname = host; + mixed.username = blank; + mixed.query = query; + mixed.port = 80; + Require(sceHttpUriCopy(©, &mixed, pool, sizeof(pool), &needed) == 0 && needed == 13 && copy.port == 80); + Require(copy.scheme == pool && copy.hostname == pool + 1 && copy.path == pool + 10 && copy.username == pool + 11 && copy.password == pool + 12); + + SceHttpUriElement inPlace = manual; + Require(sceHttpUriCopy(&inPlace, &inPlace, pool, sizeof(pool), &needed) == 0 && needed == 5 && inPlace.port == 0); + Require(inPlace.scheme == pool && inPlace.hostname == pool + 1 && inPlace.password == pool + 4 && inPlace.password[0] == 0); + inPlace = manual; + Require(sceHttpUriCopy(&inPlace, &inPlace, nullptr, 0, &needed) == 0 && needed == 26 && inPlace.scheme == scheme); + + alignas(SceHttpUriElement) char hostPool[256]; + auto* inside = reinterpret_cast(hostPool + 160); + std::memcpy(static_cast(inside), &manual, sizeof(manual)); + Require(sceHttpUriCopy(©, inside, hostPool, sizeof(hostPool), &needed) == 0 && needed == 5 && copy.port == 0 && copy.hostname[0] == 0); + + std::memcpy(hostPool + 200, "HOSTINPOOL", 11); + SceHttpUriElement pooled = manual; + pooled.hostname = hostPool + 200; + Require(sceHttpUriCopy(nullptr, &pooled, hostPool, sizeof(hostPool), &needed) == 0 && needed == 28); + Require(sceHttpUriCopy(©, &pooled, hostPool, sizeof(hostPool), &needed) == 0 && needed == 18 && copy.hostname == hostPool + 4 && copy.hostname[0] == 0); + Require(Equal(copy.path, "/p/q") && copy.path == hostPool + 5 && copy.port == 1234); } } diff --git a/core/libs/tests/GuestHttpOptions.cpp b/core/libs/tests/GuestHttpOptions.cpp index 461c44658f..5944662205 100644 --- a/core/libs/tests/GuestHttpOptions.cpp +++ b/core/libs/tests/GuestHttpOptions.cpp @@ -16,6 +16,8 @@ int APS5_VABI sceHttpCreateRequestWithURL(int, int, const char*, std::uint64_t); int APS5_VABI sceHttpDeleteRequest(int); int APS5_VABI sceHttpSetAutoRedirect(int, int); int APS5_VABI sceHttpGetAutoRedirect(int, int*); +int APS5_VABI sceHttpSetAuthEnabled(int, int); +int APS5_VABI sceHttpGetAuthEnabled(int, int*); int APS5_VABI sceHttpSetNonblock(int, int); int APS5_VABI sceHttpGetNonblock(int, int*); int APS5_VABI sceHttpTrySetNonblock(int, int); @@ -33,6 +35,8 @@ int APS5_VABI sceHttpsEnableOptionPrivate(int, std::uint32_t); int APS5_VABI sceHttpsDisableOptionPrivate(int, std::uint32_t); int APS5_VABI sceHttpAbortRequestForce(int); int APS5_VABI sceHttpSetProxy(int, int, int, const char*, std::uint16_t); +int APS5_VABI sceHttpAddRequestHeader(int, const char*, const char*, std::uint32_t); +int APS5_VABI sceHttpRemoveRequestHeader(int, const char*); } namespace { @@ -73,6 +77,23 @@ void CheckAutoRedirect(int context) { Require(sceHttpDeleteConnection(connection) == 0 && sceHttpDeleteTemplate(pattern) == 0, "delete failed"); } +void CheckAuthEnabled(int context) { + const int pattern = sceHttpCreateTemplate(context, "anyps5-test", 2, 0); + Require(Get(sceHttpGetAuthEnabled, pattern) == 0, "authentication is enabled by default"); + Require(sceHttpSetAuthEnabled(pattern, 1) == 0 && Get(sceHttpGetAuthEnabled, pattern) == 1, "sceHttpGetAuthEnabled did not return the set value"); + const int connection = sceHttpCreateConnectionWithURL(pattern, "http://example.com/", 1); + const int request = sceHttpCreateRequestWithURL(connection, 0, "http://example.com/a", 0); + Require(Get(sceHttpGetAuthEnabled, connection) == 1 && Get(sceHttpGetAuthEnabled, request) == 1, "a child did not inherit its parent's authentication option"); + Require(sceHttpSetAuthEnabled(request, 0) == 0 && Get(sceHttpGetAuthEnabled, request) == 0, "authentication could not be disabled on a request"); + Require(Get(sceHttpGetAuthEnabled, connection) == 1 && Get(sceHttpGetAuthEnabled, pattern) == 1, "setting a request changed its parent options"); + int value = -1; + Require(sceHttpGetAuthEnabled(request, nullptr) == InvalidValue, "a null authentication output was not refused"); + Require(sceHttpGetAuthEnabled(UnknownId, nullptr) == InvalidValue, "the authentication output pointer is not checked before the id"); + Require(sceHttpGetAuthEnabled(UnknownId, &value) == InvalidId && sceHttpSetAuthEnabled(UnknownId, 1) == InvalidId, "an unknown id was not refused for authentication options"); + Require(sceHttpDeleteRequest(request) == 0 && sceHttpGetAuthEnabled(request, &value) == InvalidId, "a deleted request still has authentication options"); + Require(sceHttpDeleteConnection(connection) == 0 && sceHttpDeleteTemplate(pattern) == 0, "delete failed"); +} + void CheckNonblock(int context) { const int pattern = sceHttpCreateTemplate(context, "anyps5-test", 2, 0); Require(Get(sceHttpGetNonblock, pattern) == 0 && Get(sceHttpTryGetNonblock, pattern) == 0, "a template is nonblocking by default"); @@ -145,6 +166,7 @@ void CheckTermEndsOptions() { int value = -1; HttpEpollHandle bound = nullptr; Require(sceHttpGetAutoRedirect(pattern, &value) == BeforeInit && sceHttpSetAutoRedirect(pattern, 0) == BeforeInit, "the redirect option outlived the last sceHttpTerm"); + Require(sceHttpGetAuthEnabled(pattern, &value) == BeforeInit && sceHttpSetAuthEnabled(pattern, 0) == BeforeInit, "the authentication option outlived the last sceHttpTerm"); Require(sceHttpGetNonblock(connection, &value) == BeforeInit && sceHttpTryGetNonblock(connection, &value) == BeforeInit, "the nonblocking getters work after the last sceHttpTerm"); Require(sceHttpSetNonblock(connection, 1) == BeforeInit && sceHttpTrySetNonblock(connection, 1) == BeforeInit, "the nonblocking setters work after the last sceHttpTerm"); Require(sceHttpGetAcceptEncodingGZIPEnabled(pattern, &value) == BeforeInit && sceHttpSetAcceptEncodingGZIPEnabled(pattern, 1) == BeforeInit, "the gzip option outlived the last sceHttpTerm"); @@ -163,6 +185,8 @@ void CheckUnobservedOptions(int context) { const int pattern = sceHttpCreateTemplate(context, "anyps5-test", 2, 0); const int connection = sceHttpCreateConnectionWithURL(pattern, "http://example.com/", 1); const int request = sceHttpCreateRequestWithURL(connection, 0, "http://example.com/a", 0); + Require(sceHttpAddRequestHeader(request, "X-Test", "value", 0) == 0 && sceHttpRemoveRequestHeader(request, "X-Test") == 0, "offline request header calls failed"); + Require(sceHttpRemoveRequestHeader(UnknownId, nullptr) == 0, "offline header removal started validating arguments"); Require(sceHttpCacheRedirectedConnectionEnabled(pattern, 0) == 0 && sceHttpCacheRedirectedConnectionEnabled(request, 1) == 0, "sceHttpCacheRedirectedConnectionEnabled refused a live object"); Require(sceHttpCacheRedirectedConnectionEnabled(UnknownId, 1) == InvalidId, "sceHttpCacheRedirectedConnectionEnabled took an unknown id"); for (const auto option : {sceHttpsEnableOptionPrivate, sceHttpsDisableOptionPrivate}) { @@ -188,6 +212,7 @@ int main() { const int context = sceHttpInit_nid_postfix(1, 1, 0x10000); Require(context > 0, "sceHttpInit failed"); CheckAutoRedirect(context); + CheckAuthEnabled(context); CheckNonblock(context); CheckAcceptEncodingGzip(context); CheckEpoll(context); diff --git a/core/libs/tests/GuestImeDialog.cpp b/core/libs/tests/GuestImeDialog.cpp index 574398c192..756a809fd7 100644 --- a/core/libs/tests/GuestImeDialog.cpp +++ b/core/libs/tests/GuestImeDialog.cpp @@ -14,6 +14,9 @@ int APS5_VABI sceImeDialogGetResult(Result*); int APS5_VABI sceImeDialogAbort(void); int APS5_VABI sceImeDialogTerm(void); int APS5_VABI sceImeDialogForceClose(void); +int APS5_VABI sceImeDialogGetCurrentStarState(std::int64_t); +int APS5_VABI sceImeDialogInitInternal(const Param*, const ExtendedParam*); +int APS5_VABI sceImeDialogSetPanelPosition(std::int32_t, std::int32_t); } namespace { @@ -25,6 +28,7 @@ constexpr std::uint32_t EndStatusOk = 0; constexpr std::uint32_t EndStatusUserCanceled = 1; constexpr std::uint32_t EndStatusAborted = 2; constexpr int ErrorBusy = static_cast(0x80bc0001u); +constexpr int ErrorSuspending = static_cast(0x80bc0009u); constexpr int ErrorNotOpened = static_cast(0x80bc0002u); constexpr int ErrorNotInUse = static_cast(0x80bc0107u); constexpr int ErrorInvalidMaxTextLength = static_cast(0x80bc0016u); @@ -186,4 +190,21 @@ int main() { Require(sceImeDialogInit(&noBuffer, nullptr) == ErrorInvalidInputTextBuffer); Require(sceImeDialogGetStatus() == StatusNone); Require(!ImeCapturesKeyboard_nid_postfix()); + + std::uint32_t star = 0xa5a5a5a5u; + const auto starAddress = static_cast(reinterpret_cast(&star)); + Require(sceImeDialogGetCurrentStarState(starAddress) == ErrorNotInUse && star == 0xa5a5a5a5u); + Require(sceImeDialogSetPanelPosition(640, 360) == ErrorNotInUse); + Require(sceImeDialogInitInternal(nullptr, nullptr) == ErrorInvalidAddress); + Require(sceImeDialogInitInternal(&empty, nullptr) == ErrorInvalidMaxTextLength); + Require(sceImeDialogGetStatus() == StatusNone); + Require(sceImeDialogInitInternal(¶m, nullptr) == 0); + Require(sceImeDialogGetStatus() == StatusRunning && ImeCapturesKeyboard_nid_postfix()); + Require(sceImeDialogInitInternal(¶m, nullptr) == ErrorBusy); + Require(sceImeDialogGetCurrentStarState(0) == ErrorInvalidAddress); + Require(sceImeDialogGetCurrentStarState(starAddress) == 0 && star == 0); + Require(sceImeDialogSetPanelPosition(640, 360) == ErrorSuspending); + Require(sceImeDialogAbort() == 0); + Require(sceImeDialogSetPanelPosition(0, 0) == ErrorSuspending); + Close(); } diff --git a/core/libs/tests/GuestJson.cpp b/core/libs/tests/GuestJson.cpp index 7c59af4225..b2951558b6 100644 --- a/core/libs/tests/GuestJson.cpp +++ b/core/libs/tests/GuestJson.cpp @@ -60,6 +60,8 @@ Iterator* APS5_VABI _ZNK3sce4Json5Array5beginEv(Iterator*, const Array*); Iterator* APS5_VABI _ZNK3sce4Json5Array3endEv(Iterator*, const Array*); std::size_t APS5_VABI _ZNK3sce4Json5Array4sizeEv(const Array*); const Value* APS5_VABI _ZNK3sce4Json5Array4backEv(const Array*); +void APS5_VABI _ZN3sce4Json5Array9pop_frontEv(Array*); +bool APS5_VABI _ZNK3sce4Json5Array5emptyEv(const Array*); void APS5_VABI _ZN3sce4Json5Array8iteratorD1Ev(Iterator*); Iterator* APS5_VABI _ZN3sce4Json5Array8iteratorppEv(Iterator*); Value* APS5_VABI _ZNK3sce4Json5Array8iteratordeEv(const Iterator*); @@ -360,6 +362,18 @@ static void ObjectsAndArrays() { Require(_ZNK3sce4Json5Array4sizeEv(©) == 2); Require(*_ZNK3sce4Json5Value10getBooleanEv(_ZNK3sce4Json5Array5frontEv(©))); Require(*_ZNK3sce4Json5Value7getRealEv(_ZNK3sce4Json5Array4backEv(©)) == 2.5); + _ZN3sce4Json5Array9pop_frontEv(©); + Require(_ZNK3sce4Json5Array4sizeEv(©) == 1); + Require(*_ZNK3sce4Json5Value7getRealEv(_ZNK3sce4Json5Array5frontEv(©)) == 2.5); + _ZN3sce4Json5Array9pop_frontEv(©); + Require(_ZNK3sce4Json5Array5emptyEv(©)); + threw = false; + try { + _ZN3sce4Json5Array9pop_frontEv(©); + } catch (const std::out_of_range&) { + threw = true; + } + Require(threw && _ZNK3sce4Json5Array5emptyEv(©)); _ZN3sce4Json5ArrayD1Ev(©); _ZN3sce4Json5ArrayD1Ev(&array); } diff --git a/core/libs/tests/GuestJxr.cpp b/core/libs/tests/GuestJxr.cpp new file mode 100644 index 0000000000..1bcda397b1 --- /dev/null +++ b/core/libs/tests/GuestJxr.cpp @@ -0,0 +1,78 @@ +#include "prx/libc/include/ApplicationHeap.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +extern "C" { +void* APS5_VABI malloc_nid_postfix(std::size_t size); +std::int64_t APS5_VABI PKFree_nid_postfix(void** memory); +} + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +void* lastFreed = nullptr; +int frees = 0; + +void* APS5_VABI Allocate(std::size_t size) { return std::malloc(size); } +void APS5_VABI Free(void* pointer) { + if (pointer != nullptr) { + lastFreed = pointer; + ++frees; + } + std::free(pointer); +} +void* APS5_VABI Reallocate(void* pointer, std::size_t size) { return std::realloc(pointer, size); } +void* APS5_VABI Calloc(std::size_t count, std::size_t size) { return std::calloc(count, size); } +void* APS5_VABI Align(std::size_t, std::size_t size) { return std::malloc(size); } +void* APS5_VABI Realign(void* pointer, std::size_t size, std::size_t) { return std::realloc(pointer, size); } +int APS5_VABI PosixAlign(void** pointer, std::size_t, std::size_t size) { + *pointer = std::malloc(size); + return *pointer ? 0 : 12; +} + +void InitializeHeap() { + alignas(16) static std::array process{}; + alignas(16) static std::array libc{}; + alignas(16) static std::array replacement{}; + const auto write = [](auto& data, std::size_t offset, auto value) { + std::memcpy(data.data() + offset, &value, sizeof(value)); + }; + write(process, 0, std::uint64_t{0x40}); + write(process, 8, std::uint32_t{0x4942524f}); + write(process, 0x38, libc.data()); + write(libc, 0, std::uint64_t{0x38}); + write(libc, 0x30, replacement.data()); + write(replacement, 0, std::uint64_t{0x78}); + write(replacement, 8, std::uint64_t{2}); + write(replacement, 0x20, reinterpret_cast(&Allocate)); + write(replacement, 0x28, reinterpret_cast(&Free)); + write(replacement, 0x30, reinterpret_cast(&Calloc)); + write(replacement, 0x38, reinterpret_cast(&Reallocate)); + write(replacement, 0x40, reinterpret_cast(&Align)); + write(replacement, 0x48, reinterpret_cast(&Realign)); + write(replacement, 0x50, reinterpret_cast(&PosixAlign)); + ApplicationHeapInitialize_nid_no_patch(process.data()); +} + +} + +int main() { + InitializeHeap(); + void* memory = malloc_nid_postfix(64); + Require(memory != nullptr); + void* const allocated = memory; + Require(PKFree_nid_postfix(&memory) == 0); + Require(memory == nullptr); + Require(frees == 1 && lastFreed == allocated); + Require(PKFree_nid_postfix(&memory) == 0); + Require(memory == nullptr); + Require(frees == 1); + Require(PKFree_nid_postfix(nullptr) == 0); + Require(frees == 1); + return 0; +} diff --git a/core/libs/tests/GuestKernelSys.cpp b/core/libs/tests/GuestKernelSys.cpp index 54fc6d05ad..b481f508fe 100644 --- a/core/libs/tests/GuestKernelSys.cpp +++ b/core/libs/tests/GuestKernelSys.cpp @@ -30,6 +30,8 @@ extern "C" { int APS5_VABI fstatfs_nid_postfix(int, void*); int APS5_VABI _fstatfs_nid_postfix(int, void*); +int APS5_VABI fstat_nid_postfix(int, FileStat*); +std::int64_t APS5_VABI _fstat_nid_postfix(int, FileStat*); int APS5_VABI open_nid_postfix(const char*, int, int); int APS5_VABI close_nid_postfix(int); int APS5_VABI pipe_nid_postfix(int*); @@ -59,6 +61,7 @@ std::int64_t APS5_VABI readlinkat_nid_postfix(int, const char*, char*, std::size int APS5_VABI mkdirat_nid_postfix(int, const char*, std::uint16_t); int APS5_VABI renameat_nid_postfix(int, const char*, int, const char*); int APS5_VABI fchmodat_nid_postfix(int, const char*, int, int); +int APS5_VABI lchmod_nid_postfix(const char*, std::uint16_t); int APS5_VABI fchown_nid_postfix(int, std::uint32_t, std::uint32_t); int APS5_VABI fstatat_nid_postfix(int, const char*, FileStat*, int); std::int64_t APS5_VABI sceKernelRead(int, void*, std::size_t); @@ -285,8 +288,15 @@ int main() { Require(std::memcmp(viaPublic, viaSyscall, 4) == 0 && std::memcmp(viaPublic + 16, viaSyscall + 16, 8) == 0); Require(std::memcmp(viaPublic + 280, viaSyscall + 280, 16) == 0); Require(fstatfs_nid_postfix(descriptor, nullptr) == -1 && Error() == 14); + FileStat viaFstat{}; + FileStat viaUnderscore{}; + Require(fstat_nid_postfix(descriptor, &viaFstat) == 0 && _fstat_nid_postfix(descriptor, &viaUnderscore) == 0); + Require(std::memcmp(&viaFstat, &viaUnderscore, sizeof(FileStat)) == 0 && viaFstat.st_size == 1); + Require(fstat_nid_postfix(descriptor, nullptr) == -1 && Error() == 14); Require(close_nid_postfix(descriptor) == 0); Require(fstatfs_nid_postfix(-1, viaPublic) == -1 && Error() == 9); + Require(fstat_nid_postfix(descriptor, &viaFstat) == -1 && Error() == 9); + Require(fstat_nid_postfix(-1, &viaFstat) == -1 && Error() == 9); char buffer[64]; for (const auto& component : {std::string("missing"), std::string("file.txt")}) { const auto prefix = root.generic_string() + "/" + component + "/../"; @@ -724,6 +734,38 @@ int main() { Require(symlink_nid_postfix("loop", (root / "loop").string().c_str()) == 0); Require(fchmodat_nid_postfix(base, "loop/x", 0600, 0) == -1 && Error() == 62); Require(fchmodat_nid_postfix(base, longName.c_str(), 0600, 0) == -1 && Error() == 63); +#endif + const auto modeFile = (root / "file.txt").string(); +#ifndef _WIN32 + const auto ownerOnly = std::filesystem::perms::owner_read | std::filesystem::perms::owner_write; + const auto readable = ownerOnly | std::filesystem::perms::group_read | std::filesystem::perms::others_read; +#endif + Require(lchmod_nid_postfix(modeFile.c_str(), 0600) == 0); +#ifndef _WIN32 + Require((std::filesystem::status(modeFile).permissions() & std::filesystem::perms::all) == ownerOnly); +#endif + Require(lchmod_nid_postfix(guestFile.c_str(), 0100644) == 0); +#ifndef _WIN32 + Require((std::filesystem::status(modeFile).permissions() & std::filesystem::perms::all) == readable); +#endif + Require(lchmod_nid_postfix((root / "missing.bin").string().c_str(), 0600) == -1 && Error() == 2); + Require(lchmod_nid_postfix(nullptr, 0600) == -1 && Error() == 14); + Require(lchmod_nid_postfix("", 0600) == -1 && Error() == 2); +#ifndef _WIN32 + Require(lchmod_nid_postfix((root / "loop" / "x").string().c_str(), 0600) == -1 && Error() == 62); + Require(lchmod_nid_postfix((root / longName).string().c_str(), 0600) == -1 && Error() == 63); + const auto modeLink = (root / "mode-link.txt").string(); + Require(symlink_nid_postfix("file.txt", modeLink.c_str()) == 0); +#ifdef __APPLE__ + Require(lchmod_nid_postfix(modeLink.c_str(), 0600) == 0); + Require((std::filesystem::symlink_status(modeLink).permissions() & std::filesystem::perms::all) == ownerOnly); +#else + bool lchmodThrew = false; + try { lchmod_nid_postfix(modeLink.c_str(), 0600); } catch (const std::exception&) { lchmodThrew = true; } + Require(lchmodThrew); +#endif + Require((std::filesystem::status(modeFile).permissions() & std::filesystem::perms::all) == readable); + Require(RemoveSymlink(modeLink)); #endif Require(fchown_nid_postfix(base, 0, 0) == 0); Require(fchown_nid_postfix(base, 0xFFFFFFFFu, 0xFFFFFFFFu) == 0); diff --git a/core/libs/tests/GuestKernelSysSdkVersion.cpp b/core/libs/tests/GuestKernelSysSdkVersion.cpp new file mode 100644 index 0000000000..5579fe7e5e --- /dev/null +++ b/core/libs/tests/GuestKernelSysSdkVersion.cpp @@ -0,0 +1,45 @@ +#include "ProcessParametersFixture.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include +#include +#include +#include +#include + +extern "C" int APS5_VABI sceKernelGetCompiledSdkVersion(std::int32_t* version); + +namespace { + +alignas(16) std::array process{}; + +void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "KERNEL SYS SDK VERSION: %s\n", message); + std::abort(); + } +} + +} + +int main() { + const std::uint64_t size = process.size(); + const std::uint32_t magic = 0x4942524f; + const std::uint32_t ps4SdkVersion = 0x07008001; + const std::uint32_t ps5SdkVersion = 0x0a008001; + std::memcpy(process.data(), &size, sizeof(size)); + std::memcpy(process.data() + 8, &magic, sizeof(magic)); + std::memcpy(process.data() + 0x10, &ps4SdkVersion, sizeof(ps4SdkVersion)); + std::memcpy(process.data() + 0x14, &ps5SdkVersion, sizeof(ps5SdkVersion)); + Require(ProcessParametersFixture::Install(process.data(), process.size()), "process parameters were not installed"); + + Require(sceKernelGetCompiledSdkVersion(nullptr) == static_cast(0x80020016), "null output is not EINVAL"); + std::array output{-1, 0x5a5a5a5a}; + Require(sceKernelGetCompiledSdkVersion(output.data()) == 0, "query failed"); + Require(output[0] == 0x07008001, "version is not the 32-bit field at offset 0x10 of the process parameters"); + Require(output[1] == 0x5a5a5a5a, "the query wrote past the version"); + + std::puts("KERNEL SYS SDK VERSION tests passed"); + return 0; +} diff --git a/core/libs/tests/GuestLibreSsl3Digest.cpp b/core/libs/tests/GuestLibreSsl3Digest.cpp new file mode 100644 index 0000000000..19f6f8cb2b --- /dev/null +++ b/core/libs/tests/GuestLibreSsl3Digest.cpp @@ -0,0 +1,54 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +unsigned char* APS5_VABI MD5_nid_postfix(const unsigned char*, std::size_t, unsigned char*); +unsigned char* APS5_VABI SHA1_nid_postfix(const unsigned char*, std::size_t, unsigned char*); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL3 digest check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static std::string Hex(const unsigned char* data, std::size_t size) { + static constexpr char digits[] = "0123456789abcdef"; + std::string text; + for (std::size_t i = 0; i < size; ++i) { + text += digits[data[i] >> 4]; + text += digits[data[i] & 15]; + } + return text; +} + +int main() { + const auto* abc = reinterpret_cast("abc"); + const std::string million(1000000, 'a'); + const auto* many = reinterpret_cast(million.data()); + unsigned char digest[24]; + std::memset(digest, 0xee, sizeof(digest)); + Require(MD5_nid_postfix(abc, 3, digest) == digest && Hex(digest, 16) == "900150983cd24fb0d6963f7d28e17f72" && digest[16] == 0xee); + Require(MD5_nid_postfix(nullptr, 0, digest) == digest && Hex(digest, 16) == "d41d8cd98f00b204e9800998ecf8427e"); + Require(MD5_nid_postfix(many, million.size(), digest) == digest && Hex(digest, 16) == "7707d6ae4e027c70eea2a935c2296f21"); + std::memset(digest, 0xee, sizeof(digest)); + Require(SHA1_nid_postfix(abc, 3, digest) == digest && Hex(digest, 20) == "a9993e364706816aba3e25717850c26c9cd0d89d" && digest[20] == 0xee); + Require(SHA1_nid_postfix(nullptr, 0, digest) == digest && Hex(digest, 20) == "da39a3ee5e6b4b0d3255bfef95601890afd80709"); + Require(SHA1_nid_postfix(many, million.size(), digest) == digest && Hex(digest, 20) == "34aa973cd4c4daa4f61eeb2bdbad27316534016f"); + RequireThrows([&] { MD5_nid_postfix(abc, 3, nullptr); }); + RequireThrows([&] { SHA1_nid_postfix(abc, 3, nullptr); }); + RequireThrows([&] { SHA1_nid_postfix(nullptr, 1, digest); }); + + std::puts("libSceLibreSsl3 digest tests passed"); +} diff --git a/core/libs/tests/GuestLibreSslDigest.cpp b/core/libs/tests/GuestLibreSslDigest.cpp new file mode 100644 index 0000000000..8b9ba6183b --- /dev/null +++ b/core/libs/tests/GuestLibreSslDigest.cpp @@ -0,0 +1,195 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +int APS5_VABI MD4_Init_nid_postfix(void*); +int APS5_VABI MD4_Update_nid_postfix(void*, const void*, std::size_t); +int APS5_VABI MD4_Final_nid_postfix(unsigned char*, void*); +int APS5_VABI MD5_Init_nid_postfix(void*); +int APS5_VABI MD5_Update_nid_postfix(void*, const void*, std::size_t); +int APS5_VABI MD5_Final_nid_postfix(unsigned char*, void*); +int APS5_VABI SHA1_Init_nid_postfix(void*); +int APS5_VABI SHA1_Update_nid_postfix(void*, const void*, std::size_t); +int APS5_VABI SHA1_Final_nid_postfix(unsigned char*, void*); +int APS5_VABI SHA224_Init_nid_postfix(void*); +int APS5_VABI SHA224_Update_nid_postfix(void*, const void*, std::size_t); +int APS5_VABI SHA224_Final_nid_postfix(unsigned char*, void*); +int APS5_VABI SHA256_Init_nid_postfix(void*); +int APS5_VABI SHA256_Update_nid_postfix(void*, const void*, std::size_t); +int APS5_VABI SHA256_Final_nid_postfix(unsigned char*, void*); +unsigned char* APS5_VABI SHA256_nid_postfix(const unsigned char*, std::size_t, unsigned char*); +int APS5_VABI SHA384_Init_nid_postfix(void*); +int APS5_VABI SHA384_Update_nid_postfix(void*, const void*, std::size_t); +int APS5_VABI SHA384_Final_nid_postfix(unsigned char*, void*); +int APS5_VABI SHA512_Init_nid_postfix(void*); +int APS5_VABI SHA512_Update_nid_postfix(void*, const void*, std::size_t); +int APS5_VABI SHA512_Final_nid_postfix(unsigned char*, void*); +} + +using InitFunction = int (APS5_VABI*)(void*); +using UpdateFunction = int (APS5_VABI*)(void*, const void*, std::size_t); +using FinalFunction = int (APS5_VABI*)(unsigned char*, void*); + +struct Algorithm { + const char* name; + InitFunction init; + UpdateFunction update; + FinalFunction final; + std::size_t contextSize; + std::size_t blockSize; + std::size_t digestSize; + const char* expected[6]; +}; + +static void Require(bool value, const char* what) { + if (value) return; + std::fprintf(stderr, "failed: %s\n", what); + std::abort(); +} + +template static void RequireThrows(TAction action, const char* what) { + try { action(); } catch (const std::exception&) { return; } + Require(false, what); +} + +static std::string Hex(const unsigned char* data, std::size_t size) { + static constexpr char digits[] = "0123456789abcdef"; + std::string text; + for (std::size_t i = 0; i < size; ++i) { + text += digits[data[i] >> 4]; + text += digits[data[i] & 15]; + } + return text; +} + +static std::string Digest(const Algorithm& algorithm, const std::string& message, std::size_t chunk) { + alignas(16) unsigned char context[256] {}; + unsigned char digest[64] {}; + Require(algorithm.init(context) == 1, "init returns 1"); + for (std::size_t offset = 0; offset < message.size(); offset += chunk) { + const auto length = std::min(chunk, message.size() - offset); + Require(algorithm.update(context, message.data() + offset, length) == 1, "update returns 1"); + } + Require(algorithm.final(digest, context) == 1, "final returns 1"); + return Hex(digest, algorithm.digestSize); +} + +int main() { + const std::string messages[6] { + "", + "abc", + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", + "12345678901234567890123456789012345678901234567890123456789012345678901234567890", + std::string(1000000, 'a'), + }; + const Algorithm algorithms[] { + {"MD4", MD4_Init_nid_postfix, MD4_Update_nid_postfix, MD4_Final_nid_postfix, 92, 64, 16, { + "31d6cfe0d16ae931b73c59d7e0c089c0", "a448017aaf21d8525fc10ae87aa6729d", "4691a9ec81b1a6bd1ab8557240b245c5", + nullptr, "e33b4ddc9c38f2199c3e7b164fcc0536", nullptr}}, + {"MD5", MD5_Init_nid_postfix, MD5_Update_nid_postfix, MD5_Final_nid_postfix, 92, 64, 16, { + "d41d8cd98f00b204e9800998ecf8427e", "900150983cd24fb0d6963f7d28e17f72", "8215ef0796a20bcaaae116d3876c664a", + "03dd8807a93175fb062dfb55dc7d359c", "57edf4a22be3c955ac49da2e2107b67a", "7707d6ae4e027c70eea2a935c2296f21"}}, + {"SHA1", SHA1_Init_nid_postfix, SHA1_Update_nid_postfix, SHA1_Final_nid_postfix, 96, 64, 20, { + "da39a3ee5e6b4b0d3255bfef95601890afd80709", "a9993e364706816aba3e25717850c26c9cd0d89d", "84983e441c3bd26ebaae4aa1f95129e5e54670f1", + "a49b2446a02c645bf419f995b67091253a04a259", "50abf5706a150990a08b2c5ea40fa0e585554732", "34aa973cd4c4daa4f61eeb2bdbad27316534016f"}}, + {"SHA224", SHA224_Init_nid_postfix, SHA224_Update_nid_postfix, SHA224_Final_nid_postfix, 112, 64, 28, { + "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f", "23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7", + "75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525", "c97ca9a559850ce97a04a96def6d99a9e0e0e2ab14e6b8df265fc0b3", + "b50aecbe4e9bb0b57bc5f3ae760a8e01db24f203fb3cdcd13148046e", "20794655980c91d8bbb4c1ea97618a4bf03f42581948b2ee4ee7ad67"}}, + {"SHA256", SHA256_Init_nid_postfix, SHA256_Update_nid_postfix, SHA256_Final_nid_postfix, 112, 64, 32, { + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", + "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1", "cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1", + "f371bc4a311f2b009eef952dd83ca80e2b60026c8e935592d0f9c308453c813e", "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0"}}, + {"SHA384", SHA384_Init_nid_postfix, SHA384_Update_nid_postfix, SHA384_Final_nid_postfix, 216, 128, 48, { + "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b", + "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7", + "3391fdddfc8dc7393707a65b1b4709397cf8b1d162af05abfe8f450de5f36bc6b0455a8520bc4e6f5fe95b1fe3c8452b", + "09330c33f71147e83d192fc782cd1b4753111b173b3b05d22fa08086e3b0f712fcc7c71a557e2db966c3e9fa91746039", + "b12932b0627d1c060942f5447764155655bd4da0c9afa6dd9b9ef53129af1b8fb0195996d2de9ca0df9d821ffee67026", + "9d0e1809716474cb086e834e310a4a1ced149e9c00f248527972cec5704c2a5b07b8b3dc38ecc4ebae97ddd87f3d8985"}}, + {"SHA512", SHA512_Init_nid_postfix, SHA512_Update_nid_postfix, SHA512_Final_nid_postfix, 216, 128, 64, { + "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", + "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f", + "204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445", + "8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909", + "72ec1ef1124a45b047e8b7c75a932195135bb61de24ec0d1914042246e0aec3a2354e093d76f3048b456764346900cb130d2a4fd5dd16abb5e30bcb850dee843", + "e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973ebde0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b"}}, + }; + + for (const auto& algorithm : algorithms) { + for (int i = 0; i < 6; ++i) { + if (!algorithm.expected[i]) continue; + const auto& message = messages[i]; + const std::size_t chunks[] {message.size() ? message.size() : 1, 1, algorithm.blockSize - 1, algorithm.blockSize, algorithm.blockSize + 1, 1000}; + for (auto chunk : chunks) { + if (i == 5 && chunk < 64) continue; + Require(Digest(algorithm, message, chunk) == algorithm.expected[i], algorithm.name); + } + } + + alignas(16) unsigned char context[256] {}; + alignas(16) unsigned char copy[256] {}; + unsigned char digest[64] {}; + unsigned char copyDigest[64] {}; + const bool wide = algorithm.blockSize == 128; + const auto num = [&](const unsigned char* c) { + std::uint32_t value; + std::memcpy(&value, c + (wide ? 208 : (algorithm.contextSize == 112 ? 104 : algorithm.contextSize - 4)), 4); + return value; + }; + const auto bitsLow = [&](const unsigned char* c) { + const std::size_t offset = wide ? 64 : (algorithm.contextSize == 96 ? 20 : algorithm.contextSize == 112 ? 32 : 16); + std::uint64_t value = 0; + std::memcpy(&value, c + offset, wide ? 8 : 4); + return value; + }; + Require(algorithm.init(context) == 1, "init"); + Require(algorithm.update(context, nullptr, 0) == 1, "empty update with null data"); + Require(algorithm.update(context, "ab", 2) == 1, "partial update"); + Require(num(context) == 2 && bitsLow(context) == 16, "context records the buffered bytes and the bit count"); + std::memcpy(copy, context, sizeof(copy)); + Require(algorithm.update(context, "c", 1) == 1 && algorithm.update(copy, "c", 1) == 1, "update of a copied context"); + Require(algorithm.final(digest, context) == 1 && algorithm.final(copyDigest, copy) == 1, "final of a copied context"); + Require(Hex(digest, algorithm.digestSize) == algorithm.expected[1] && !std::memcmp(digest, copyDigest, sizeof(digest)), "copied context digest"); + if (!wide) Require(num(context) == 0, "final clears the buffered count"); + RequireThrows([&] { algorithm.init(nullptr); }, "null context"); + RequireThrows([&] { algorithm.update(context, nullptr, 1); }, "null data"); + if (!wide) RequireThrows([&] { algorithm.final(nullptr, context); }, "null digest"); + } + + alignas(16) unsigned char context[256] {}; + unsigned char digest[64] {}; + Require(SHA512_Init_nid_postfix(context) == 1 && SHA512_Final_nid_postfix(nullptr, context) == 0, "SHA512_Final without a digest buffer"); + SHA256_Init_nid_postfix(context); + std::uint32_t length = 33; + std::memcpy(context + 108, &length, 4); + Require(SHA256_Final_nid_postfix(digest, context) == 0, "SHA256_Final with an oversized digest length"); + SHA256_Init_nid_postfix(context); + length = 16; + std::memcpy(context + 108, &length, 4); + std::memset(digest, 0xee, sizeof(digest)); + Require(SHA256_Final_nid_postfix(digest, context) == 1 && Hex(digest, 16) == "e3b0c44298fc1c149afbf4c8996fb924" && digest[16] == 0xee, "SHA256_Final truncates to the digest length"); + SHA512_Init_nid_postfix(context); + length = 32; + std::memcpy(context + 212, &length, 4); + RequireThrows([&] { SHA512_Final_nid_postfix(digest, context); }, "SHA-512/256 digest length"); + SHA256_Init_nid_postfix(context); + length = 64; + std::memcpy(context + 104, &length, 4); + RequireThrows([&] { SHA256_Update_nid_postfix(context, "a", 1); }, "corrupt buffered count"); + + const auto* abc = reinterpret_cast("abc"); + Require(SHA256_nid_postfix(abc, 3, digest) == digest && Hex(digest, 32) == "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", "SHA256 one-shot"); + Require(SHA256_nid_postfix(nullptr, 0, digest) == digest && Hex(digest, 32) == "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "SHA256 one-shot of nothing"); + RequireThrows([&] { SHA256_nid_postfix(abc, 3, nullptr); }, "SHA256 one-shot without a digest buffer"); + std::puts("libSceLibreSsl digest tests passed"); +} diff --git a/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp b/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp index 99f3f83152..f9a03f0d52 100644 --- a/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp +++ b/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp @@ -56,6 +56,29 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14Sessi int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEE8pushBackERKS8_(IntrusivePtr*, const IntrusivePtr*); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEEC1EPS6_PFvS8_EPNS2_10LibContextE(IntrusivePtr*, void*, Deleter, void*); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEED1Ev(IntrusivePtr*); + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter2V219WebApiFilterRequestEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter2V219WebApiFilterRequestEEC1ERS7_(IntrusivePtr*, const IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter2V219WebApiFilterRequestEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V113BasicPresenceEEEEEED1Ev(IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEE3getEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEE3getEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V113BasicPresenceEEEEEEdeEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V113BasicPresenceEEEEEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEEdeEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEEptEv(const IntrusivePtr*); } static void Require(bool value) { if (!value) std::abort(); } @@ -279,6 +302,9 @@ int main() { _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEC1Ev, _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEC1Ev, _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter2V219WebApiFilterRequestEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEEC1Ev, }; for (const auto construct : constructors) TestConstruct(construct); @@ -293,12 +319,27 @@ int main() { _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEptEv, _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6BinaryEEptEv, _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEEdeEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEE3getEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEE3getEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V113BasicPresenceEEEEEEdeEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V113BasicPresenceEEEEEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEEdeEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEEptEv, }; for (const auto access : accessors) TestAccess(access); const Copy copies[] = { _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1ERS7_, _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1ERS7_, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter2V219WebApiFilterRequestEEC1ERS7_, }; for (const auto copy : copies) TestCopy(copy); @@ -311,6 +352,10 @@ int main() { {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEED1Ev, 0}, {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEED1Ev, 0}, {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEED1Ev, VectorReferenceCountOffset}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter2V219WebApiFilterRequestEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V113BasicPresenceEEEEEED1Ev, VectorReferenceCountOffset}, }; for (const auto& testCase : destructors) TestDestroy(testCase); diff --git a/core/libs/tests/GuestNpCppWebApiSignedOut.cpp b/core/libs/tests/GuestNpCppWebApiSignedOut.cpp new file mode 100644 index 0000000000..455bc254b0 --- /dev/null +++ b/core/libs/tests/GuestNpCppWebApiSignedOut.cpp @@ -0,0 +1,189 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +extern "C" { +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void*); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void*); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_(); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriendsC1Ev(void*); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriendsD1Ev(void*); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriends10initializeEPNS1_6Common10LibContextEPKc(); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriends9terminateEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi10getFriendsEiRKNS4_21ParameterToGetFriendsERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_18GetFriendsResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void*); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void*); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_(); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsersC1Ev(void*); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsersD1Ev(void*); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsers10initializeEPNS1_6Common10LibContextE(); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsers9terminateEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi16getBlockingUsersEiRKNS4_27ParameterToGetBlockingUsersERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_24GetBlockingUsersResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void*); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void*); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_(); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresencesC1Ev(void*); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresencesD1Ev(void*); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresences10initializeEPNS1_6Common10LibContextEPKc(); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresences9terminateEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi17getBasicPresencesEiRKNS4_28ParameterToGetBasicPresencesERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_25GetBasicPresencesResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void*); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void*); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_(); +int APS5_VABI _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi26ParameterToFilterProfanityC1Ev(void*); +int APS5_VABI _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi26ParameterToFilterProfanityD1Ev(void*); +int APS5_VABI _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi26ParameterToFilterProfanity10initializeEPNS1_6Common10LibContextEPKcSA_NS6_12IntrusivePtrINS3_19WebApiFilterRequestEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi26ParameterToFilterProfanity9terminateEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi15filterProfanityEiRKNS4_26ParameterToFilterProfanityERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_23FilterProfanityResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void*); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void*); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_(); +int APS5_VABI _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi44ParameterToGetCommunicationRestrictionStatusC1Ev(void*); +int APS5_VABI _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi44ParameterToGetCommunicationRestrictionStatusD1Ev(void*); +int APS5_VABI _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi44ParameterToGetCommunicationRestrictionStatus10initializeEPNS1_6Common10LibContextEm(); +int APS5_VABI _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi44ParameterToGetCommunicationRestrictionStatus9terminateEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi33getCommunicationRestrictionStatusEiRKNS4_44ParameterToGetCommunicationRestrictionStatusERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_38CommunicationRestrictionStatusResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi15ProfanityFilter2V226WebApiFilterRequestFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_19WebApiFilterRequestEEE(); +} + +static void Require(bool value) { if (!value) std::abort(); } + +static constexpr int NotSignedIn = static_cast(0x80552907); + +using Object = int (APS5_VABI*)(void*); +using Call = int (APS5_VABI*)(); + +struct RequestFlow { + Object transactionConstruct; + Object transactionDestroy; + Call setOnFinishedCallback; + Call setResponseInformationOption; + Call start; + Call finish; + Call getResponseHeaders; + Object parameterConstruct; + Object parameterDestroy; + Call initialize; + Call terminate; + Call call; +}; + +static const RequestFlow flows[] = { + { + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev, + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev, + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv, + _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_, + _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriendsC1Ev, + _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriendsD1Ev, + _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriends10initializeEPNS1_6Common10LibContextEPKc, + _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriends9terminateEv, + _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi10getFriendsEiRKNS4_21ParameterToGetFriendsERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_18GetFriendsResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE, + }, + { + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev, + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev, + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv, + _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_, + _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsersC1Ev, + _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsersD1Ev, + _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsers10initializeEPNS1_6Common10LibContextE, + _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsers9terminateEv, + _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi16getBlockingUsersEiRKNS4_27ParameterToGetBlockingUsersERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_24GetBlockingUsersResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE, + }, + { + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev, + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev, + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv, + _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_, + _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresencesC1Ev, + _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresencesD1Ev, + _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresences10initializeEPNS1_6Common10LibContextEPKc, + _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresences9terminateEv, + _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi17getBasicPresencesEiRKNS4_28ParameterToGetBasicPresencesERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_25GetBasicPresencesResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE, + }, + { + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev, + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev, + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv, + _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_, + _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi26ParameterToFilterProfanityC1Ev, + _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi26ParameterToFilterProfanityD1Ev, + _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi26ParameterToFilterProfanity10initializeEPNS1_6Common10LibContextEPKcSA_NS6_12IntrusivePtrINS3_19WebApiFilterRequestEEE, + _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi26ParameterToFilterProfanity9terminateEv, + _ZN3sce2Np9CppWebApi15ProfanityFilter2V212ProfanityApi15filterProfanityEiRKNS4_26ParameterToFilterProfanityERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_23FilterProfanityResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE, + }, + { + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev, + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev, + _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE21setOnFinishedCallbackEPFvliS8_PvEPFvliSC_ESC_, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv, + _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_, + _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi44ParameterToGetCommunicationRestrictionStatusC1Ev, + _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi44ParameterToGetCommunicationRestrictionStatusD1Ev, + _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi44ParameterToGetCommunicationRestrictionStatus10initializeEPNS1_6Common10LibContextEm, + _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi44ParameterToGetCommunicationRestrictionStatus9terminateEv, + _ZN3sce2Np9CppWebApi30CommunicationRestrictionStatus2V333CommunicationRestrictionStatusApi33getCommunicationRestrictionStatusEiRKNS4_44ParameterToGetCommunicationRestrictionStatusERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_38CommunicationRestrictionStatusResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE, + }, +}; + +static void TestRequestFlow(const RequestFlow& flow) { + alignas(16) unsigned char transaction[0x100]; + alignas(16) unsigned char parameter[0x100]; + std::memset(transaction, 0x5a, sizeof(transaction)); + std::memset(parameter, 0x5a, sizeof(parameter)); + + Require(flow.transactionConstruct(transaction) == 0); + Require(flow.setOnFinishedCallback() == 0); + Require(flow.setResponseInformationOption() == 0); + Require(flow.parameterConstruct(parameter) == 0); + Require(flow.initialize() == 0); + Require(flow.call() == NotSignedIn); + Require(flow.start() == NotSignedIn); + Require(flow.getResponseHeaders() == NotSignedIn); + Require(flow.finish() == 0); + Require(flow.terminate() == 0); + Require(flow.parameterDestroy(parameter) == 0); + Require(flow.transactionDestroy(transaction) == 0); +} + +int main() { + for (const auto& flow : flows) TestRequestFlow(flow); + Require(_ZN3sce2Np9CppWebApi15ProfanityFilter2V226WebApiFilterRequestFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_19WebApiFilterRequestEEE() == NotSignedIn); + return 0; +} diff --git a/core/libs/tests/GuestNpManager.cpp b/core/libs/tests/GuestNpManager.cpp index 9c51efbe00..67e63ff3b0 100644 --- a/core/libs/tests/GuestNpManager.cpp +++ b/core/libs/tests/GuestNpManager.cpp @@ -49,6 +49,15 @@ void APS5_VABI sceNpRegisterGamePresenceCallback(void* callback, void* userdata) int APS5_VABI sceNpUnregisterNpReachabilityStateCallback(void); } +extern "C" { +int APS5_VABI sceNpCheckNpAvailabilityA(int reqId, int userId); +int APS5_VABI sceNpGetAccountDateOfBirthA(int userId, void* dateOfBirth); +int APS5_VABI sceNpGetAccountLanguageA(int reqId, int userId, void* language); +int APS5_VABI sceNpGetGamePresenceStatus(const NpOnlineId* onlineId, std::uint32_t* status); +int APS5_VABI sceNpGetGamePresenceStatusA(int userId, std::uint32_t* status); +int APS5_VABI sceNpGetParentalControlInfoA(int reqId, int userId, std::int8_t* age, void* info); +} + namespace { constexpr int InvalidArgument = static_cast(0x80550003u); @@ -281,6 +290,62 @@ void CheckOnlineIdQueries() { } } +void CheckUserIdQueries() { + constexpr int InvalidUserId = -1; + constexpr int OtherUserId = 0x10000; + constexpr int NeverCreated = 0x7fffffff; + unsigned char output[64]; + std::memset(output, 0x5a, sizeof(output)); + unsigned char untouched[64]; + std::memset(untouched, 0x5a, sizeof(untouched)); + std::int8_t age = 0x5a; + const int request = sceNpCreateRequest(); + Require(request > 0, "sceNpCreateRequest must return a positive request id"); + for (const int userId : {InitialUserId, OtherUserId}) { + Require(sceNpCheckNpAvailabilityA(request, userId) == SignedOut, "sceNpCheckNpAvailabilityA must report the user as signed out"); + Require(sceNpGetAccountLanguageA(request, userId, output) == SignedOut, "sceNpGetAccountLanguageA must report the user as signed out"); + Require(sceNpGetParentalControlInfoA(request, userId, &age, output) == SignedOut, "sceNpGetParentalControlInfoA must report the user as signed out"); + Require(sceNpGetAccountDateOfBirthA(userId, output) == SignedOut, "sceNpGetAccountDateOfBirthA must report the user as signed out"); + } + Require(sceNpCheckNpAvailabilityA(request, InvalidUserId) == InvalidArgument, "sceNpCheckNpAvailabilityA must reject the invalid user id"); + Require(sceNpCheckNpAvailabilityA(0, InitialUserId) == InvalidArgument, "sceNpCheckNpAvailabilityA must reject request id 0"); + Require(sceNpCheckNpAvailabilityA(NeverCreated, InitialUserId) == RequestNotFound, "sceNpCheckNpAvailabilityA must reject a request that was never created"); + Require(sceNpCheckNpAvailabilityA(NeverCreated, InvalidUserId) == InvalidArgument, "sceNpCheckNpAvailabilityA must check the user before the request"); + Require(sceNpGetAccountLanguageA(request, InitialUserId, nullptr) == InvalidArgument, "sceNpGetAccountLanguageA must reject a null output"); + Require(sceNpGetAccountLanguageA(request, InvalidUserId, output) == InvalidArgument, "sceNpGetAccountLanguageA must reject the invalid user id"); + Require(sceNpGetAccountLanguageA(0, InitialUserId, output) == InvalidArgument, "sceNpGetAccountLanguageA must reject request id 0"); + Require(sceNpGetAccountLanguageA(NeverCreated, InitialUserId, output) == RequestNotFound, "sceNpGetAccountLanguageA must reject a request that was never created"); + Require(sceNpGetAccountLanguageA(NeverCreated, InitialUserId, nullptr) == InvalidArgument, "sceNpGetAccountLanguageA must check the output before the request"); + Require(sceNpGetParentalControlInfoA(request, InitialUserId, nullptr, output) == InvalidArgument, "sceNpGetParentalControlInfoA must reject a null age"); + Require(sceNpGetParentalControlInfoA(request, InitialUserId, &age, nullptr) == InvalidArgument, "sceNpGetParentalControlInfoA must reject a null info"); + Require(sceNpGetParentalControlInfoA(request, InvalidUserId, &age, output) == InvalidArgument, "sceNpGetParentalControlInfoA must reject the invalid user id"); + Require(sceNpGetParentalControlInfoA(0, InitialUserId, &age, output) == InvalidArgument, "sceNpGetParentalControlInfoA must reject request id 0"); + Require(sceNpGetParentalControlInfoA(NeverCreated, InitialUserId, &age, output) == RequestNotFound, "sceNpGetParentalControlInfoA must reject a request that was never created"); + Require(sceNpGetParentalControlInfoA(NeverCreated, InvalidUserId, &age, output) == InvalidArgument, "sceNpGetParentalControlInfoA must check the user before the request"); + Require(sceNpGetAccountDateOfBirthA(InitialUserId, nullptr) == InvalidArgument, "sceNpGetAccountDateOfBirthA must reject a null output"); + Require(sceNpGetAccountDateOfBirthA(InvalidUserId, output) == InvalidArgument, "sceNpGetAccountDateOfBirthA must reject the invalid user id"); + Require(sceNpDeleteRequest(request) == 0, "sceNpDeleteRequest failed"); + Require(sceNpCheckNpAvailabilityA(request, InitialUserId) == RequestNotFound, "sceNpCheckNpAvailabilityA must reject a deleted request"); + Require(sceNpGetAccountLanguageA(request, InitialUserId, output) == RequestNotFound, "sceNpGetAccountLanguageA must reject a deleted request"); + Require(sceNpGetParentalControlInfoA(request, InitialUserId, &age, output) == RequestNotFound, "sceNpGetParentalControlInfoA must reject a deleted request"); + Require(std::memcmp(output, untouched, sizeof(output)) == 0 && age == 0x5a, "a signed-out account query wrote its output"); + constexpr std::uint32_t Sentinel = 0x5a5a5a5au; + constexpr std::uint32_t Offline = 0; + NpOnlineId onlineId{}; + std::memcpy(onlineId.data, "player", 6); + for (const int userId : {InitialUserId, OtherUserId}) { + std::uint32_t status = Sentinel; + Require(sceNpGetGamePresenceStatusA(userId, &status) == 0 && status == Offline, "sceNpGetGamePresenceStatusA must report the user as offline"); + } + std::uint32_t status = Sentinel; + Require(sceNpGetGamePresenceStatusA(InvalidUserId, &status) == InvalidArgument, "sceNpGetGamePresenceStatusA must reject the invalid user id"); + Require(sceNpGetGamePresenceStatusA(InitialUserId, nullptr) == InvalidArgument, "sceNpGetGamePresenceStatusA must reject a null status"); + Require(sceNpGetGamePresenceStatus(nullptr, &status) == InvalidArgument, "sceNpGetGamePresenceStatus must reject a null online id"); + Require(sceNpGetGamePresenceStatus(&onlineId, nullptr) == InvalidArgument, "sceNpGetGamePresenceStatus must reject a null status"); + Require(status == Sentinel, "a rejected presence query wrote its status"); + Require(sceNpGetGamePresenceStatus(&onlineId, &status) == 0 && status == Offline, "sceNpGetGamePresenceStatus must report the online id as offline"); +} + } struct ContentRestrictionFields { @@ -325,6 +390,7 @@ void CheckPlusAndContentRestriction() { int main() { CheckOnlineIdQueries(); + CheckUserIdQueries(); CheckStateCallbacksA(); CheckLegacyStateCallback(); CheckRequestTimeouts(); diff --git a/core/libs/tests/GuestNpParty.cpp b/core/libs/tests/GuestNpParty.cpp index d47398d76a..e60834546c 100644 --- a/core/libs/tests/GuestNpParty.cpp +++ b/core/libs/tests/GuestNpParty.cpp @@ -4,23 +4,85 @@ #include #include -extern "C" int APS5_VABI sceNpPartyGetState(std::uint16_t* state); +extern "C" { +int APS5_VABI sceNpPartyGetState(std::uint16_t* state); +int APS5_VABI sceNpPartyInitialize(const void* param); +int APS5_VABI sceNpPartyTerminate(void); +int APS5_VABI sceNpPartyCheckCallback(void); +int APS5_VABI sceNpPartyRegisterHandlerA(const void* handlers, void* userdata); +int APS5_VABI sceNpPartyGetStateAsUserA(std::int32_t asUserId, std::uint16_t* state); +int APS5_VABI sceNpPartyGetId(void* id); +int APS5_VABI sceNpPartyGetMembersA(void* memberList); +int APS5_VABI sceNpPartyGetMemberInfoA(std::uint16_t memberId, void* memberInfo); +int APS5_VABI sceNpPartyGetMemberSessionInfo(std::uint16_t memberId, void* memberGameSessionInfo); +int APS5_VABI sceNpPartyGetMemberVoiceInfo(std::uint16_t asMemberId, std::uint16_t memberId, std::uint8_t* memberVoiceState); +int APS5_VABI sceNpPartySendBinaryMessage(const void* sendParams); +} -int main() { +namespace { + +constexpr int kAlreadyInitialized = static_cast(0x80552502u); +constexpr int kNotInitialized = static_cast(0x80552503u); +constexpr int kInvalidArgument = static_cast(0x80552504u); +constexpr int kNotInParty = static_cast(0x80552506u); + +bool check(bool value, const char* message) { + if (!value) std::fprintf(stderr, "%s\n", message); + return value; +} + +bool stateTests() { std::uint16_t states[3]; std::memset(states, 0xa5, sizeof(states)); - if (sceNpPartyGetState(nullptr) != static_cast(0x80552504u)) { - std::fprintf(stderr, "a null state was not refused with INVALID_ARGUMENT\n"); - return 1; - } - if (sceNpPartyGetState(&states[1]) != 0 || states[1] != 2) { - std::fprintf(stderr, "the state is not NOT_IN_PARTY\n"); - return 1; - } - if (states[0] != 0xa5a5 || states[2] != 0xa5a5) { - std::fprintf(stderr, "sceNpPartyGetState wrote past its 16-bit state\n"); - return 1; - } - std::puts("NpParty state tests passed"); + return check(sceNpPartyGetState(nullptr) == kInvalidArgument, "a null state was not refused with INVALID_ARGUMENT") + && check(sceNpPartyGetState(&states[1]) == 0 && states[1] == 2, "the state is not NOT_IN_PARTY") + && check(states[0] == 0xa5a5 && states[2] == 0xa5a5, "sceNpPartyGetState wrote past its 16-bit state") + && check(sceNpPartyGetStateAsUserA(1, nullptr) == kInvalidArgument, "a null user state was not refused with INVALID_ARGUMENT") + && check(sceNpPartyGetStateAsUserA(1, &states[0]) == 0 && states[0] == 2, "the user state is not NOT_IN_PARTY") + && check(states[2] == 0xa5a5, "sceNpPartyGetStateAsUserA wrote past its 16-bit state"); +} + +bool lifecycleTests() { + std::uint64_t param[4] = {}; + std::uint64_t handlers[8] = {}; + return check(sceNpPartyTerminate() == kNotInitialized, "terminate before initialize did not fail") + && check(sceNpPartyCheckCallback() == kNotInitialized, "check callback before initialize did not fail") + && check(sceNpPartyRegisterHandlerA(handlers, nullptr) == kNotInitialized, "register handler before initialize did not fail") + && check(sceNpPartyInitialize(nullptr) == kInvalidArgument, "a null initialize param was not refused") + && check(sceNpPartyInitialize(param) == 0, "initialize failed") + && check(sceNpPartyInitialize(param) == kAlreadyInitialized, "a second initialize did not fail") + && check(sceNpPartyRegisterHandlerA(nullptr, nullptr) == kInvalidArgument, "null handlers were not refused") + && check(sceNpPartyRegisterHandlerA(handlers, param) == 0, "register handler failed") + && check(sceNpPartyCheckCallback() == 0, "check callback failed") + && check(sceNpPartyTerminate() == 0, "terminate failed") + && check(sceNpPartyTerminate() == kNotInitialized, "a second terminate did not fail") + && check(sceNpPartyInitialize(param) == 0, "initialize after terminate failed") + && check(sceNpPartyTerminate() == 0, "terminate after reinitialize failed"); +} + +bool notInPartyTests() { + std::uint8_t out[64]; + std::memset(out, 0xa5, sizeof(out)); + bool ok = check(sceNpPartyGetId(nullptr) == kInvalidArgument, "a null party id was not refused") + && check(sceNpPartyGetId(out) == kNotInParty, "get id did not report NOT_IN_PARTY") + && check(sceNpPartyGetMembersA(nullptr) == kInvalidArgument, "a null member list was not refused") + && check(sceNpPartyGetMembersA(out) == kNotInParty, "get members did not report NOT_IN_PARTY") + && check(sceNpPartyGetMemberInfoA(1, nullptr) == kInvalidArgument, "a null member info was not refused") + && check(sceNpPartyGetMemberInfoA(1, out) == kNotInParty, "get member info did not report NOT_IN_PARTY") + && check(sceNpPartyGetMemberSessionInfo(1, nullptr) == kInvalidArgument, "a null session info was not refused") + && check(sceNpPartyGetMemberSessionInfo(1, out) == kNotInParty, "get session info did not report NOT_IN_PARTY") + && check(sceNpPartyGetMemberVoiceInfo(1, 2, nullptr) == kInvalidArgument, "a null voice state was not refused") + && check(sceNpPartyGetMemberVoiceInfo(1, 2, out) == kNotInParty, "get voice info did not report NOT_IN_PARTY") + && check(sceNpPartySendBinaryMessage(nullptr) == kInvalidArgument, "null send params were not refused") + && check(sceNpPartySendBinaryMessage(out) == kNotInParty, "send did not report NOT_IN_PARTY"); + for (std::uint8_t byte : out) ok = ok && check(byte == 0xa5, "a NOT_IN_PARTY call wrote its output"); + return ok; +} + +} + +int main() { + if (!stateTests() || !lifecycleTests() || !notInPartyTests()) return 1; + std::puts("NpParty tests passed"); return 0; } diff --git a/core/libs/tests/GuestNpTus.cpp b/core/libs/tests/GuestNpTus.cpp index 11d9cf5cf5..d252efcfaa 100644 --- a/core/libs/tests/GuestNpTus.cpp +++ b/core/libs/tests/GuestNpTus.cpp @@ -8,6 +8,8 @@ extern "C" { int APS5_VABI sceNpTusCreateNpTitleCtxA(std::uint32_t serviceLabel, int userId); int APS5_VABI sceNpTusDeleteNpTitleCtx(int titleCtxId); +int APS5_VABI sceNpTssCreateNpTitleCtx(std::uint32_t serviceLabel, const NpId* npId); +int APS5_VABI sceNpTssCreateNpTitleCtxA(std::uint32_t serviceLabel, int userId); int APS5_VABI sceNpTusCreateRequest(int titleCtxId); int APS5_VABI sceNpTusDeleteRequest(int reqId); int APS5_VABI sceNpTusAbortRequest(int reqId); @@ -51,6 +53,34 @@ static void RequireFinished(int reqId, int expected) { Require(sceNpTusPollAsync(reqId, &result) == 0 && result == expected); } +static void CheckTssTitleContexts() { + const int shared = sceNpTssCreateNpTitleCtxA(0, 1); + Require(shared > 0); + const int request = sceNpTusCreateRequest(shared); + Require(request > 0); + Require(sceNpTusDeleteNpTitleCtx(shared) == 0); + Require(sceNpTusDeleteNpTitleCtx(shared) == INVALID_ID); + Require(sceNpTusDeleteRequest(request) == INVALID_ID); + + const NpId npId{}; + Require(sceNpTssCreateNpTitleCtx(0, nullptr) == INSUFFICIENT_ARGUMENT); + Require(sceNpTssCreateNpTitleCtx(0xFFFFFFFFu, nullptr) == INSUFFICIENT_ARGUMENT); + Require(sceNpTssCreateNpTitleCtx(0xFFFFFFFFu, &npId) == INVALID_ARGUMENT); + + std::array contexts{}; + for (std::size_t index = 0; index < contexts.size(); ++index) { + const std::size_t form = index % 3; + contexts[index] = form == 0 ? sceNpTusCreateNpTitleCtxA(0, 1) : form == 1 ? sceNpTssCreateNpTitleCtxA(0, 1) : sceNpTssCreateNpTitleCtx(0, &npId); + Require(contexts[index] > 0); + } + Require(sceNpTssCreateNpTitleCtxA(0, 1) == TOO_MANY_OBJECTS); + Require(sceNpTssCreateNpTitleCtx(0, &npId) == TOO_MANY_OBJECTS); + Require(sceNpTusCreateNpTitleCtxA(0, 1) == TOO_MANY_OBJECTS); + Require(sceNpTssCreateNpTitleCtx(0, nullptr) == INSUFFICIENT_ARGUMENT); + Require(sceNpTssCreateNpTitleCtx(0xFFFFFFFFu, &npId) == INVALID_ARGUMENT); + for (const int context : contexts) Require(sceNpTusDeleteNpTitleCtx(context) == 0); +} + static void CheckMultiSlotVariables() { const int context = sceNpTusCreateNpTitleCtxA(0, 1); Require(context > 0); @@ -271,4 +301,5 @@ int main() { Require(sceNpTusCreateRequest(contexts[1]) > 0); for (const int child : children) Require(sceNpTusPollAsync(child, &result) == INVALID_ID); for (std::size_t index = 1; index < contexts.size(); ++index) Require(sceNpTusDeleteNpTitleCtx(contexts[index]) == 0); + CheckTssTitleContexts(); } diff --git a/core/libs/tests/GuestPad.cpp b/core/libs/tests/GuestPad.cpp index 3cc56175b8..6dfa8c9a9c 100644 --- a/core/libs/tests/GuestPad.cpp +++ b/core/libs/tests/GuestPad.cpp @@ -30,6 +30,9 @@ int APS5_VABI scePadSetAngularVelocityDeadbandState(int, bool); int APS5_VABI scePadSetAngularVelocityBiasCorrectionState(int, bool); int APS5_VABI scePadIsRemoteController(int, bool*); int APS5_VABI scePadGetAccessibilityControllerState(int, std::uint32_t*); +bool APS5_VABI scePadIsDS4Connected(std::int32_t); +bool APS5_VABI scePadIsBlasterConnected(std::int32_t); +bool APS5_VABI scePadIsMoveConnected(std::int32_t); int APS5_VABI scePadGetControllerInformation(int, PadControllerInformation*); int APS5_VABI scePadGetExtControllerInformation(int, void*); int APS5_VABI scePadResetLightBar(int); @@ -316,6 +319,14 @@ static void CheckRemoteController(int handle) { Require(!remote); } +static void CheckTrackerDevices(int handle) { + for (const int candidate : {handle, handle + 1}) { + Require(!scePadIsDS4Connected(candidate)); + Require(!scePadIsBlasterConnected(candidate)); + Require(!scePadIsMoveConnected(candidate)); + } +} + static void CheckAccessibilityControllerState(int handle) { std::uint32_t state = 7; Require(scePadGetAccessibilityControllerState(handle + 1, nullptr) == PAD_ERROR_INVALID_ARG); @@ -460,6 +471,7 @@ int main() { CheckLatchedPressLifetime(handle); CheckReadStateHandle(handle); CheckRemoteController(handle); + CheckTrackerDevices(handle); CheckAccessibilityControllerState(handle); CheckInjectedState(commandPath); PadLightBarParam lightBar{255, 128, 64}; diff --git a/core/libs/tests/GuestPosixFilesystem.cpp b/core/libs/tests/GuestPosixFilesystem.cpp index 0e3326d3d0..fbe8e7566e 100644 --- a/core/libs/tests/GuestPosixFilesystem.cpp +++ b/core/libs/tests/GuestPosixFilesystem.cpp @@ -1,4 +1,5 @@ #include "prx/libc/include/General.hpp" +#include "SceTypes.hpp" #include #include #include @@ -27,6 +28,20 @@ int APS5_VABI socket_nid_postfix(int, int, int); std::int64_t APS5_VABI read_nid_postfix(int, void*, std::size_t); std::int64_t APS5_VABI write_nid_postfix(int, const void*, std::size_t); std::int64_t APS5_VABI lseek_nid_postfix(int, std::int64_t, int); +int APS5_VABI access_nid_postfix(const char*, int); +int APS5_VABI __wrap_access(const char*, int); +int APS5_VABI clock_gettime_nid_postfix(int, KernelTimespec*); +int APS5_VABI __wrap_clock_gettime(int, KernelTimespec*); +char* APS5_VABI getcwd_nid_postfix(char*, std::size_t); +char* APS5_VABI __wrap_getcwd(char*, std::size_t); +char* APS5_VABI getenv_nid_postfix(const char*); +char* APS5_VABI __wrap_getenv(const char*); +int APS5_VABI setenv_nid_postfix(const char*, const char*, int); +int APS5_VABI lstat_nid_postfix(const char*, FileStat*); +int APS5_VABI __wrap_lstat(const char*, FileStat*); +void* APS5_VABI mmap_nid_postfix(void*, std::size_t, int, int, int, std::int64_t) noexcept; +void* APS5_VABI __wrap_mmap(void*, std::size_t, int, int, int, std::int64_t); +int APS5_VABI munmap_nid_postfix(void*, std::size_t) noexcept; } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -134,6 +149,52 @@ int main() { Require(isatty_nid_postfix(slave) == 1 && *__error_nid_postfix() == 123); Require(::close(slave) == 0 && ::close(terminal) == 0); #endif + Require(setenv_nid_postfix("WEBKIT_WRAP_SET", "set", 1) == 0); + char* const variable = getenv_nid_postfix("WEBKIT_WRAP_SET"); + Require(variable != nullptr && std::strcmp(variable, "set") == 0 && __wrap_getenv("WEBKIT_WRAP_SET") == variable); + Require(getenv_nid_postfix("WEBKIT_WRAP_UNSET") == nullptr && __wrap_getenv("WEBKIT_WRAP_UNSET") == nullptr); + Require(access_nid_postfix(pattern.c_str(), 0) == 0 && __wrap_access(pattern.c_str(), 0) == 0); + Require(access_nid_postfix("/anyps5-posix-test/absent", 0) == -1); + const int accessError = *__error_nid_postfix(); + *__error_nid_postfix() = 0; + Require(__wrap_access("/anyps5-posix-test/absent", 0) == -1 && *__error_nid_postfix() == accessError && accessError == 2); + std::array expectedDirectory{}; + std::array wrappedDirectory{}; + Require(getcwd_nid_postfix(expectedDirectory.data(), expectedDirectory.size()) == expectedDirectory.data()); + Require(__wrap_getcwd(wrappedDirectory.data(), wrappedDirectory.size()) == wrappedDirectory.data()); + Require(std::strcmp(expectedDirectory.data(), wrappedDirectory.data()) == 0); + Require(getcwd_nid_postfix(wrappedDirectory.data(), 1) == nullptr); + const int directoryError = *__error_nid_postfix(); + *__error_nid_postfix() = 0; + Require(__wrap_getcwd(wrappedDirectory.data(), 1) == nullptr && *__error_nid_postfix() == directoryError && directoryError == 34); + FileStat expectedStatus{}; + FileStat wrappedStatus{}; + Require(lstat_nid_postfix(pattern.c_str(), &expectedStatus) == 0 && __wrap_lstat(pattern.c_str(), &wrappedStatus) == 0); + Require((wrappedStatus.st_mode & 0170000) == 0100000 && wrappedStatus.st_size == static_cast(sizeof(payload))); + Require(std::memcmp(&expectedStatus, &wrappedStatus, sizeof(FileStat)) == 0); + const auto notAfter = [](const KernelTimespec& earlier, const KernelTimespec& later) { + return earlier.tv_sec < later.tv_sec || (earlier.tv_sec == later.tv_sec && earlier.tv_nsec <= later.tv_nsec); + }; + KernelTimespec before{}; + KernelTimespec during{}; + KernelTimespec after{}; + Require(clock_gettime_nid_postfix(4, &before) == 0 && __wrap_clock_gettime(4, &during) == 0 && clock_gettime_nid_postfix(4, &after) == 0); + Require(notAfter(before, during) && notAfter(during, after)); + Require(clock_gettime_nid_postfix(4, nullptr) == -1 && *__error_nid_postfix() == 14); + *__error_nid_postfix() = 0; + Require(__wrap_clock_gettime(4, nullptr) == -1 && *__error_nid_postfix() == 14); + constexpr std::size_t page = 0x4000; + void* const failed = reinterpret_cast(static_cast(-1)); + void* const mapped = __wrap_mmap(nullptr, page, 3, 0x1002, -1, 0); + Require(mapped != failed); + auto* const bytes = static_cast(mapped); + bytes[0] = 0x5a; + bytes[page - 1] = 0xa5; + Require(bytes[0] == 0x5a && bytes[page - 1] == 0xa5); + Require(munmap_nid_postfix(mapped, page) == 0); + Require(mmap_nid_postfix(nullptr, page, 8, 0x1002, -1, 0) == failed && *__error_nid_postfix() == 22); + *__error_nid_postfix() = 0; + Require(__wrap_mmap(nullptr, page, 8, 0x1002, -1, 0) == failed && *__error_nid_postfix() == 22); RemovePathAlias_nid_no_patch("/anyps5-posix-test"); Require(std::filesystem::remove_all(root) > 0); } diff --git a/core/libs/tests/GuestScreenShot.cpp b/core/libs/tests/GuestScreenShot.cpp new file mode 100644 index 0000000000..836b70da94 --- /dev/null +++ b/core/libs/tests/GuestScreenShot.cpp @@ -0,0 +1,123 @@ +#include "ProcessParametersFixture.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +int APS5_VABI sceScreenShotDisable(); +int APS5_VABI sceScreenShotEnable(); +int APS5_VABI sceScreenShotIsDisabled(int id, int* isDisabled); +int APS5_VABI sceScreenShotDisableNotification(); +int APS5_VABI sceScreenShotEnableNotification(); +int APS5_VABI sceScreenShotSetOverlayImage(const char* path, int offsetX, int offsetY); +int APS5_VABI sceScreenShotSetOverlayImageWithOrigin(const char* path, int offsetX, int offsetY, int origin); +} + +namespace { + +constexpr int ErrorArgument = static_cast(0x80BE0001); +constexpr int ErrorOverlayFile = static_cast(0x80BE0003); +constexpr int ErrorOverlaySize = static_cast(0x80BE000E); + +alignas(16) std::array process{}; +std::string pathPrefix; + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "ScreenShot: %s\n", message); + std::abort(); +} + +void InstallProcessParameters(std::uint32_t sdkVersion) { + const std::uint64_t size = process.size(); + const std::uint32_t magic = 0x4942524f; + std::memcpy(process.data(), &size, sizeof(size)); + std::memcpy(process.data() + 8, &magic, sizeof(magic)); + std::memcpy(process.data() + 0x10, &sdkVersion, sizeof(sdkVersion)); + Require(ProcessParametersFixture::Install(process.data(), process.size()), "process parameters were not installed"); +} + +class TempPath { +public: + explicit TempPath(const char* tag) : name(pathPrefix + tag) {} + ~TempPath() { + std::error_code error; + std::filesystem::remove_all(name, error); + } + const char* c_str() const { return name.c_str(); } + void Fill(std::size_t bytes) const { + std::ofstream file(name, std::ios::binary | std::ios::trunc); + const std::string chunk(4096, 'x'); + for (std::size_t written = 0; written < bytes; written += chunk.size()) { + file.write(chunk.data(), static_cast(std::min(chunk.size(), bytes - written))); + } + } + +private: + std::string name; +}; + +void TestDisableState() { + int value = -1; + Require(sceScreenShotIsDisabled(0, &value) == 0 && value == 0, "capture starts enabled"); + Require(sceScreenShotDisable() == 0, "disable"); + Require(sceScreenShotIsDisabled(0, &value) == 0 && value == 1, "disable is reported"); + Require(sceScreenShotEnable() == 0, "enable"); + Require(sceScreenShotIsDisabled(0, &value) == 0 && value == 0, "enable is reported"); + value = 7; + Require(sceScreenShotIsDisabled(-1, &value) == ErrorArgument && value == 7, "id -1 is rejected"); + Require(sceScreenShotIsDisabled(0, nullptr) == ErrorArgument, "null result is rejected"); + Require(sceScreenShotDisableNotification() == 0 && sceScreenShotEnableNotification() == 0, "notification toggles"); +} + +void TestOverlay(bool large) { + const int maxX = large ? 3839 : 1919; + const int maxY = large ? 2159 : 1079; + const std::size_t maxSize = large ? 8u << 20 : 2u << 20; + TempPath image("image"); + image.Fill(4096); + Require(sceScreenShotSetOverlayImage(image.c_str(), 0, 0) == 0, "overlay at the origin"); + Require(sceScreenShotSetOverlayImage(image.c_str(), maxX, maxY) == 0, "overlay at the last offsets"); + Require(sceScreenShotSetOverlayImage(image.c_str(), maxX + 1, 0) == ErrorArgument, "x past the screen"); + Require(sceScreenShotSetOverlayImage(image.c_str(), 0, maxY + 1) == ErrorArgument, "y past the screen"); + Require(sceScreenShotSetOverlayImage(image.c_str(), -1, 0) == ErrorArgument, "negative x"); + Require(sceScreenShotSetOverlayImage(nullptr, 0, 0) == ErrorArgument, "null path"); + const std::string longPath(1024, 'a'); + Require(sceScreenShotSetOverlayImage(longPath.c_str(), 0, 0) == ErrorArgument, "path of 1024 characters"); + TempPath missing("missing"); + Require(sceScreenShotSetOverlayImage(missing.c_str(), 0, 0) == ErrorOverlayFile, "missing file"); + TempPath directory("directory"); + std::filesystem::create_directory(directory.c_str()); + Require(sceScreenShotSetOverlayImage(directory.c_str(), 0, 0) == ErrorOverlayFile, "directory"); + TempPath empty("empty"); + empty.Fill(0); + Require(sceScreenShotSetOverlayImage(empty.c_str(), 0, 0) == ErrorOverlaySize, "empty file"); + TempPath full("full"); + full.Fill(maxSize); + Require(sceScreenShotSetOverlayImage(full.c_str(), 0, 0) == 0, "file at the size limit"); + full.Fill(maxSize + 1); + Require(sceScreenShotSetOverlayImage(full.c_str(), 0, 0) == ErrorOverlaySize, "file past the size limit"); + for (int origin = 1; origin <= 9; ++origin) Require(sceScreenShotSetOverlayImageWithOrigin(image.c_str(), 0, 0, origin) == 0, "origins 1 to 9"); + Require(sceScreenShotSetOverlayImageWithOrigin(image.c_str(), 0, 0, 0) == ErrorArgument, "origin 0"); + Require(sceScreenShotSetOverlayImageWithOrigin(image.c_str(), 0, 0, 10) == ErrorArgument, "origin 10"); + Require(sceScreenShotSetOverlayImageWithOrigin(nullptr, 0, 0, 0) == ErrorArgument, "origin checked first"); +} + +} + +int main(int argc, char** argv) { + const bool large = argc > 1 && std::strcmp(argv[1], "large") == 0; + pathPrefix = std::string("anyps5-screenshot-") + (large ? "large-" : "small-") + std::to_string(std::random_device{}()) + "-"; + InstallProcessParameters(large ? 0x3500000u : 0x34fffffu); + TestDisableState(); + TestOverlay(large); + return 0; +} diff --git a/core/libs/tests/GuestSignals.cpp b/core/libs/tests/GuestSignals.cpp index 7f4740ac07..a5ca92484b 100644 --- a/core/libs/tests/GuestSignals.cpp +++ b/core/libs/tests/GuestSignals.cpp @@ -36,6 +36,7 @@ struct GuestSigaction { GuestSignalSet mask; }; static_assert(sizeof(GuestSigaction) == 32); +extern GuestSignalSet _sigintr_nid_postfix; static bool Same(const GuestSigaction& left, const GuestSigaction& right) { return left.handler == right.handler && left.flags == right.flags && std::memcmp(&left.mask, &right.mask, sizeof(left.mask)) == 0; } @@ -398,4 +399,24 @@ int main() { Require(sigpending_nid_postfix(&pendingIgnored) == 0 && pendingIgnored.bits[0] == 0); Require(sigprocmask_nid_postfix(3, &noneBlocked, nullptr) == 0); Require(sigaction_nid_postfix(30, &restored, nullptr) == 0); + Require(_sigintr_nid_postfix.bits[0] == 0 && _sigintr_nid_postfix.bits[1] == 0 && _sigintr_nid_postfix.bits[2] == 0 && + _sigintr_nid_postfix.bits[3] == 0); + GuestSigaction interruptible{}; + Require(signal_nid_postfix(30, Callback) == nullptr); + Require(sigaction_nid_postfix(30, nullptr, &interruptible) == 0 && interruptible.flags == 0x2); + _sigintr_nid_postfix.bits[0] = 1u << 29; + Require(signal_nid_postfix(30, Callback) == Callback); + Require(sigaction_nid_postfix(30, nullptr, &interruptible) == 0 && + interruptible.handler == reinterpret_cast(Callback) && interruptible.flags == 0); + signal_nid_postfix(31, Callback); + Require(sigaction_nid_postfix(31, nullptr, &interruptible) == 0 && interruptible.flags == 0x2); + _sigintr_nid_postfix.bits[3] = 1u << 31; + Require(signal_nid_postfix(128, Callback) == nullptr); + Require(sigaction_nid_postfix(128, nullptr, &interruptible) == 0 && interruptible.flags == 0); + _sigintr_nid_postfix.bits[0] = 0; + _sigintr_nid_postfix.bits[3] = 0; + Require(signal_nid_postfix(30, Callback) == Callback); + Require(sigaction_nid_postfix(30, nullptr, &interruptible) == 0 && interruptible.flags == 0x2); + Require(signal_nid_postfix(30, nullptr) == Callback && signal_nid_postfix(31, nullptr) == Callback && + signal_nid_postfix(128, nullptr) == Callback); } diff --git a/core/libs/tests/GuestSslTransport.cpp b/core/libs/tests/GuestSslTransport.cpp index 927dc47414..4d956c68be 100644 --- a/core/libs/tests/GuestSslTransport.cpp +++ b/core/libs/tests/GuestSslTransport.cpp @@ -39,6 +39,8 @@ int APS5_VABI sceSslSetVerifyCallback(int, int (APS5_VABI *)(int, unsigned int, int APS5_VABI sceSslConnect(int); int APS5_VABI sceSslRead(int, void*, std::size_t, int); int APS5_VABI sceSslWrite(int, void*, std::size_t, int); +int APS5_VABI sceSslRecv(int, void*, std::size_t); +int APS5_VABI sceSslSend(int, void*, std::size_t); int APS5_VABI sceSslClose(int); int APS5_VABI sceSslEnableVerifyOption(int, std::uint32_t); int APS5_VABI sceSslDisableVerifyOption(int, std::uint32_t); @@ -458,13 +460,10 @@ int main(int argc, char** argv) { Require(selected && std::strcmp(selected, "http/1.1") == 0, "the negotiated ALPN protocol is not reported"); Require(sceSslWrite(connection, buffer.data(), 1, 1) == ErrorInvalidValue, "unknown write flags were accepted"); char request[] = "ping"; - Require(sceSslWrite(connection, request, 4, 0) == 4, "write the request"); - Require(sceSslRead(connection, buffer.data(), buffer.size(), 4) == ErrorInvalidValue, "unknown read flags were accepted"); - Require(sceSslRead(0, nullptr, 1, -1) == ErrorInvalidValue, "read flags did not precede other validation"); - Require(sceSslRead(connection, buffer.data(), 2, 3) == 2 && std::memcmp(buffer.data(), "po", 2) == 0, "short peek failed"); - Require(sceSslRead(connection, buffer.data(), 4, 3) == 4 && std::memcmp(buffer.data(), "pong", 4) == 0, "repeated larger peek consumed bytes"); + Require(sceSslSend(connection, request, 4) == 4, "send the request"); Require(sceSslRead(connection, buffer.data(), 2, 1) == 2 && std::memcmp(buffer.data(), "po", 2) == 0, "a partial read did not return the available bytes"); - Require(sceSslRead(connection, buffer.data(), buffer.size(), 0) == 2 && std::memcmp(buffer.data(), "ng", 2) == 0, "a full read did not stop at the end of the stream"); + Require(sceSslRecv(connection, buffer.data(), 2) == 2 && std::memcmp(buffer.data(), "ng", 2) == 0, "receive did not fill the remaining reply"); + Require(sceSslRecv(connection, buffer.data(), buffer.size()) == 0, "receive did not stop at the end of the stream"); Require(sceSslRead(connection, buffer.data(), buffer.size(), 1) == 0, "close notify did not end the stream"); Require(sceSslClose(connection) == 0, "close the connection"); Require(sceSslRead(connection, buffer.data(), buffer.size(), 0) == 0, "a read after the close did not return 0"); diff --git a/core/libs/tests/GuestUltCondition.cpp b/core/libs/tests/GuestUltCondition.cpp index 5bec09da1b..3922c753ef 100644 --- a/core/libs/tests/GuestUltCondition.cpp +++ b/core/libs/tests/GuestUltCondition.cpp @@ -15,6 +15,7 @@ int APS5_VABI sceUltMutexCreate(void*, const char*, void*, const UltMutexOptPara int APS5_VABI sceUltMutexLock(void*); int APS5_VABI sceUltMutexUnlock(void*); int APS5_VABI sceUltMutexDestroy(void*); +int APS5_VABI sceUltConditionVariableCreate(void*, const char*, void*, const void*, std::uint32_t); int APS5_VABI _sceUltConditionVariableCreate(void*, const char*, void*, const void*, std::uint32_t); int APS5_VABI sceUltConditionVariableWait(void*); int APS5_VABI sceUltConditionVariableSignal(void*); @@ -65,6 +66,9 @@ static void Validation() { Object unused; CHECK(sceUltMutexDestroy(nullptr) == Null); CHECK(sceUltMutexDestroy(&unused) == State); + CHECK(sceUltConditionVariableCreate(nullptr, nullptr, nullptr, nullptr, 0) == Null); + CHECK(sceUltConditionVariableCreate(&unused, nullptr, &unused, nullptr, 0) == Invalid); + CHECK(sceUltConditionVariableCreate(&f.condition, nullptr, &f.mutex, nullptr, 0) == State); CHECK(_sceUltConditionVariableCreate(nullptr, nullptr, nullptr, nullptr, 0) == Null); CHECK(_sceUltConditionVariableCreate(&unused, nullptr, &unused, nullptr, 0) == Invalid); CHECK(_sceUltConditionVariableCreate(&f.condition, nullptr, &f.mutex, nullptr, 0) == State); diff --git a/core/libs/tests/GuestUltSync.cpp b/core/libs/tests/GuestUltSync.cpp index 2a368aa57b..7b2e1e51ed 100644 --- a/core/libs/tests/GuestUltSync.cpp +++ b/core/libs/tests/GuestUltSync.cpp @@ -25,6 +25,8 @@ int APS5_VABI sceUltUlthreadOptParamInitialize(void*, std::uint32_t); int APS5_VABI _sceUltWaitingQueueResourcePoolCreate(void*, const char*, std::uint32_t, std::uint32_t, void*, const void*); int APS5_VABI sceUltWaitingQueueResourcePoolDestroy(void*); int APS5_VABI sceUltReaderWriterLockOptParamInitialize(void*, std::uint32_t); +int APS5_VABI sceUltConditionVariableOptParamInitialize(void*, std::uint32_t); +int APS5_VABI _sceUltConditionVariableOptParamInitialize(void*, std::uint32_t); int APS5_VABI sceUltReaderWriterLockCreate(void*, const char*, void*, const void*, std::uint32_t); int APS5_VABI sceUltReaderWriterLockDestroy(void*); int APS5_VABI sceUltReaderWriterLockLockRead(void*); @@ -98,7 +100,8 @@ void MutexTryLock() { void OptParams() { using Initializer = int (APS5_VABI *)(void*, std::uint32_t); for (const Initializer initialize : {sceUltSemaphoreOptParamInitialize, sceUltQueueOptParamInitialize, sceUltQueueDataResourcePoolOptParamInitialize, - sceUltWaitingQueueResourcePoolOptParamInitialize, sceUltUlthreadOptParamInitialize, sceUltReaderWriterLockOptParamInitialize}) { + sceUltWaitingQueueResourcePoolOptParamInitialize, sceUltUlthreadOptParamInitialize, sceUltReaderWriterLockOptParamInitialize, + sceUltConditionVariableOptParamInitialize, _sceUltConditionVariableOptParamInitialize}) { std::uint8_t bytes[136]; std::memset(bytes, 0xa5, sizeof(bytes)); CHECK(initialize(nullptr, 0) == Null); diff --git a/core/libs/tests/GuestUsbd.cpp b/core/libs/tests/GuestUsbd.cpp index 59e59542b0..358e369eea 100644 --- a/core/libs/tests/GuestUsbd.cpp +++ b/core/libs/tests/GuestUsbd.cpp @@ -50,6 +50,7 @@ void APS5_VABI sceUsbdFillInterruptTransfer(void* transfer, void* deviceHandle, std::int32_t APS5_VABI sceUsbdCancelTransfer(void* transfer); std::int32_t APS5_VABI sceUsbdEventHandlingOk(); void APS5_VABI sceUsbdFreeConfigDescriptor(void* config); +void APS5_VABI sceUsbdFillControlSetup(std::uint8_t* buffer, std::uint8_t requestType, std::uint8_t request, std::uint16_t value, std::uint16_t index, std::uint16_t length); } namespace { @@ -293,6 +294,13 @@ int main(int argc, char** argv) { sceUsbdFreeTransfer(nullptr); Require(sceUsbdEventHandlingOk() == 1, "event handling not allowed"); sceUsbdFreeConfigDescriptor(nullptr); + Require(Throws([] { sceUsbdFillControlSetup(nullptr, 0x80, 6, 0x0100, 0, 18); }), "null control setup buffer accepted"); + std::uint8_t setupBuffer[8] = {}; + sceUsbdFillControlSetup(setupBuffer, 0x80, 6, 0x0100, 0x0409, 18); + Require(setupBuffer[0] == 0x80 && setupBuffer[1] == 6, "request type or request not filled"); + Require(setupBuffer[2] == 0x00 && setupBuffer[3] == 0x01, "value not filled little-endian"); + Require(setupBuffer[4] == 0x09 && setupBuffer[5] == 0x04, "index not filled little-endian"); + Require(setupBuffer[6] == 18 && setupBuffer[7] == 0, "length not filled little-endian"); if (argc == 2 && std::strcmp(argv[1], "device") == 0) Device(); sceUsbdExit(); std::puts("USBD tests passed"); diff --git a/core/libs/tests/GuestUserService.cpp b/core/libs/tests/GuestUserService.cpp index 4e6e1809a4..3e6d7559da 100644 --- a/core/libs/tests/GuestUserService.cpp +++ b/core/libs/tests/GuestUserService.cpp @@ -5,6 +5,8 @@ #include extern "C" { +int APS5_VABI sceUserServiceGetUserNumber(int user_id, int32_t* number); +int APS5_VABI sceUserServiceGetPlatformPrivacyWs1(int32_t user_id, int32_t* value); int APS5_VABI sceUserServiceGetForegroundUser(int* user_id); int APS5_VABI sceUserServiceGetRegisteredUserIdList(UserServiceRegisteredUserIdList* user_id_list); int APS5_VABI sceUserServiceGetUserColor(int user_id, int* color); @@ -28,6 +30,17 @@ void Require(bool value) { if (!value) std::abort(); } int main() { Require(sceUserServiceGetForegroundUser(nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + int32_t userNumber = -1; + Require(sceUserServiceGetUserNumber(USER_SERVICE_INITIAL_USER_ID, nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetUserNumber(123, &userNumber) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetUserNumber(USER_SERVICE_INITIAL_USER_ID, &userNumber) == USER_SERVICE_OK); + Require(userNumber == 1); + + int32_t privacy = -1; + Require(sceUserServiceGetPlatformPrivacyWs1(USER_SERVICE_INITIAL_USER_ID, nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetPlatformPrivacyWs1(USER_SERVICE_INITIAL_USER_ID, &privacy) == USER_SERVICE_OK); + Require(privacy == 0); + int foreground = -1; Require(sceUserServiceGetForegroundUser(&foreground) == USER_SERVICE_OK); Require(foreground == USER_SERVICE_INITIAL_USER_ID); diff --git a/core/libs/tests/GuestVdecsw.cpp b/core/libs/tests/GuestVdecsw.cpp index 14fdca44a9..6bb929a1e0 100644 --- a/core/libs/tests/GuestVdecsw.cpp +++ b/core/libs/tests/GuestVdecsw.cpp @@ -9,6 +9,7 @@ #include "prx/libc/include/General.hpp" #include "Vp9Fixture.hpp" #include "H264Fixture.hpp" +#include "HevcFixture.hpp" struct ComputeMemoryInfo { std::uint64_t thisSize; @@ -133,6 +134,51 @@ struct AvcPictureInfo { std::uint8_t vui[44]; }; +struct HevcPictureInfo { + std::uint64_t thisSize; + bool isValid; + std::uint64_t ptsData; + std::uint64_t dtsData; + std::uint64_t attachedData; + std::uint32_t picWidthInLumaSamples; + std::uint32_t picHeightInLumaSamples; + std::uint8_t bitDepthLumaMinus8; + std::uint8_t bitDepthChromaMinus8; + std::uint8_t timingInfoPresentFlag; + std::uint32_t numUnitsInTick; + std::uint32_t timeScale; + std::uint32_t aspectRatioInfoPresentFlag; + std::uint8_t aspectRatioIdc; + std::uint16_t sarWidth; + std::uint16_t sarHeight; + std::uint8_t videoSignal[8]; + std::uint32_t picStruct; + std::uint32_t sourceScanType; + std::uint32_t duplicateFlag; + std::uint32_t conformanceWindowFlag; + std::uint32_t confWinLeftOffset; + std::uint32_t confWinRightOffset; + std::uint32_t confWinTopOffset; + std::uint32_t confWinBottomOffset; + std::uint32_t defaultDisplayWindow[5]; + std::uint8_t chromaLocation[4]; + std::uint8_t presentFlags[11]; + std::uint8_t idrPictureFlag; + std::uint8_t irapPictureFlag; + std::uint8_t generalProfileSpace; + std::uint8_t generalTierFlag; + std::uint8_t generalProfileIdc; + std::uint8_t generalSourceFlags[3]; + std::uint8_t generalLevelIdc; + std::uint8_t subLayer[9]; + std::uint8_t frameCroppingFlag; + std::uint32_t frameCropLeftOffset; + std::uint32_t frameCropRightOffset; + std::uint32_t frameCropTopOffset; + std::uint32_t frameCropBottomOffset; +}; + +static_assert(sizeof(HevcPictureInfo) == 0xb8 && offsetof(HevcPictureInfo, idrPictureFlag) == 0x93 && offsetof(HevcPictureInfo, frameCroppingFlag) == 0xa5); static_assert(sizeof(DecoderConfigInfo) == 0x50 && sizeof(OutputInfo) == 0x38 && offsetof(OutputInfo, isLastFrame) == 9 && offsetof(OutputInfo, pictureCount) == 0xb && sizeof(AvcPictureInfo) == 0x78); extern "C" { @@ -150,7 +196,7 @@ int APS5_VABI sceVdecswTrySyncDecodeOutput(std::uint64_t, OutputInfo*); int APS5_VABI sceVdecswSyncDecodeOutput(std::uint64_t, OutputInfo*); int APS5_VABI sceVdecswGetVp9PictureInfo(const OutputInfo*, Vp9PictureInfo*); int APS5_VABI sceVdecswGetAvcPictureInfo(const OutputInfo*, AvcPictureInfo*, AvcPictureInfo*); -int APS5_VABI sceVdecswGetHevcPictureInfo(const OutputInfo*, void*); +int APS5_VABI sceVdecswGetHevcPictureInfo(const OutputInfo*, HevcPictureInfo*); int APS5_VABI sceVdecswFinalizeDecodeSequence(std::uint64_t); int APS5_VABI sceVdecswResetDecoder(std::uint64_t); } @@ -158,11 +204,15 @@ int APS5_VABI sceVdecswResetDecoder(std::uint64_t); namespace { using namespace H264Fixture; +using namespace HevcFixture; using namespace Vp9Fixture; constexpr int OutputPending = static_cast(0x81510115u); constexpr int InputQueueEmpty = static_cast(0x81510116u); +constexpr std::uint32_t CodecAvc = 1; +constexpr std::uint32_t CodecHevc = 974921; constexpr std::uint32_t CodecVp9 = 2382845; +constexpr std::array HevcDisplayOrderUnits{0, 3, 2, 1, 5, 4}; void check(bool condition, const std::string& message) { if (!condition) throw std::runtime_error(message); @@ -173,12 +223,12 @@ struct Session { std::uint64_t decoder = 0; DecoderMemoryInfo memory{sizeof(DecoderMemoryInfo)}; - Session() { + explicit Session(std::uint32_t codec = CodecAvc, std::uint32_t profile = 100, std::uint32_t level = 42) { ComputeMemoryInfo compute{sizeof(ComputeMemoryInfo)}; check(sceVdecswQueryComputeMemoryInfo(&compute) == 0 && compute.cpuGpuMemorySize != 0, "compute memory query failed"); const ComputeConfigInfo computeConfig{sizeof(ComputeConfigInfo), 3, 3, true}; check(sceVdecswAllocateComputeQueue(&computeConfig, &compute, &queue) == 0, "compute queue allocation failed"); - DecoderConfigInfo config{sizeof(DecoderConfigInfo), 1, 1, 100, 42, static_cast(Width), static_cast(Height), 4, 3, queue, 0x3f, 0, true, {}}; + DecoderConfigInfo config{sizeof(DecoderConfigInfo), 1, codec, profile, level, static_cast(Width), static_cast(Height), 4, 3, queue, 0x3f, 0, true, {}}; check(sceVdecswQueryDecoderMemoryInfo(&config, &memory) == 0 && memory.maxFrameBufferSize != 0, "decoder memory query failed"); check(sceVdecswCreateDecoder(&config, &memory, &decoder) == 0, "decoder creation failed"); } @@ -245,6 +295,76 @@ void testDecode() { check(sceVdecswTrySyncDecodeInput(session.decoder, &result) == InputQueueEmpty, "a finalized decoder did not report an empty input queue"); } +void testHevc() { + Session session(CodecHevc, 1, 30); + const auto units = hevcUnits(HevcStream.data(), HevcStream.size()); + check(units.size() == HevcHashes.size(), "unexpected HEVC access unit count"); + std::vector buffer(session.memory.maxFrameBufferSize); + std::size_t pictures = 0; + bool ended = false; + const auto drain = [&] { + for (;;) { + const FrameBuffer frame{sizeof(FrameBuffer), buffer.data(), buffer.size()}; + check(sceVdecswSetDecodeOutput(session.decoder, &frame) == 0, "HEVC output buffer was rejected"); + OutputInfo output{sizeof(OutputInfo)}; + const auto status = sceVdecswTrySyncDecodeOutput(session.decoder, &output); + if (status == OutputPending) return; + check(status == 0, "HEVC output sync failed"); + if (!output.isValid) { + ended = true; + return; + } + check(pictures < HevcHashes.size() && output.codecType == CodecHevc && output.pictureCount == 1 && output.frameWidth == Width && output.frameHeight == Height, "unexpected HEVC picture geometry"); + HevcPictureInfo info{sizeof(HevcPictureInfo)}; + check(sceVdecswGetHevcPictureInfo(&output, &info) == 0 && info.isValid, "HEVC picture info missing"); + const auto unit = HevcDisplayOrderUnits[pictures]; + check(info.ptsData == 1000 + unit && info.dtsData == unit && info.attachedData == 0xa0 + unit, "HEVC pictures out of display order"); + check(info.idrPictureFlag == (pictures == 0 ? 1 : 0) && info.irapPictureFlag == (pictures == 0 ? 1 : 0), "HEVC random access flags differ"); + check(info.generalProfileIdc == 1 && info.generalLevelIdc == 30 && info.bitDepthLumaMinus8 == 0 && info.bitDepthChromaMinus8 == 0, "HEVC profile, level or bit depth differs"); + check(info.picWidthInLumaSamples == Width && info.picHeightInLumaSamples == Height && info.conformanceWindowFlag == 0 && info.frameCroppingFlag == 0, "HEVC picture geometry in the picture info is wrong"); + AvcPictureInfo avc{sizeof(AvcPictureInfo)}; + bool avcThrows = false; + try { + sceVdecswGetAvcPictureInfo(&output, &avc, nullptr); + } catch (const std::exception&) { + avcThrows = true; + } + check(avcThrows, "AVC picture info was returned for an HEVC picture"); + check(hashNv12(buffer.data(), output.framePitch, output.frameHeight) == HevcHashes[pictures], "HEVC picture " + std::to_string(pictures) + " differs from the reference"); + ++pictures; + } + }; + for (std::size_t unit = 0; unit < units.size(); ++unit) { + const InputData input{sizeof(InputData), units[unit].data(), units[unit].size(), 1000 + unit, unit, 0xa0 + unit}; + check(sceVdecswSetDecodeInput(session.decoder, &input) == 0, "HEVC decode input was rejected"); + InputResult consumed{sizeof(InputResult)}; + check(sceVdecswTrySyncDecodeInput(session.decoder, &consumed) == 0 && consumed.decodedAu == units[unit].data(), "an HEVC access unit was not consumed"); + drain(); + } + check(sceVdecswFinalizeDecodeSequence(session.decoder) == 0, "HEVC finalize failed"); + while (!ended) drain(); + check(pictures == HevcHashes.size(), "missing HEVC pictures after finalizing the sequence"); +} + +void testHevcCrop() { + Session session(CodecHevc, 1, 30); + std::vector buffer(session.memory.maxFrameBufferSize); + const FrameBuffer frame{sizeof(FrameBuffer), buffer.data(), buffer.size()}; + check(sceVdecswSetDecodeOutput(session.decoder, &frame) == 0, "cropped HEVC output buffer was rejected"); + const InputData input{sizeof(InputData), HevcCroppedStream.data(), HevcCroppedStream.size(), 1000, 0, 0xa0}; + check(sceVdecswSetDecodeInput(session.decoder, &input) == 0, "cropped HEVC decode input was rejected"); + InputResult consumed{sizeof(InputResult)}; + check(sceVdecswSyncDecodeInput(session.decoder, &consumed) == 0, "the cropped HEVC access unit was not consumed"); + check(sceVdecswFinalizeDecodeSequence(session.decoder) == 0, "cropped HEVC finalize failed"); + OutputInfo output{sizeof(OutputInfo)}; + check(sceVdecswSyncDecodeOutput(session.decoder, &output) == 0 && output.isValid && output.frameWidth == 64 && output.frameHeight == 40, "unexpected cropped HEVC picture geometry"); + HevcPictureInfo info{sizeof(HevcPictureInfo)}; + check(sceVdecswGetHevcPictureInfo(&output, &info) == 0 && info.isValid, "cropped HEVC picture info missing"); + check(info.picWidthInLumaSamples == 64 && info.picHeightInLumaSamples == 40, "cropped HEVC coded size differs"); + check(info.conformanceWindowFlag == 1 && info.confWinLeftOffset == 0 && info.confWinRightOffset == 0 && info.confWinTopOffset == 0 && info.confWinBottomOffset == 2, "HEVC conformance window differs"); + check(info.frameCroppingFlag == 1 && info.frameCropLeftOffset == 0 && info.frameCropRightOffset == 0 && info.frameCropTopOffset == 0 && info.frameCropBottomOffset == 2, "HEVC frame cropping differs"); +} + void testWithoutOutput() { Session session; const auto units = accessUnits(false); @@ -278,13 +398,14 @@ void testSyncWrappers() { check(sceVdecswFinalizeDecodeSequence(session.decoder) == 0, "finalize failed"); OutputInfo output{sizeof(OutputInfo)}; check(sceVdecswSyncDecodeOutput(session.decoder, &output) == 0 && output.isValid && output.isLastFrame, "sync output did not return the finalized picture"); + HevcPictureInfo hevc{sizeof(HevcPictureInfo)}; bool hevcThrows = false; try { - sceVdecswGetHevcPictureInfo(&output, &result); + sceVdecswGetHevcPictureInfo(&output, &hevc); } catch (const std::exception&) { hevcThrows = true; } - check(hevcThrows, "GetHevcPictureInfo must still throw"); + check(hevcThrows, "HEVC picture info was returned for an H.264 picture"); } struct Vp9Session { @@ -457,9 +578,29 @@ void testVp9PictureInfoOfAvcPicture() { } +void testHevcMain10() { + Session session(CodecHevc, 2, 30); + std::vector buffer(session.memory.maxFrameBufferSize); + const FrameBuffer frame{sizeof(FrameBuffer), buffer.data(), buffer.size()}; + check(sceVdecswSetDecodeOutput(session.decoder, &frame) == 0, "Main 10 HEVC output buffer was rejected"); + const InputData input{sizeof(InputData), HevcMain10Stream.data(), HevcMain10Stream.size(), 1000, 0, 0xa0}; + check(sceVdecswSetDecodeInput(session.decoder, &input) == 0, "Main 10 HEVC decode input was rejected"); + InputResult consumed{sizeof(InputResult)}; + check(sceVdecswSyncDecodeInput(session.decoder, &consumed) == 0, "the Main 10 HEVC access unit was not consumed"); + check(sceVdecswFinalizeDecodeSequence(session.decoder) == 0, "Main 10 HEVC finalize failed"); + OutputInfo output{sizeof(OutputInfo)}; + check(sceVdecswSyncDecodeOutput(session.decoder, &output) == 0 && output.isValid, "the Main 10 HEVC picture was not output"); + HevcPictureInfo info{sizeof(HevcPictureInfo)}; + check(sceVdecswGetHevcPictureInfo(&output, &info) == 0 && info.isValid, "Main 10 HEVC picture info missing"); + check(info.generalProfileIdc == 2 && info.bitDepthLumaMinus8 == 2 && info.bitDepthChromaMinus8 == 2, "Main 10 HEVC profile or bit depth differs"); +} + int main() { try { testDecode(); + testHevc(); + testHevcCrop(); + testHevcMain10(); testWithoutOutput(); testSyncWrappers(); testVp9PictureInfo(); diff --git a/core/libs/tests/GuestVideodec.cpp b/core/libs/tests/GuestVideodec.cpp index f4ad8e987e..894d6f96ee 100644 --- a/core/libs/tests/GuestVideodec.cpp +++ b/core/libs/tests/GuestVideodec.cpp @@ -83,8 +83,33 @@ struct InputData { std::uint64_t attachedData; }; +struct Decoder2ConfigInfo { + std::uint64_t thisSize; + std::uint32_t resourceType; + std::uint32_t codecType; + std::uint32_t profile; + std::uint32_t maxLevel; + std::int32_t maxFrameWidth; + std::int32_t maxFrameHeight; + std::int32_t maxDpbFrameCount; + std::uint32_t decodePipelineDepth; +}; + +struct Decoder2MemoryInfo { + std::uint64_t thisSize; + std::uint64_t cpuMemorySize; + void* cpuMemory; + std::uint64_t gpuMemorySize; + void* gpuMemory; + std::uint64_t cpuGpuMemorySize; + void* cpuGpuMemory; + std::uint64_t maxFrameBufferSize; + std::uint32_t frameBufferAlignment; +}; + static_assert(sizeof(PictureInfo) == 112 && sizeof(AvcInfo) == 36); static_assert(sizeof(ConfigInfo) == 40 && sizeof(ResourceInfo) == 56 && sizeof(Ctrl) == 24); +static_assert(sizeof(Decoder2ConfigInfo) == 40 && sizeof(Decoder2MemoryInfo) == 72); extern "C" { int APS5_VABI sceVideodecCreateDecoder(const ConfigInfo*, const ResourceInfo*, Ctrl*); @@ -92,6 +117,8 @@ int APS5_VABI sceVideodecDecode(Ctrl*, const InputData*, FrameBuffer*, PictureIn int APS5_VABI sceVideodecDeleteDecoder(Ctrl*); int APS5_VABI sceVideodecFlush(Ctrl*, FrameBuffer*, PictureInfo*); int APS5_VABI sceVideodecReset(Ctrl*); +int APS5_VABI sceVideodecQueryResourceInfo(const ConfigInfo*, ResourceInfo*); +int APS5_VABI sceVideodec2QueryDecoderMemoryInfo_nid_postfix(const Decoder2ConfigInfo*, Decoder2MemoryInfo*); } namespace { @@ -105,6 +132,7 @@ constexpr int ErrorAuSize = static_cast(0x80C10009); constexpr int ErrorAuPointer = static_cast(0x80C1000A); constexpr int ErrorFrameBufferSize = static_cast(0x80C1000B); constexpr int ErrorFrameBufferPointer = static_cast(0x80C1000C); +constexpr int ErrorConfigInfo = static_cast(0x80C1000E); constexpr int ErrorArgumentPointer = static_cast(0x80C1000F); constexpr std::uint32_t Pitch = 64; @@ -230,6 +258,50 @@ void testReset() { check(!picture.isValid, "a reset decoder produced a picture"); } +void testQueryResourceInfo(std::int32_t width, std::int32_t height, std::uint32_t profile, std::uint32_t level) { + const ConfigInfo config{sizeof(ConfigInfo), 1, profile, level, width, height, 4, 0}; + ResourceInfo resource{sizeof(ResourceInfo)}; + check(sceVideodecQueryResourceInfo(&config, &resource) == 0, "the resource query failed"); + const Decoder2ConfigInfo mapped{sizeof(Decoder2ConfigInfo), 1, 1, profile, level, width, height, 4, 1}; + Decoder2MemoryInfo memory{sizeof(Decoder2MemoryInfo)}; + check(sceVideodec2QueryDecoderMemoryInfo_nid_postfix(&mapped, &memory) == 0, "the Videodec2 memory query failed"); + check(resource.thisSize == sizeof(ResourceInfo), "the resource information size was changed"); + check(resource.cpuMemorySize == memory.cpuMemorySize, "the CPU memory size differs from libSceVideodec2"); + check(resource.cpuGpuMemorySize == memory.cpuGpuMemorySize, "the CPU-GPU memory size differs from libSceVideodec2"); + check(resource.maxFrameBufferSize == memory.maxFrameBufferSize, "the frame buffer size differs from libSceVideodec2"); + check(resource.frameBufferAlignment == memory.frameBufferAlignment, "the frame buffer alignment differs from libSceVideodec2"); +} + +void testQueryResourceInfo() { + testQueryResourceInfo(static_cast(Width), static_cast(CodedHeight), 100, 42); + testQueryResourceInfo(1920, 1080, 100, 42); + testQueryResourceInfo(3840, 2160, 100, 51); + const ConfigInfo config{sizeof(ConfigInfo), 1, 100, 42, static_cast(Width), + static_cast(CodedHeight), 4, 0}; + ResourceInfo resource{sizeof(ResourceInfo)}; + check(sceVideodecQueryResourceInfo(&config, &resource) == 0, "the resource query failed"); + check(resource.maxFrameBufferSize >= FrameBytes, "the frame buffer size does not hold a decoded picture"); + Ctrl ctrl{sizeof(Ctrl)}; + check(sceVideodecCreateDecoder(&config, &resource, &ctrl) == 0, "the queried resources were refused"); + check(sceVideodecDeleteDecoder(&ctrl) == 0, "the decoder was not deleted"); + check(sceVideodecQueryResourceInfo(nullptr, &resource) == ErrorArgumentPointer, "a null configuration was accepted"); + check(sceVideodecQueryResourceInfo(&config, nullptr) == ErrorArgumentPointer, "null resource information was accepted"); + const ResourceInfo sentinel{sizeof(ResourceInfo), 7, nullptr, 7, nullptr, 7, 7}; + auto refused = sentinel; + auto shortConfig = config; + shortConfig.thisSize = sizeof(ConfigInfo) - 8; + check(sceVideodecQueryResourceInfo(&shortConfig, &refused) == ErrorStructSize, "a short configuration was accepted"); + auto shortResource = sentinel; + shortResource.thisSize = sizeof(ResourceInfo) - 8; + check(sceVideodecQueryResourceInfo(&config, &shortResource) == ErrorStructSize, "short resource information was accepted"); + auto unsized = config; + unsized.maxFrameWidth = 0; + check(sceVideodecQueryResourceInfo(&unsized, &refused) == ErrorConfigInfo, "a configuration without a frame size was accepted"); + check(refused.cpuMemorySize == 7 && refused.cpuGpuMemorySize == 7 && refused.maxFrameBufferSize == 7 && refused.frameBufferAlignment == 7, + "a refused query wrote the resource information"); + check(shortResource.cpuMemorySize == 7 && shortResource.maxFrameBufferSize == 7, "a refused query wrote the resource information"); +} + } int main() { @@ -238,6 +310,7 @@ int main() { testDecodeCropped(); testErrors(); testReset(); + testQueryResourceInfo(); std::puts("Videodec tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/tests/GuestVideodec2.cpp b/core/libs/tests/GuestVideodec2.cpp index 58b3f5240e..565f4454b4 100644 --- a/core/libs/tests/GuestVideodec2.cpp +++ b/core/libs/tests/GuestVideodec2.cpp @@ -11,6 +11,7 @@ #include #include "prx/libc/include/General.hpp" #include "H264Fixture.hpp" +#include "HevcFixture.hpp" #include "Vp9Fixture.hpp" struct DecoderConfigInfo { @@ -119,78 +120,11 @@ int APS5_VABI sceVideodec2GetAvcPictureInfo_nid_postfix(const OutputInfo*, AvcPi namespace { using namespace H264Fixture; - +using namespace HevcFixture; constexpr std::uint32_t CodecHevc = 974921; constexpr std::uint32_t CodecVp9 = 2382845; -constexpr std::array HevcStream{ - 0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x10, 0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, - 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x1e, - 0x95, 0x90, 0x09, 0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, - 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x1e, 0xa0, 0x20, 0x82, 0x96, 0x59, 0x59, 0x2e, - 0x4c, 0xaf, 0x01, 0x68, 0x08, 0x00, 0x00, 0x03, 0x00, 0x08, 0x00, 0x00, 0x03, 0x00, 0xf0, 0x40, - 0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0xc1, 0x73, 0xd0, 0x89, 0x00, 0x00, 0x01, 0x28, 0x01, 0xaf, - 0x08, 0xe0, 0xbb, 0xcb, 0xc1, 0x71, 0x26, 0x0f, 0xbf, 0x6c, 0xcb, 0x4d, 0x2b, 0x2c, 0xf4, 0x08, - 0xe0, 0x17, 0xeb, 0x16, 0xcc, 0x07, 0xd8, 0xdd, 0x11, 0xf8, 0x5e, 0x73, 0x79, 0x57, 0xcb, 0x69, - 0xa3, 0x1c, 0x9c, 0x43, 0x14, 0xdb, 0x61, 0x0e, 0xc5, 0xc6, 0x9c, 0x9c, 0xf8, 0xf2, 0x07, 0x35, - 0xf1, 0x98, 0x6c, 0x1a, 0x86, 0x53, 0x5a, 0xcd, 0x4d, 0xda, 0xff, 0xfb, 0x01, 0xda, 0xc3, 0x61, - 0xb7, 0x8c, 0xae, 0xc9, 0x76, 0xe4, 0x05, 0xd6, 0xfd, 0xe4, 0xcd, 0x93, 0x2c, 0xb1, 0x41, 0x29, - 0xce, 0x22, 0xf7, 0x77, 0x64, 0x0f, 0x1f, 0xd2, 0xc3, 0x68, 0x32, 0x2f, 0x83, 0xd7, 0x53, 0xbe, - 0x23, 0xff, 0x8c, 0x46, 0x18, 0x33, 0x90, 0x28, 0xa3, 0xef, 0xd7, 0xb1, 0x46, 0x84, 0xa0, 0xc2, - 0xd4, 0xd9, 0xce, 0xdf, 0x0a, 0x05, 0xec, 0xad, 0x63, 0xa4, 0x78, 0x27, 0x80, 0x4e, 0x3b, 0x4c, - 0xa6, 0x42, 0xbc, 0xe3, 0x39, 0x4d, 0xc4, 0xd3, 0x44, 0xf1, 0x22, 0xd0, 0x64, 0x9f, 0xd0, 0x88, - 0x7f, 0x39, 0x6d, 0x06, 0xc7, 0x79, 0x75, 0x2a, 0x44, 0x3d, 0x21, 0x41, 0x53, 0x60, 0x8d, 0x08, - 0x9f, 0xd0, 0x34, 0xa6, 0x72, 0x70, 0x3b, 0xa9, 0xfa, 0x62, 0xb1, 0x19, 0x9b, 0x0b, 0xa2, 0xfc, - 0xb4, 0xb3, 0x2c, 0x58, 0xad, 0xc6, 0xf3, 0xea, 0xc4, 0xd0, 0xf9, 0xea, 0x9e, 0xa7, 0xa4, 0x87, - 0xb8, 0x19, 0x68, 0xf1, 0xc1, 0x6b, 0x87, 0xa5, 0x99, 0xa0, 0x69, 0x24, 0x28, 0x80, 0xba, 0xd2, - 0x57, 0xc6, 0xc0, 0x1e, 0x69, 0x1b, 0xce, 0x96, 0xcd, 0x85, 0x9d, 0x59, 0x32, 0x92, 0x71, 0xe7, - 0x15, 0xf2, 0x9b, 0x71, 0x4b, 0xd4, 0xb6, 0x99, 0xb0, 0x71, 0x9b, 0x9b, 0xdb, 0x33, 0x28, 0x86, - 0x5e, 0x44, 0xf3, 0x5d, 0x5d, 0x7b, 0x87, 0x6c, 0x99, 0x8a, 0x6d, 0xaf, 0xf8, 0xdd, 0x39, 0x14, - 0xc0, 0x10, 0x85, 0xaf, 0x3a, 0x77, 0x60, 0x8b, 0xb7, 0x5f, 0x67, 0x45, 0xb4, 0xa7, 0xe7, 0x44, - 0x77, 0x0a, 0xb9, 0xe1, 0xb2, 0xbe, 0xde, 0x1b, 0x5a, 0xb8, 0xc8, 0x9b, 0x1f, 0x15, 0x37, 0xdc, - 0xeb, 0x55, 0x12, 0x37, 0xa1, 0xab, 0x9c, 0xcd, 0xdd, 0x42, 0x5d, 0x90, 0x89, 0xc5, 0x99, 0x54, - 0x6e, 0x9a, 0xd2, 0x71, 0x07, 0xe3, 0x81, 0x20, 0xad, 0xd4, 0x7c, 0xad, 0x69, 0xaf, 0xc7, 0x2d, - 0x7c, 0x84, 0xf1, 0x5d, 0x43, 0x4a, 0x3c, 0xf6, 0xf8, 0x7e, 0x6a, 0xb2, 0xdd, 0x00, 0xda, 0x30, - 0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x30, 0x00, 0x00, 0x01, 0x02, 0x01, 0xd0, 0x19, 0x5f, 0x84, - 0x31, 0x84, 0x50, 0x62, 0xcd, 0x44, 0xa6, 0x4e, 0xbb, 0x2e, 0x87, 0xda, 0x21, 0x25, 0x1c, 0x13, - 0xfe, 0xc2, 0x97, 0xa3, 0xd5, 0xfd, 0xe2, 0x62, 0xd6, 0xe5, 0x50, 0x7a, 0xda, 0x98, 0x8d, 0xcf, - 0xbe, 0x40, 0x5d, 0x69, 0xb6, 0x5f, 0xdc, 0x32, 0x68, 0x05, 0x67, 0x70, 0xfd, 0xa0, 0xd3, 0xcd, - 0x88, 0xeb, 0x12, 0x2e, 0xac, 0xf5, 0x57, 0xa2, 0xc2, 0x81, 0x22, 0x6b, 0x8d, 0x69, 0x6d, 0xac, - 0x41, 0x05, 0x5b, 0x0b, 0x79, 0xe0, 0x86, 0x05, 0x86, 0x8b, 0x02, 0x51, 0x7d, 0x6e, 0x13, 0x63, - 0x36, 0xe6, 0x89, 0x22, 0x75, 0xd9, 0xb0, 0x39, 0x27, 0xcd, 0xac, 0x78, 0xe0, 0x3a, 0x6a, 0x17, - 0x15, 0x05, 0x83, 0x3a, 0xe9, 0x38, 0x7c, 0x75, 0x38, 0xf3, 0xd8, 0x42, 0x57, 0xa1, 0xef, 0x68, - 0xb8, 0xb5, 0x44, 0xfe, 0x4d, 0x66, 0xa1, 0x09, 0xc5, 0x62, 0xff, 0x87, 0x7e, 0xdb, 0x41, 0x6f, - 0x4d, 0x5f, 0x16, 0x49, 0xef, 0xca, 0xda, 0x67, 0x01, 0x29, 0x0b, 0xd6, 0xd8, 0xfc, 0x0b, 0x22, - 0xc8, 0x28, 0xff, 0xb4, 0xb3, 0xa9, 0xf8, 0x9b, 0x78, 0x7d, 0x33, 0xbf, 0xa6, 0x9b, 0x39, 0x64, - 0x57, 0x41, 0x1f, 0x27, 0x05, 0xfc, 0xcd, 0x44, 0xcd, 0xb2, 0xea, 0x79, 0xe0, 0x00, 0x00, 0x00, - 0x01, 0x46, 0x01, 0x50, 0x00, 0x00, 0x01, 0x02, 0x01, 0xe0, 0x44, 0x97, 0xe1, 0x8f, 0x40, 0x3d, - 0x73, 0xde, 0xdf, 0xf5, 0x1c, 0xff, 0xf6, 0xe2, 0x12, 0x16, 0x3d, 0xb6, 0x75, 0xad, 0x9e, 0xb5, - 0x2b, 0xe7, 0x4a, 0x40, 0xe9, 0x91, 0xd6, 0x86, 0x86, 0x2d, 0xcb, 0xe4, 0xb6, 0x58, 0x4a, 0x2d, - 0x7b, 0x1b, 0xdb, 0xc5, 0x48, 0x2d, 0x09, 0x4d, 0x32, 0x30, 0xde, 0xaa, 0x61, 0xc2, 0x27, 0x64, - 0x54, 0x71, 0x89, 0x32, 0x05, 0xc0, 0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x50, 0x00, 0x00, 0x01, - 0x00, 0x01, 0xe0, 0x24, 0xff, 0xfa, 0x2c, 0x6e, 0xdc, 0x78, 0x3d, 0x08, 0x6d, 0x3b, 0xcf, 0xcc, - 0xb6, 0x16, 0xeb, 0x9d, 0x5f, 0x46, 0x0b, 0x24, 0x0e, 0x97, 0x99, 0x06, 0x00, 0x00, 0x00, 0x01, - 0x46, 0x01, 0x30, 0x00, 0x00, 0x01, 0x02, 0x01, 0xd0, 0x28, 0x95, 0xd7, 0xdc, 0x41, 0xc0, 0x61, - 0x14, 0xd6, 0xee, 0xd4, 0xe8, 0x24, 0x3e, 0x7f, 0x82, 0x22, 0xb0, 0x20, 0xb9, 0xf1, 0x69, 0x9b, - 0xfe, 0xcc, 0xb7, 0x2e, 0xbd, 0xdb, 0x20, 0xfa, 0xe5, 0x97, 0xd9, 0x95, 0x1d, 0x7e, 0x49, 0x35, - 0x69, 0xdb, 0x40, 0xa3, 0x5c, 0x0f, 0x74, 0x6f, 0x00, 0x3d, 0x36, 0x05, 0xc0, 0xc4, 0x37, 0x15, - 0xf9, 0x01, 0x2d, 0xdf, 0xcb, 0x70, 0x3d, 0xfa, 0x47, 0xec, 0x80, 0x07, 0x48, 0x08, 0xc8, 0x54, - 0xd3, 0xe3, 0x00, 0xa3, 0xe4, 0xce, 0x40, 0x05, 0x0b, 0x51, 0x73, 0x56, 0x33, 0x82, 0xa8, 0x0a, - 0xc0, 0xd3, 0xc5, 0xf1, 0xeb, 0x32, 0x8e, 0x4e, 0xa7, 0x6f, 0xb2, 0x64, 0x43, 0x54, 0x71, 0xd4, - 0xaf, 0xc0, 0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x50, 0x00, 0x00, 0x01, 0x00, 0x01, 0xe0, 0x82, - 0x2f, 0x5f, 0xdc, 0x63, 0x70, 0x3f, 0xaf, 0x66, 0x75, 0x52, 0xe7, 0x7f, 0x62, 0xfb, 0x84, 0x9c, - 0xef, 0x60, 0x8f, 0xfa, 0x7e, 0x5f, 0x8b, 0x40, 0x15, 0x34, 0x69, 0xe2, 0xc4, 0xfc, 0xff, 0x53, - 0xb8, 0x1e, 0xc7, 0x20, 0x55, 0xdb, 0x5e, 0x05, 0xbd, 0x7a, 0x85, 0xb4, 0xb7, 0xc1, 0x8d, 0xdb, - 0x0f, 0xa6, 0xab, 0xb9, 0x00, 0x2a, 0xdb, 0x83, 0xa0, 0xb0, 0xc5, 0x76, 0x6e, 0xe1, 0xb8, 0x72, - 0xfd, 0x09, 0x61, 0x19, 0xe0, -}; - -constexpr std::array HevcHashes{ - 0x7291c31ee57c5065ull, 0x37b15adc47810098ull, 0xc84737b65a65b247ull, - 0xe39bc891f8286267ull, 0x734caf1ba961b3c2ull, 0x7deb44bd1ed30531ull, -}; - constexpr std::array AvcIpppStream{ 0x00, 0x00, 0x00, 0x01, 0x09, 0x10, 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0xc0, 0x0a, 0xda, 0x11, 0xec, 0x04, 0x40, 0x00, 0x00, 0x03, 0x00, 0x40, 0x00, 0x00, 0x0f, 0x03, 0xc4, 0x89, 0xa8, 0x00, @@ -235,24 +169,6 @@ constexpr std::array AvcIpppStream{ 0x43, 0xd5, 0x7b, 0x00, 0x00, 0x00, 0x01, 0x09, 0x30, 0x00, 0x00, 0x01, 0x41, 0x9a, 0x60, 0x35, 0xc6, 0xc0}; -constexpr std::array HevcMain10Stream{ - 0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x10, 0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, - 0xff, 0x02, 0x20, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x1e, - 0x95, 0x98, 0x09, 0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x02, 0x20, 0x00, 0x00, 0x03, 0x00, - 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x1e, 0xa0, 0x20, 0x82, 0x93, 0x65, 0x95, 0x9a, - 0xe4, 0xca, 0xf0, 0x16, 0x80, 0x80, 0x00, 0x00, 0x03, 0x00, 0x80, 0x00, 0x00, 0x0f, 0x04, 0x00, - 0x00, 0x00, 0x01, 0x44, 0x01, 0xc1, 0x73, 0xd0, 0x89, 0x00, 0x00, 0x01, 0x28, 0x01, 0xaf, 0x08, - 0x60, 0xf2, 0xfb, 0x04, 0xef, 0x67, 0x67, 0xdc, 0xc7, 0x8e, 0xc7, 0x42, 0x63, 0xeb, 0x7d, 0x28, - 0x73, 0x95, 0x14, 0x0c, 0x71, 0x10, 0x8b, 0x65, 0x04, 0xbd, 0x03, 0xe6, 0x04, 0x35, 0xb9, 0x9b, - 0xec, 0x60, 0x2e, 0xea, 0xcf, 0xd0, 0xdd, 0x2d, 0x3a, 0x62, 0x33, 0x9d, 0x7a, 0x74, 0xb0, 0xeb, - 0x0c, 0x06, 0xe6, 0xa5, 0x30, 0xa7, 0xea, 0xed, 0xc2, 0xa5, 0x8e, 0x7c, 0xda, 0x1b, 0x9d, 0xe4, - 0xac, 0x2b, 0x38, 0xdb, 0x9c, 0xe8, 0x51, 0x34, 0x46, 0xd2, 0x04, 0x1a, 0x77, 0x87, 0x7a, 0x19, - 0x70, 0xf9, 0x4f, 0xcb, 0x03, 0x3c, 0x74, 0x25, 0xb9, 0x92, 0x3b, 0x95, 0xe9, 0xd8, 0xaa, 0x9b, - 0x6b, 0x9b, 0x1f, 0x70, 0x25, 0x2a, 0xe7, 0x6c, 0xc3, 0x7e, 0x55, 0x39, 0xf5, 0x06, 0x5a, 0x23, - 0xb1, 0x2e, 0xf6, 0x01, 0xac, 0x71, 0xb9, 0x20, 0xb4, 0xef, 0x1d, 0x42, 0x7e, 0x0e, 0x01, 0x1d, - 0xaf, 0x0a, 0x13, 0x04, 0x8e, 0x03, 0x4d, 0x1e, 0xc4, 0xe0, -}; - void check(bool condition, const std::string& message) { if (!condition) throw std::runtime_error(message); } @@ -307,17 +223,6 @@ void testFailures() { check(sceVideodec2DeleteDecoder_nid_postfix(handle) == 0, "delete failed"); } -std::vector> hevcUnits(const std::uint8_t* stream, std::size_t size) { - std::vector starts; - for (std::size_t index = 0; index + 4 < size; ++index) { - if (stream[index] == 0 && stream[index + 1] == 0 && stream[index + 2] == 0 && stream[index + 3] == 1 && ((stream[index + 4] >> 1u) & 0x3fu) == 35) starts.push_back(index); - } - starts.push_back(size); - std::vector> units; - for (std::size_t unit = 0; unit + 1 < starts.size(); ++unit) units.emplace_back(stream + starts[unit], stream + starts[unit + 1]); - return units; -} - using Vp9Fixture::Vp9Hashes; using Vp9Fixture::ivfFrames; diff --git a/core/libs/tests/GuestVoice.cpp b/core/libs/tests/GuestVoice.cpp index cb12940acb..2b9e024dcc 100644 --- a/core/libs/tests/GuestVoice.cpp +++ b/core/libs/tests/GuestVoice.cpp @@ -34,6 +34,11 @@ int APS5_VABI sceVoiceResumePortAll(void); int APS5_VABI sceVoiceGetResourceInfo(VoiceResourceInfo*); int APS5_VABI sceVoiceEnableChat(void); int APS5_VABI sceVoiceDisableChat(void); +int APS5_VABI sceVoiceInitHQ(const VoiceInitParam*, std::int32_t); +int APS5_VABI sceVoiceVADSetVersion(std::int32_t); +int APS5_VABI sceVoiceSetMuteFlagAll(bool); +int APS5_VABI sceVoiceSetBitRate(std::uint32_t, std::uint32_t); +int APS5_VABI sceVoiceUpdatePort(std::uint32_t, const VoicePortParam*); } namespace { @@ -70,6 +75,9 @@ void TestNotInitialized() { Require(std::memcmp(&info, &sentinel, sizeof(info)) == 0); Require(sceVoicePausePortAll() == NotInitialized); Require(sceVoiceResumePortAll() == NotInitialized); + Require(sceVoiceSetMuteFlagAll(true) == NotInitialized); + Require(sceVoiceSetBitRate(0, 16000) == NotInitialized); + Require(sceVoiceUpdatePort(0, nullptr) == NotInitialized); } std::uint32_t CreatePort(std::int32_t type, float volume) { @@ -139,6 +147,24 @@ int main() { Require(sceVoiceSetThreadsParams(&threads) == invalidArgument); threads.mode = 1; RequireThrows([&] { sceVoiceSetThreadsParams(&threads); }); + VoiceThreadsParams scheduled{1, 700, 0x7f, 700, 0, 0x7f, 700, 0, 0x7f}; + Require(sceVoiceSetThreadsParams(&scheduled) == 0); + scheduled = {1, 256, 0x1fff, 767, 0, 1, 512, 0, 0x1000}; + Require(sceVoiceSetThreadsParams(&scheduled) == 0); + for (const auto priority : {255, 768, 0, -1}) { + for (int slot = 0; slot < 3; ++slot) { + VoiceThreadsParams outside{1, 700, 0x7f, 700, 0, 0x7f, 700, 0, 0x7f}; + (slot == 0 ? outside.firstPriority : slot == 1 ? outside.secondPriority : outside.thirdPriority) = priority; + RequireThrows([&] { sceVoiceSetThreadsParams(&outside); }); + } + } + for (const std::uint64_t affinity : {std::uint64_t{0}, std::uint64_t{0x2000}, std::uint64_t{0x3fff}, UINT64_MAX}) { + for (int slot = 0; slot < 3; ++slot) { + VoiceThreadsParams outside{1, 700, 0x7f, 700, 0, 0x7f, 700, 0, 0x7f}; + (slot == 0 ? outside.firstAffinity : slot == 1 ? outside.secondAffinity : outside.thirdAffinity) = affinity; + RequireThrows([&] { sceVoiceSetThreadsParams(&outside); }); + } + } threads.mode = 0; Require(sceVoiceStart(nullptr) == invalidArgument); Require(sceVoiceStart(&start) == invalidArgument); @@ -155,6 +181,8 @@ int main() { start.mem_size = 0x40000; Require(sceVoiceSetThreadsParams(nullptr) == invalidArgument); Require(sceVoiceSetThreadsParams(&threads) == static_cast(0x804e080bu)); + scheduled = {1, 700, 0x7f, 700, 0, 0x7f, 700, 0, 0x7f}; + Require(sceVoiceSetThreadsParams(&scheduled) == static_cast(0x804e080bu)); Require(sceVoiceGetResourceInfo(nullptr) == invalidArgument); struct ResourceOutput { VoiceResourceInfo info; @@ -220,6 +248,56 @@ int main() { Require(sceVoiceReadFromOPort(output, data, &size) == 0 && size == 0); Require(sceVoiceDisconnectIPortFromOPort(input, output) == 0); + Require(sceVoiceGetMuteFlag(input, &muted) == 0 && !muted); + Require(sceVoiceSetMuteFlagAll(true) == 0); + Require(sceVoiceGetMuteFlag(input, &muted) == 0 && muted); + Require(sceVoiceSetMuteFlagAll(false) == 0); + Require(sceVoiceGetMuteFlag(input, &muted) == 0 && !muted); + Require(sceVoiceGetMuteFlag(output, &muted) == 0 && muted); + + std::uint32_t portBitrate = 0; + Require(sceVoiceSetBitRate(input, 7300) == 0); + Require(sceVoiceGetBitRate(input, &portBitrate) == 0 && portBitrate == 7300); + VoicePortInfo rateInfo{}; + Require(sceVoiceGetPortInfo(input, &rateInfo) == 0 && rateInfo.frame_size == 19); + Require(sceVoiceSetBitRate(output + 1, 7300) == portNotFound); + Require(sceVoiceSetBitRate(128, 7300) == invalidPort); + RequireThrows([&] { sceVoiceSetBitRate(output, 7300); }); + RequireThrows([&] { sceVoiceSetBitRate(input, 7301); }); + RequireThrows([&] { sceVoiceSetBitRate(input, 0x80000000u); }); + Require(sceVoiceGetBitRate(input, &portBitrate) == 0 && portBitrate == 7300); + + VoicePortParam update{}; + update.port_type = 2; + update.mute = 1; + update.volume = 0.75f; + update.voice.bitrate = 14400; + Require(sceVoiceUpdatePort(input, &update) == 0); + Require(sceVoiceGetMuteFlag(input, &muted) == 0 && muted); + Require(sceVoiceGetVolume(input, &volume) == 0 && volume == 0.75f); + Require(sceVoiceGetBitRate(input, &portBitrate) == 0 && portBitrate == 14400); + Require(sceVoiceGetPortInfo(input, &rateInfo) == 0 && rateInfo.frame_size == 27); + update.voice.bitrate = 1; + RequireThrows([&] { sceVoiceUpdatePort(input, &update); }); + Require(sceVoiceGetBitRate(input, &portBitrate) == 0 && portBitrate == 14400); + update.voice.bitrate = 14400; + update.port_type = 3; + RequireThrows([&] { sceVoiceUpdatePort(input, &update); }); + RequireThrows([&] { sceVoiceUpdatePort(input, nullptr); }); + Require(sceVoiceUpdatePort(output + 1, &update) == portNotFound); + Require(sceVoiceUpdatePort(128, &update) == invalidPort); + VoicePortParam pcm{}; + pcm.port_type = 3; + pcm.volume = std::numeric_limits::quiet_NaN(); + Require(sceVoiceUpdatePort(output, &pcm) == 0); + Require(sceVoiceGetMuteFlag(output, &muted) == 0 && !muted); + Require(sceVoiceGetVolume(output, &volume) == 0 && volume == 0.0f); + pcm.volume = 0.25f; + pcm.mute = 1; + Require(sceVoiceUpdatePort(output, &pcm) == 0); + + Require(sceVoiceSetMuteFlagAll(true) == 0); + Require(sceVoiceDeletePort(input) == 0); Require(sceVoiceResetPort(input) == portNotFound); Require(sceVoicePausePort(input) == portNotFound); @@ -406,4 +484,17 @@ int main() { } Require(sceVoiceEnd_nid_postfix() == 0); + Require(sceVoiceVADSetVersion(0) == 0 && sceVoiceVADSetVersion(1) == 0); + Require(sceVoiceVADSetVersion(2) == invalidArgument && sceVoiceVADSetVersion(-1) == invalidArgument); + init.app_type = 0x10000000; + start.mem_size = 0x40000; + Require(sceVoiceInitHQ(nullptr, 100) == invalidArgument && sceVoiceInitHQ(&init, 0) == invalidArgument); + Require(sceVoiceInitHQ(&init, 100) == 0); + Require(sceVoiceInitHQ(&init, 100) == AlreadyInitialized); + Require(sceVoiceInit(&init, 100) == AlreadyInitialized); + Require(sceVoiceStart(&start) == 0); + Require(sceVoiceEnd_nid_postfix() == 0); + Require(sceVoiceInit(&init, 100) == 0); + Require(sceVoiceInitHQ(&init, 100) == AlreadyInitialized); + Require(sceVoiceEnd_nid_postfix() == 0); } diff --git a/core/libs/tests/GuestVorbisDec.cpp b/core/libs/tests/GuestVorbisDec.cpp index 80bce8c123..d34eb5d9ad 100644 --- a/core/libs/tests/GuestVorbisDec.cpp +++ b/core/libs/tests/GuestVorbisDec.cpp @@ -1,99 +1,649 @@ -#include "SceTypes.hpp" +#include "prx/libSceVorbisDec/include/GuestVorbisFile.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +#include +#include +#include +#include #include #include #include +#include #include +#include + +using namespace VorbisDec; extern "C" { -int APS5_VABI sceVorbisDecOpenCallbacks(void); -int APS5_VABI sceVorbisDecClear(void); -int APS5_VABI sceVorbisDecBitrate(void); -int APS5_VABI sceVorbisDecBitrateInstant(void); -int APS5_VABI sceVorbisDecStreams(void); -int APS5_VABI sceVorbisDecSeekable(void); -int APS5_VABI sceVorbisDecSerialNumber(void); -int APS5_VABI sceVorbisDecRawTotal(void); -int APS5_VABI sceVorbisDecPcmTotal(void); -int APS5_VABI sceVorbisDecTimeTotal(void); -int APS5_VABI sceVorbisDecRawSeek(void); -int APS5_VABI sceVorbisDecPcmSeek(void); -int APS5_VABI sceVorbisDecPcmSeekPage(void); -int APS5_VABI sceVorbisDecTimeSeek(void); -int APS5_VABI sceVorbisDecTimeSeekPage(void); -int APS5_VABI sceVorbisDecRawSeekLap(void); -int APS5_VABI sceVorbisDecPcmSeekLap(void); -int APS5_VABI sceVorbisDecPcmSeekPageLap(void); -int APS5_VABI sceVorbisDecTimeSeekLap(void); -int APS5_VABI sceVorbisDecTimeSeekPageLap(void); -int APS5_VABI sceVorbisDecRawTell(void); -int APS5_VABI sceVorbisDecPcmTell(void); -int APS5_VABI sceVorbisDecTimeTell(void); -int APS5_VABI sceVorbisDecInfo(void); -int APS5_VABI sceVorbisDecComment(void); -int APS5_VABI sceVorbisDecRead(void); -int APS5_VABI sceVorbisDecReadFloat(void); -int APS5_VABI sceVorbisDecReadFilter(void); -int APS5_VABI sceVorbisDecCrossLap(void); -int APS5_VABI sceVorbisDecHalfrate(void); -int APS5_VABI sceVorbisDecHalfrateP(void); +int APS5_VABI sceVorbisDecOpenCallbacks(void* datasource, void* vf, const char* initial, std::int64_t ibytes, GuestCallbacks callbacks); +int APS5_VABI sceVorbisDecClear(void* vf); +std::int64_t APS5_VABI sceVorbisDecBitrate(void* vf, int link); +std::int64_t APS5_VABI sceVorbisDecBitrateInstant(void* vf); +std::int64_t APS5_VABI sceVorbisDecStreams(void* vf); +std::int64_t APS5_VABI sceVorbisDecSeekable(void* vf); +std::int64_t APS5_VABI sceVorbisDecSerialNumber(void* vf, int link); +std::int64_t APS5_VABI sceVorbisDecRawTotal(void* vf, int link); +std::int64_t APS5_VABI sceVorbisDecPcmTotal(void* vf, int link); +double APS5_VABI sceVorbisDecTimeTotal(void* vf, int link); +int APS5_VABI sceVorbisDecRawSeek(void* vf, std::int64_t position); +int APS5_VABI sceVorbisDecPcmSeek(void* vf, std::int64_t position); +int APS5_VABI sceVorbisDecPcmSeekPage(void* vf, std::int64_t position); +int APS5_VABI sceVorbisDecTimeSeek(void* vf, double position); +int APS5_VABI sceVorbisDecTimeSeekPage(void* vf, double position); +int APS5_VABI sceVorbisDecRawSeekLap(void* vf, std::int64_t position); +int APS5_VABI sceVorbisDecPcmSeekLap(void* vf, std::int64_t position); +int APS5_VABI sceVorbisDecPcmSeekPageLap(void* vf, std::int64_t position); +int APS5_VABI sceVorbisDecTimeSeekLap(void* vf, double position); +int APS5_VABI sceVorbisDecTimeSeekPageLap(void* vf, double position); +std::int64_t APS5_VABI sceVorbisDecRawTell(void* vf); +std::int64_t APS5_VABI sceVorbisDecPcmTell(void* vf); +double APS5_VABI sceVorbisDecTimeTell(void* vf); +GuestInfo* APS5_VABI sceVorbisDecInfo(void* vf, int link); +GuestComment* APS5_VABI sceVorbisDecComment(void* vf, int link); +std::int64_t APS5_VABI sceVorbisDecRead(void* vf, char* buffer, int length, int bigEndian, int word, int isSigned, int* bitstream); +std::int64_t APS5_VABI sceVorbisDecReadFloat(void* vf, float*** pcmChannels, int samples, int* bitstream); +std::int64_t APS5_VABI sceVorbisDecReadFilter(void* vf, char* buffer, int length, int bigEndian, int word, int isSigned, int* bitstream, GuestFilter filter, void* filterParam); +int APS5_VABI sceVorbisDecCrossLap(void* first, void* second); +int APS5_VABI sceVorbisDecHalfrate(void* vf, int flag); +int APS5_VABI sceVorbisDecHalfrateP(void* vf); int APS5_VABI sceVorbisDecGetDecodePriority(void); int APS5_VABI sceVorbisDecSetDecodePriority(void); int APS5_VABI sceVorbisDecGetLastDecodeError(void); } -template -static bool Throws(TCall call) { +namespace { + +constexpr int FirstSerial = 0x1234abcd; +constexpr int SecondSerial = -0x61c88647; +constexpr int FirstChannels = 3; +constexpr int SecondChannels = 2; +constexpr long FirstRate = 44100; +constexpr long SecondRate = 22050; +constexpr long FirstSamples = 44100; +constexpr long SecondSamples = 11025; +constexpr std::int64_t DecodedBytes = (FirstSamples * FirstChannels + SecondSamples * SecondChannels) * 2; +constexpr unsigned char Pattern = 0xA5; + +void Check(bool value, int line) { + if (value) return; + std::fprintf(stderr, "VorbisDec check failed at line %d\n", line); + std::abort(); +} +#define Require(value) Check((value), __LINE__) + +template +bool Throws(TCall call) { try { call(); - } catch (const std::runtime_error&) { + } catch (const TException&) { return true; } return false; } -static void Require(bool value, const char* what) { - if (value) return; - std::printf("VorbisDec test failed: %s\n", what); - std::abort(); +struct Source { + const std::vector* bytes = nullptr; + std::size_t position = 0; + int closes = 0; + void* probe = nullptr; + std::int64_t probed = -1; + bool throwOnRead = false; + bool throwOnClose = false; +}; + +std::size_t ReadBytes(Source& source, void* buffer, std::size_t size, std::size_t count) { + if (source.throwOnRead) throw std::runtime_error("read callback"); + if (source.probe) source.probed = sceVorbisDecStreams(source.probe); + if (size == 0) return 0; + const std::size_t items = std::min(count, (source.bytes->size() - source.position) / size); + if (items != 0) std::memcpy(buffer, source.bytes->data() + source.position, items * size); + source.position += items * size; + return items; +} + +int SeekBytes(Source& source, std::int64_t offset, int whence) { + const auto size = static_cast(source.bytes->size()); + const std::int64_t base = whence == SEEK_SET ? 0 : whence == SEEK_CUR ? static_cast(source.position) : size; + if (offset < -base || offset > size - base) return -1; + source.position = static_cast(base + offset); + return 0; +} + +std::size_t APS5_VABI GuestRead(void* buffer, std::size_t size, std::size_t count, void* source) { + return ReadBytes(*static_cast(source), buffer, size, count); +} + +int APS5_VABI GuestSeek(void* source, std::int64_t offset, int whence) { + return SeekBytes(*static_cast(source), offset, whence); +} + +int APS5_VABI GuestClose(void* source) { + auto& closing = *static_cast(source); + ++closing.closes; + if (closing.throwOnClose) throw std::runtime_error("close callback"); + return 0; +} + +std::int64_t APS5_VABI GuestTell(void* source) { + return static_cast(static_cast(source)->position); +} + +std::size_t HostRead(void* buffer, std::size_t size, std::size_t count, void* source) { + return ReadBytes(*static_cast(source), buffer, size, count); +} + +int HostSeek(void* source, ogg_int64_t offset, int whence) { + return SeekBytes(*static_cast(source), offset, whence); +} + +int HostClose(void* source) { + ++static_cast(source)->closes; + return 0; +} + +long HostTell(void* source) { + return static_cast(static_cast(source)->position); +} + +constexpr GuestCallbacks SeekableCallbacks{GuestRead, GuestSeek, GuestClose, GuestTell}; +constexpr GuestCallbacks StreamingCallbacks{GuestRead, nullptr, GuestClose, nullptr}; +const ov_callbacks HostSeekableCallbacks{HostRead, HostSeek, HostClose, HostTell}; +const ov_callbacks HostStreamingCallbacks{HostRead, nullptr, HostClose, nullptr}; + +struct GuestFile { + alignas(16) std::array bytes; + + GuestFile() { bytes.fill(Pattern); } + + void* Get() { return bytes.data(); } + + bool Untouched() const { + return std::all_of(bytes.begin(), bytes.end(), [](unsigned char value) { return value == Pattern; }); + } +}; + +struct FilterLog { + int calls = 0; + std::int64_t firstChannels = 0; + std::int64_t lastChannels = 0; + std::int64_t samples = 0; +}; + +void Halve(float** pcm, std::int64_t channels, std::int64_t samples) { + for (std::int64_t channel = 0; channel < channels; ++channel) + for (std::int64_t index = 0; index < samples; ++index) pcm[channel][index] *= 0.5f; +} + +void APS5_VABI GuestHalve(float** pcm, std::int64_t channels, std::int64_t samples, void* param) { + auto* log = static_cast(param); + if (log->calls++ == 0) log->firstChannels = channels; + log->lastChannels = channels; + log->samples += samples; + Halve(pcm, channels, samples); +} + +void HostHalve(float** pcm, long channels, long samples, void*) { + Halve(pcm, channels, samples); +} + +void AppendPage(std::vector& out, const ogg_page& page) { + out.insert(out.end(), page.header, page.header + page.header_len); + out.insert(out.end(), page.body, page.body + page.body_len); +} + +void EncodeLink(std::vector& out, int serial, int channels, long rate, long samples, const char* title) { + vorbis_info info; + vorbis_info_init(&info); + Require(vorbis_encode_init_vbr(&info, channels, rate, 0.1f) == 0); + vorbis_comment comment; + vorbis_comment_init(&comment); + vorbis_comment_add_tag(&comment, "TITLE", title); + vorbis_comment_add_tag(&comment, "ARTIST", "AnyPS5"); + vorbis_dsp_state dsp; + Require(vorbis_analysis_init(&dsp, &info) == 0); + vorbis_block block; + vorbis_block_init(&dsp, &block); + ogg_stream_state stream; + ogg_stream_init(&stream, serial); + ogg_packet identification; + ogg_packet comments; + ogg_packet codebooks; + vorbis_analysis_headerout(&dsp, &comment, &identification, &comments, &codebooks); + ogg_stream_packetin(&stream, &identification); + ogg_stream_packetin(&stream, &comments); + ogg_stream_packetin(&stream, &codebooks); + ogg_page page; + while (ogg_stream_flush(&stream, &page) != 0) AppendPage(out, page); + for (long done = 0;;) { + const long count = std::min(1024L, samples - done); + if (count > 0) { + float** buffer = vorbis_analysis_buffer(&dsp, static_cast(count)); + for (int channel = 0; channel < channels; ++channel) { + const double frequency = 220.0 * (channel + 1) + 37.0 * serial / 1e9; + for (long index = 0; index < count; ++index) { + buffer[channel][index] = static_cast(0.3 * std::sin(6.283185307179586 * frequency * static_cast(done + index) / static_cast(rate))); + } + } + } + vorbis_analysis_wrote(&dsp, static_cast(count)); + done += count; + while (vorbis_analysis_blockout(&dsp, &block) == 1) { + vorbis_analysis(&block, nullptr); + vorbis_bitrate_addblock(&block); + ogg_packet packet; + while (vorbis_bitrate_flushpacket(&dsp, &packet) == 1) { + ogg_stream_packetin(&stream, &packet); + while (ogg_stream_pageout_fill(&stream, &page, 1024) != 0) AppendPage(out, page); + } + } + if (count == 0) break; + } + while (ogg_stream_flush(&stream, &page) != 0) AppendPage(out, page); + ogg_stream_clear(&stream); + vorbis_block_clear(&block); + vorbis_dsp_clear(&dsp); + vorbis_comment_clear(&comment); + vorbis_info_clear(&info); +} + +std::int64_t RequireSameRead(void* vf, OggVorbis_File& reference, int bigEndian = 0, int word = 2, int isSigned = 1) { + std::array guest{}; + std::array host{}; + int guestStream = -1; + int hostStream = -1; + const std::int64_t got = sceVorbisDecRead(vf, guest.data(), static_cast(guest.size()), bigEndian, word, isSigned, &guestStream); + const long expected = ov_read(&reference, host.data(), static_cast(host.size()), bigEndian, word, isSigned, &hostStream); + Require(got == expected); + Require(got <= 0 || (guestStream == hostStream && std::memcmp(guest.data(), host.data(), static_cast(got)) == 0)); + return got; +} + +std::int64_t DecodeTogether(void* vf, OggVorbis_File& reference) { + std::int64_t total = 0; + for (;;) { + const std::int64_t got = RequireSameRead(vf, reference); + Require(got >= 0 || got == OV_HOLE); + if (got == 0) return total; + if (got > 0) total += got; + } +} + +void RequireSameTells(void* vf, OggVorbis_File& reference) { + Require(sceVorbisDecPcmTell(vf) == ov_pcm_tell(&reference)); + Require(sceVorbisDecRawTell(vf) == ov_raw_tell(&reference)); + Require(sceVorbisDecTimeTell(vf) == ov_time_tell(&reference)); +} + +void TestSeekable(const std::vector& stream) { + GuestFile guest; + GuestFile probe; + void* vf = guest.Get(); + Source source{&stream}; + source.probe = probe.Get(); + Require(sceVorbisDecOpenCallbacks(&source, vf, nullptr, 0, SeekableCallbacks) == 0); + Require(source.probed == 0 && source.closes == 0); + Source referenceSource{&stream}; + OggVorbis_File reference{}; + Require(ov_open_callbacks(&referenceSource, &reference, nullptr, 0, HostSeekableCallbacks) == 0); + + Require(sceVorbisDecStreams(vf) == 2 && sceVorbisDecSeekable(vf) == 1); + Require(sceVorbisDecSerialNumber(vf, 0) == FirstSerial && sceVorbisDecSerialNumber(vf, 1) == SecondSerial); + Require(sceVorbisDecSerialNumber(vf, -1) == FirstSerial && sceVorbisDecSerialNumber(vf, 7) == SecondSerial); + Require(sceVorbisDecPcmTotal(vf, 0) == FirstSamples && sceVorbisDecPcmTotal(vf, 1) == SecondSamples); + Require(sceVorbisDecPcmTotal(vf, -1) == FirstSamples + SecondSamples && sceVorbisDecPcmTotal(vf, 2) == OV_EINVAL); + Require(sceVorbisDecRawTotal(vf, -1) == ov_raw_total(&reference, -1) && sceVorbisDecRawTotal(vf, -1) > 0); + Require(sceVorbisDecRawTotal(vf, 0) == ov_raw_total(&reference, 0) && sceVorbisDecRawTotal(vf, 1) == ov_raw_total(&reference, 1)); + Require(sceVorbisDecTimeTotal(vf, 0) == 1.0 && sceVorbisDecTimeTotal(vf, 1) == 0.5 && sceVorbisDecTimeTotal(vf, -1) == 1.5); + Require(sceVorbisDecBitrate(vf, -1) == ov_bitrate(&reference, -1) && sceVorbisDecBitrate(vf, 1) == ov_bitrate(&reference, 1)); + Require(sceVorbisDecBitrate(vf, -1) > 0 && sceVorbisDecBitrate(vf, 2) == OV_EINVAL); + Require(sceVorbisDecBitrateInstant(vf) == OV_FALSE); + + GuestInfo* first = sceVorbisDecInfo(vf, 0); + GuestInfo* second = sceVorbisDecInfo(vf, 1); + Require(first && second && first != second && sceVorbisDecInfo(vf, 0) == first); + Require(sceVorbisDecInfo(vf, -1) == first && sceVorbisDecInfo(vf, 2) == nullptr); + Require(first->version == 0 && first->channels == FirstChannels && first->rate == FirstRate); + Require(second->version == 0 && second->channels == SecondChannels && second->rate == SecondRate); + for (int link = 0; link < 2; ++link) { + const GuestInfo* info = sceVorbisDecInfo(vf, link); + const vorbis_info* host = ov_info(&reference, link); + Require(info->bitrate_upper == host->bitrate_upper && info->bitrate_nominal == host->bitrate_nominal); + Require(info->bitrate_lower == host->bitrate_lower && info->bitrate_window == host->bitrate_window && info->codec_setup != nullptr); + } + const GuestComment* comment = sceVorbisDecComment(vf, 1); + Require(comment && comment->comments == 2 && comment->comment_lengths[0] == 12 && comment->comment_lengths[1] == 13); + Require(std::strcmp(comment->user_comments[0], "TITLE=second") == 0 && std::strcmp(comment->user_comments[1], "ARTIST=AnyPS5") == 0); + Require(std::strcmp(comment->vendor, ov_comment(&reference, 1)->vendor) == 0); + Require(sceVorbisDecComment(vf, -1) == sceVorbisDecComment(vf, 0) && sceVorbisDecComment(vf, 2) == nullptr); + Require(std::strcmp(sceVorbisDecComment(vf, 0)->user_comments[0], "TITLE=first") == 0); + + const std::int64_t firstRead = RequireSameRead(vf, reference); + Require(firstRead > 0 && sceVorbisDecBitrateInstant(vf) == ov_bitrate_instant(&reference)); + Require(sceVorbisDecInfo(vf, -1) == first); + Require(firstRead + DecodeTogether(vf, reference) == DecodedBytes); + Require(sceVorbisDecPcmTell(vf) == FirstSamples + SecondSamples && sceVorbisDecTimeTell(vf) == 1.5); + RequireSameTells(vf, reference); + Require(sceVorbisDecInfo(vf, -1) == second && sceVorbisDecSerialNumber(vf, -1) == SecondSerial); + + Require(sceVorbisDecPcmSeek(vf, 0) == 0 && ov_pcm_seek(&reference, 0) == 0); + Require(DecodeTogether(vf, reference) == DecodedBytes); + + Require(sceVorbisDecPcmSeek(vf, 1000) == 0 && ov_pcm_seek(&reference, 1000) == 0); + Require(sceVorbisDecPcmTell(vf) == 1000); + RequireSameTells(vf, reference); + Require(RequireSameRead(vf, reference) > 0 && RequireSameRead(vf, reference, 1, 2, 0) > 0 && RequireSameRead(vf, reference, 0, 1, 0) > 0); + Require(sceVorbisDecPcmSeekPage(vf, FirstSamples + 3000) == ov_pcm_seek_page(&reference, FirstSamples + 3000)); + RequireSameTells(vf, reference); + Require(RequireSameRead(vf, reference) > 0 && sceVorbisDecInfo(vf, -1) == second); + Require(sceVorbisDecTimeSeek(vf, 0.25) == 0 && ov_time_seek(&reference, 0.25) == 0); + Require(sceVorbisDecPcmTell(vf) == FirstRate / 4); + RequireSameTells(vf, reference); + Require(RequireSameRead(vf, reference) > 0 && sceVorbisDecInfo(vf, -1) == first); + Require(sceVorbisDecTimeSeekPage(vf, 1.2) == ov_time_seek_page(&reference, 1.2)); + RequireSameTells(vf, reference); + Require(RequireSameRead(vf, reference) > 0); + const auto middle = static_cast(stream.size() / 2); + Require(sceVorbisDecRawSeek(vf, middle) == 0 && ov_raw_seek(&reference, middle) == 0); + RequireSameTells(vf, reference); + Require(RequireSameRead(vf, reference) > 0); + + Require(sceVorbisDecPcmSeekLap(vf, 5000) == 0 && ov_pcm_seek_lap(&reference, 5000) == 0); + RequireSameTells(vf, reference); + Require(RequireSameRead(vf, reference) > 0 && RequireSameRead(vf, reference) > 0); + Require(sceVorbisDecPcmSeekPageLap(vf, 30000) == ov_pcm_seek_page_lap(&reference, 30000)); + RequireSameTells(vf, reference); + Require(RequireSameRead(vf, reference) > 0); + Require(sceVorbisDecRawSeekLap(vf, middle / 2) == ov_raw_seek_lap(&reference, middle / 2)); + RequireSameTells(vf, reference); + Require(RequireSameRead(vf, reference) > 0); + Require(sceVorbisDecTimeSeekLap(vf, 1.1) == 0 && ov_time_seek_lap(&reference, 1.1) == 0); + RequireSameTells(vf, reference); + Require(RequireSameRead(vf, reference) > 0 && sceVorbisDecInfo(vf, -1) == second); + Require(sceVorbisDecTimeSeekPageLap(vf, 0.75) == ov_time_seek_page_lap(&reference, 0.75)); + RequireSameTells(vf, reference); + Require(RequireSameRead(vf, reference) > 0 && sceVorbisDecInfo(vf, -1) == first); + + Require(sceVorbisDecPcmSeek(vf, -1) == OV_EINVAL && sceVorbisDecPcmSeek(vf, FirstSamples + SecondSamples + 1) == OV_EINVAL); + Require(sceVorbisDecTimeSeek(vf, -0.5) == OV_EINVAL && sceVorbisDecTimeSeek(vf, 2.0) == OV_EINVAL); + Require(sceVorbisDecRawSeek(vf, -1) == OV_EINVAL && sceVorbisDecRawSeek(vf, middle * 3) == OV_EINVAL); + + Require(source.closes == 0 && sceVorbisDecClear(vf) == 0 && source.closes == 1); + Require(sceVorbisDecStreams(vf) == 0 && sceVorbisDecInfo(vf, 0) == nullptr && sceVorbisDecPcmTell(vf) == OV_EINVAL); + Require(sceVorbisDecClear(vf) == 0 && source.closes == 1); + Require(ov_clear(&reference) == 0 && referenceSource.closes == 1); + Require(source.probed == 0 && guest.Untouched() && probe.Untouched()); +} + +void TestStreaming(const std::vector& stream) { + GuestFile guest; + void* vf = guest.Get(); + const auto* initial = reinterpret_cast(stream.data()); + Source source{&stream, 64}; + Require(sceVorbisDecOpenCallbacks(&source, vf, initial, 64, StreamingCallbacks) == 0); + Source referenceSource{&stream, 64}; + OggVorbis_File reference{}; + Require(ov_open_callbacks(&referenceSource, &reference, initial, 64, HostStreamingCallbacks) == 0); + + Require(sceVorbisDecSeekable(vf) == 0 && sceVorbisDecStreams(vf) == 1); + Require(sceVorbisDecPcmTotal(vf, -1) == OV_EINVAL && sceVorbisDecRawTotal(vf, 0) == OV_EINVAL && sceVorbisDecTimeTotal(vf, -1) == OV_EINVAL); + Require(sceVorbisDecRawSeek(vf, 0) == OV_ENOSEEK && sceVorbisDecPcmSeek(vf, 0) == OV_ENOSEEK && sceVorbisDecTimeSeekPage(vf, 0.0) == OV_ENOSEEK); + Require(sceVorbisDecBitrate(vf, 0) == ov_bitrate(&reference, 0) && sceVorbisDecBitrate(vf, 1) == ov_bitrate(&reference, 1)); + Require(sceVorbisDecSerialNumber(vf, 3) == FirstSerial); + GuestInfo* info = sceVorbisDecInfo(vf, 0); + Require(info && info == sceVorbisDecInfo(vf, 5) && info == sceVorbisDecInfo(vf, -1)); + Require(info->channels == FirstChannels && info->rate == FirstRate); + const GuestComment* comment = sceVorbisDecComment(vf, 4); + Require(comment && std::strcmp(comment->user_comments[0], "TITLE=first") == 0); + + Require(DecodeTogether(vf, reference) == DecodedBytes); + Require(info->channels == SecondChannels && info->rate == SecondRate && sceVorbisDecInfo(vf, 0) == info); + Require(std::strcmp(comment->user_comments[0], "TITLE=second") == 0); + Require(sceVorbisDecSerialNumber(vf, 0) == SecondSerial); + RequireSameTells(vf, reference); + Require(sceVorbisDecClear(vf) == 0 && source.closes == 1); + Require(ov_clear(&reference) == 0 && referenceSource.closes == 1); + Require(guest.Untouched()); +} + +void TestFloatAndFilter(const std::vector& stream) { + GuestFile guest; + void* vf = guest.Get(); + Source source{&stream}; + Require(sceVorbisDecOpenCallbacks(&source, vf, nullptr, 0, SeekableCallbacks) == 0); + Source referenceSource{&stream}; + OggVorbis_File reference{}; + Require(ov_open_callbacks(&referenceSource, &reference, nullptr, 0, HostSeekableCallbacks) == 0); + + for (int round = 0; round < 6; ++round) { + float** pcm = nullptr; + float** hostPcm = nullptr; + int guestStream = -1; + int hostStream = -1; + const std::int64_t got = sceVorbisDecReadFloat(vf, &pcm, 300, &guestStream); + const long expected = ov_read_float(&reference, &hostPcm, 300, &hostStream); + Require(got == expected && got > 0 && got <= 300 && guestStream == hostStream && guestStream == 0); + for (int channel = 0; channel < FirstChannels; ++channel) { + Require(std::memcmp(pcm[channel], hostPcm[channel], static_cast(got) * sizeof(float)) == 0); + } + } + Require(sceVorbisDecReadFloat(vf, nullptr, 50, nullptr) == ov_read_float(&reference, nullptr, 50, nullptr)); + RequireSameTells(vf, reference); + + FilterLog log; + std::int64_t total = 0; + for (;;) { + std::array guestBytes{}; + std::array hostBytes{}; + int guestStream = -1; + int hostStream = -1; + const std::int64_t got = sceVorbisDecReadFilter(vf, guestBytes.data(), static_cast(guestBytes.size()), 0, 2, 1, &guestStream, GuestHalve, &log); + const long expected = ov_read_filter(&reference, hostBytes.data(), static_cast(hostBytes.size()), 0, 2, 1, &hostStream, HostHalve, nullptr); + Require(got == expected && got >= 0); + if (got == 0) break; + Require(guestStream == hostStream && std::memcmp(guestBytes.data(), hostBytes.data(), static_cast(got)) == 0); + total += got; + } + Require(total > 0 && log.calls > 0 && log.samples > 0 && log.firstChannels == FirstChannels && log.lastChannels == SecondChannels); + + Require(sceVorbisDecPcmSeek(vf, 0) == 0 && ov_pcm_seek(&reference, 0) == 0); + std::array filtered{}; + std::array plain{}; + int bitstream = -1; + const int calls = log.calls; + const std::int64_t got = sceVorbisDecReadFilter(vf, filtered.data(), static_cast(filtered.size()), 0, 2, 1, &bitstream, nullptr, &log); + Require(got > 0 && got == ov_read(&reference, plain.data(), static_cast(plain.size()), 0, 2, 1, &bitstream)); + Require(std::memcmp(filtered.data(), plain.data(), static_cast(got)) == 0 && log.calls == calls); + Require(sceVorbisDecClear(vf) == 0 && source.closes == 1 && ov_clear(&reference) == 0); + Require(guest.Untouched()); +} + +void TestHalfrate(const std::vector& stream) { + GuestFile guest; + void* vf = guest.Get(); + Source source{&stream}; + Require(sceVorbisDecOpenCallbacks(&source, vf, nullptr, 0, SeekableCallbacks) == 0); + Source referenceSource{&stream}; + OggVorbis_File reference{}; + Require(ov_open_callbacks(&referenceSource, &reference, nullptr, 0, HostSeekableCallbacks) == 0); + + Require(sceVorbisDecHalfrateP(vf) == 0); + Require(RequireSameRead(vf, reference) > 0 && RequireSameRead(vf, reference) > 0); + Require(sceVorbisDecHalfrate(vf, 1) == 0 && ov_halfrate(&reference, 1) == 0); + Require(sceVorbisDecHalfrateP(vf) == 1 && ov_halfrate_p(&reference) == 1); + RequireSameTells(vf, reference); + const std::int64_t half = DecodeTogether(vf, reference); + Require(half > 0 && half < DecodedBytes / 2); + RequireSameTells(vf, reference); + Require(sceVorbisDecPcmTell(vf) > FirstSamples); + Require(sceVorbisDecHalfrate(vf, 0) == 0 && sceVorbisDecHalfrateP(vf) == 0); + Require(sceVorbisDecClear(vf) == 0 && source.closes == 1 && ov_clear(&reference) == 0); + Require(guest.Untouched()); +} + +void TestCrossLap(const std::vector& stream) { + GuestFile first; + GuestFile second; + GuestFile closed; + GuestFile otherClosed; + Source firstSource{&stream}; + Source secondSource{&stream}; + Require(sceVorbisDecOpenCallbacks(&secondSource, second.Get(), nullptr, 0, SeekableCallbacks) == 0); + firstSource.probe = second.Get(); + Require(sceVorbisDecOpenCallbacks(&firstSource, first.Get(), nullptr, 0, SeekableCallbacks) == 0); + Require(firstSource.probed == 2); + Source firstReferenceSource{&stream}; + Source secondReferenceSource{&stream}; + OggVorbis_File firstReference{}; + OggVorbis_File secondReference{}; + Require(ov_open_callbacks(&firstReferenceSource, &firstReference, nullptr, 0, HostSeekableCallbacks) == 0); + Require(ov_open_callbacks(&secondReferenceSource, &secondReference, nullptr, 0, HostSeekableCallbacks) == 0); + + Require(RequireSameRead(first.Get(), firstReference) > 0 && RequireSameRead(first.Get(), firstReference) > 0); + Require(sceVorbisDecPcmSeek(second.Get(), FirstSamples + 2000) == 0 && ov_pcm_seek(&secondReference, FirstSamples + 2000) == 0); + Require(sceVorbisDecCrossLap(first.Get(), second.Get()) == 0 && ov_crosslap(&firstReference, &secondReference) == 0); + for (int round = 0; round < 3; ++round) { + Require(RequireSameRead(second.Get(), secondReference) > 0); + Require(RequireSameRead(first.Get(), firstReference) > 0); + } + RequireSameTells(first.Get(), firstReference); + RequireSameTells(second.Get(), secondReference); + Require(sceVorbisDecCrossLap(first.Get(), first.Get()) == 0); + Require(sceVorbisDecCrossLap(first.Get(), closed.Get()) == OV_EINVAL && sceVorbisDecCrossLap(closed.Get(), second.Get()) == OV_EINVAL); + Require(sceVorbisDecCrossLap(closed.Get(), otherClosed.Get()) == OV_EINVAL && sceVorbisDecCrossLap(closed.Get(), closed.Get()) == 0); + Require(sceVorbisDecCrossLap(nullptr, nullptr) == 0); + OggVorbis_File zeroed{}; + Require(sceVorbisDecCrossLap(closed.Get(), nullptr) == OV_EINVAL && ov_crosslap(&zeroed, nullptr) == OV_EINVAL); + Require(Throws([&] { sceVorbisDecCrossLap(first.Get(), nullptr); })); + Require(Throws([&] { sceVorbisDecCrossLap(nullptr, closed.Get()); })); + Require(sceVorbisDecClear(first.Get()) == 0 && sceVorbisDecClear(second.Get()) == 0); + Require(firstSource.closes == 1 && secondSource.closes == 1); + Require(ov_clear(&firstReference) == 0 && ov_clear(&secondReference) == 0); + Require(first.Untouched() && second.Untouched() && closed.Untouched() && otherClosed.Untouched()); +} + +void TestNeverOpened() { + GuestFile guest; + void* vf = guest.Get(); + Require(sceVorbisDecStreams(vf) == 0 && sceVorbisDecSeekable(vf) == 0); + Require(sceVorbisDecSerialNumber(vf, -1) == 0 && sceVorbisDecSerialNumber(vf, 0) == 0 && sceVorbisDecSerialNumber(vf, 3) == 0); + Require(sceVorbisDecBitrate(vf, -1) == OV_EINVAL && sceVorbisDecBitrate(vf, 0) == OV_EINVAL && sceVorbisDecBitrateInstant(vf) == OV_EINVAL); + Require(sceVorbisDecRawTotal(vf, -1) == OV_EINVAL && sceVorbisDecPcmTotal(vf, 0) == OV_EINVAL && sceVorbisDecTimeTotal(vf, -1) == OV_EINVAL); + Require(sceVorbisDecRawSeek(vf, 0) == OV_EINVAL && sceVorbisDecPcmSeek(vf, 0) == OV_EINVAL && sceVorbisDecPcmSeekPage(vf, 0) == OV_EINVAL); + Require(sceVorbisDecTimeSeek(vf, 0.0) == OV_EINVAL && sceVorbisDecTimeSeekPage(vf, 0.0) == OV_EINVAL); + Require(sceVorbisDecRawSeekLap(vf, 0) == OV_EINVAL && sceVorbisDecPcmSeekLap(vf, 0) == OV_EINVAL && sceVorbisDecPcmSeekPageLap(vf, 0) == OV_EINVAL); + Require(sceVorbisDecTimeSeekLap(vf, 0.0) == OV_EINVAL && sceVorbisDecTimeSeekPageLap(vf, 0.0) == OV_EINVAL); + Require(sceVorbisDecRawTell(vf) == OV_EINVAL && sceVorbisDecPcmTell(vf) == OV_EINVAL && sceVorbisDecTimeTell(vf) == OV_EINVAL); + Require(sceVorbisDecInfo(vf, -1) == nullptr && sceVorbisDecInfo(vf, 0) == nullptr); + Require(sceVorbisDecComment(vf, -1) == nullptr && sceVorbisDecComment(vf, 0) == nullptr); + std::array buffer{}; + int bitstream = 7; + Require(sceVorbisDecRead(vf, buffer.data(), static_cast(buffer.size()), 0, 2, 1, &bitstream) == OV_EINVAL && bitstream == 7); + float** pcm = nullptr; + Require(sceVorbisDecReadFloat(vf, &pcm, 64, &bitstream) == OV_EINVAL && pcm == nullptr && bitstream == 7); + FilterLog log; + Require(sceVorbisDecReadFilter(vf, buffer.data(), static_cast(buffer.size()), 0, 2, 1, &bitstream, GuestHalve, &log) == OV_EINVAL); + Require(log.calls == 0 && bitstream == 7); + Require(sceVorbisDecHalfrate(vf, 1) == OV_EINVAL && sceVorbisDecHalfrateP(vf) == OV_EINVAL); + Require(sceVorbisDecClear(vf) == 0 && sceVorbisDecClear(nullptr) == 0); + Require(guest.Untouched()); + + Source source; + Require(Throws([&] { sceVorbisDecOpenCallbacks(&source, nullptr, nullptr, 0, SeekableCallbacks); })); + Require(Throws([] { sceVorbisDecStreams(nullptr); })); + Require(Throws([] { sceVorbisDecInfo(nullptr, 0); })); + Require(Throws([] { sceVorbisDecPcmSeek(nullptr, 0); })); +} + +void TestFailedOpens(const std::vector& stream) { + GuestFile guest; + void* vf = guest.Get(); + std::vector noise(8192); + std::uint32_t state = 0x2545f491; + for (auto& value : noise) { + state = state * 1664525u + 1013904223u; + value = static_cast(state >> 24); + } + Source noiseSource{&noise}; + Require(sceVorbisDecOpenCallbacks(&noiseSource, vf, nullptr, 0, SeekableCallbacks) == OV_ENOTVORBIS); + Require(noiseSource.closes == 0 && sceVorbisDecStreams(vf) == 0 && sceVorbisDecInfo(vf, 0) == nullptr); + Require(sceVorbisDecClear(vf) == 0 && noiseSource.closes == 0); + + Source untold{&stream}; + constexpr GuestCallbacks NoTell{GuestRead, GuestSeek, GuestClose, nullptr}; + Require(sceVorbisDecOpenCallbacks(&untold, vf, nullptr, 0, NoTell) == OV_EINVAL); + Require(untold.closes == 0 && sceVorbisDecStreams(vf) == 0 && sceVorbisDecClear(vf) == 0 && untold.closes == 0); + + constexpr GuestCallbacks NoRead{nullptr, GuestSeek, GuestClose, GuestTell}; + Source unread{&stream}; + OggVorbis_File reference{}; + const ov_callbacks hostNoRead{nullptr, HostSeek, HostClose, HostTell}; + Require(sceVorbisDecOpenCallbacks(&unread, vf, nullptr, 0, NoRead) == ov_open_callbacks(&unread, &reference, nullptr, 0, hostNoRead)); + Require(unread.closes == 0 && sceVorbisDecStreams(vf) == 0); + + Require(sceVorbisDecOpenCallbacks(nullptr, vf, nullptr, 0, SeekableCallbacks) == OV_ENOTVORBIS); + + Source previous{&stream}; + Source next{&stream}; + Require(sceVorbisDecOpenCallbacks(&previous, vf, nullptr, 0, SeekableCallbacks) == 0); + Require(sceVorbisDecOpenCallbacks(&next, vf, nullptr, 0, StreamingCallbacks) == 0); + Require(previous.closes == 0 && sceVorbisDecSeekable(vf) == 0 && sceVorbisDecStreams(vf) == 1); + Require(sceVorbisDecClear(vf) == 0 && previous.closes == 0 && next.closes == 1); + + Source unsized{&stream}; + Require(sceVorbisDecOpenCallbacks(&unsized, vf, nullptr, std::int64_t{1} << 32, SeekableCallbacks) == 0); + Require(sceVorbisDecStreams(vf) == 2 && sceVorbisDecClear(vf) == 0 && unsized.closes == 1); + + const auto* initial = reinterpret_cast(stream.data()); + const auto size = static_cast(stream.size()); + Require(sceVorbisDecOpenCallbacks(nullptr, vf, initial, size, SeekableCallbacks) == 0); + Require(ov_open_callbacks(nullptr, &reference, initial, static_cast(size), HostSeekableCallbacks) == 0); + Require(sceVorbisDecSeekable(vf) == 0 && DecodeTogether(vf, reference) == DecodedBytes); + Require(sceVorbisDecClear(vf) == 0 && ov_clear(&reference) == 0); + Require(guest.Untouched()); +} + +void TestThrowingCallbacks(const std::vector& stream) { + GuestFile guest; + Source source{&stream}; + Require(sceVorbisDecOpenCallbacks(&source, guest.Get(), nullptr, 0, SeekableCallbacks) == 0); + std::array buffer{}; + int bitstream = -1; + source.throwOnRead = true; + bool threw = false; + for (int call = 0; call < 1024 && !threw; ++call) { + threw = Throws([&] { sceVorbisDecRead(guest.Get(), buffer.data(), static_cast(buffer.size()), 0, 2, 1, &bitstream); }); + } + Require(threw); + source.throwOnRead = false; + source.throwOnClose = true; + Require(Throws([&] { sceVorbisDecClear(guest.Get()); })); + Require(source.closes == 1 && sceVorbisDecStreams(guest.Get()) == 0); + Require(sceVorbisDecClear(guest.Get()) == 0 && source.closes == 1); + Require(guest.Untouched()); +} + } int main() { - Require(Throws([] { sceVorbisDecOpenCallbacks(); }), "sceVorbisDecOpenCallbacks must throw"); - Require(Throws([] { sceVorbisDecClear(); }), "sceVorbisDecClear must throw"); - Require(Throws([] { sceVorbisDecBitrate(); }), "sceVorbisDecBitrate must throw"); - Require(Throws([] { sceVorbisDecBitrateInstant(); }), "sceVorbisDecBitrateInstant must throw"); - Require(Throws([] { sceVorbisDecStreams(); }), "sceVorbisDecStreams must throw"); - Require(Throws([] { sceVorbisDecSeekable(); }), "sceVorbisDecSeekable must throw"); - Require(Throws([] { sceVorbisDecSerialNumber(); }), "sceVorbisDecSerialNumber must throw"); - Require(Throws([] { sceVorbisDecRawTotal(); }), "sceVorbisDecRawTotal must throw"); - Require(Throws([] { sceVorbisDecPcmTotal(); }), "sceVorbisDecPcmTotal must throw"); - Require(Throws([] { sceVorbisDecTimeTotal(); }), "sceVorbisDecTimeTotal must throw"); - Require(Throws([] { sceVorbisDecRawSeek(); }), "sceVorbisDecRawSeek must throw"); - Require(Throws([] { sceVorbisDecPcmSeek(); }), "sceVorbisDecPcmSeek must throw"); - Require(Throws([] { sceVorbisDecPcmSeekPage(); }), "sceVorbisDecPcmSeekPage must throw"); - Require(Throws([] { sceVorbisDecTimeSeek(); }), "sceVorbisDecTimeSeek must throw"); - Require(Throws([] { sceVorbisDecTimeSeekPage(); }), "sceVorbisDecTimeSeekPage must throw"); - Require(Throws([] { sceVorbisDecRawSeekLap(); }), "sceVorbisDecRawSeekLap must throw"); - Require(Throws([] { sceVorbisDecPcmSeekLap(); }), "sceVorbisDecPcmSeekLap must throw"); - Require(Throws([] { sceVorbisDecPcmSeekPageLap(); }), "sceVorbisDecPcmSeekPageLap must throw"); - Require(Throws([] { sceVorbisDecTimeSeekLap(); }), "sceVorbisDecTimeSeekLap must throw"); - Require(Throws([] { sceVorbisDecTimeSeekPageLap(); }), "sceVorbisDecTimeSeekPageLap must throw"); - Require(Throws([] { sceVorbisDecRawTell(); }), "sceVorbisDecRawTell must throw"); - Require(Throws([] { sceVorbisDecPcmTell(); }), "sceVorbisDecPcmTell must throw"); - Require(Throws([] { sceVorbisDecTimeTell(); }), "sceVorbisDecTimeTell must throw"); - Require(Throws([] { sceVorbisDecInfo(); }), "sceVorbisDecInfo must throw"); - Require(Throws([] { sceVorbisDecComment(); }), "sceVorbisDecComment must throw"); - Require(Throws([] { sceVorbisDecRead(); }), "sceVorbisDecRead must throw"); - Require(Throws([] { sceVorbisDecReadFloat(); }), "sceVorbisDecReadFloat must throw"); - Require(Throws([] { sceVorbisDecReadFilter(); }), "sceVorbisDecReadFilter must throw"); - Require(Throws([] { sceVorbisDecCrossLap(); }), "sceVorbisDecCrossLap must throw"); - Require(Throws([] { sceVorbisDecHalfrate(); }), "sceVorbisDecHalfrate must throw"); - Require(Throws([] { sceVorbisDecHalfrateP(); }), "sceVorbisDecHalfrateP must throw"); - Require(Throws([] { sceVorbisDecGetDecodePriority(); }), "sceVorbisDecGetDecodePriority must throw"); - Require(Throws([] { sceVorbisDecSetDecodePriority(); }), "sceVorbisDecSetDecodePriority must throw"); - Require(Throws([] { sceVorbisDecGetLastDecodeError(); }), "sceVorbisDecGetLastDecodeError must throw"); + std::vector stream; + EncodeLink(stream, FirstSerial, FirstChannels, FirstRate, FirstSamples, "first"); + EncodeLink(stream, SecondSerial, SecondChannels, SecondRate, SecondSamples, "second"); + + TestSeekable(stream); + TestStreaming(stream); + TestFloatAndFilter(stream); + TestHalfrate(stream); + TestCrossLap(stream); + TestNeverOpened(); + TestFailedOpens(stream); + TestThrowingCallbacks(stream); + + Require(Throws([] { sceVorbisDecGetDecodePriority(); })); + Require(Throws([] { sceVorbisDecSetDecodePriority(); })); + Require(Throws([] { sceVorbisDecGetLastDecodeError(); })); std::puts("All VorbisDec guest tests passed"); return 0; diff --git a/core/libs/tests/GuestWebBrowserDialogCookies.cpp b/core/libs/tests/GuestWebBrowserDialogCookies.cpp new file mode 100644 index 0000000000..723bada25d --- /dev/null +++ b/core/libs/tests/GuestWebBrowserDialogCookies.cpp @@ -0,0 +1,146 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +struct ResetCookieParam { + std::uint64_t size; + std::uint8_t reserved[256]; +}; + +struct SetCookieParam { + std::uint64_t size; + const char* url; + const char* cookie; + std::uint8_t reserved[256]; +}; + +extern "C" { +int APS5_VABI sceWebBrowserDialogInitialize(void); +int APS5_VABI sceWebBrowserDialogTerminate(void); +int APS5_VABI sceWebBrowserDialogOpen(const void* param); +int APS5_VABI sceWebBrowserDialogClose(void); +int APS5_VABI sceWebBrowserDialogUpdateStatus(void); +int APS5_VABI sceWebBrowserDialogSetCookie(const SetCookieParam* param); +int APS5_VABI sceWebBrowserDialogResetCookie(const ResetCookieParam* param); +} + +namespace { + +constexpr int NOT_INITIALIZED = static_cast(0x80B80003u); +constexpr int INVALID_STATE = static_cast(0x80B80006u); +constexpr int NOT_RUNNING = static_cast(0x80B8000Bu); +constexpr int PARAM_INVALID = static_cast(0x80B8000Au); +constexpr int ARG_NULL = static_cast(0x80B8000Du); + +void Require(bool value, const char* what) { + if (value) return; + std::fprintf(stderr, "FAIL: %s\n", what); + std::abort(); +} + +SetCookieParam Cookie(const char* url, const char* cookie) { + SetCookieParam param {}; + param.size = sizeof(param); + param.url = url; + param.cookie = cookie; + return param; +} + +ResetCookieParam Reset() { + ResetCookieParam param {}; + param.size = sizeof(param); + return param; +} + +void CheckSetCookieParameters() { + auto valid = Cookie("https://example.com", "a=b"); + Require(sceWebBrowserDialogSetCookie(&valid) == 0, "valid SetCookie"); + Require(sceWebBrowserDialogSetCookie(nullptr) == ARG_NULL, "SetCookie null"); + + auto wrongSize = valid; + wrongSize.size = sizeof(ResetCookieParam); + Require(sceWebBrowserDialogSetCookie(&wrongSize) == PARAM_INVALID, "SetCookie size"); + auto noUrl = Cookie(nullptr, "a=b"); + Require(sceWebBrowserDialogSetCookie(&noUrl) == PARAM_INVALID, "SetCookie null url"); + auto emptyUrl = Cookie("", "a=b"); + Require(sceWebBrowserDialogSetCookie(&emptyUrl) == PARAM_INVALID, "SetCookie empty url"); + auto noCookie = Cookie("https://example.com", nullptr); + Require(sceWebBrowserDialogSetCookie(&noCookie) == PARAM_INVALID, "SetCookie null cookie"); + auto emptyCookie = Cookie("https://example.com", ""); + Require(sceWebBrowserDialogSetCookie(&emptyCookie) == PARAM_INVALID, "SetCookie empty cookie"); + + const std::string longestUrl(0x7ff, 'u'); + const std::string longUrl(0x800, 'u'); + auto longest = Cookie(longestUrl.c_str(), "a=b"); + Require(sceWebBrowserDialogSetCookie(&longest) == 0, "SetCookie 2047-byte url"); + auto tooLong = Cookie(longUrl.c_str(), "a=b"); + Require(sceWebBrowserDialogSetCookie(&tooLong) == PARAM_INVALID, "SetCookie 2048-byte url"); + + const std::string longestCookie(0xfff, 'c'); + const std::string longCookie(0x1000, 'c'); + auto longestValue = Cookie("https://example.com", longestCookie.c_str()); + Require(sceWebBrowserDialogSetCookie(&longestValue) == 0, "SetCookie 4095-byte cookie"); + auto tooLongValue = Cookie("https://example.com", longCookie.c_str()); + Require(sceWebBrowserDialogSetCookie(&tooLongValue) == PARAM_INVALID, "SetCookie 4096-byte cookie"); + + auto reserved = valid; + reserved.reserved[255] = 1; + Require(sceWebBrowserDialogSetCookie(&reserved) == PARAM_INVALID, "SetCookie reserved"); +} + +void CheckResetCookieParameters() { + auto valid = Reset(); + Require(sceWebBrowserDialogResetCookie(&valid) == 0, "valid ResetCookie"); + Require(sceWebBrowserDialogResetCookie(nullptr) == ARG_NULL, "ResetCookie null"); + auto wrongSize = valid; + wrongSize.size = sizeof(SetCookieParam); + Require(sceWebBrowserDialogResetCookie(&wrongSize) == PARAM_INVALID, "ResetCookie size"); + auto reserved = valid; + reserved.reserved[0] = 1; + Require(sceWebBrowserDialogResetCookie(&reserved) == PARAM_INVALID, "ResetCookie reserved"); +} + +void CheckOpenDialog() { + std::uint8_t open[64] = {}; + auto cookie = Cookie("https://example.com", "a=b"); + auto reset = Reset(); + Require(sceWebBrowserDialogOpen(open) == 0, "Open"); + Require(sceWebBrowserDialogSetCookie(&cookie) == INVALID_STATE, "SetCookie while open"); + Require(sceWebBrowserDialogSetCookie(nullptr) == INVALID_STATE, "SetCookie checks the open dialog before null"); + Require(sceWebBrowserDialogResetCookie(nullptr) == ARG_NULL, "ResetCookie checks null before the open dialog"); + Require(sceWebBrowserDialogResetCookie(&reset) == INVALID_STATE, "ResetCookie while open"); + Require(sceWebBrowserDialogUpdateStatus() == 3, "UpdateStatus finished"); + Require(sceWebBrowserDialogSetCookie(&cookie) == 0, "SetCookie after UpdateStatus"); + Require(sceWebBrowserDialogResetCookie(&reset) == 0, "ResetCookie after UpdateStatus"); + + Require(sceWebBrowserDialogOpen(open) == 0, "second Open"); + Require(sceWebBrowserDialogClose() == NOT_RUNNING, "Close of a finished dialog"); + Require(sceWebBrowserDialogSetCookie(&cookie) == 0, "SetCookie after Close"); + + Require(sceWebBrowserDialogOpen(open) == 0, "third Open"); + Require(sceWebBrowserDialogTerminate() == 0, "Terminate"); + Require(sceWebBrowserDialogInitialize() == 0, "Initialize again"); + Require(sceWebBrowserDialogResetCookie(&reset) == 0, "ResetCookie after Terminate"); +} + +} + +int main() { + auto cookie = Cookie("https://example.com", "a=b"); + auto reset = Reset(); + Require(sceWebBrowserDialogSetCookie(&cookie) == NOT_INITIALIZED, "SetCookie before Initialize"); + Require(sceWebBrowserDialogSetCookie(nullptr) == NOT_INITIALIZED, "SetCookie null before Initialize"); + Require(sceWebBrowserDialogResetCookie(&reset) == NOT_INITIALIZED, "ResetCookie before Initialize"); + Require(sceWebBrowserDialogResetCookie(nullptr) == NOT_INITIALIZED, "ResetCookie null before Initialize"); + + Require(sceWebBrowserDialogInitialize() == 0, "Initialize"); + CheckSetCookieParameters(); + CheckResetCookieParameters(); + CheckOpenDialog(); + Require(sceWebBrowserDialogTerminate() == 0, "final Terminate"); + std::puts("guest web browser dialog cookies: PASS"); + return 0; +} diff --git a/core/libs/tests/HevcFixture.hpp b/core/libs/tests/HevcFixture.hpp new file mode 100644 index 0000000000..7bacee3a02 --- /dev/null +++ b/core/libs/tests/HevcFixture.hpp @@ -0,0 +1,133 @@ +#pragma once + +#include +#include +#include +#include + +namespace HevcFixture { + +inline constexpr std::array HevcStream{ + 0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x10, 0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, + 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x1e, + 0x95, 0x90, 0x09, 0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, + 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x1e, 0xa0, 0x20, 0x82, 0x96, 0x59, 0x59, 0x2e, + 0x4c, 0xaf, 0x01, 0x68, 0x08, 0x00, 0x00, 0x03, 0x00, 0x08, 0x00, 0x00, 0x03, 0x00, 0xf0, 0x40, + 0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0xc1, 0x73, 0xd0, 0x89, 0x00, 0x00, 0x01, 0x28, 0x01, 0xaf, + 0x08, 0xe0, 0xbb, 0xcb, 0xc1, 0x71, 0x26, 0x0f, 0xbf, 0x6c, 0xcb, 0x4d, 0x2b, 0x2c, 0xf4, 0x08, + 0xe0, 0x17, 0xeb, 0x16, 0xcc, 0x07, 0xd8, 0xdd, 0x11, 0xf8, 0x5e, 0x73, 0x79, 0x57, 0xcb, 0x69, + 0xa3, 0x1c, 0x9c, 0x43, 0x14, 0xdb, 0x61, 0x0e, 0xc5, 0xc6, 0x9c, 0x9c, 0xf8, 0xf2, 0x07, 0x35, + 0xf1, 0x98, 0x6c, 0x1a, 0x86, 0x53, 0x5a, 0xcd, 0x4d, 0xda, 0xff, 0xfb, 0x01, 0xda, 0xc3, 0x61, + 0xb7, 0x8c, 0xae, 0xc9, 0x76, 0xe4, 0x05, 0xd6, 0xfd, 0xe4, 0xcd, 0x93, 0x2c, 0xb1, 0x41, 0x29, + 0xce, 0x22, 0xf7, 0x77, 0x64, 0x0f, 0x1f, 0xd2, 0xc3, 0x68, 0x32, 0x2f, 0x83, 0xd7, 0x53, 0xbe, + 0x23, 0xff, 0x8c, 0x46, 0x18, 0x33, 0x90, 0x28, 0xa3, 0xef, 0xd7, 0xb1, 0x46, 0x84, 0xa0, 0xc2, + 0xd4, 0xd9, 0xce, 0xdf, 0x0a, 0x05, 0xec, 0xad, 0x63, 0xa4, 0x78, 0x27, 0x80, 0x4e, 0x3b, 0x4c, + 0xa6, 0x42, 0xbc, 0xe3, 0x39, 0x4d, 0xc4, 0xd3, 0x44, 0xf1, 0x22, 0xd0, 0x64, 0x9f, 0xd0, 0x88, + 0x7f, 0x39, 0x6d, 0x06, 0xc7, 0x79, 0x75, 0x2a, 0x44, 0x3d, 0x21, 0x41, 0x53, 0x60, 0x8d, 0x08, + 0x9f, 0xd0, 0x34, 0xa6, 0x72, 0x70, 0x3b, 0xa9, 0xfa, 0x62, 0xb1, 0x19, 0x9b, 0x0b, 0xa2, 0xfc, + 0xb4, 0xb3, 0x2c, 0x58, 0xad, 0xc6, 0xf3, 0xea, 0xc4, 0xd0, 0xf9, 0xea, 0x9e, 0xa7, 0xa4, 0x87, + 0xb8, 0x19, 0x68, 0xf1, 0xc1, 0x6b, 0x87, 0xa5, 0x99, 0xa0, 0x69, 0x24, 0x28, 0x80, 0xba, 0xd2, + 0x57, 0xc6, 0xc0, 0x1e, 0x69, 0x1b, 0xce, 0x96, 0xcd, 0x85, 0x9d, 0x59, 0x32, 0x92, 0x71, 0xe7, + 0x15, 0xf2, 0x9b, 0x71, 0x4b, 0xd4, 0xb6, 0x99, 0xb0, 0x71, 0x9b, 0x9b, 0xdb, 0x33, 0x28, 0x86, + 0x5e, 0x44, 0xf3, 0x5d, 0x5d, 0x7b, 0x87, 0x6c, 0x99, 0x8a, 0x6d, 0xaf, 0xf8, 0xdd, 0x39, 0x14, + 0xc0, 0x10, 0x85, 0xaf, 0x3a, 0x77, 0x60, 0x8b, 0xb7, 0x5f, 0x67, 0x45, 0xb4, 0xa7, 0xe7, 0x44, + 0x77, 0x0a, 0xb9, 0xe1, 0xb2, 0xbe, 0xde, 0x1b, 0x5a, 0xb8, 0xc8, 0x9b, 0x1f, 0x15, 0x37, 0xdc, + 0xeb, 0x55, 0x12, 0x37, 0xa1, 0xab, 0x9c, 0xcd, 0xdd, 0x42, 0x5d, 0x90, 0x89, 0xc5, 0x99, 0x54, + 0x6e, 0x9a, 0xd2, 0x71, 0x07, 0xe3, 0x81, 0x20, 0xad, 0xd4, 0x7c, 0xad, 0x69, 0xaf, 0xc7, 0x2d, + 0x7c, 0x84, 0xf1, 0x5d, 0x43, 0x4a, 0x3c, 0xf6, 0xf8, 0x7e, 0x6a, 0xb2, 0xdd, 0x00, 0xda, 0x30, + 0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x30, 0x00, 0x00, 0x01, 0x02, 0x01, 0xd0, 0x19, 0x5f, 0x84, + 0x31, 0x84, 0x50, 0x62, 0xcd, 0x44, 0xa6, 0x4e, 0xbb, 0x2e, 0x87, 0xda, 0x21, 0x25, 0x1c, 0x13, + 0xfe, 0xc2, 0x97, 0xa3, 0xd5, 0xfd, 0xe2, 0x62, 0xd6, 0xe5, 0x50, 0x7a, 0xda, 0x98, 0x8d, 0xcf, + 0xbe, 0x40, 0x5d, 0x69, 0xb6, 0x5f, 0xdc, 0x32, 0x68, 0x05, 0x67, 0x70, 0xfd, 0xa0, 0xd3, 0xcd, + 0x88, 0xeb, 0x12, 0x2e, 0xac, 0xf5, 0x57, 0xa2, 0xc2, 0x81, 0x22, 0x6b, 0x8d, 0x69, 0x6d, 0xac, + 0x41, 0x05, 0x5b, 0x0b, 0x79, 0xe0, 0x86, 0x05, 0x86, 0x8b, 0x02, 0x51, 0x7d, 0x6e, 0x13, 0x63, + 0x36, 0xe6, 0x89, 0x22, 0x75, 0xd9, 0xb0, 0x39, 0x27, 0xcd, 0xac, 0x78, 0xe0, 0x3a, 0x6a, 0x17, + 0x15, 0x05, 0x83, 0x3a, 0xe9, 0x38, 0x7c, 0x75, 0x38, 0xf3, 0xd8, 0x42, 0x57, 0xa1, 0xef, 0x68, + 0xb8, 0xb5, 0x44, 0xfe, 0x4d, 0x66, 0xa1, 0x09, 0xc5, 0x62, 0xff, 0x87, 0x7e, 0xdb, 0x41, 0x6f, + 0x4d, 0x5f, 0x16, 0x49, 0xef, 0xca, 0xda, 0x67, 0x01, 0x29, 0x0b, 0xd6, 0xd8, 0xfc, 0x0b, 0x22, + 0xc8, 0x28, 0xff, 0xb4, 0xb3, 0xa9, 0xf8, 0x9b, 0x78, 0x7d, 0x33, 0xbf, 0xa6, 0x9b, 0x39, 0x64, + 0x57, 0x41, 0x1f, 0x27, 0x05, 0xfc, 0xcd, 0x44, 0xcd, 0xb2, 0xea, 0x79, 0xe0, 0x00, 0x00, 0x00, + 0x01, 0x46, 0x01, 0x50, 0x00, 0x00, 0x01, 0x02, 0x01, 0xe0, 0x44, 0x97, 0xe1, 0x8f, 0x40, 0x3d, + 0x73, 0xde, 0xdf, 0xf5, 0x1c, 0xff, 0xf6, 0xe2, 0x12, 0x16, 0x3d, 0xb6, 0x75, 0xad, 0x9e, 0xb5, + 0x2b, 0xe7, 0x4a, 0x40, 0xe9, 0x91, 0xd6, 0x86, 0x86, 0x2d, 0xcb, 0xe4, 0xb6, 0x58, 0x4a, 0x2d, + 0x7b, 0x1b, 0xdb, 0xc5, 0x48, 0x2d, 0x09, 0x4d, 0x32, 0x30, 0xde, 0xaa, 0x61, 0xc2, 0x27, 0x64, + 0x54, 0x71, 0x89, 0x32, 0x05, 0xc0, 0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x50, 0x00, 0x00, 0x01, + 0x00, 0x01, 0xe0, 0x24, 0xff, 0xfa, 0x2c, 0x6e, 0xdc, 0x78, 0x3d, 0x08, 0x6d, 0x3b, 0xcf, 0xcc, + 0xb6, 0x16, 0xeb, 0x9d, 0x5f, 0x46, 0x0b, 0x24, 0x0e, 0x97, 0x99, 0x06, 0x00, 0x00, 0x00, 0x01, + 0x46, 0x01, 0x30, 0x00, 0x00, 0x01, 0x02, 0x01, 0xd0, 0x28, 0x95, 0xd7, 0xdc, 0x41, 0xc0, 0x61, + 0x14, 0xd6, 0xee, 0xd4, 0xe8, 0x24, 0x3e, 0x7f, 0x82, 0x22, 0xb0, 0x20, 0xb9, 0xf1, 0x69, 0x9b, + 0xfe, 0xcc, 0xb7, 0x2e, 0xbd, 0xdb, 0x20, 0xfa, 0xe5, 0x97, 0xd9, 0x95, 0x1d, 0x7e, 0x49, 0x35, + 0x69, 0xdb, 0x40, 0xa3, 0x5c, 0x0f, 0x74, 0x6f, 0x00, 0x3d, 0x36, 0x05, 0xc0, 0xc4, 0x37, 0x15, + 0xf9, 0x01, 0x2d, 0xdf, 0xcb, 0x70, 0x3d, 0xfa, 0x47, 0xec, 0x80, 0x07, 0x48, 0x08, 0xc8, 0x54, + 0xd3, 0xe3, 0x00, 0xa3, 0xe4, 0xce, 0x40, 0x05, 0x0b, 0x51, 0x73, 0x56, 0x33, 0x82, 0xa8, 0x0a, + 0xc0, 0xd3, 0xc5, 0xf1, 0xeb, 0x32, 0x8e, 0x4e, 0xa7, 0x6f, 0xb2, 0x64, 0x43, 0x54, 0x71, 0xd4, + 0xaf, 0xc0, 0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x50, 0x00, 0x00, 0x01, 0x00, 0x01, 0xe0, 0x82, + 0x2f, 0x5f, 0xdc, 0x63, 0x70, 0x3f, 0xaf, 0x66, 0x75, 0x52, 0xe7, 0x7f, 0x62, 0xfb, 0x84, 0x9c, + 0xef, 0x60, 0x8f, 0xfa, 0x7e, 0x5f, 0x8b, 0x40, 0x15, 0x34, 0x69, 0xe2, 0xc4, 0xfc, 0xff, 0x53, + 0xb8, 0x1e, 0xc7, 0x20, 0x55, 0xdb, 0x5e, 0x05, 0xbd, 0x7a, 0x85, 0xb4, 0xb7, 0xc1, 0x8d, 0xdb, + 0x0f, 0xa6, 0xab, 0xb9, 0x00, 0x2a, 0xdb, 0x83, 0xa0, 0xb0, 0xc5, 0x76, 0x6e, 0xe1, 0xb8, 0x72, + 0xfd, 0x09, 0x61, 0x19, 0xe0, +}; + +inline constexpr std::array HevcMain10Stream{ + 0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x10, 0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, + 0xff, 0x02, 0x20, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x1e, + 0x95, 0x98, 0x09, 0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x02, 0x20, 0x00, 0x00, 0x03, 0x00, + 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x1e, 0xa0, 0x20, 0x82, 0x93, 0x65, 0x95, 0x9a, + 0xe4, 0xca, 0xf0, 0x16, 0x80, 0x80, 0x00, 0x00, 0x03, 0x00, 0x80, 0x00, 0x00, 0x0f, 0x04, 0x00, + 0x00, 0x00, 0x01, 0x44, 0x01, 0xc1, 0x73, 0xd0, 0x89, 0x00, 0x00, 0x01, 0x28, 0x01, 0xaf, 0x08, + 0x60, 0xf2, 0xfb, 0x04, 0xef, 0x67, 0x67, 0xdc, 0xc7, 0x8e, 0xc7, 0x42, 0x63, 0xeb, 0x7d, 0x28, + 0x73, 0x95, 0x14, 0x0c, 0x71, 0x10, 0x8b, 0x65, 0x04, 0xbd, 0x03, 0xe6, 0x04, 0x35, 0xb9, 0x9b, + 0xec, 0x60, 0x2e, 0xea, 0xcf, 0xd0, 0xdd, 0x2d, 0x3a, 0x62, 0x33, 0x9d, 0x7a, 0x74, 0xb0, 0xeb, + 0x0c, 0x06, 0xe6, 0xa5, 0x30, 0xa7, 0xea, 0xed, 0xc2, 0xa5, 0x8e, 0x7c, 0xda, 0x1b, 0x9d, 0xe4, + 0xac, 0x2b, 0x38, 0xdb, 0x9c, 0xe8, 0x51, 0x34, 0x46, 0xd2, 0x04, 0x1a, 0x77, 0x87, 0x7a, 0x19, + 0x70, 0xf9, 0x4f, 0xcb, 0x03, 0x3c, 0x74, 0x25, 0xb9, 0x92, 0x3b, 0x95, 0xe9, 0xd8, 0xaa, 0x9b, + 0x6b, 0x9b, 0x1f, 0x70, 0x25, 0x2a, 0xe7, 0x6c, 0xc3, 0x7e, 0x55, 0x39, 0xf5, 0x06, 0x5a, 0x23, + 0xb1, 0x2e, 0xf6, 0x01, 0xac, 0x71, 0xb9, 0x20, 0xb4, 0xef, 0x1d, 0x42, 0x7e, 0x0e, 0x01, 0x1d, + 0xaf, 0x0a, 0x13, 0x04, 0x8e, 0x03, 0x4d, 0x1e, 0xc4, 0xe0, +}; + +inline constexpr std::array HevcCroppedStream{ + 0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, + 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x1e, 0x95, 0x98, 0x09, 0x00, 0x00, 0x00, 0x01, + 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, + 0x00, 0x1e, 0xa0, 0x20, 0x82, 0x9f, 0x79, 0x65, 0x66, 0xab, 0xca, 0xf0, 0x16, 0x80, 0x80, 0x00, + 0x00, 0x03, 0x00, 0x80, 0x00, 0x00, 0x0f, 0x04, 0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0xc1, 0x73, + 0xd1, 0x89, 0x00, 0x00, 0x01, 0x28, 0x01, 0xaf, 0x08, 0x59, 0xf2, 0xec, 0x80, 0xf9, 0x31, 0xa3, + 0xfa, 0x90, 0xca, 0x97, 0x42, 0xb5, 0x6f, 0xcb, 0xc8, 0xd2, 0x51, 0xa2, 0x3f, 0xe0, 0x0d, 0x82, + 0x6f, 0x1f, 0x37, 0xd4, 0x65, 0x18, 0x78, 0x60, 0x51, 0x3b, 0x98, 0xd0, 0x44, 0xfa, 0xff, 0x0b, + 0xd5, 0xdc, 0x09, 0xea, 0xf7, 0x79, 0xb4, 0xd2, 0xbc, 0x97, 0x53, 0xf2, 0x7d, 0x10, 0x5c, 0xc8, + 0x3a, 0xe0, 0xa7, 0xd8, 0xe2, 0xfc, 0x1d, 0x80, 0x09, 0xcd, 0x31, 0x09, 0xa4, 0xa9, 0x05, 0x5d, + 0x08, 0x74, 0x50, 0xf5, 0xbe, 0xf9, 0xc8, 0xa6, 0xef, 0xe4, 0xff, 0xd6, 0x86, 0x02, 0xb6, 0xa2, + 0x3f, 0x73, 0x80, 0xdb, 0x4e, 0x76, 0xa8, 0xc7, 0xac, 0x4b, 0x5b, 0xbf, 0xe1, 0x88, 0x18, 0xce, + 0x14, 0x5e, 0xf4, 0xb3, 0x57, 0xe6, 0x83, 0xe1, 0x94, 0x8f, 0x55, 0x77, 0xbb, 0x1c, 0x77, 0xd3, + 0x9e, 0xdc, 0xd5, 0x8a, 0x61, 0xb2, 0xf4, 0x77, 0xb8, 0xd1, 0x27, 0x5c, 0x41, 0x75, 0x0a, 0x82, + 0xf5, 0xe8, 0x32, 0x45, 0xae, 0x94, 0x07, 0xcb, 0xde, 0x35, 0x53, 0x3c, 0x0d, 0xf6, 0x5d, 0x1f, + 0x97, 0x0b, 0x04, 0x90, 0x4b, 0xd3, 0xf6, 0x04, 0x58, 0x26, 0xe2, 0x08, 0xe5, 0xb3, 0xa3, 0x05, + 0x5a, 0x8f, 0x6b, 0x03, 0x4b, 0x0a, 0xd4, 0x63, 0x9d, 0x2e, 0xe2, 0x19, 0xa3, 0xde, 0xb5, 0x53, + 0xf2, 0x07, 0xdc, 0xc8, 0xda, 0x2c, 0xfa, 0x2e, 0x62, 0x39, 0xf3, 0xfa, 0x2a, 0x77, 0xc2, 0xb9, + 0x76, 0x63, 0x58, 0xd3, 0xf8, 0xa6, 0xcb, 0x2b, 0xa4, 0xa8, 0xc8, 0x4a, 0xed, 0xbe, 0x8e, 0xf0, + 0xc9, 0x12, 0xf4, 0xb2, 0x91, 0xad, 0xd2, 0x18, 0xcc, 0x0c, 0x4d, 0x56, 0xd9, 0xa3, 0x09, 0x6c, + 0x01, 0x3a, 0xa4, 0xa5, 0x10, 0x98, 0x4d, 0x85, 0x92, 0xa2, 0xfa, 0x50, 0xfe, 0x2d, 0x42, 0xab, + 0xf3, 0x98, 0xe7, 0x16, 0xf1, 0x0b, 0xac, 0x74, 0x5d, 0xcd, 0xfa, 0xe3, 0x75, 0xf3, 0x66, 0x1e, + 0xf0, 0xa3, 0xad, 0x82, 0x8e, 0x25, 0x2c, 0x8f, 0xe1, 0xac, 0xcb, 0xca, 0x0b, 0x19, 0x32, 0x83, + 0x4a, 0xfd, 0x30, 0xc4, 0xe5, 0x97, 0x73, 0xfd, 0x43, 0xd0, +}; + +inline constexpr std::array HevcHashes{ + 0x7291c31ee57c5065ull, 0x37b15adc47810098ull, 0xc84737b65a65b247ull, + 0xe39bc891f8286267ull, 0x734caf1ba961b3c2ull, 0x7deb44bd1ed30531ull, +}; + +inline std::vector> hevcUnits(const std::uint8_t* stream, std::size_t size) { + std::vector starts; + for (std::size_t index = 0; index + 4 < size; ++index) { + if (stream[index] == 0 && stream[index + 1] == 0 && stream[index + 2] == 0 && stream[index + 3] == 1 && ((stream[index + 4] >> 1u) & 0x3fu) == 35) starts.push_back(index); + } + starts.push_back(size); + std::vector> units; + for (std::size_t unit = 0; unit + 1 < starts.size(); ++unit) units.emplace_back(stream + starts[unit], stream + starts[unit + 1]); + return units; +} + +} diff --git a/core/shader/recompiler/CacheKey.hpp b/core/shader/recompiler/CacheKey.hpp index d95621f9a1..cf6d2f2fe5 100644 --- a/core/shader/recompiler/CacheKey.hpp +++ b/core/shader/recompiler/CacheKey.hpp @@ -4,6 +4,7 @@ #include "Recompiler.hpp" #include "RdnaDecoder/RdnaDescriptorFormat.hpp" #include "prx/libc/include/HostThreadLocal.hpp" +#include #include #include #include @@ -233,6 +234,10 @@ private: append(key, value.fetchEmbedded); append(key, value.paClVsOutCntl); append(key, value.depthRemapMode); + append(key, value.pointList); + append(key, std::bit_cast(value.pointSize)); + append(key, std::bit_cast(value.pointSizeMin)); + append(key, std::bit_cast(value.pointSizeMax)); if (value.resourcesNum > value.resources.size()) throw std::runtime_error("Shader cache: invalid vertex resource count"); append(key, value.resourcesNum); for (std::uint32_t i = 0; i < value.resourcesNum; ++i) { diff --git a/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp b/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp index 11a9d92195..cf3880a037 100644 --- a/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp +++ b/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp @@ -1,4 +1,5 @@ #include "ControlFlow/RequestSerializer.hpp" +#include #include namespace ShaderRecompiler { @@ -831,7 +832,7 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const std::string buffer; Writer writer(buffer); writer.WriteU32(0x41505335u); - writer.WriteU32(42u); + writer.WriteU32(43u); writeShaderBinary(writer, request.shader); writeGuestContext(writer, request.context); writeSpirvTarget(writer, request.target); @@ -863,6 +864,12 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const } writer.WriteBool(request.target.float32FlushToZero); writer.WriteBool(request.target.float32DenormPreserve); + if (request.context.vertex.has_value()) { + writer.WriteBool(request.context.vertex->pointList); + writer.WriteU32(std::bit_cast(request.context.vertex->pointSize)); + writer.WriteU32(std::bit_cast(request.context.vertex->pointSizeMin)); + writer.WriteU32(std::bit_cast(request.context.vertex->pointSizeMax)); + } return base64Encode(buffer); } @@ -871,7 +878,7 @@ DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const Reader reader(decoded); if (reader.ReadU32() != 0x41505335u) throw std::runtime_error("invalid recompile request signature"); const auto version = reader.ReadU32(); - if (version < 1u || version > 42u) throw std::runtime_error("unsupported recompile request serialization version"); + if (version < 1u || version > 43u) throw std::runtime_error("unsupported recompile request serialization version"); DeserializedRequest result{}; result.request.shader = readShaderBinary(reader, result.shaderCode, result.shaderHeader); result.request.context = readGuestContext(reader, result, version); @@ -917,6 +924,14 @@ DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const } if (version >= 36u) result.request.target.float32FlushToZero = reader.ReadBool(); if (version >= 41u) result.request.target.float32DenormPreserve = reader.ReadBool(); + if (version >= 43u && result.request.context.vertex.has_value()) { + auto& vertex = *result.request.context.vertex; + vertex.pointList = reader.ReadBool(); + vertex.pointSize = std::bit_cast(reader.ReadU32()); + vertex.pointSizeMin = std::bit_cast(reader.ReadU32()); + vertex.pointSizeMax = std::bit_cast(reader.ReadU32()); + result.vertex = vertex; + } if (version >= 21u && reader.Remaining() != 0u) throw std::runtime_error("RequestSerializer: unexpected trailing data"); return result; } diff --git a/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp b/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp index 84034d4306..3a9286f578 100644 --- a/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp +++ b/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp @@ -141,6 +141,10 @@ struct ShaderVertexInputInfo { std::uint32_t scratchSizeDwords = 0; std::uint32_t hostSubgroupSize = 64; std::uint32_t paClVsOutCntl = 0; + bool pointList = false; + float pointSize = 1.0f; + float pointSizeMin = 0.0f; + float pointSizeMax = 0.0f; ShaderClipSpaceTransform clipSpace; ShaderDepthRemap depthRemap; ShaderMeshInputInfo mesh; diff --git a/core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp b/core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp index aa37749e51..a111de6891 100644 --- a/core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp +++ b/core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp @@ -624,6 +624,7 @@ void ShaderInfoCollector::Collect(IrProgram& program, const ShaderStageInputInfo } CollectBuiltinInputs(program, next); CollectOutputs(program, inputInfo, next); + if ((program.Resources().stage == IrShaderStage::Vertex || program.Resources().stage == IrShaderStage::Mesh) && inputInfo.vertex != nullptr && inputInfo.vertex->pointList) AddOutput(next, StageOutputKind::PointSize, 0, 0, "gl_PointSize"); CollectSamplerUses(program, next); program.Info() = std::move(next); program.Metadata().shaderInfoComplete = true; diff --git a/core/shader/recompiler/Recompiler.hpp b/core/shader/recompiler/Recompiler.hpp index 23c4658d37..4cb5a69c8d 100644 --- a/core/shader/recompiler/Recompiler.hpp +++ b/core/shader/recompiler/Recompiler.hpp @@ -214,6 +214,10 @@ struct ShaderVertexStageInfo { bool fetchEmbedded; std::uint32_t paClVsOutCntl = 0; std::uint32_t depthRemapMode = 0; + bool pointList = false; + float pointSize = 1.0f; + float pointSizeMin = 0.0f; + float pointSizeMax = 0.0f; }; struct ShaderFloatMode { diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp index c82ab5fe76..57eb96f9ce 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp @@ -9,6 +9,7 @@ #include "PipelineSpecialization.hpp" #include #include +#include #include #include @@ -582,6 +583,10 @@ std::uint32_t ExportVector(SpirvValueEmitContext& ctx, std::uint32_t data, const return vector; } +float PointSizeFloor(const ShaderVertexInputInfo& vertex) { + return std::max(vertex.pointSizeMin, std::numeric_limits::min()); +} + void EmitAuxPositionExport(SpirvValueEmitContext& ctx, std::uint32_t data, const ExportInfo& exp) { auto& state = ctx.state; if (state.inputInfo.vertex == nullptr) { @@ -614,7 +619,13 @@ void EmitAuxPositionExport(SpirvValueEmitContext& ctx, std::uint32_t data, const const auto f32 = state.module.AllocateId(); state.module.AddFunction(spv::OpBitcast, TypeF32(state), f32, raw); if (output.pointSize) { - state.module.AddFunction(spv::OpStore, state.pointSizeVariable, f32); + auto size = f32; + if (state.inputInfo.vertex->pointList) { + size = state.module.AllocateId(); + state.module.AddFunction(spv::OpExtInst, TypeF32(state), size, GlslStd450(state), GLSLstd450FClamp, f32, ConstantF32(state, std::bit_cast(PointSizeFloor(*state.inputInfo.vertex))), ConstantF32(state, std::bit_cast(state.inputInfo.vertex->pointSizeMax))); + } + const auto pointer = state.program.Resources().stage == IrShaderStage::Mesh ? MeshOutputPointer(state, StageOutputKind::PointSize) : state.pointSizeVariable; + state.module.AddFunction(spv::OpStore, pointer, size); continue; } const auto storeDistance = [&](StageOutputKind kind, std::uint32_t variable, std::uint32_t index) { @@ -772,7 +783,7 @@ std::uint32_t MeshOutputType(SpirvEmitterState& state, StageOutputKind kind) { if (kind == StageOutputKind::Layer) { return TypeU32(state); } - return IsMeshDistance(kind) ? TypeF32(state) : TypeF32Vector(state, 4u); + return IsMeshDistance(kind) || kind == StageOutputKind::PointSize ? TypeF32(state) : TypeF32Vector(state, 4u); } std::uint32_t MeshDistancePointer(SpirvEmitterState& state, const SpirvOutputBinding& output, std::uint32_t vertex) { @@ -976,7 +987,7 @@ void DefineMeshOutputs(SpirvEmitterState& state) { output.meshDataVariable = MeshArray(state, spv::StorageClassPrivate, TypeF32(state), state.laneCount); continue; } - if (output.kind != StageOutputKind::Position && output.kind != StageOutputKind::Parameter && output.kind != StageOutputKind::Layer) { + if (output.kind != StageOutputKind::Position && output.kind != StageOutputKind::Parameter && output.kind != StageOutputKind::Layer && output.kind != StageOutputKind::PointSize) { throw std::runtime_error("unsupported mesh output kind"); } const auto type = MeshOutputType(state, output.kind); @@ -992,7 +1003,7 @@ void DefineMeshOutputs(SpirvEmitterState& state) { if (output.kind == StageOutputKind::Parameter) { state.module.AddAnnotation(spv::OpDecorate, output.variableId, spv::DecorationLocation, output.location); } else if (output.kind != StageOutputKind::Layer) { - state.module.AddAnnotation(spv::OpDecorate, output.variableId, spv::DecorationBuiltIn, spv::BuiltInPosition); + state.module.AddAnnotation(spv::OpDecorate, output.variableId, spv::DecorationBuiltIn, output.kind == StageOutputKind::PointSize ? spv::BuiltInPointSize : spv::BuiltInPosition); } if (output.kind == StageOutputKind::Position) { state.module.AddAnnotation(spv::OpDecorate, output.variableId, spv::DecorationInvariant); @@ -1083,6 +1094,15 @@ void EmitMeshEntryPoint(SpirvEmitterState& state) { EmitLabel(state, state.module.AllocateId()); for (std::uint32_t half = 0; half < state.laneCount; half++) { state.module.AddFunction(spv::OpStore, MeshElement(state, state.meshPrimitiveData, spv::StorageClassPrivate, TypeU32(state), ConstantU32(state, half)), ConstantU32(state, 0x80000000u)); + if (state.inputInfo.vertex->pointList) { + const auto& vertex = *state.inputInfo.vertex; + const bool fromVertex = (vertex.paClVsOutCntl & (1u << 16u)) != 0u; + if (!fromVertex && !(vertex.pointSize > 0.0f)) FailEmit("mesh points need a positive PA_SU_POINT_SIZE"); + if (fromVertex && !(vertex.pointSizeMax > 0.0f)) FailEmit("mesh point sizes need a positive PA_SU_POINT_MINMAX maximum"); + const auto size = fromVertex ? std::clamp(vertex.pointSize, PointSizeFloor(vertex), vertex.pointSizeMax) : vertex.pointSize; + state.laneHalf = half; + state.module.AddFunction(spv::OpStore, MeshOutputPointer(state, StageOutputKind::PointSize), ConstantF32(state, std::bit_cast(size))); + } } const auto firstInvocation = state.module.AllocateId(); state.laneHalf = 0; @@ -1573,6 +1593,14 @@ void EmitProgram(SpirvEmitterState& state) { } if (AlphaToCoverage(state)) StoreCoverage(state, ConstantI32(state, -1)); EmitBdaAnchors(state); + if (state.pointSizeVariable != 0u && state.inputInfo.vertex != nullptr && state.inputInfo.vertex->pointList) { + const auto& vertex = *state.inputInfo.vertex; + const bool fromVertex = (vertex.paClVsOutCntl & (1u << 16u)) != 0u; + if (!fromVertex && !(vertex.pointSize > 0.0f)) FailEmit("a point list without a vertex point size needs a positive PA_SU_POINT_SIZE"); + if (fromVertex && !(vertex.pointSizeMax > 0.0f)) FailEmit("a vertex point size needs a positive PA_SU_POINT_MINMAX maximum"); + const auto size = fromVertex ? std::clamp(vertex.pointSize, PointSizeFloor(vertex), vertex.pointSizeMax) : vertex.pointSize; + state.module.AddFunction(spv::OpStore, state.pointSizeVariable, ConstantF32(state, std::bit_cast(size))); + } if (OrderedPixelShader(state)) { state.module.AddFunction(spv::OpBeginInvocationInterlockEXT); } diff --git a/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp b/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp index ecffac9de7..f1f7de5a92 100644 --- a/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp +++ b/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp @@ -190,6 +190,10 @@ ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const Gues vertexStorage.fetchEmbedded = vertex.fetchEmbedded; vertexStorage.paClVsOutCntl = vertex.paClVsOutCntl; vertexStorage.depthRemap = {vertex.depthRemapMode}; + vertexStorage.pointList = vertex.pointList; + vertexStorage.pointSize = vertex.pointSize; + vertexStorage.pointSizeMin = vertex.pointSizeMin; + vertexStorage.pointSizeMax = vertex.pointSizeMax; vertexStorage.fetchExternal = false; vertexStorage.fetchAttribReg = static_cast(vertex.fetchAttribReg); vertexStorage.fetchBufferReg = static_cast(vertex.fetchBufferReg); @@ -235,6 +239,7 @@ ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const Gues target.provokingVertex = mesh->provokingVertex; target.esgsItemSize = mesh->esgsItemSize; target.outputPrimitiveSize = mesh->outputPrimitiveSize; + if (target.outputPrimitiveSize == 1u) vertexStorage.pointList = true; } ShaderStageInputInfo result; result.vertex = &vertexStorage; diff --git a/core/shader/recompiler/tests/RequestSerializerTests.cpp b/core/shader/recompiler/tests/RequestSerializerTests.cpp index 2696063735..b1db645fe1 100644 --- a/core/shader/recompiler/tests/RequestSerializerTests.cpp +++ b/core/shader/recompiler/tests/RequestSerializerTests.cpp @@ -57,6 +57,11 @@ void InsertPixelDefaultsAfterFrontFace(std::vector& bytes, bool sa bytes.insert(bytes.begin() + 282u, sampleOrder ? 51u : 35u, 0u); } +void AppendVertexTail(std::vector& bytes) { + bytes.insert(bytes.end(), sizeof(std::uint64_t) + 1u + 5u + 1u + 1u, 0u); + bytes.insert(bytes.end(), {0u, 0u, 0u, 0x80u, 0x3fu, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u}); +} + std::vector DecodeBase64(std::string_view text) { constexpr std::string_view alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; std::vector bytes; @@ -214,12 +219,12 @@ int main() { Require(request.graphics->draw.indexAddress == 0xabcdef3400u && request.graphics->draw.indexCount == 57u && request.graphics->draw.indexElementBytes == 4u && request.graphics->draw.instanceCount == 9u); Require(request.target.float16DenormPreserve == preserve); auto upgraded = bytes; - upgraded[4] = 42u; + upgraded[4] = 43u; InsertMeshBarycentricFlag(upgraded); if (fixture.pixel) upgraded.insert(upgraded.begin() + 258u, {0u, 0u, 0u}); if (fixture.pixel) InsertPixelDefaultsAfterFrontFace(upgraded); if (fixture.tessellation) upgraded.insert(upgraded.end() - 35u, 6u, 0u); - upgraded.insert(upgraded.end(), sizeof(std::uint64_t) + 1u + 5u + 1u + 1u, 0u); + AppendVertexTail(upgraded); Require(serializer.Serialize(request) == EncodeBase64(upgraded)); auto truncated = bytes; truncated.pop_back(); @@ -254,12 +259,12 @@ int main() { layerRejected = std::string_view(error.what()) == "RequestSerializer: truncated data"; } Require(layerRejected); - layered[4] = 42u; + layered[4] = 43u; InsertMeshBarycentricFlag(layered); layered.insert(layered.begin() + 259u, {0u, 0u}); InsertPixelDefaultsAfterFrontFace(layered); if (fixture.tessellation) layered.insert(layered.end() - 35u, 6u, 0u); - layered.insert(layered.end(), sizeof(std::uint64_t) + 1u + 5u + 1u + 1u, 0u); + AppendVertexTail(layered); Require(serializer.Serialize(layerReplay.request) == EncodeBase64(layered)); } } @@ -280,11 +285,11 @@ int main() { Require(replay.request.context.pixel->rasterPrimitive == ShaderRecompiler::RasterPrimitive::Rectangle); Require(replay.request.context.vertex->paClVsOutCntl == 0x00050a05u); Require(replay.request.target.float16DenormPreserve == preserve); - bytes[4] = 42u; + bytes[4] = 43u; InsertMeshBarycentricFlag(bytes); bytes.insert(bytes.begin() + 259u, {0u, 0u}); InsertPixelDefaultsAfterFrontFace(bytes); - bytes.insert(bytes.end(), sizeof(std::uint64_t) + 1u + 5u + 1u + 1u, 0u); + AppendVertexTail(bytes); Require(serializer.Serialize(replay.request) == EncodeBase64(bytes)); auto truncated23 = DecodeBase64(encoded23); @@ -410,11 +415,11 @@ int main() { if (fixture.pixel) main38.insert(main38.begin() + 259u, {0u, 0u}); if (fixture.pixel) InsertPixelDefaultsAfterFrontFace(main38, false); auto upgraded = main38; - upgraded[4] = 42u; + upgraded[4] = 43u; if (fixture.pixel) upgraded.insert(upgraded.begin() + 317u, 16u, 0u); if (fixture.tessellation) upgraded.insert(upgraded.end() - 35u, 1u, 0u); main38.insert(main38.end(), sizeof(std::uint64_t) + 1u + 5u + 1u, 0u); - upgraded.insert(upgraded.end(), sizeof(std::uint64_t) + 1u + 5u + 1u + 1u, 0u); + AppendVertexTail(upgraded); Require(serializer.Serialize(request) == EncodeBase64(upgraded)); const auto replay38 = serializer.Deserialize(EncodeBase64(main38)); Require(replay38.request.graphics.has_value() && replay38.request.graphics->tessellation.has_value() == fixture.tessellation); @@ -422,6 +427,35 @@ int main() { Require(replay38.request.graphics->draw.indexAddress == 0xabcdef3400u && replay38.request.graphics->draw.indexCount == 57u && replay38.request.graphics->draw.indexElementBytes == 4u && replay38.request.graphics->draw.instanceCount == 9u); Require(replay38.request.target.float16DenormPreserve == preserve); Require(serializer.Serialize(replay38.request) == EncodeBase64(upgraded)); + if (fixture.tessellation) { + for (const bool merged : {false, true}) { + auto pointed = request; + pointed.graphics->tessellation->mergedHull = merged; + pointed.context.vertex->pointList = true; + pointed.context.vertex->pointSize = 3.0f; + pointed.context.vertex->pointSizeMin = 1.0f; + pointed.context.vertex->pointSizeMax = 8.0f; + auto version43 = DecodeBase64(serializer.Serialize(pointed)); + Require(version43.size() > 14u && version43[4] == 43u); + const auto replay43 = serializer.Deserialize(EncodeBase64(version43)); + Require(replay43.request.graphics->tessellation->mergedHull == merged && replay43.request.context.vertex->pointList && replay43.request.context.vertex->pointSize == 3.0f && replay43.request.context.vertex->pointSizeMin == 1.0f && replay43.request.context.vertex->pointSizeMax == 8.0f); + auto version42 = version43; + version42.resize(version42.size() - 13u); + version42[4] = 42u; + const auto replay42 = serializer.Deserialize(EncodeBase64(version42)); + Require(replay42.request.graphics->tessellation->mergedHull == merged && !replay42.request.context.vertex->pointList && replay42.request.context.vertex->pointSize == 1.0f && replay42.request.context.vertex->pointSizeMin == 0.0f && replay42.request.context.vertex->pointSizeMax == 0.0f); + auto version41 = version42; + if (fixture.pixel) version41.erase(version41.begin() + 317u, version41.begin() + 333u); + version41[4] = 41u; + const auto replay41 = serializer.Deserialize(EncodeBase64(version41)); + Require(replay41.request.graphics->tessellation->mergedHull == merged && !replay41.request.context.vertex->pointList && replay41.request.context.vertex->pointSize == 1.0f && replay41.request.context.vertex->pointSizeMin == 0.0f && replay41.request.context.vertex->pointSizeMax == 0.0f); + auto version39 = version41; + version39.pop_back(); + version39[4] = 39u; + const auto replay39 = serializer.Deserialize(EncodeBase64(version39)); + Require(replay39.request.graphics->tessellation->mergedHull == merged && !replay39.request.context.vertex->pointList && replay39.request.context.vertex->pointSize == 1.0f && replay39.request.context.vertex->pointSizeMin == 0.0f && replay39.request.context.vertex->pointSizeMax == 0.0f); + } + } for (const bool dither : {false, true}) { if (!fixture.pixel && dither) continue; auto main25 = bytes; @@ -430,14 +464,14 @@ int main() { const auto replay25 = serializer.Deserialize(EncodeBase64(main25)); Require(!fixture.pixel || (replay25.request.context.pixel->layerExported == layer && replay25.request.context.pixel->ditheredAlphaToCoverage == dither && replay25.request.context.pixel->reversedBlend == 0u)); Require(replay25.request.target.float16DenormPreserve == preserve && (!fixture.tessellation || replay25.request.graphics->tessellation->primitiveGroupSize == 37u)); - main25[4] = 42u; + main25[4] = 43u; InsertMeshBarycentricFlag(main25); if (fixture.pixel) { main25.insert(main25.begin() + 260u, 0u); InsertPixelDefaultsAfterFrontFace(main25); } if (fixture.tessellation) main25.insert(main25.end() - 35u, 1u, 0u); - main25.insert(main25.end(), sizeof(std::uint64_t) + 1u + 5u + 1u + 1u, 0u); + AppendVertexTail(main25); Require(serializer.Serialize(replay25.request) == EncodeBase64(main25)); } } @@ -453,14 +487,14 @@ int main() { const auto replay = serializer.Deserialize(encoded); Require(replay.request.target.float16DenormPreserve == preserve && replay.request.target.float32FlushToZero == !preserve && replay.request.target.float32DenormPreserve == preserve); auto bytes = DecodeBase64(encoded); - Require(bytes.size() >= 8u && bytes[4] == 42u && bytes[5] == 0u && bytes[6] == 0u && bytes[7] == 0u); + Require(bytes.size() >= 8u && bytes[4] == 43u && bytes[5] == 0u && bytes[6] == 0u && bytes[7] == 0u); auto version40 = bytes; version40.pop_back(); version40[4] = 40u; const auto replay40 = serializer.Deserialize(EncodeBase64(version40)); Require(!replay40.request.target.float32DenormPreserve && replay40.request.target.float32FlushToZero == !preserve); auto future = bytes; - future[4] = 43u; + future[4] = 44u; bool futureRejected = false; try { static_cast(serializer.Deserialize(EncodeBase64(future))); @@ -526,6 +560,76 @@ int main() { const auto replay35 = serializer.Deserialize(EncodeBase64(version35)); Require(replay35.request.target.spannedWaves == spanned && !replay35.request.target.float32FlushToZero); } + ShaderRecompiler::RecompileRequest pointRequest{}; + pointRequest.context.vertex = ShaderRecompiler::ShaderVertexStageInfo{}; + pointRequest.context.vertex->paClVsOutCntl = 0x01210000u; + pointRequest.context.vertex->pointList = true; + pointRequest.context.vertex->pointSize = 4.0f; + pointRequest.context.vertex->pointSizeMin = 0.5f; + pointRequest.context.vertex->pointSizeMax = 8.0f; + const auto pointEncoded = serializer.Serialize(pointRequest); + const auto pointReplay = serializer.Deserialize(pointEncoded); + Require(pointReplay.request.context.vertex.has_value() && pointReplay.request.context.vertex->paClVsOutCntl == 0x01210000u && pointReplay.request.context.vertex->pointList); + Require(pointReplay.request.context.vertex->pointSize == 4.0f && pointReplay.request.context.vertex->pointSizeMin == 0.5f && pointReplay.request.context.vertex->pointSizeMax == 8.0f); + Require(serializer.Serialize(pointReplay.request) == pointEncoded); + auto pointBytes = DecodeBase64(pointEncoded); + pointBytes.pop_back(); + bool pointRejected = false; + try { + static_cast(serializer.Deserialize(EncodeBase64(pointBytes))); + } catch (const std::runtime_error& error) { + pointRejected = std::string_view(error.what()) == "RequestSerializer: truncated data"; + } + Require(pointRejected); + + pointRequest.target.depthRangeRemap = true; + pointRequest.target.float32FlushToZero = true; + pointRequest.context.vertex->depthRemapMode = 2u; + const auto remappedPoint = serializer.Deserialize(serializer.Serialize(pointRequest)); + Require(remappedPoint.request.target.depthRangeRemap && remappedPoint.request.context.vertex->depthRemapMode == 2u); + Require(serializer.Serialize(remappedPoint.request) == serializer.Serialize(pointRequest)); + + for (const bool spanned : {false, true}) { + for (const std::uint8_t version : {32u, 33u, 34u, 35u, 36u, 37u, 38u}) { + pointRequest.target.spannedWaves = spanned; + auto legacy = DecodeBase64(serializer.Serialize(pointRequest)); + legacy.resize(legacy.size() - (version >= 36u ? 14u : version >= 35u ? 15u : 20u)); + legacy[4] = version; + const auto replayLegacy = serializer.Deserialize(EncodeBase64(legacy)); + Require(replayLegacy.request.target.spannedWaves == spanned); + Require(replayLegacy.request.target.float32FlushToZero == (version >= 36u)); + Require(replayLegacy.request.target.depthRangeRemap == (version >= 35u)); + Require(replayLegacy.request.context.vertex.has_value()); + Require(replayLegacy.request.context.vertex->depthRemapMode == (version >= 35u ? 2u : 0u)); + Require(replayLegacy.request.context.vertex->paClVsOutCntl == pointRequest.context.vertex->paClVsOutCntl); + Require(!replayLegacy.request.context.vertex->pointList && replayLegacy.request.context.vertex->pointSize == 1.0f); + Require(replayLegacy.request.context.vertex->pointSizeMin == 0.0f && replayLegacy.request.context.vertex->pointSizeMax == 0.0f); + const auto upgradedLegacy = DecodeBase64(serializer.Serialize(replayLegacy.request)); + Require(upgradedLegacy[4] == 43u && upgradedLegacy.size() == legacy.size() + (version >= 36u ? 14u : version >= 35u ? 15u : 20u)); + } + } + + pixel.sampleOrder = {0x76543210u, 0x01234567u, 0x10325476u, 0x67452301u}; + pointRequest.context.pixel = pixel; + const auto sampledPoint = serializer.Deserialize(serializer.Serialize(pointRequest)); + Require(sampledPoint.request.context.vertex->pointList && sampledPoint.request.context.pixel->sampleOrder == pixel.sampleOrder); + auto legacy42 = DecodeBase64(serializer.Serialize(pointRequest)); + legacy42.resize(legacy42.size() - 13u); + legacy42[4] = 42u; + const auto replay42 = serializer.Deserialize(EncodeBase64(legacy42)); + Require(replay42.request.context.pixel->sampleOrder == pixel.sampleOrder && !replay42.request.context.vertex->pointList && replay42.request.context.vertex->pointSize == 1.0f); + pixel.sampleOrder = {}; + pointRequest.context.pixel = pixel; + auto legacy34 = DecodeBase64(serializer.Serialize(pointRequest)); + legacy34.resize(legacy34.size() - 20u); + const auto stencil34 = std::search(legacy34.begin(), legacy34.end(), pixel.sampleLocations.begin(), pixel.sampleLocations.begin() + 4) + pixel.sampleLocations.size(); + const auto legacyPixelTail = static_cast(1u + sizeof(pixel.sampleOrder)); + Require(legacy34.end() - stencil34 > legacyPixelTail && std::all_of(stencil34, stencil34 + legacyPixelTail, [](std::uint8_t value) { return value == 0u; })); + legacy34.erase(stencil34, stencil34 + legacyPixelTail); + legacy34[4] = 34u; + const auto replay34 = serializer.Deserialize(EncodeBase64(legacy34)); + Require(replay34.request.context.pixel.has_value() && replay34.request.context.pixel->iteratesSamples && replay34.request.context.pixel->positionLocation == pixel.positionLocation && replay34.request.context.pixel->sampleLocations == pixel.sampleLocations); + Require(replay34.request.context.vertex.has_value() && !replay34.request.context.vertex->pointList && replay34.request.context.vertex->pointSize == 1.0f); return 0; } diff --git a/docs/dev/BUILD.md b/docs/dev/BUILD.md index 1b1a1287ac..7778788a16 100644 --- a/docs/dev/BUILD.md +++ b/docs/dev/BUILD.md @@ -125,7 +125,7 @@ Build configuration parameters: SDL and FreeType settings forced by the root `CMakeLists.txt` cannot be overridden with `-D`. -With `ANYPS5_USE_SYSTEM_DEPS=ON` those libraries come from the system (on Debian and Ubuntu: `libsdl2-dev`, `libfreetype-dev`, `libvulkan-dev`, `spirv-headers`, `glslang-dev`, `glslang-tools` and `spirv-tools`). A missing package, Vulkan-Headers older than 1.4.310 (the agc driver uses `VK_KHR_maintenance8`), or an SDL2 without the HIDAPI joystick driver (sdl2-compat over SDL3, or a build with HIDAPI disabled, which breaks the pad and disk-audio tests), fails the configure with an error and never falls back to the submodule. The forced SDL and FreeType settings from the root `CMakeLists.txt` do not apply with the flag ON: the system SDL2 comes with whatever its distribution enabled. The prx libraries are shared objects, so SDL2 is linked from the system as a shared library; the default build keeps its own static SDL2. `ffmpeg-core`, `libjpeg-turbo`, `stb`, `VulkanMemoryAllocator` and `LibAtrac9` always stay bundled and still need `git submodule update --init`. +With `ANYPS5_USE_SYSTEM_DEPS=ON` those libraries come from the system (on Debian and Ubuntu: `libsdl2-dev`, `libfreetype-dev`, `libvulkan-dev`, `spirv-headers`, `glslang-dev`, `glslang-tools` and `spirv-tools`). A missing package, Vulkan-Headers older than 1.4.310 (the agc driver uses `VK_KHR_maintenance8`), or an SDL2 without the HIDAPI joystick driver (sdl2-compat over SDL3, or a build with HIDAPI disabled, which breaks the pad and disk-audio tests), fails the configure with an error and never falls back to the submodule. The forced SDL and FreeType settings from the root `CMakeLists.txt` do not apply with the flag ON: the system SDL2 comes with whatever its distribution enabled. The prx libraries are shared objects, so SDL2 is linked from the system as a shared library; the default build keeps its own static SDL2. `ffmpeg-core`, `libjpeg-turbo`, `stb`, `VulkanMemoryAllocator`, `LibAtrac9`, `ogg` and `vorbis` always stay bundled and still need `git submodule update --init`. ## Quick syntax checks diff --git a/docs/dev/HW_ORACLE.md b/docs/dev/HW_ORACLE.md index e48f86ed1a..c675934529 100644 --- a/docs/dev/HW_ORACLE.md +++ b/docs/dev/HW_ORACLE.md @@ -10,6 +10,8 @@ the tool, but differs from gfx1013 in more places. Say which GPU you measured on ## Requirements +### Linux + - Linux with the `amdgpu` driver and read/write access to `/dev/kfd` (usually the `render` group). Root is not needed. - The ROCm HSA runtime with its headers: `libhsa-runtime-dev` (Debian, Ubuntu), `rocm-runtime-devel` (Fedora), `hsa-rocr` (Arch), or AMD's ROCm packages. A runtime under `ROCM_PATH` or `/opt/rocm` is used first. The rest of ROCm @@ -20,6 +22,17 @@ the tool, but differs from gfx1013 in more places. Say which GPU you measured on The first run builds `oracle.c` into `~/.cache/anyps5-hw-oracle/` (`HW_ORACLE_CACHE` overrides it). The target is read from the GPU (`HW_ORACLE_TARGET` overrides it). +### Windows + +- AMD's Windows driver, which installs the HIP runtime (`amdhip64_6.dll`) and the code object manager (`amd_comgr_2.dll`) + in `System32`. ROCm, a C compiler, `clang` and `ld.lld` are not needed. +- Python 3 from python.org or the Microsoft Store. + +On Windows `oracle_hip.py` replaces `oracle.c`: comgr assembles and links the kernel, and HIP runs it. The CLI, the Python +interface and the results are the same as on Linux. `HW_ORACLE_RUNTIME` picks the runtime (`hip` on Windows, `hsa` +elsewhere). `HW_ORACLE_HIP` and `HW_ORACLE_COMGR` name other DLLs. `ROCR_VISIBLE_DEVICES` selects the GPU, through +`HIP_VISIBLE_DEVICES`, unless that variable is already set. + ## Kernel `template.s` wraps the body under test. Per lane: diff --git a/docs/dev/debt/libSceAgc.md b/docs/dev/debt/libSceAgc.md index ab5eaacf4e..e83a029f5e 100644 --- a/docs/dev/debt/libSceAgc.md +++ b/docs/dev/debt/libSceAgc.md @@ -21,7 +21,7 @@ - [sceAgcDcbSet{Cx,Sh,Uc}RegistersIndirectGetSize](../../../core/libs/prx/libSceAgc/DcbState/src/Registers.cpp) (libSceAgc) - the count argument is ignored: PPSA28416 calls them with leftover register contents (0xf5f5039c), so the real functions likely take none; the size is 20 bytes either way - [sceAgcUpdateInterpolantMapping_0100](../../../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - name from its NID (`k0E7vkgqAuE`); the `(regs, vs, ps)` arguments are the declared ones, behaviour unknown - [sceAgcWriteDataPatchSetDst, sceAgcWriteDataPatchSetCachePolicy and their Async forms](../../../core/libs/prx/libSceAgc/Patch/src/WaitRegMem.cpp) (libSceAgc) - the signatures `(packet, uint8 value)` are inferred from the other patch functions, as no public declaration was found; the value is taken in the encoding of the `dst` and `cachePolicy` arguments of `sceAgcDcbWriteData` (graphics) and `sceAgcAcbWriteData` (Async, compute), and the patched packet is the one those calls would write. `sceAgcAsyncWriteDataPatchSetAddressOrOffset` is assumed to patch the address like `sceAgcWriteDataPatchSetAddressOrOffset`, as the compute WRITE_DATA packet keeps it in the same dwords -- [sceAgcCreateInterpolantMapping_10000](../../../core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp) (libSceAgc) - name from its NID (`dbOlWdppb4o`); same arguments as `sceAgcCreateInterpolantMapping_0100`; PPSA26344 passes three pointers at each of nine observed call sites (`0x9F914E`, `0x9F91E2`, `0x9F9216`, `0x1604BBD`, `0x16122D7`, `0x1618285`, `0x161838C`, `0x16184EE`, `0x1618607`), consistent with that argument assumption, but does not read the return value there, so its return type is not established. The `SPI_PS_INPUT_CNTL` bits for `is_f16` 2 inputs follow KytyPS5, not confirmed on hardware +- [sceAgcCreateInterpolantMapping_10000](../../../core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp) (libSceAgc) - name from its NID (`dbOlWdppb4o`); same arguments as `sceAgcCreateInterpolantMapping_0100`; PPSA26344 passes three pointers at each of nine observed call sites (`0x9F914E`, `0x9F91E2`, `0x9F9216`, `0x1604BBD`, `0x16122D7`, `0x1618285`, `0x161838C`, `0x16184EE`, `0x1618607`), consistent with that argument assumption, but does not read the return value there, so its return type is not established. - [sceAgcGetDataPacketPayloadRange](../../../core/libs/prx/libSceAgc/Misc/src/PacketInfo.cpp) (libSceAgc) - signature, the `{base, size in bytes}` output and the type 0 range (header + 1, one dword longer than the type 1 payload) from KytyPS5 only, matching `sceAgcGetDataPacketPayloadAddress_0090`; prosper returns header + 2 for both types from that address function - [sceAgcGetDataPacketPayloadAddress](../../../core/libs/prx/libSceAgc/Misc/src/PacketInfo.cpp) (libSceAgc) - forward to `sceAgcGetDataPacketPayloadAddress_0090` and the inferred (addr, cmd, type) parameters follow SharpProspero and decrypted firmware 3.20 NID mapping; not verified on console hardware - [sceAgcCbMemSemaphore](../../../core/libs/prx/libSceAgc/Cb/src/Sync.cpp) (libSceAgc) - signature (cb, semaphoreAddr, operation, modifier, value) follows SharpProspero; writes 4-dword PM4 0x39 packet with control word 0x80100000 following console oracle measurements in prosper (cases agcbuild_CbMemSemaphore_a/b/c); operation, modifier and value are unused @@ -29,9 +29,7 @@ - [sceAgcAcbAtomicGds_0900](../../../core/libs/prx/libSceAgc/Acb/src/Control.cpp) (libSceAgc) - name from its NID (`gQkqkLttcpw`), signature unknown - [sceAgcDcbPrimeUtcl2](../../../core/libs/prx/libSceAgc/DcbState/src/Display.cpp) (libSceAgc) - assumed to write the packet `sceAgcAcbPrimeUtcl2` writes: PRIME_UTCL2 has the same 5 dwords on the graphics and compute rings. Not confirmed on a title. Both write a NOP of that size, as there is no TLB to prime - [sceAgcDcbQueueEndOfShaderActionGetSize](../../../core/libs/prx/libSceAgc/DcbState/src/ContextState.cpp) (libSceAgc) - the builder is inline in the SDK and not exported; the size is assumed to be that of the RELEASE_MEM packet `sceAgcCbReleaseMem` writes (32 bytes), the same as `sceAgcAcbQueueEndOfShaderActionGetSize`, as in [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/main/src/game_compat.c) and [REmu](https://github.com/xnetcat/REmu/blob/main/crates/remu-runtime/src/agc_dcb.rs) -- [sceAgcUpdateInterpolantMapping_10000](../../../core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp) (libSceAgc) - name from its NID (`vieBRwlh1Lw`), not in the aerolib NID list; the `(regs, gs, ps)` arguments of `sceAgcCreateInterpolantMapping_0100` come from OpenAGC only - [sceAgcDcbSetZPassPredicationEnableGetSize, sceAgcDcbSetPredicationDisableGetSize, sceAgcDcbSetBoolPredicationEnableGetSize](../../../core/libs/prx/libSceAgc/DcbState/src/Predication.cpp) (libSceAgc) - the builders are inline in the SDK and not exported; the size is assumed to be that of the SET_PREDICATION packet `sceAgcDcbSetPredication` writes (16 bytes) -- [sceAgcUpdateInterpolantMapping_10000](../../../core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp) (libSceAgc) follows [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/3c7e8072f0d7684e930454794fd5c02056946d73/src/game_compat.c) only, not confirmed on hardware: the `sceAgcCreateInterpolantMapping_10000` transform without the identity tail, so it writes one register per pixel shader input and none for a pixel shader without inputs, and a null pixel shader throws. For an `is_f16` 1 or 3 input whose `default_value` and `default_value_hi` differ it throws: OpenAGC takes `DEFAULT_VAL` from `default_value_hi`, while KytyPS5, sharpemu and `sceAgcCreateInterpolantMapping_10000` here take it from `default_value` - [sceAgcAcbWaitUntilSafeForRendering](../../../core/libs/prx/libSceAgc/Acb/src/Control.cpp) (libSceAgc) - `(buffer, videoOutHandle, displayBufferIndex)`, the declared arguments, which PPSA12544 (01.000.010) passes inside `waitUntilSafeForRendering` and `waitUntilSafeForRendering VR` markers on an asynchronous compute command buffer, ignoring the result. It writes the same rendering-wait packet as `sceAgcDcbWaitUntilSafeForRendering`, which the driver now accepts on compute queues too; the wait blocks only that queue's worker. Whether the console encodes the compute variant differently is not known - [sceAgcGetDefaultCxStateFlat](../../../core/libs/prx/libSceAgc/Misc/src/RegisterDefaults.cpp) (libSceAgc) - copies up to `size` bytes (clamped to 2048 bytes, the 512-dword `AgcContextState`) from the default context state initialized from the version 8 public register default groups (CX, SH, UC), indexed by register offset, following OpenAGC `src/context_state.c`; returns `0x80890001` on null destination; version selection without `sceAgcInit` is unverified - [sceAgcDcbSetIndexIndirectArgs, sceAgcDcbSetIndexIndirectArgsGetSize](../../../core/libs/prx/libSceAgc/DcbDraw/src/IndexBuffer.cpp) (libSceAgc) - the `(buffer, attributeAddress, attributeIndex)` signature, a 64-bit address and a 32-bit index, is [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/3c7e8072f0d7684e930454794fd5c02056946d73/src/game_compat.c#L1742-L1757)'s. The console oracle in prosper measured the packet, an INDEX_ATTRIBUTES_INDIRECT (opcode 0x91) holding the low address dword, the high address dword and the index ([measured](https://github.com/mattias800/prosper/blob/3ea5cb9a846b6dcd00f5073360462bbda4f72288/prosper/tests/data/console_oracle/agcbuild.golden.tsv#L172-L174)), and a size of 16 bytes ([measured](https://github.com/mattias800/prosper/blob/3ea5cb9a846b6dcd00f5073360462bbda4f72288/prosper/tests/data/console_oracle/agc.golden.tsv#L67)). The console clears the low four address bits and keeps the low 16 bits of the index; here a nonzero address that is not 16-byte aligned, or an index above 0xffff, throws, as `sceAgcDcbSetIndexBuffer` throws for a misaligned address. A null address is written as 0, as the console writes it. No title is known to call them. diff --git a/docs/dev/debt/libSceAgcDriver.md b/docs/dev/debt/libSceAgcDriver.md index ed15035dc9..d795f5d53f 100644 --- a/docs/dev/debt/libSceAgcDriver.md +++ b/docs/dev/debt/libSceAgcDriver.md @@ -11,7 +11,9 @@ - [sceAgcDriverSubmitAcb, sceAgcDriverSubmitMultiAcbs](../../../core/libs/prx/libSceAgcDriver/Submit/src/Acb.cpp) (libSceAgcDriver) accept compute queues 0x20 to 0x57 and throw for any other. On a PS5 (firmware 13.40, measured 2026-10-19 from inside a retail title) `sceAgcDriverSubmitAcb` returned 0 and ran a label-only buffer on queues 0x20, 0x30, 0x40, 0x50, 0x57 and also 0x58, which throws here; queue 0x1F faulted inside the console's driver (SIGSEGV in libkernel on the calling thread), which throws here too. Queues above 0x58 and `sceAgcDriverSubmitMultiAcbs` were not measured. Closes when: the console's highest accepted compute queue is measured -- [sceAgcDriverAcquireHDRScopesACQ, sceAgcDriverGetCoredumpAddress, sceAgcDriverHp3dLockEnd, sceAgcDriverHp3dLockStart, sceAgcDriverIsEsvmProcess, sceAgcDriverNopDataWritePacket, sceAgcDriverReleaseHDRScopesACQ, sceAgcDriverSetTargetRingForDiag, sceAgcDriverSetValueToSharedGds, sceAgcDriverSetWorkloadStreamInactive, sceAgcDriverSubmitReprojectionAcb, sceAgcDriverSuspendPointSubmitCdbg, sceAgcDriverSysGetGfxAppGpuResetStatus](../../../core/libs/prx/libSceAgcDriver/Unimplemented.cpp) (libSceAgcDriver) - signatures and behavior are unverified; these parameterless declarations only resolve imports and throw through `NotImplemented_nid_no_patch`. +- [sceAgcDriverAcquireHDRScopesACQ, sceAgcDriverHp3dLockEnd, sceAgcDriverHp3dLockStart, sceAgcDriverIsEsvmProcess, sceAgcDriverNopDataWritePacket, sceAgcDriverReleaseHDRScopesACQ, sceAgcDriverSetValueToSharedGds, sceAgcDriverSetWorkloadStreamInactive, sceAgcDriverSubmitReprojectionAcb, sceAgcDriverSuspendPointSubmitCdbg, sceAgcDriverSysGetGfxAppGpuResetStatus](../../../core/libs/prx/libSceAgcDriver/Unimplemented.cpp) (libSceAgcDriver) - signatures and behavior are unverified; these parameterless declarations only resolve imports and throw through `NotImplemented_nid_no_patch`. +- [sceAgcDriverGetCoredumpAddress](../../../core/libs/prx/libSceAgcDriver/Unimplemented.cpp) (libSceAgcDriver) returns the driver's shared coredump-area pointer, which is null when no coredump area is allocated. AnyPS5 reports coredump mode disabled and does not allocate this area; the export returns null. Enabling and populating an AGC coredump area is not implemented. +- [sceAgcDriverSetTargetRingForDiag](../../../core/libs/prx/libSceAgcDriver/Unimplemented.cpp) (libSceAgcDriver) returns `0x8A6D0001` (`DRIVER_FEATURE_UNAVAILABLE`), matching the retail PS5 export. The retail diagnostic log message is not reproduced. - [Initial point context](../../../core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp) - new queues and CLEAR_STATE seed PA_SU_POINT_SIZE 0x00080008 and PA_SU_POINT_MINMAX 0xffff0000, matching every public context table in libSceAgc/Command/include/RegisterDefaultsData.hpp. Cocoon (PPSA08766) reaches geometry point output without writing these registers in the observed startup. Applying the SDK context defaults to initial/CLEAR_STATE driver context is inferred; GPU power-on reset state is not measured and is not the source of these values. Explicit writes and pushed/restored contexts retain their own values. @@ -19,7 +21,8 @@ - [Multisampled R_X and Z_X layouts](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTextureLayout.cpp) implement single-mip 2D arrays with 2, 4 or 8 equal samples and fragments, using the non-RB+ 16-pipe GFX10 equations from [AMD Addrlib](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/imported/addrlib/src/gfx10/gfx10SwizzlePattern.h). R_X accepts 1/2/4/8/16-byte elements and Z_X accepts 1/2/4-byte elements. Tile and detile preserve element bytes and leave padding untouched; they do not synchronize resident images or publish compression metadata. Other modes, sample counts, element sizes and pipe/bank XOR bits outside the supported configuration throw. PS5 address-layout equivalence has not been measured. - [Optional BMP frame dumps](../../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) use checked host stdio. An open, header, pixel or close failure prints one diagnostic and disables subsequent BMP dumps for the process; queued frames drain without more file I/O. Pixel validation and allocation failures retain the existing fatal path. The CPU fixture checks identical BMP bytes and real pipe failures. VideoOut flips on an RTX 3060 continue after a first dump that cannot be opened; header, pixel and close failures during presentation are unmeasured. -- [Inactive vertex point size](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) - `PA_CL_VS_OUT_CNTL.USE_VTX_POINT_SIZE` is accepted for lines, triangles and rectangles with `GS_EN` and `HS_EN` clear, when polygon mode is disabled or both faces are filled. Points, patches and absent routing or rasterization registers still throw. Bit 16 selects the misc export's X component as PointSize ([AMD CIK register reference](https://www.amd.com/content/dam/amd/en/documents/radeon-tech-docs/programmer-references/CIK_3D_registers_v2.pdf), PA_CL_VS_OUT_CNTL); ignoring it for non-point rasterization follows [Vulkan PointSize](https://docs.vulkan.org/refpages/latest/refpages/source/PointSize.html). This is inferred, not measured on console hardware. XashPS5 (PPSA19111 v1.3) sets `0x01210000` on a filled triangle draw. +- [Inactive vertex point size](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) - `PA_CL_VS_OUT_CNTL.USE_VTX_POINT_SIZE` is accepted for lines, triangles and rectangles with `GS_EN` and `HS_EN` clear, when polygon mode is disabled or both faces are filled. Patches and absent routing or rasterization registers still throw; point lists on that routing use it (next entry). Bit 16 selects the misc export's X component as PointSize ([AMD CIK register reference](https://www.amd.com/content/dam/amd/en/documents/radeon-tech-docs/programmer-references/CIK_3D_registers_v2.pdf), PA_CL_VS_OUT_CNTL); ignoring it for non-point rasterization follows [Vulkan PointSize](https://docs.vulkan.org/refpages/latest/refpages/source/PointSize.html). This is inferred, not measured on console hardware. XashPS5 (PPSA19111 v1.3) sets `0x01210000` on a filled triangle draw. +- [Vertex-path point lists](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - throws for a register size of 0, a clamp maximum of 0 with `USE_VTX_POINT_SIZE`, unequal register width and height, a clamp minimum above its maximum, `USE_VTX_POINT_SIZE` without `VS_OUT_MISC_VEC_ENA`, and point sprite coordinates; sizes above one pixel need `largePoints`. Inferred, not measured: that `PA_SU_POINT_MINMAX` clamps the exported size and not the register size ([radeonsi](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-24.0.0/src/gallium/drivers/radeonsi/si_state.c#L1047-1064)), and that an exported size of 0 or less covers nothing (here: the smallest point). A point list with `GS_EN` and `HS_EN` clear draws as Vulkan points, and the vertex program [writes](../../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) `PointSize`: the misc export's X, clamped, under `USE_VTX_POINT_SIZE`, else the `PA_SU_POINT_SIZE` height (12.4 fixed-point half sizes). - [RELEASE_MEM of the system counter](../../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (AGC driver) - a `RELEASE_MEM` with `DATA_SEL = 4` throws: the frequency and epoch of the console's 64-bit system counter are not known. [amdgpu's GFX10 packet header](https://github.com/torvalds/linux/blob/master/drivers/gpu/drm/amd/amdgpu/nvd.h) names the value "send 64bit sys counter value", beside 3, the GPU counter, which the driver writes as a 100 MHz count of the host's steady clock. An older KFD header names 4 a CP performance counter instead. A PS4 title through #6977 writes it at the end of each frame. - [Point and line polygon modes](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - front and back polygon types that differ while both faces are drawn throw, as Vulkan has one `polygonMode` per draw. Points are drawn only one pixel wide (`PA_SU_POINT_SIZE = 0x00080008` inside `PA_SU_POINT_MINMAX`, the `VK_KHR_maintenance5` size of a point whose shader does not write `PointSize`); other sizes, a per-vertex size and devices without `maintenance5` throw. Lines must be one pixel wide (`PA_SU_LINE_CNTL`) and use Vulkan's default line rasterization. Depth bias with a point or line polygon mode, and such a mode on a rectangle list, throw. The fields follow the [AMD CIK register reference](https://www.amd.com/content/dam/amd/en/documents/radeon-tech-docs/programmer-references/CIK_3D_registers_v2.pdf); the console's rasterization of these points and lines is not measured. - [Native multisampled depth clears](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) accept the recognized HTILE mask kernel's direct full-range dispatch after its GPU writes complete. Every metadata byte needs tracked driver-store coverage since notification, no intervening CPU or depth write, and HTILE words proving the current clear value of zero or one. Partial, overlapping, indirect and unproven producers remain unsupported. Existing sampled views retain the native image; guest depth publication and metadata-fill transitions remain unimplemented. Windows shared imports retain write tracking through separate aliases; private host imports that lose tracking and copied or mirrored HTILE uploads remain unsupported. Clear-word encoding and console equivalence remain unmeasured on PS5 hardware. @@ -31,7 +34,7 @@ - [Indices far past every vertex fetch](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) - when a draw's highest index is at least 64Ki vertices past the largest per-vertex record count, every index past that count is moved to it in a copy of the indices, so the zero-padded vertex copies are not sized to the original index (PPSA04159 draws a list of `0xffffffff` with primitive restart off, which asked for 160 GiB). Each attribute fetched by vertex index reads zeros there as at the original index (out-of-bounds select 0 or 1); a draw with another out-of-bounds select keeps its indices. The vertex index the shader sees changes for the moved indices, which a shader that reads it directly would notice - [Depth clear draws](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - a draw with `DB_RENDER_CONTROL.DEPTH_CLEAR_ENABLE` is taken to write `DB_DEPTH_CLEAR` to every pixel it covers whatever the depth test, depth bounds and depth bias say: it is drawn with an always-passing depth write and the viewport depth range pinned to the clear value. The draw is rasterized as submitted, so its coverage is the draw's own. Whether the DB still applies the depth test or HiZ to such a draw is not measured - [GS_FAST_LAUNCH geometry](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - `VGT_SHADER_STAGES_EN.GS_FAST_LAUNCH = 1` with a point list and `GE_CNTL` groups of one vertex and one primitive decodes as one point per subgroup, with the primitive limit taken from `GE_NGG_SUBGRP_CNTL.PRIM_AMP_FACTOR` and the GS/ES VGPR counts not required. Follows how RADV drives mesh shaders on gfx10.3 (`ac_nir_lower_ngg.c`, the workgroup index comes from the vertex id of the first thread), not measured on the console. Fast launch 2, other group sizes or input primitives and indexed fast-launch draws still throw. PPSA03671's fast-launch draws decode, but the title stops earlier in a wave64 vertex program, so they have not reached the GPU yet. -- [Point-list geometry output](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - a geometry program with `VGT_GS_OUT_PRIM_TYPE = 0` (point list) runs as a mesh shader with `OutputPoints` and scalar `PrimitivePointIndicesEXT`. Each primitive export emits one point whose vertex index is extracted from bits [8:0] of the packed word. A subgroup gets up to `maxVertices` points, with provoking vertex 0 and point raster primitive topology. Rectangle lists, per-stream types and point or line strips from a vertex program on the mesh path still throw. +- [Point geometry output](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - a geometry program with `VGT_GS_OUT_PRIM_TYPE = 0` emits mesh points with `OutputPoints` and scalar `PrimitivePointIndicesEXT` as required by [SPV_EXT_mesh_shader](https://github.khronos.org/SPIRV-Registry/extensions/EXT/SPV_EXT_mesh_shader.html). Each packed primitive export contributes its first 9-bit vertex index and its null bit, using the same NGG primitive packing as line and triangle outputs. The subgroup reserves at least `GE_MAX_OUTPUT_PER_SUBGROUP` point slots, as the line path does; the console budget is not measured. Points use the register size or the misc-X point-size export and clamp interpreted by the vertex-point path; those register, clamp and rasterization inferences remain unmeasured on the console. Point sprites remain unsupported. Point output from a vertex workgroup still throws. Cocoon (PPSA08766) reaches this path with triangle-strip input. - [Line-strip geometry output](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - a geometry program with `VGT_GS_OUT_PRIM_TYPE = 1` (line strip) runs as a mesh shader with `OutputLinesEXT`: each primitive export is one line whose two vertex indices are packed like the three of a triangle, as Mesa's NGG lowering exports line strips (`ac_nir_lower_ngg_gs.c`), and each input primitive gets `VGT_GS_MAX_VERT_OUT - 1` lines where a triangle strip gets `- 2` triangles. A subgroup gets at least `GE_MAX_OUTPUT_PER_SUBGROUP` line slots; whether the console does the same is unmeasured. A fast-launch subgroup takes its line count from `GE_NGG_SUBGRP_CNTL.PRIM_AMP_FACTOR`. Line output counts as lines for polygon offset decode. The lines are drawn 1 pixel wide with Vulkan default rasterization; `PA_SU_LINE_CNTL` is not read, and console line rules are not measured. - [Alpha-to-coverage](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - only `DB_ALPHA_TO_MASK = 0xaa01` is accepted: the enable bit with the four quad offsets at 2 and no `OFFSET_ROUND`, the undithered setting [radeonsi](https://gitlab.freedesktop.org/mesa/mesa/-/blob/bffd91947efc1932da99aca69fc540d6d4201b0c/src/gallium/drivers/radeonsi/si_state.c#L371-L381) programs. Draws are single-sampled, so the pixel shader [writes](../../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) `SampleMask` 0 when its MRT0 alpha is below one half or NaN and all ones otherwise, and runs with late fragment tests. That rule was measured on a Radeon 660M (gfx1035, RADV with `RADV_DEBUG=noatocdithering`, which programs the same register) for 0, -0, 0.25, the two floats below one half, one half, the two above, 0.75, 1, 2, -1, both infinities and both NaNs, and is pinned in [AlphaToCoverage.cpp](../../../core/libs/prx/libSceAgcDriver/tests/execution/AlphaToCoverage.cpp); it is not measured on gfx1013. Vulkan's `alphaToCoverageEnable` is not used: the NVIDIA driver 615.78.08 dithers single-sample coverage (an alpha of 0.25 writes half the pixels of an RTX 3050 Laptop target). Other offsets, `OFFSET_ROUND`, a sample-mask export, and an MRT0 that is missing, swizzled, packed or exported without a floating-point alpha throw. A draw without a color target on MRT0 keeps alpha-to-coverage from its MRT0 export, as [radeonsi exports MRT0 as `32_AR` for alpha-to-coverage without a color buffer](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-24.0.0/src/gallium/drivers/radeonsi/si_state_shaders.cpp#L2425-L2431). A draw that enables alpha-to-coverage with no MRT0 export still throws: what the console covers then is not measured. Default dithered `DB_ALPHA_TO_MASK = 0x18701` additionally uses a per-quad sample-mask table [measured on gfx1036](https://github.com/boykopovar/AnyPS5/pull/4451#issuecomment-6124848921) for eight standard samples with FP16_ABGR or 32_ABGR exports; other sample counts, custom locations, EQAA combinations and dithered UNORM16/SNORM16 exports throw. The dithered rule is not measured on gfx1013. - [A one-sample target under a multisampled rasterizer without MSAA_ENABLE](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - `PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES` > 0 with `PA_SC_MODE_CNTL_0.MSAA_ENABLE` clear, one-sample color targets and no depth target, or a one-sample depth target with no color target or one-sample color targets, is drawn as a one-sample draw at the pixel center. Without the bit every sample is taken to be at the pixel center (see the entry for a multisampled target rasterized without MSAA_ENABLE below), and a one-sample target is taken to keep the coverage of sample 0, so a one-sample depth target is tested and written once per pixel with the depth at the pixel center. That is the rule [`VK_AMD_mixed_attachment_samples`](https://github.com/KhronosGroup/Vulkan-Docs/blob/193ebe195f47ffc5004683d1659e71c725658c4d/chapters/fragops.adoc#L2510-L2516) gives a color target with fewer samples than the rasterizer, but Vulkan reaches it only beside a depth or stencil target with the rasterizer's sample count ([VUID-VkGraphicsPipelineCreateInfo-subpass-01505](https://github.com/KhronosGroup/Vulkan-Docs/blob/193ebe195f47ffc5004683d1659e71c725658c4d/chapters/pipelines.adoc#L3033-L3041)), so here it is an analogy, and for a one-sample depth target an extension of it. [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp#L127-L128) also rasterizes with the largest sample count of the bound depth and color targets ([L416-L417](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp#L416-L417), [L477-L480](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp#L477-L480)), and reads `PA_SC_AA_CONFIG` only for a `SAMPLE_COVERAGE` input ([L174-L177](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp#L174-L177)). This is not measured on hardware. An `ANCILLARY` or `SAMPLE_COVERAGE` pixel input and a sample-mask export throw in a pixel program that runs, and so does `PS_ITER_SAMPLE` with one-sample color targets and no depth target, because what the console gives them under a multisampled configuration is not known; a depth-only draw that skips its pixel program is drawn with the null program, and its `PS_ITER_SAMPLE` and pixel input bits are ignored. With a one-sample depth target, a pixel program with `PS_ITER_SAMPLE` and neither input runs once per pixel; if the console runs it once per sample, its atomics and append or consume counters count differently. Points and alpha-to-coverage throw as for a multisampled target. With a one-sample depth target, line and point lists (`VGT_PRIMITIVE_TYPE` 1-3, even when a geometry program turns them into triangles), `DB_EQAA` Z interpolation (`INTERPOLATE_COMP_Z`, `INTERPOLATE_SRC_Z`) and overrasterization (`OVERRASTERIZATION_AMOUNT`, `ENABLE_POSTZ_OVERRASTERIZATION`) also throw. METAL GEAR SOLID: Peace Walker (PPSA18216) draws a one-sample color target under a 4x configuration this way; the first draw of its opening movie binds a one-sample depth target and no color target under the same configuration. diff --git a/docs/dev/debt/libSceAjm.native.md b/docs/dev/debt/libSceAjm.native.md index c7d4e74ca1..5fd76482c4 100644 --- a/docs/dev/debt/libSceAjm.native.md +++ b/docs/dev/debt/libSceAjm.native.md @@ -8,11 +8,11 @@ - [ATRAC9 vibration decoding](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native): channel configurations 6 (mono) and 7 (dual mono) use the pinned [vgmstream decoder](https://github.com/vgmstream/vgmstream/blob/7c520e8f9b078dfde2f84f1b5bcbad0c6c415806/src/coding/libs/atrac9_dec.c), including its 12/24/48 kHz profile limits. Synthetic fixtures cover interleaving, reset after malformed input, partial superframes and PCM16/PCM32/float output; console PCM rounding and physical haptic output are unverified. Ordinary channel configurations and discrete multichannel streams still use LibAtrac9. Decoder creation and decoding run under the existing AJM instance lock because the dependency rebuilds shared Huffman tables during initialization. - [sceAjmBatchJobGetInfo](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - writes the result and format sideband of a run job without buffers with the `SIDEBAND_FORMAT` flag (0x400000000000), as [KytyPS5](https://github.com/KytyPS5/KytyPS5) does, not confirmed on a PS5 title; the format reports bitrate 0 for ATRAC9 as in KytyPS5, while [shadPS4](https://github.com/shadps4-emu/shadPS4) derives it from the superframe size; before the instance is initialized it returns the not-initialized result (1) and no format, like the other run jobs here, while KytyPS5 returns result 0 with the format of the uninitialized decoder - [sceAjmDecMp3ParseFrame](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - signature, result layout and rate tables taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) reimplementation of the PS4 library, not confirmed on a PS5 title: Layer I/II headers are accepted when no original-file-length (`parse_ofl`) lookup is requested, which is supported only for Layer III; MPEG-2.5 stops at 64 kbps. The `parse_ofl` layout (Xing/Info + LAME tag after side information, with the standard fixed Xing/Info offset retained for encoders such as [LAME](https://github.com/lameproject/lame/blob/master/libmp3lame/VbrTag.c#L854-L862), VBRI at byte 36 or after side information, Fraunhofer `0xB4` block with its CRC, encoder delay = samples per frame + LAME delay + 529) also comes from shadPS4 only, and the Fraunhofer block has no other public description. -- [sceAjmBatchJobGetCodecInfo, sceAjmBatchJobGetGaplessDecode](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - the `RUN_GET_CODEC_INFO` flag (0x800), the ATRAC9 codec info layout and its next frame size are taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [RPCSX](https://github.com/RPCSX/rpcsx) reimplementations, not confirmed on a PS5 title; a run job without buffers returns result 0, as in shadPS4. The gapless sideband reports the total and skip counts the title set and does not count them down: RPCSX does the same, shadPS4 counts them down and BryKytyPS5 returns its current counts +- [sceAjmBatchJobGetCodecInfo, sceAjmBatchJobGetGaplessDecode](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - the `RUN_GET_CODEC_INFO` flag (0x800), the ATRAC9 codec info layout and its next frame size are taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [RPCSX](https://github.com/RPCSX/rpcsx) reimplementations, not confirmed on a PS5 title (the one call in PPSA04354 passes a 24-byte result buffer, the 8-byte result plus the 16-byte ATRAC9 codec info written here; the call does not show the codec or the fields); a run job without buffers returns result 0, as in shadPS4. The gapless sideband reports the total and skip counts the title set and does not count them down: RPCSX does the same, shadPS4 counts them down and BryKytyPS5 returns its current counts - [AJM Opus decoder](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native, codec 24) - the initialize parameter layout and little-endian `u16` packet byte count come from The Smurfs Dreams (`2, 48000, 0`) and Lost Soul Aside (`4, 48000, 1`). The third word is interpreted as the mapping family, as in [KytyPS5's initialize structure](https://github.com/KytyPS5/KytyPS5/blob/984cfcebd141b211aa9e0a8cde63b0ebe652d07a/src/libs/ajm/opus_decoder.h#L11-L15). Family 1 uses the conventional stream counts, coupled counts and channel map from [libopus 1.5.2's surround layout](https://github.com/xiph/opus/blob/v1.5.2/src/opus_multistream_encoder.c#L47-L55), passed to FFmpeg through an [RFC 7845 section 5.1](https://datatracker.ietf.org/doc/html/rfc7845#section-5.1) header with zero pre-skip and gain; gapless skipping remains controlled by AJM sideband parameters. FFmpeg returns PCM in its native speaker order. The implicit stream layout, PCM channel order and invalid family-0 channel count result are not verified on a console. NHL 27 (PPSA34063) passes `1, 48000, 255`. Family 255 with one channel is decoded as one mono stream, the same as family 0; this is assumed, since the stream count, coupled stream count and channel mapping that a family-255 Opus header carries ([RFC 7845 section 5.1.1](https://datatracker.ietf.org/doc/html/rfc7845#section-5.1.1)) are not passed. Only 48000 Hz, mapping families 0 and 1, and family 255 with one channel are implemented; other rates and families, and family 255 with more channels, throw - [AJM codec 23](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - taken as interleaved `s16` PCM that only passes through the resampler, from Broken Sword - Shadow of the Templars: Reforged (PPSA21480); no source names a codec 23 ([shadPS4's PS4 header](https://github.com/shadps4-emu/shadPS4/blob/5aa7ffa3f4f21bba827d7865ed8f69f98f993542/src/core/libraries/ajm/ajm.h#L19-L25) ends its codec list with `Max = 23`) and the name `CODEC_PCM` is ours. The title decodes its Ogg Opus and Vorbis audio itself and, for every block, initializes the instance (flags 0x1002) with `0, 2, 44100`, sets the ratio 0.91875 (44100/48000) and decodes 235 stereo frames into a 256-frame output. The 12 initialize bytes are taken as input format (0 is `s16`, other values throw), channel count and sample rate; the rate is only reported in the format sideband; other sizes throw; zero or more than 8 channels or a zero rate return the invalid-parameter result as for Opus, not verified. A run consumes its whole input and emits every output frame whose position (output index × ratio) is below the input frame count, with the 4-point Catmull-Rom of the other codecs' resampler and the first and last input frames repeated past the edges, so each run restarts at position 0; `totalDecodedSamples` counts input frames; `s32` and float outputs are accepted although only `s16` was seen. Throwing because the console's behaviour is unknown: a run after a resampled run without an initialize or context clear in between (whether the console carries the resampler position across runs), a ratio change per sample, gapless counts, an empty input or one that is not a whole number of frames, an output that cannot hold the resampled input, run flags other than `SIDEBAND_STREAM` and initialization through `sceAjmBatchJobControl` - [sceAjmBatchJobSetResampleParametersEx](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - the `(info, instance, float ratio_start, float ratio_change_per_sample, flags, result)` signature is inferred from the name and from `sceAjmBatchJobSetResampleParameters`, and PPSA02225's one call agrees with that layout (`rdi`, `esi`, `xmm0` and `xmm1` as two single floats, `edx`, `rcx`) without showing `flags` or the float roles. With a zero change per sample it sets the ratio as `sceAjmBatchJobSetResampleParameters` does (Watch Dogs: Legion, PPSA01500, calls it so). How a non-zero change ramps the ratio is not confirmed on a console (no source: KytyPS5 ignores it, prosper stubs it, the PS5 firmware does not export the batch builders to a payload): it is assumed to be added to the ratio after each emitted output frame, clamped at zero, and kept across batches. RIDE 4 (PPSA01599) passes one call with a change of 4.6e-7, so the exact rule matters little there. -- [sceAjmBatchJobControl](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - RESET (bit 13), INITIALIZE (bit 14) and the sideband input order (gapless decode, then initialize parameters) come from [shadPS4](https://github.com/shadps4-emu/shadPS4) (PS4); other flags throw +- [sceAjmBatchJobControl](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - RESET (bit 13), INITIALIZE (bit 14) and the sideband input order (gapless decode, then initialize parameters) come from [shadPS4](https://github.com/shadps4-emu/shadPS4) (PS4); the one call in PPSA25936 passes the flags 0x4000 (bit 14 only), an 8-byte input and an 8-byte output, which agrees with INITIALIZE alone and the 8-byte parameters, without showing the codec or the meaning of the words; other flags throw - [sceAjmBatchJobControl](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - RESET is assumed to clear the context as sceAjmBatchJobClearContext does - [sceAjmBatchJobControl](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - initialize parameters are 8 bytes for ATRAC9 and M4AAC, 12 for Opus and none for MP3, as one title passes them; other codecs and sizes, and an output other than the 8-byte result, throw - [sceAjmBatchJobControl](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - SIDEBAND_GAPLESS_DECODE with RESET overwrites both counts as in [RPCSX](https://github.com/RPCSX/rpcsx); shadPS4 ignores a zero count when the other is nonzero. Without RESET they differ, so it throws @@ -20,6 +20,7 @@ ### Functional +- [sceAjmDecWVorbisCreateHeaderPacket](../../../core/libs/prx/libSceAjm.native/Export.cpp) (libSceAjmWVorbis, implemented in libSceAjm.native) - refuses an output smaller than the whole packet (56 bytes plus the setup packet) with `0x8A6E0003`. The PS5 12.00 bit writer only checks that each write starts inside the buffer, so the console can accept a buffer a few bytes short and write past it - [AJM batch contexts](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native): start, wait and cancel reject context IDs not allocated by initialization; batches belong to their creating context, as in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/0ebea15c24253b60f514fc37a348dabc8d0bcb90/src/core/libraries/ajm/ajm.cpp). This is not verified on PS5. Finalization still does not invalidate contexts or release their instances and outstanding batches; jobs execute synchronously and cancellation does not interrupt them. - [ATRAC9 gapless segments](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - a run job stops after the gapless total of output samples counted from the start of the segment, leaving the rest of its input unconsumed, and the next run starts the next segment with the decoder as it is and without a second skip. FMOD in PPSA29141 sets a total shorter than a sound's frames and reads the frames after it on later runs; with the output held at the total its stream reader waited for them forever and the last buffer repeated. The console's handling of the frames after the total is not documented; a reset (`SetGaplessDecode` with the reset flag, `ClearContext`, initialization) starts a new segment. An ATRAC9 run job without `RUN_MULTIPLE_FRAMES` decodes its whole input as one with it: FMOD in PPSA29141 puts two run jobs in one batch with the second's input right after the whole of the first's. Whether the console stops after one frame there is not verified; MP3 and Opus run jobs still stop after one frame diff --git a/docs/dev/debt/libSceAudio3d.md b/docs/dev/debt/libSceAudio3d.md index e83c9da9a2..9c17a838c6 100644 --- a/docs/dev/debt/libSceAudio3d.md +++ b/docs/dev/debt/libSceAudio3d.md @@ -20,11 +20,7 @@ - [sceAudio3dPortGetStatus](../../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - unknown signature - [sceAudio3dReportRegisterHandler](../../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - unknown signature - [sceAudio3dReportUnregisterHandler](../../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - unknown signature -- [sceAudio3dGetSpeakerArrayMemorySize](../../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - unknown signature -- [sceAudio3dCreateSpeakerArray](../../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - unknown signature -- [sceAudio3dDeleteSpeakerArray](../../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - unknown signature -- [sceAudio3dGetSpeakerArrayMixCoefficients](../../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - unknown signature -- [sceAudio3dGetSpeakerArrayMixCoefficients2](../../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - unknown signature +- [sceAudio3dCreateSpeakerArray](../../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - the speaker-array calls return the AudioOut2 result unchanged: the console translates AudioOut2 speaker-array errors into Audio3d codes, but this project's AudioOut2 speaker-array calls throw instead of returning error codes, so there is nothing to translate yet - [Audio3d bed downmix coefficients](../../../core/libs/prx/libSceAudio3d/Mixer.hpp) (libSceAudio3d) - the centre and the surrounds fold into stereo with a 0.7071 gain and the LFE channel is dropped, which follows [shadPS4](https://github.com/shadps4-emu/shadPS4) (PS4): its `DOWNMIX_CENTER` and `DOWNMIX_SURROUND` are 0.7071 and its `DOWNMIX_LFE` is 0, so the `if constexpr` that adds the LFE is compiled out. [prosper](https://github.com/mattias800/prosper) disagrees on the LFE only: it folds it into both sides with a 0.5 gain, in its AudioOut2 main bed path, which is not an Audio3d path. KytyPS5 has no Audio3d mixing at all. The absolute level is unmeasured: fpPS4 scales the front by 1 + 3/sqrt(2) instead of 1 - [Audio3d bed channel order](../../../core/libs/prx/libSceAudio3d/Mixer.hpp) (libSceAudio3d) - the buffer is read as L, R, C, LFE, Ls, Rs, Lrs, Rrs, the standard order, which three references agree on: [shadPS4](https://github.com/shadps4-emu/shadPS4) reads the front, centre, LFE and surround samples at those indices, [prosper](https://github.com/mattias800/prosper) documents FL, FR, FC, LFE and then surround pairs, and fpPS4 names them `_FL=0 _FR=1 _FC=2 _LF=3 _SL=4 _SR=5 _BL=6 _BR=7`. fpPS4's `STD` layout swaps the side and back pairs, which this library does not model - [sceAudio3dAudioOutOutput](../../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - the return is the number of frames per channel in the queued block, which follows [KytyPS5](https://github.com/KytyPS5/KytyPS5) and [prosper](https://github.com/mattias800/prosper), whose `sceAudioOutOutput` returns the grain, and this project's own `sceAudioOutOutput`, which returns the port's `samplesNum` (the buffer length in frames). [shadPS4](https://github.com/shadps4-emu/shadPS4) returns the total samples instead (`len * channels`) and fpPS4 returns 0; no console measurement settles it diff --git a/docs/dev/debt/libSceAudioIn.md b/docs/dev/debt/libSceAudioIn.md index 1a0ad3f1ce..abb06131ee 100644 --- a/docs/dev/debt/libSceAudioIn.md +++ b/docs/dev/debt/libSceAudioIn.md @@ -6,7 +6,6 @@ ### Unknown function info -- [sceAudioInIsPadEmbeddedMic](../../../core/libs/prx/libSceAudioIn/Export.cpp) - the PS5 module uses the low handle byte as a port index and returns whether its resolved device type equals 8, propagating negative device errors. Host capture devices have no corresponding controller-microphone classification; the protected throwing body remains unchanged. - [sceAudioInDeviceIdOpen](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown signature - [sceAudioInDeviceIdAsyncOpen](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown signature - [sceAudioInSetUsbGain](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown signature @@ -40,5 +39,6 @@ ### Functional +- [sceAudioInIsPadEmbeddedMic](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - returns 0 for every open port: on the console it returns 1 when the port captures from the controller's built-in microphone and 0 for another input device, but ports here capture from the host's default input device - [sceAudioInHqOpen, sceAudioInAsyncOpen](../../../core/libs/prx/libSceAudioIn/Export.cpp) - PS5 wrapper validation requires 48000 Hz followed by exactly 128 samples for both calls; HqOpen then requires format 2. The host uses the same SDL capture path for both, so native asynchronous scheduling and high-quality processing are not reproduced. Common port validation, device selection and host capture pacing remain incomplete. - [sceAudioInGetGain, sceAudioInGetRerouteCount](../../../core/libs/prx/libSceAudioIn/Export.cpp) - GetGain writes 1.0f before handle validation and returns 0x80260105 for a null output, as confirmed by the PS5 module. Native handle bit checks and uninitialized/inactive port errors differ from the host port table. Native reroute counters follow device reassignment; the host opens each SDL device once and reports zero. diff --git a/docs/dev/debt/libSceAudioOut.md b/docs/dev/debt/libSceAudioOut.md index 0bb8753202..050b5a78dd 100644 --- a/docs/dev/debt/libSceAudioOut.md +++ b/docs/dev/debt/libSceAudioOut.md @@ -14,7 +14,7 @@ - [sceAudioOut2Set3DLatency](../../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) (libSceAudioOut) - the two 32-bit arguments and selector validation match the system library: selector 0 accepts outputs 0 through 2; selector 255 accepts outputs 1 and 2. No third latency argument exists. The host has no system 3D rendering stage, so the call still does not apply latency and does not reproduce the system initialization and output-availability checks. - [sceAudioOut2MasteringInit](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) - native initialization requires AudioOut2 initialization (0x80268006 otherwise), returns success immediately when already initialized, and otherwise rejects nonzero flags with 0x80268201. Term returns 0x80268203 without mastering initialization and clears the mastering state after a successful service call. The protected Init body remains unchanged; host Term still returns success without this lifecycle or native mastering allocations. - [sceAudioOut2MasteringGetState and sceAudioOut2MasteringSetParam](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) (libSceAudioOut) - the state layout, the states ids 1 (default) and 2 (V2), the descriptor ids `0x01010001`, `0x01020001` and `0x01010003` and the errors `0x80268201` and `0x80268204` follow [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/df5b9b92be7dfd463b47756b707d6d356a9f0550/src/libs/libAudio2.cpp), not a console measurement. GetState zeroes the states and writes the two descriptors; with no mastering stage the compressor and limiter values stay zero. SetParam accepts any parameter set and records nothing, as KytyPS5 does; a null parameter returns `0x80268201`, the mastering invalid-API-parameter error KytyPS5 returns for a null state, which is an inference rather than a measurement. The meaning of `output` and `user` is unknown and both are ignored -- [sceAudioOut2SetSystemDebugState](../../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) (libSceAudioOut) - records the debug state id in the trace and returns 0, as [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/df5b9b92be7dfd463b47756b707d6d356a9f0550/src/libs/libAudio2.cpp) does; the host has no system mix to apply it to. The debug state ids are unknown, so every id is accepted, and a null param returns `0x80260502` like the other AudioOut2 calls. Not measured on a console +- [sceAudioOut2SetSystemDebugState](../../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) (libSceAudioOut) - records the debug state id in the trace and returns 0, as [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/df5b9b92be7dfd463b47756b707d6d356a9f0550/src/libs/libAudio2.cpp) does; the host has no system mix to apply it to. Id `0x20` with a 4-byte param throws, and the not-initialized error is not returned. Not measured on a console - [sceAudioOut2GetSpeakerArrayCoefficients](../../../core/libs/prx/libSceAudioOut/src/AudioOut2SpeakerArray.cpp) (libSceAudioOut) - validates the handle and the coefficient count like the ambisonics call, then writes the front pair at full gain and silence for every other speaker, ignoring the position, spread, height awareness and downmix spread radius: [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/df5b9b92be7dfd463b47756b707d6d356a9f0550/src/libs/libAudio2.cpp) and [prosper](https://github.com/mattias800/prosper/blob/main/prosper/src/hle/audio/hle_audio.cpp) both return that pair. The console's VBAP gains over the array's directions are not measured - [sceAudioOutSetMixLevelPadSpk](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - no source applies the level (shadPS4 only stores it); it is assumed to be a linear gain on the port's own output, and the -9 dB default (11626) is applied to every pad speaker port, including those of titles that never call the function - [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 @@ -99,7 +99,7 @@ - [sceAudioOut2GetTvCorrectionInfo](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOut2GetIpcHandle](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOut2GetSystemStateEx](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature -- [sceAudioOut2MasteringInitEx](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature +- [sceAudioOut2MasteringInitEx](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) (libSceAudioOut) accepts only flag bit 31, following the shared Init/InitEx validation routine in the decrypted PS5 12.00 `libSceAudioOut` analyzed for #9027. The mode is not applied because this implementation has no mastering stage; other firmware versions are unverified. - [sceAudioOut2SetDebugHeadphoneMode](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOut2DebugStateCtrl](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature - [sceAudioOut2ContextBedWrite](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature diff --git a/docs/dev/debt/libSceCommonDialog.md b/docs/dev/debt/libSceCommonDialog.md index 0e7ac021bf..f46aaf0394 100644 --- a/docs/dev/debt/libSceCommonDialog.md +++ b/docs/dev/debt/libSceCommonDialog.md @@ -3,3 +3,7 @@ ### Silent stubs - [libSceCommonDialog](../../../core/libs/prx/libSceCommonDialog/Export.cpp) + +### Functional + +- [sceCommonDialogInitialize](../../../core/libs/prx/libSceCommonDialog/Export.cpp) (libSceCommonDialog) - returning `ALREADY_SYSTEM_INITIALIZED` (`0x80B80002`) from a repeated call is not measured on a console by the project. A third-party PS5 log ([obSCEne](https://github.com/project-oops/obSCEne/blob/67b4c529eb3c271184117e26bb54330531ef2cf6/reports/hardware/report-20261014-004800.log#L260-L261), firmware 12.40) records two calls, `0x0` and then `0x80b80002`; a third call is not covered. The SDK error names, shadPS4 (a PS4 emulator) and SharpProspero give the same value; PPSA09695 compares the result with `0x80b80002` (call site 0x5F7BD0) and PPSA06327 stops on `0x80b80004`. Closes when: measured on a PS5, including a third call. diff --git a/docs/dev/debt/libSceFontFt.md b/docs/dev/debt/libSceFontFt.md index 3aa05e385d..c5c609e43f 100644 --- a/docs/dev/debt/libSceFontFt.md +++ b/docs/dev/debt/libSceFontFt.md @@ -3,6 +3,7 @@ ### Functional - [Font format support](../../../core/libs/prx/libSceFontFt/Export.cpp): format queries now take the font library, validate its magic and lock it while checking its actual FreeType modules. The host registers its default modules at creation rather than enabling formats individually, so format-selection state and guest allocator failures during format registration are not reproduced. Unsupported foreign drivers throw. +- [TrueType GX support](../../../core/libs/prx/libSceFontFt/Export.cpp): the GX query checks the bundled FreeType TrueType module, whose configuration enables GX variation support. The console's response for a FreeType build without GX variation support has not been measured. ### Unknown function info diff --git a/docs/dev/debt/libSceFreeType.md b/docs/dev/debt/libSceFreeType.md index 6d4f98438d..c1ba9a8614 100644 --- a/docs/dev/debt/libSceFreeType.md +++ b/docs/dev/debt/libSceFreeType.md @@ -2,7 +2,9 @@ ### Unknown function info -- [FT_Select_Charmap, FT_Set_Charmap, FT_Get_First_Char and FT_Get_Next_Char](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - console signatures and errors are unverified; the LP64 guest adapter follows the bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftobjs.c). Character iteration uses the low 32 bits of the input code, as that version does internally, and updates the guest's selected charmap pointer without refreshing unrelated glyph or outline data. Guest charmap fields are treated as read-only copies of the native records. +- [FT_Get_Sfnt_Name_Count, FT_Get_Sfnt_Name](../../../core/libs/prx/libSceFreeType/src/Names.cpp) (libSceFreeType) - console signatures, record layout and errors are unverified; the adapter follows bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftsnames.c) and assumes the public LP64 `FT_SfntName` layout (24 bytes, string pointer at 8, byte length at 16). Strings retain their original encoding, borrow native face storage and remain valid only while the face is alive + +- [FT_Select_Charmap, FT_Set_Charmap, FT_Get_First_Char, FT_Get_Next_Char](../../../core/libs/prx/libSceFreeType/Export.cpp), [FT_Get_Glyph_Name, FT_Get_Name_Index, FT_Get_Postscript_Name](../../../core/libs/prx/libSceFreeType/src/Names.cpp) (libSceFreeType) - console signatures, naming rules and errors are unverified; the LP64 adapter follows bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftobjs.c). Iteration uses the low 32 input bits and updates only the selected guest charmap; charmap fields are read-only copies. Naming preserves LP64 invalid-index errors on Windows. PostScript names borrow native face storage and remain valid only while the face is alive - [FT_Init_FreeType, FT_New_Face, FT_Set_Pixel_Sizes, FT_Get_Char_Index, FT_Load_Glyph, FT_Render_Glyph](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - the PS4 library is taken to be FreeType 2 built for LP64: the face, size, charmap, glyph slot, bitmap and outline layouts follow the public headers of the bundled FreeType 2.13.3 with a 64-bit `long`, which matches the offsets the OpenOrbis samples read (`face->glyph` at 0x98, `slot->advance` at 0x80, `slot->bitmap` at 0x98, `slot->bitmap_left` at 0xc0). Which FreeType version and options the console library is built with is unknown, so the behaviour and error codes are those of FreeType 2.13.3. The OpenOrbis font, keyboard, threading, system and SDL2 samples import these six functions @@ -10,6 +12,10 @@ ### Functional +- [FT_Set_Transform](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - console ABI, transform arithmetic and FreeType-version equivalence are unverified. The name and NID `bcM5xfX2SeA` come from the [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceFreeTypeOt.c); the 32-byte LP64 matrix, 16-byte vector, null defaults and glyph behavior follow bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftobjs.c). Input components outside the native `FT_Long` range throw without changing the transform; on Windows this rejects values outside signed 32 bits. Native arithmetic limits remain those of the host library. + - [libSceFreeType](../../../core/libs/prx/libSceFreeType/src/GuestObjects.cpp) (libSceFreeType) - private fields (`driver`, `memory`, `stream`, `sizes_list`, `extensions`) and `subglyphs` stay null. Face, size and glyph-slot records are copies. Only edits to outline points and flags are written back before `FT_Render_Glyph`; replacing arrays or exceeding the host `FT_Pos` range throws. `FT_New_Face` reads the whole file into memory and throws for files of 2 GiB or more on Windows. On macOS, native FreeType symbols remain exported and could resolve guest imports without adaptation; Windows and Linux hide them with `--exclude-libs`. - [FT_New_Memory_Face](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - on Windows, the host FT_Long is 32 bits, so buffer sizes of 2 GiB or more or below -2 GiB throw before the font buffer is read + +- [FT_Set_Char_Size](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - console ABI, FreeType version and error compatibility are unverified. The name and NID `xuY99Qcb108` are in the [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceFreeTypeOt.c); the LP64 signature and behavior follow bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftobjs.c), not a console measurement. After applying its defaults and minimum-size clamp, requests whose `dimension * dpi + 36` exceeds the host's signed `FT_Long` range throw before the native call. This is a 32-bit limit on Windows; larger console requests remain unsupported. diff --git a/docs/dev/debt/libSceGameLiveStreaming.md b/docs/dev/debt/libSceGameLiveStreaming.md index 8e3d5b8193..75299aa071 100644 --- a/docs/dev/debt/libSceGameLiveStreaming.md +++ b/docs/dev/debt/libSceGameLiveStreaming.md @@ -2,7 +2,7 @@ ### Functional -- [sceGameLiveStreamingEnableLiveStreaming](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - imported by PS4 titles, which do not load without it, and added without an implementation: declared without parameters, every call throws +- [sceGameLiveStreamingEnableLiveStreaming](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) - a call outside `sceGameLiveStreamingInitialize` and `sceGameLiveStreamingTerminate` throws, as the module's error for it is not confirmed (`sceGameLiveStreamingTerminate` and `sceGameLiveStreamingGetCurrentStatus2` return `0x80A00004`; [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/src/ps4_libscegamelivestreaming.pas#L18) does not return its `SCE_GAME_LIVE_STREAMING_ERROR_NOT_INITIALIZED` from this call). Otherwise both values are accepted and change nothing, as no broadcast is ever on air. The signature, `int sceGameLiveStreamingEnableLiveStreaming(const bool)`, follows [Orbital](https://github.com/AlexAltea/orbital/blob/cc408367da6a6ebbf5ac1ac559fefeadc73b2831/tools/ida/db_types.json#L810) and [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/src/ps4_libscegamelivestreaming.pas#L111) - [libSceGameLiveStreaming](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) - native initialization requires exactly 0x4000 heap bytes before checking repeated initialization. Host lifecycle and pointer errors now match the wrapper, but service registration, allocation failures and asynchronous service readiness are not modelled. The host becomes ready immediately after initialization. - [sceGameLiveStreamingGetCurrentStatus2](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) - the supplied module confirms the 72-byte layout and inactive status (user -1, remaining bytes zero). Live service counters, active-user changes and broadcast-resolution conversion are unimplemented. - [sceGameLiveStreamingGetProgramInfo](../../../core/libs/prx/libSceGameLiveStreaming/Export.cpp) (libSceGameLiveStreaming) - the PS5 12.00 module clears the 0x134C-byte program info, asks the system service for its first 0xFD4 bytes and returns `0x80A00002` for a null pointer and `0x80A00004` before initialization. With nothing broadcast here the info stays zero, which assumes the service reports an empty program when nothing is on air; the field layout is unknown diff --git a/docs/dev/debt/libSceHmd.md b/docs/dev/debt/libSceHmd.md index 3af366507e..cf169b2329 100644 --- a/docs/dev/debt/libSceHmd.md +++ b/docs/dev/debt/libSceHmd.md @@ -9,6 +9,7 @@ - [libSceHmd internal and capture exports](../../../core/libs/prx/libSceHmd/Export.cpp) - input/output directions and argument registers are recovered for shared VR2D data, device binding, optical and serial queries, screen status and capture operations. VR2D records are 128 bytes, optical output 16, serial output 16, PU position output 20, crash-dump output 68, audio/video status 32, and reprojection debug outputs 216 or 1136. Payload field meanings and capture callback contracts remain incomplete; these structures use forward declarations. - [libSceHmd queries without a headset](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - the signatures, the distortion work memory (`0x20000`, aligned to `0x400`) and reprojection buffer sizes (garlic `0x100000`, onion `0x810`, both aligned to `0x100`), and the results of `sceHmdGetFieldOfView`, `sceHmdGet2DEyeOffset`, `sceHmdGetInertialSensorData` (`HANDLE_INVALID` once initialized; field of view checks its output for null first) and `sceHmdGetAssyError` (null output, then `DEVICE_DISCONNECTED` once initialized) follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/hmd/hmd.cpp) (PS4) with no headset, not a PS5 measurement. Before initialization `sceHmdGetFieldOfView` returns `NOT_INITIALIZED` for a non-null output, where shadPS4 checks the handle first and returns `HANDLE_INVALID`. The third argument of `sceHmdGetInertialSensorData` is unnamed there and taken as a count +- [libSceHmd VR2D queries](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - `sceHmdInternalIsGameVr2d`, `sceHmdInternalIsMiniAppVr2d`, `sceHmdInternalIsCommonDlgVr2d` and `sceHmdInternalIsCommonDlgMiniAppVr2d` return false: nothing runs in VR2D without a headset, and the console answers 0 to the same four queries in libSceHmd2 (`Hmd2BeforeInitializeZero` in [libSceHmd2](libSceHmd2.md)). The libSceHmd answers themselves are not measured. - [sceHmdDistortionGetCompoundEyeCorrectionCommand](../../../core/libs/prx/libSceHmd/Export.cpp) returns a constant error without consuming arguments in the supplied module; unused argument types remain unresolved. - [sceHmdInternalGetDefaultLedData](../../../core/libs/prx/libSceHmd/Export.cpp) takes an unused handle, a 32-bit mask output and a nine-byte LED output. Output sizes match the paired setter; mask bits and LED payload semantics remain unresolved. The protected body is unchanged. @@ -20,6 +21,6 @@ [Reprojection start signatures](../../../core/libs/prx/libSceHmd/Export.cpp) are recovered, including the five arguments of `StartMultilayer2` hidden by an incomplete decompilation. Standard and wide-near starts take frame parameters, pose, 64-bit flip argument and reserved pointer; overlay variants add an overlay descriptor. The 2D variant takes parameters, flip argument and reserved pointer. Multilayer takes a layer array/count, submission parameters, pose, flip argument and reserved pointer. Frame, pose, overlay and submission field semantics remain incomplete. Protected bodies are unchanged. -[Distortion and 2D VR command queries](../../../core/libs/prx/libSceHmd/Export.cpp) take a 32-bit HMD handle, input descriptor and command-buffer output. The output contains a command pointer at offset zero and a 32-bit dword count at offset eight. Descriptor fields and GPU resource types remain incomplete; the standard, wide-near and 2D descriptors are distinct. Protected bodies are unchanged. +[Distortion and 2D VR command queries](../../../core/libs/prx/libSceHmd/Export.cpp) take a 32-bit HMD handle, input descriptor and command-buffer output. The output contains a command pointer at offset zero and a 32-bit dword count at offset eight. Descriptor fields and GPU resource types remain incomplete; the standard, wide-near and 2D descriptors are distinct. Without a headset `sceHmdOpen` never issues a handle, so the stored handle stays 0. As firmware 13.42's libSceHmd does when its exports are run under an emulator ([#9577](https://github.com/boykopovar/AnyPS5/pull/9577#issuecomment-6336871370)), the distortion queries return `HANDLE_INVALID` for any other handle before checking initialization, and `NOT_INITIALIZED` for handle 0 before it when the title's compiled SDK version is below 4.50. The 2D VR query first returns `PARAMETER_NULL` for a null descriptor or output or a null pointer at descriptor offset 0, 8 or 0x20, and `PARAMETER_INVALID` for a non-zero field at 0x34 (32-bit) or 0x38 (64-bit). None writes the output. From SDK 4.50 the firmware first checks state the library sets up when it starts, which is not measured ([#9577](https://github.com/boykopovar/AnyPS5/pull/9577#issuecomment-6343495321)), and what handle 0 gets after initialization is not measured either, so both throw. -The ELF export table aliases the [Distortion correction/2D command and work-memory queries](../../../core/libs/prx/libSceHmd/Export.cpp) to their corresponding HMD queries, confirming identical signatures. Distortion initialization consumes only a work-memory pointer. No shader code or calibration constants are copied; protected bodies remain unchanged. +The ELF export table aliases the [Distortion correction/2D command and work-memory queries](../../../core/libs/prx/libSceHmd/Export.cpp) to their corresponding HMD queries, confirming identical signatures. Distortion initialization consumes only a work-memory pointer. No shader code or calibration constants are copied; each aliased command query answers as its twin, and distortion initialization keeps its protected body. diff --git a/docs/dev/debt/libSceHttp.md b/docs/dev/debt/libSceHttp.md index 6cc44f2301..7d65b883c1 100644 --- a/docs/dev/debt/libSceHttp.md +++ b/docs/dev/debt/libSceHttp.md @@ -13,15 +13,16 @@ - [sceHttpSetCookieEnabled](../../../core/libs/prx/libSceHttp/Export.cpp) - the two-argument signature and 0/1 enable values follow [VitaSDK](https://github.com/vitasdk/vita-headers/blob/master/include/psp2/net/http.h) and [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.cpp); PPSA01521 passes 0 to disable cookies. Disabling succeeds because the offline HTTP backend has no cookie engine; enabling throws. Invalid enable values use the same error as `sceHttpSetInflateGZIPEnabled`. Like the existing offline HTTP setters, the handle is not validated; requests still fail with the network error. Console behavior is unverified - [sceHttpParseResponseHeader](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - signature follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Net/Http.cs); the result, value bounds and error codes follow a payload process on a PS5 (firmware 13.60). Unmeasured there: a bare carriage return, and vertical tab or form feed around the value, which are skipped as blanks before it as before. A value end above `INT_MAX` throws instead of wrapping -- [sceHttpParseStatusLine](../../../core/libs/prx/libSceHttp/src/Parse.cpp) (libSceHttp) - behaviour and error codes follow the [shadPS4](https://github.com/shadps4-emu/shadPS4) reimplementation of the PS4 library, not confirmed on a PS5 title; unlike it, no byte past `lineLen` is read -- [sceHttpUriMerge](../../../core/libs/prx/libSceHttp/src/Uri.cpp) (libSceHttp) - signature follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Net/Http.cs); validation, size formula, absolute/relative split and path join follow the disassembly-like body in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.cpp), not confirmed on a PS5 title or hardware. The base URL goes through our `sceHttpUriParse`, which removes dot segments from the base path, while the relative part is appended unswept; base query and fragment are dropped. The relative reference is only classified by its scheme and a leading `//`, and only references with `//` are validated; as in shadPS4, `//host/x` is returned unchanged instead of taking the base scheme, a reference with a scheme but no `//` (`mailto:x`) is joined onto the base directory, and `?q` and `#f` are appended to the base directory instead of the base path. +- [sceHttpParseStatusLine](../../../core/libs/prx/libSceHttp/src/Parse.cpp) (libSceHttp) - follows a payload process on a PS5 (firmware 13.60) for 124 lines and lengths. As on the console, `lineLen` is only checked to be at least 8 and to leave the 3 code digits (`lineLen - index < 3` in unsigned arithmetic): the version digits and the `.` and space after them are read past `lineLen`, and the line feed is searched up to and including the byte at `lineLen`, so a length that stops right before the line feed still parses. When the version ends past `lineLen` both subtractions wrap and the search goes on until a line feed; what the console does when none follows in readable memory was not measured +- [sceHttpUriMerge](../../../core/libs/prx/libSceHttp/src/Uri.cpp) (libSceHttp) - signature follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Net/Http.cs); results, errors and `require` (the base's `sceHttpUriParse` size plus 2 and twice the two string lengths, also for a reference copied unchanged) follow a payload process on a PS5 (firmware 13.60). A reference with an authority (`//` after an optional scheme) is copied unchanged; any other, including `mailto:x`, `http:d`, `?q` and `#f`, is appended unswept to the base path up to its last `/`, and the base query and fragment are dropped. A reference that `sceHttpUriParse` rejects returns that error, which was not measured. +- [sceHttpUriParse](../../../core/libs/prx/libSceHttp/src/Uri.cpp) (libSceHttp) - follows a payload process on a PS5 (firmware 13.60) for every ASCII byte inside a scheme, userinfo and hostname, and for letters, digits, `+`, `-` and `.` at the start of a scheme, where a digit ends it; other bytes at the start of a scheme are taken as they are inside one. The userinfo ends at the first `@` after bytes from the RFC 3986 userinfo set; a hostname is letters, digits, `-`, `_` and `.` without a leading `.`, and the first byte outside it starts the path instead of failing (`http://bad host/` has the host `bad` and the path ` host/`). A port is up to 5 digits that end at `/` or the end, at most 65536, which reads as 0; an empty port takes 443 for a scheme starting with `https`, 80 for one starting with `http` (case-insensitive) and 0 otherwise. The path is swept as `sceHttpUriSweepPath` does, while the pool keeps room for the unswept path. With a pool too small the console returns `ERROR_OUT_OF_MEMORY`, leaves `require` alone and has already written part of the pool; how much is not known, so nothing is written here. - [sceHttpUriSweepPath](../../../core/libs/prx/libSceHttp/src/Uri.cpp) (libSceHttp) - follows a payload process on a PS5 (firmware 13.60), which agrees with the [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.cpp) reimplementation of the PS4 library: it is not RFC 3986 5.2.4, a path that doesn't start with `/` is copied unchanged and a trailing `.` or `..` is kept. Like the console, the byte at `srcSize - 1` is read as the `/` that ends a `..` segment, and `srcSize == 1` with a leading `/` writes `/`; whether it also ends a `.` segment was not measured, so a `.` there is still kept as before - [sceHttpSetResponseHeaderMaxSize, sceHttpRedirectCacheFlush, sceHttpsUnloadCert](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - signatures follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.cpp) (PS4); no PS5 reference has them - [sceHttpSetRequestStatusCallback](../../../core/libs/prx/libSceHttp/Export.cpp) - the three-argument ABI follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.cpp). Registrations and user data are retained and removed on request deletion; the offline backend never dispatches callbacks. Invalid ids return `0x80431100`, an uncited assumption, not a verified console error. Handle hierarchy, inheritance and deleted-handle validation remain incomplete; callback statuses and dispatch timing are unknown. - [sceHttpSetRedirectCallback, sceHttpSetCookieRecvCallback, sceHttpsSetSslVersion, sceHttpsGetSslError](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - signatures follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.cpp) (PS4), which only logs and returns 0; a null `errNum` or `detail` in `sceHttpsGetSslError` is assumed to return `SCE_HTTP_ERROR_INVALID_VALUE` like `sceHttpGetLastErrno` - [sceHttpSetAuthInfoCallback](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - signature and callback type follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.h) (PS4); no PS5 reference has them. The `id` is not checked, as in the other libSceHttp setters - [sceHttpWaitRequest](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the timeout is assumed to be in microseconds and a negative timeout to wait without limit; the invalid-value error for a null handle or event array or a non-positive event count is assumed, not measured. No request is ever pending, so a wait returns 0 events when its timeout passes -- [sceHttpUriCopy](../../../core/libs/prx/libSceHttp/src/Uri.cpp) (libSceHttp) - read from the disassembly of libSceHttp.sprx on firmware 13.60, not measured on a console and not compared with other emulators. The arguments are (out, src, pool, prepare, require), `prepare` before `require`, the reverse of `sceHttpUriParse`. A null source returns `ERROR_INVALID_URL`; without an output or a pool, a null `require` returns `ERROR_INVALID_VALUE`, otherwise the size is only reported. Only the scheme, hostname, path, username and password are copied, in that order, each with its terminator (a missing one becomes an empty string and takes one byte), and the port; the query, the fragment and the opaque flag stay 0. A pool that is too small clears the output and the pool and returns `ERROR_OUT_OF_MEMORY` (0x80431022, the console's out-of-size error) +- [sceHttpUriCopy](../../../core/libs/prx/libSceHttp/src/Uri.cpp) (libSceHttp) - unverified: whether the strings are written before the size check, which a short pool hides by ending cleared. The rest follows a payload process on a PS5 (firmware 13.60): the arguments are (out, src, pool, prepare, require) as in the disassembly; the whole output and all `prepare` pool bytes are cleared before the source is read, so a source that is the output or lies in the pool copies as empty; the scheme, hostname, path, username and password are copied in that order with terminators, a null one as empty, and the port, while the query, fragment and opaque flag stay 0; a short pool returns 0x80431022 and leaves `require` alone; a null source returns 0x80433060, and no output or pool with a null `require` 0x804311FE - [sceHttpDbgEnableProfile](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the export is absent from the supplied native module; its signature remains unresolved. - [sceHttpDbgSetPrintf](../../../core/libs/prx/libSceHttp/Export.cpp) returns constant success without consuming arguments in the supplied module; the callback contract remains unresolved. - [sceHttpDbgShowStat](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the native diagnostic consumes no arguments; it prints library state. The throwing implementation remains unchanged. @@ -31,7 +32,7 @@ ### Functional -- [sceHttpUriParse, sceHttpUriBuild, sceHttpUriMerge](../../../core/libs/prx/libSceHttp/src/Uri.cpp) - console behavior for non-ASCII scheme and hostname bytes remains unmeasured. ASCII character classification and scheme case folding follow [RFC 3986 sections 3.1 and 3.2](https://www.rfc-editor.org/rfc/rfc3986#section-3.1), independently of the host locale; parsing retains the existing C-locale rejection of those bytes. +- [sceHttpUriParse, sceHttpUriBuild, sceHttpUriMerge](../../../core/libs/prx/libSceHttp/src/Uri.cpp) - bytes 0x80 to 0xFF were measured only as 0x80, 0xC3 and 0xFF in a hostname; the others, and any of them in a scheme or userinfo, are taken as the measured ASCII bytes outside those sets are. ASCII classification and scheme case folding are independent of the host locale. A bracketed host such as `[::1]` is parsed, merged and built without its brackets, as a PS5 (firmware 13.60) does; a PS4 (firmware 10.71) takes no bracketed host (`http://[::1]x/` has an empty host and the path `[::1]x/`) and keeps `[::1]:80` in a merge, as measured in #9058, so this follows the PS5 only. - [sceHttpUriEscape](../../../core/libs/prx/libSceHttp/src/Uri.cpp) escapes every byte except the ASCII unreserved set in [RFC 3986 section 2.3](https://www.rfc-editor.org/rfc/rfc3986#section-2.3), independently of the host locale. Console behaviour for non-ASCII input remains unmeasured. diff --git a/docs/dev/debt/libSceImeDialog.md b/docs/dev/debt/libSceImeDialog.md index 58f32f8d68..84726f50bc 100644 --- a/docs/dev/debt/libSceImeDialog.md +++ b/docs/dev/debt/libSceImeDialog.md @@ -6,7 +6,8 @@ ### Unknown function info -- [sceImeDialogForTestFunction, sceImeDialogGetCurrentStarState, sceImeDialogInitInternal, sceImeDialogSetPanelPosition](../../../core/libs/prx/libSceImeDialog/Export.cpp) (libSceImeDialog) - their signatures follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/ime/ime_dialog.h#L34-L51), not measured on a console. They are declared solely to resolve known imports, `sceImeDialogForTestFunction` without parameters and the others with shadPS4's parameters; this is not an ABI claim, and every call throws. +- [sceImeDialogForTestFunction](../../../core/libs/prx/libSceImeDialog/Export.cpp) (libSceImeDialog) - declared without parameters to resolve a known import, following [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/ime/ime_dialog.h#L34-L51); not measured on a console and not an ABI claim. +- [sceImeDialogGetCurrentStarState, sceImeDialogInitInternal, sceImeDialogSetPanelPosition](../../../core/libs/prx/libSceImeDialog/Export.cpp) (libSceImeDialog) - not measured on a console; signatures and checks follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/ime/ime_dialog.cpp). `sceImeDialogInitInternal` runs `sceImeDialogInit`, as shadPS4's two share one path apart from user flags this library does not keep. There is no movable panel, so `sceImeDialogSetPanelPosition` never succeeds: `DIALOG_NOT_IN_USE` without a dialog and `IME_SUSPENDING` while one is running or finished, the states in which shadPS4 refuses it too. The star state is always 0 - [sceImeDialogForceClose](../../../core/libs/prx/libSceImeDialog/Export.cpp) (libSceImeDialog) - not measured on a PS5: its `int (void)` signature and behaviour follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/ime/ime_dialog.cpp#L571-L601) (PS4). It closes a running or finished dialog, as `sceImeDialogTerm` does, without writing the typed text to the title's buffer, which shadPS4 updates while typing and this library only on Enter, and returns `0x80bc0107` with no dialog open, the `NotInUse` code of [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Dialog/ImeDialog.cs#L224-L225), while this library's `sceImeDialogTerm` returns `0x80bc0002` in the same state. Closes when: measured on a PS5 - [sceImeDialogInit, sceImeDialogGetStatus, sceImeDialogGetResult, sceImeDialogAbort, sceImeDialogTerm](../../../core/libs/prx/libSceImeDialog/Export.cpp) (libSceImeDialog) - the statuses (none 0, running 1, finished 2), the end statuses (ok 0, user canceled 1, aborted 2) and the errors of `sceImeDialogInit` for an open dialog (`0x80bc0001`), a null parameter (`0x80bc0031`), a maximum text length outside 1 to 2048 (`0x80bc0016`) and a null text buffer (`0x80bc0017`) follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/b2d7ec7d31f9ad9426db93afe079db8679b3e1e0/src/core/libraries/ime/ime_dialog.cpp); none is measured on a PS5. A result, abort or term on a dialog that is not finished, running or open returns `0x80bc0002`, where shadPS4 returns `0x80bc0105` to `0x80bc0107`, and `sceImeDialogTerm` also closes a running dialog, which shadPS4 refuses. The text reaches the title's buffer only when Enter accepts it diff --git a/docs/dev/debt/libSceJemspace.md b/docs/dev/debt/libSceJemspace.md new file mode 100644 index 0000000000..c0baf800a1 --- /dev/null +++ b/docs/dev/debt/libSceJemspace.md @@ -0,0 +1,5 @@ +# libSceJemspace technical debt + +### Unknown function info + +- The thirteen exports in [Export.cpp](../../../core/libs/prx/libSceJemspace/Export.cpp) resolve imports only and throw through `NotImplemented_nid_no_patch`. Each declared name hashes to a defined function NID in the `libSceJemspace` ELF export table. The module firmware version is unverified. The no-parameter declarations do not establish argument layouts or return types. No allocator behavior, memory ownership, allocation failure handling, statistics layout, or console compatibility is implemented or verified. diff --git a/docs/dev/debt/libSceJson2.md b/docs/dev/debt/libSceJson2.md index 706f8c4594..9c411149b3 100644 --- a/docs/dev/debt/libSceJson2.md +++ b/docs/dev/debt/libSceJson2.md @@ -3,6 +3,7 @@ ### Unknown function info - [JSON real-number conversion](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) uses locale-independent `to_chars` with 17 significant digits and `from_chars`, following JSON's decimal-point grammar in RFC 8259 section 6. Out-of-range input falls back to a classic-locale stream so underflow retains the prior signed-zero behavior and overflow throws. Console rounding and formatting are unverified. +- [sce::Json::Array::pop_front](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - removes and destroys the first element; on an empty array it throws `std::out_of_range`, as `front` and `back` do here. The console's result for an empty array is unverified. - [sce::Json::Parser::parse](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - a null text pointer with nonzero size throws instead of being dereferenced. A number that overflows a double (e.g. `1e309`) throws: whether the console rejects the document or stores an infinite real is unknown. Syntax validation for number tokens and destination state after a failed parse still differ from the system parser - [sce::Json::InitParameter2, InitParameterRtti and InitParameterRtti2](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - RTTI allocator adaptation and the `InitParameterRtti` layout remain unverified. `setAllocatorRtti` stores the raw allocation-parameter pointer as the allocator; `initialize` ignores allocator, buffer-size and special-float settings. Special-float enum meanings and parser support for non-finite numbers remain unresolved; serialization of non-finite values throws. Setter return types remain unverified. - [sce::Json::Object::Pair, sce::Json::Array::iterator and sce::Json::Object::iterator members](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - the signatures follow the mangled names, with `Array::iterator::operator++(int)` and `Object::iterator::operator++(int)` returning their copy through a result pointer before `this` as `begin` does, since the iterators have non-trivial destructors. `Pair(const String&, const Value&)` and default `Pair()` deep-copy or initialize into the layout `Object` iteration already hands out (key at 0, value at 0x10) and zeroes the 8 bytes between them; what the console keeps there is unknown. diff --git a/docs/dev/debt/libSceJxr.md b/docs/dev/debt/libSceJxr.md index 20faabeefb..0fbc29eb6c 100644 --- a/docs/dev/debt/libSceJxr.md +++ b/docs/dev/debt/libSceJxr.md @@ -2,4 +2,8 @@ ### Unknown function info -- [libSceJxr pending exports](../../../core/libs/prx/libSceJxr/Export.cpp) - 4 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceJxr.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceJxr pending exports](../../../core/libs/prx/libSceJxr/Export.cpp) - 4 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceJxr.c) and were cross-checked against the same module in PS5 firmware 13.60. `PixelFormatLookup`, `readIS` and `writeIS` have unverified signatures and behaviour; their declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. + +### Functional + +- [PKFree](../../../core/libs/prx/libSceJxr/Export.cpp) (libSceJxr) follows Microsoft's jxrlib [JXRGlue.c](https://github.com/4creators/jxrlib/blob/f7521879862b9085318e814c6157490dd9dbbdb4/jxrgluelib/JXRGlue.c#L53-L62), whose four exported names the module shares: a non-null `ppv` has `*ppv` released with the guest `free` and set to null, and the result is always `WMP_errSuccess` (0) as a 64-bit `ERR` (`long`). That the console's module is built from jxrlib, and that its `PKFree` releases through libc's `free`, is inferred from the export names and not checked against the console's code. Closes when: the console's `PKFree` is disassembled or measured on a PS5 diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index b8330484a5..bcaab509a1 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,4 +2,5 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 399 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 377 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); the module's LibreSSL version is unknown and nothing is checked on the console. Behaviour that differs between LibreSSL versions throws: `SHA256` with a null digest buffer (older versions return a static buffer) and `SHA512_Final` with the SHA-512/224 or SHA-512/256 digest length. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw diff --git a/docs/dev/debt/libSceLibreSsl3.md b/docs/dev/debt/libSceLibreSsl3.md index 3fbd00e901..dba672f91f 100644 --- a/docs/dev/debt/libSceLibreSsl3.md +++ b/docs/dev/debt/libSceLibreSsl3.md @@ -2,4 +2,9 @@ ### Unknown function info -- [libSceLibreSsl3 pending exports](../../../core/libs/prx/libSceLibreSsl3/Export.cpp) - 87 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl3.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl3 pending exports](../../../core/libs/prx/libSceLibreSsl3/Export.cpp) - 84 named callable exports, excluding `CRYPTO_memcmp`, `MD5` and `SHA1`, remain unimplemented from the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl3.c), cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. + +### Functional + +- [CRYPTO_memcmp](../../../core/libs/prx/libSceLibreSsl3/Export.cpp) follows the public [LibreSSL signature and equality contract](https://man.openbsd.org/CRYPTO_memcmp.3), without PS5 verification. The exact nonzero result for unequal buffers is unspecified by that contract. Content-independent execution of its volatile byte reads still depends on the compiler; inspect optimized code when changing toolchains. Closes when: measured on a PS5 and the supported toolchains' generated code is checked. +- [MD5 and SHA1](../../../core/libs/prx/libSceLibreSsl3/src/Digest.cpp) (libSceLibreSsl3) - follow LibreSSL's [md5.c, sha1.c](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto) and hash through libSceLibreSsl's `MD5_*` and `SHA1_*`. A null digest buffer throws: LibreSSL up to 3.8 returns a static buffer, later versions do not check it. Nothing is checked on the console diff --git a/docs/dev/debt/libSceNKWebKit.md b/docs/dev/debt/libSceNKWebKit.md new file mode 100644 index 0000000000..e515ec10a7 --- /dev/null +++ b/docs/dev/debt/libSceNKWebKit.md @@ -0,0 +1,5 @@ +# libSceNKWebKit technical debt + +### Known export names; ABI prototypes unverified + +- The 150 `sceNKWebKitUnknown_*_nid_no_patch` exports in [Export.cpp](../../../core/libs/prx/libSceNKWebKit/Export.cpp) are present in the PS5 3.20 `libSceNKWebKit` export inventory from [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs). Their NIDs are derived from exported mangled symbols. Prototypes and behavior are unverified; each uses the no-parameter import-resolution convention and throws through `NotImplemented_nid_no_patch`. These declarations make no ABI or behavior claim. diff --git a/docs/dev/debt/libSceNetCtl.md b/docs/dev/debt/libSceNetCtl.md index 96b0e7ed58..aaa9cf2f9d 100644 --- a/docs/dev/debt/libSceNetCtl.md +++ b/docs/dev/debt/libSceNetCtl.md @@ -19,3 +19,4 @@ - [libSceNetCtl initialization](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - the library starts initialized, as titles find it: it is the internal sysmodule `0x80000009`, and PPSA11386, PPSA20049, PPSA02530, PPSA14632 and PPSA15878 register and check NetCtl callbacks without importing `sceNetCtlInit`. PPSA11386 executes `int 0x42` at start-up unless `sceNetCtlRegisterCallback` returns 0. So the supplied module's `SCE_NET_CTL_ERROR_NOT_INITIALIZED` answers are unreachable, since Term is a no-op. Whether the console's process start or the module's start sets that state is not measured. - [sceNetCtlGetInfo](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) finds the host's default route in `/proc/net/route` on Linux and, on macOS, in the routing table read with `sysctl(NET_RT_FLAGS, RTF_GATEWAY)`, where an interface-scoped default route ranks after the unscoped one - [sceNetCtlGetIfStat](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) throws on macOS: an unprivileged process reads the interface byte counters (`getifaddrs` and `sysctl(NET_RT_IFLIST2)` alike) reduced to their low 32 bits, so a total would wrap every 4 GiB +- [Host interface cache](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - on Windows, `sceNetCtlGetState`, `sceNetCtlGetStateV6`, `sceNetCtlCheckCallback`, `sceNetCtlGetIfStat` and `sceNetCtlGetInfo` reuse the last host interface query until `NotifyUnicastIpAddressChange`, `NotifyIpInterfaceChange` or `NotifyRouteChange2` reports a change, instead of calling `GetAdaptersAddresses` on every call. A DNS server change with no address, interface or route change is seen only at the next such change. If the notifications cannot be registered, every call queries the host as before. diff --git a/docs/dev/debt/libSceNgs2.md b/docs/dev/debt/libSceNgs2.md index 50b1203e89..a3bd2a6776 100644 --- a/docs/dev/debt/libSceNgs2.md +++ b/docs/dev/debt/libSceNgs2.md @@ -3,4 +3,4 @@ ### Unknown function info - [sceNgs2FftInit, sceNgs2FftProcess, sceNgs2FftQuerySize](../../../core/libs/prx/libSceNgs2/PendingExports.cpp) (libSceNgs2) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNgs2.c), but no credible prototypes were found in the cited sources. They are declared without parameters solely to resolve known imports and make no ABI claim. -- [sceNgs2CustomRackGetModuleInfo, sceNgs2GetWaveformFrameInfo, sceNgs2ParseWaveformUser, sceNgs2SystemEnumHandles, sceNgs2SystemEnumRackHandles, sceNgs2VoiceGetMatrixInfo](../../../core/libs/prx/libSceNgs2/PendingExports.cpp) (libSceNgs2) - names and NIDs are in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNgs2.c); signatures follow [SharpProspero's Ngs2 bindings](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/Ngs2.cs), not measured on a console. Opaque structure pointers use `void*`; these stubs throw for import resolution. +- [sceNgs2CustomRackGetModuleInfo, sceNgs2ParseWaveformUser, sceNgs2SystemEnumHandles, sceNgs2SystemEnumRackHandles, sceNgs2VoiceGetMatrixInfo](../../../core/libs/prx/libSceNgs2/PendingExports.cpp) (libSceNgs2) - names and NIDs are in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNgs2.c); signatures follow [SharpProspero's Ngs2 bindings](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/Ngs2.cs), not measured on a console. Opaque structure pointers use `void*`; these stubs throw for import resolution. diff --git a/docs/dev/debt/libSceNgs2.native.md b/docs/dev/debt/libSceNgs2.native.md index b26266e77b..3bdb203f0c 100644 --- a/docs/dev/debt/libSceNgs2.native.md +++ b/docs/dev/debt/libSceNgs2.native.md @@ -19,11 +19,14 @@ - [NGS2 matrix levels](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - levels are clamped to -4..4 and NaN is kept, as the PS4 firmware 11.00 `libSceNgs2.sprx` stores them; not yet confirmed on the PS5 library. That library rejects a level count of 0 and keeps the matrix; the PS5 error code is unknown, so a count of 0 throws, except on a sampler voice without a format, where it clears the matrix (see NGS2 voices without a format). `sceNgs2VoiceRunCommands` is not in that library, so its matrix levels command is assumed to clamp the same way - [NGS2 sampler voice state flag EMPTY](../../../core/libs/prx/libSceNgs2.native/src/Render.cpp) (libSceNgs2.native) - a playing sampler voice that still accepts blocks (`CONTINUE`) and has played all queued blocks reports `INUSE|PLAYING|EMPTY` (0x20) and keeps playing, so blocks added later play on. PPSA18391 (engine: Clickteam Fusion) reads `state_flags & 0x22 != 2` from `sceNgs2VoiceGetState` as the end of a sound and only then reuses the voice. When the console sets and clears EMPTY, and whether it also reports it for other voice types, is unverified on hardware. - [NGS2 port matrix](../../../core/libs/prx/libSceNgs2.native/src/Render.cpp) (libSceNgs2.native) - a port without a matrix, or with a selected matrix whose levels were never set, mixes through a default channel map: mono reaches both stereo channels at -3 dB and the 5.1 and 7.1 centre, stereo folds to mono at -3 dB per side, and wider sources fold down with 0.5 and -3 dB gains. The map and the equivalence of an unset matrix with no matrix were measured from the PS4 firmware 11.00 `libSceNgs2.sprx`, not yet from the PS5 library. That library only accepts submixer and mastering voices with 1, 2, 6 or 8 channels, so the map into 3, 4, 5 or 7 channels throws. A matrix set with fewer levels than the destination needs still mixes only the rows it has -- [NGS2 custom submixer rack](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native, rack 0x4002) - structure layouts from [shadPS4](https://github.com/shadps4-emu/shadPS4)'s `ngs2_custom.h`; module id 0x1f for UserFx2 and 0x1d for UserFx, voice parameter ids `0x40001f00 | module index` and `0x40001d00 | module index`, the process flags (1 on the first process call after setup, 2 after a parameter change, 0 otherwise) and the call order (setup per voice at rack creation, process after the inputs are mixed, cleanup at destruction) come only from [KytyPS5](https://github.com/KytyPS5/KytyPS5) and shadPS4. Only UserFx and UserFx2 modules on a single buffer are accepted: other module ids, more buffers, a module control handler, different input and output channel counts, setup flags and default rack options throw. Each module's state is a separate buffer, not the `state_offset` slice of the voice state, and `sceNgs2VoiceGetState` still throws for this rack. As in KytyPS5, UserFx2 process runs only on grains where an input produced samples, so an effect tail stops with the input and a pending flag 2 waits for the next audible grain -- [NGS2 custom module ids](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native) - a custom rack with a module id other than 0x13 (sampler rack), 0x1d and 0x1f (submixer rack) throws. Trails in the Sky 2nd Chapter (PPSA35169) creates a rack with module id 0x20 (`NGS2: custom module id 0x20 is not implemented`, #4246); which module it is, and its structure, are unknown. +- [NGS2 custom submixer rack](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native, rack 0x4002) - layouts come from [shadPS4](https://github.com/shadps4-emu/shadPS4)'s `ngs2_custom.h`. UserFx2's id 0x1f, parameter id `0x40001f00 | index`, process flags (1 on the first call after setup, 2 after a param change, else 0) and call order (setup per voice at creation, process after input mixing, cleanup at destruction) come only from [KytyPS5](https://github.com/KytyPS5/KytyPS5). 0x1d is the id PPSA03061 passes (#5139); its UserFx name and parameter id `0x40001d00 | index` are unsourced. Only these two modules and Gain on one buffer work: a module control handler, unequal input and output channels, setup flags and default rack options throw. Module state is a separate buffer, not the `state_offset` slice of the voice state, and `sceNgs2VoiceGetState` throws for this rack. As in KytyPS5, UserFx2 runs only on grains with input samples, so an effect tail stops with the input +- [NGS2 custom module ids](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native) - Reverb (0x1b), PitchShift (0x20) and Convolution (0x23) are not processed, nor Gain (0x15) on a custom sampler rack: a rack that passes the creation checks with one throws, as do ids besides these, 0x13, 0x1d and 0x1f. Per [fma-rc's reading of the 12.70 module](https://github.com/boykopovar/AnyPS5/issues/8039#issuecomment-6294323150), creation returns `0x804A8302` for a non-zero state size other than the module's (0 for Gain and Reverb, 8 for PitchShift), and `0x804A8013`, `0x804A8099` and `0x804A8098` for a bad Reverb option size or word and PitchShift word. Unknown: the check order, the errors for a Gain option, a missing Reverb or PitchShift option, other PitchShift and Convolution option sizes, and Convolution's state (64 bytes from SDK 3.0). fma-rc maps 0x19 to UserFx, as KytyPS5 does; it still throws, and 0x1d on PS5 is unknown. PPSA04540 uses 0x15, 0x19, 0x1b and 0x23 (#8039), PPSA35169 0x20 (#4246) +- [NGS2 custom Gain module](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native, rack 0x4002) - follows [fma-rc's reading of the 12.70 module](https://github.com/boykopovar/AnyPS5/issues/8039#issuecomment-6319332943): parameter `0x40001500 | module index` holds 37 linear levels, one per channel, default 0, and the module writes source × level into dest, ramping linearly over the grain from the previous level. Unverified: where the ramp starts and ends (here the grain's first sample is one step past the previous level and its last sample is at the new level), whether stopping or setting up the voice again resets the levels, and what the library does with a level outside -32 to 32 (here it throws). On a custom sampler rack, whose parameter index is one higher per fma-rc, Gain throws. A Gain module with an option throws: fma-rc reads Gain as taking none, and the error the library returns for one is unknown. The extra buffer is ignored, as fma-rc reads it +- [NGS2 custom rack buffers and channels](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native, racks 0x4001 and 0x4002) - racks with 2 to 4 buffers, custom submixers with 9 to 37 channels and submixer `max_inputs` other than 1 or 2 throw: how modules and ports route several buffers is unknown, and voices hold at most 8 channels. Per [fma-rc's reading of the 12.70 module](https://github.com/boykopovar/AnyPS5/issues/8039#issuecomment-6294323150), creation returns `0x804A830B` for a buffer count outside 1 to 4 or below the submixer's `max_inputs`, `0x804A8306` for a module source, extra or dest buffer id at or above the count, and `0x804A8052` for more than 37 submixer channels; the check order is not known. `sceNgs2RackQueryBufferSize` throws for these options, as no source says whether it checks them. A port buffer id other than 0 throws. PPSA04540 creates racks with up to 4 buffers and a 16-channel custom submixer (#8039) - [NGS2 reverb rack](../../../core/libs/prx/libSceNgs2.native/src/Reverb.cpp) (libSceNgs2.native, rack 0x2001) - follows the PS4 firmware 11.00 `libSceNgs2.sprx`, read and checked against its output at 11.025 to 48 kHz, including parameter changes during playback (differences at float rounding level); not yet compared with the PS5 library. Structure layouts come from [shadPS4](https://github.com/shadps4-emu/shadPS4)'s `ngs2_reverb.h` and the param ids 0x20010000 (setup) and 0x20010001 (I3DL2) from PPSA22520. Only the default `reverb_size` of 1 is handled, with its 14400-sample reflection buffer; other sizes and the library's half-rate mode throw. System sample rates below about 7.5 kHz, where an allpass delay would have no samples, throw. Setup only accepts equal input and output channel counts - [sceNgs2ReportRegisterHandler, sceNgs2ReportUnregisterHandler](../../../core/libs/prx/libSceNgs2.native/src/System.cpp) (libSceNgs2.native, NIDs `uBIN24Tv2MI`, `nPzb7Ly-VjE`) keep a registry of report handlers. Register rejects a null handler with an invalid argument and returns `INVALID_OUT_ADDRESS` for a null out handle, then stores the report type, handler and user data under a freshly allocated opaque handle; unregister removes and frees the matching record and throws for a handle that is not registered. The signatures (report type, handler, user data, out handle) and (report handle) follow [SharpProspero](https://github.com/SvenGDK/SharpProspero) and [shadPS4](https://github.com/shadps4-emu/shadPS4)'s `ngs2_report.h`, and the 24-byte `Ngs2ReportDataHeader` passed to a handler is its `OrbisNgs2ReportDataHeader`. No report is produced yet, so no registered handler is ever invoked and PPSA04540, which imports both and registers a CPU load handler (type 5), still receives none. - [sceNgs2SystemSetSampleRate, sceNgs2SystemSetUserData, sceNgs2SystemGetUserData, sceNgs2SystemLock, sceNgs2SystemUnlock](../../../core/libs/prx/libSceNgs2.native/src/System.cpp) (libSceNgs2.native) - shadPS4 only checks the system handle (`INVALID_SYSTEM_HANDLE`) and SharpProspero names what each call does; neither gives the other limits. A sample rate of 0 and a null user data pointer throw, any other rate is accepted, and changing the rate while a sampler filter is enabled throws because the filter coefficients were computed for the old rate. `sceNgs2SystemLock` and `sceNgs2SystemUnlock` follow [prosper's notes](https://github.com/mattias800/prosper/blob/d61eb2c0ad2de879ef74df9c4aa050c512804a18/prosper/src/hle/audio/hle_audio.cpp#L4367-L4384) on the PS5 module: Lock takes a per-system lock and counts nested holds, Unlock releases one hold from any thread and returns 0 without a hold, and `sceNgs2SystemRender` only tries the lock: while another thread holds it, Render clears one grain of each output, leaves the render count and voices unchanged and returns 0. The other calls do not wait for the system lock. Peace Walker (PPSA18216) holds it while it spins on a flag that its audio thread clears only after `sceNgs2SystemRender` and eight `sceNgs2VoiceGetStateFlags` calls, so on the console neither can wait for it. Whether the console's voice and rack calls take the lock at all is not known. `sceNgs2RackLock` still holds the library's lock until `sceNgs2RackUnlock`, as before +- [sceNgs2GetWaveformFrameInfo](../../../core/libs/prx/libSceNgs2.native/src/Atrac9.cpp) (libSceNgs2.native) - not measured on a console; shadPS4 only stubs it, and prosper does not cover the call. The outputs are taken to be the frame fields of the parsed waveform info, which [prosper's reading of `libSceNgs2.native.sprx`](https://github.com/mattias800/prosper/blob/30572df43ae20ff28f995db56149f7af42235741/prosper/src/hle/audio/ngs2_waveform.cpp#L224-L231) fills for ATRAC9 with the superframe bytes and samples, the frames per superframe and a one-frame delay, whatever the `fact` chunk says. `PCM_I16L` answers channels * 2 bytes, 1 sample, 1 unit and no delay, as for parsed 16-bit PCM, itself assumed. Other types throw. Null outputs return `INVALID_OUT_ADDRESS` and a bad format `INVALID_WAVEFORM_FORMAT`, as in `sceNgs2CalcWaveformBlock`; the codes are unverified - [sceNgs2RackSetUserData, sceNgs2RackGetUserData](../../../core/libs/prx/libSceNgs2.native/src/Rack.cpp) (libSceNgs2.native) - the signatures follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/Ngs2.cs#L418-L421) and [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/ngs2/ngs2.cpp#L86-L111), which only logs them; neither gives an error. A handle that is not a live rack answers `INVALID_RACK_HANDLE` before a null user data pointer is checked, and the null pointer throws, as in `sceNgs2SystemGetUserData`. A new rack starts with 0. Not measured on a console - [sceNgs2RackGetInfo](../../../core/libs/prx/libSceNgs2.native/src/Rack.cpp) (libSceNgs2.native) - field order from [shadPS4](https://github.com/shadps4-emu/shadPS4)'s `OrbisNgs2RackInfo` with the 64-byte name of the other info structures (216 bytes); `type` is assumed to be the rack id divided by 0x1000, `min_grain_samples` 64 (the system minimum) and `state_flags` 1; `uid` comes from a per-process rack counter, `render_count` is the owning system's render count and `active_voice_count` counts voices that are not empty (paused and stopped voices included). The last process ratio and tick and `active_channel_work_count` are left zero - [sceNgs2VoiceQueryInfo](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - only info id 0x4001 is known: PPSA22520 passes an 8-byte buffer and branches on the first word being 1 or 2, so it is assumed to be the voice channel count (0 before the voice is set up); the second word is zeroed and other ids throw @@ -36,7 +39,7 @@ - [sceNgs2GeomCalcListener](../../../core/libs/prx/libSceNgs2.native/src/Geom.cpp) builds an orthonormal world-to-listener transform. The row-major matrix, front-axis priority and flag 1 for right-handed input follow [PS5PCEM](https://github.com/iStark/PS5PCEM/blob/d2534bac6325a2c61c0fa88977e5321074304484/src/hle/ngs2_geom.zig); they have not been measured on PS5 hardware. Velocity stays in world coordinates and positive finite sound speed is preserved. Orientation vectors are normalised and up is made perpendicular to front, so non-unit and slightly tilted vectors are accepted. When front and up are parallel, up is replaced by `(0,1,0)` or `(1,0,0)` so the basis stays defined: PPSA15552 (Dead Cells) passes `orient_front={0,0,1}` with `orient_up={0,0,1}` after moving the listener, which PS5PCEM rejects; the console error code is unknown. Zero orientation vectors, nonfinite input and an unrepresentable translation still throw without changing the output; console error codes are unknown, and a nonpositive sound speed throws where PS5PCEM and [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/df5b9b92be7dfd463b47756b707d6d356a9f0550/src/libs/ngs2.cpp) default it to 343. KytyPS5 always writes the identity matrix, so the transform rests on PS5PCEM plus that Dead Cells case. Dead Cells (PPSA15552) reaches this call after the custom sampler rack in #4334. This does not implement source attenuation, panning or Doppler in `sceNgs2GeomApply`. - [NGS2 custom sampler rack](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native, rack 0x4001) - implements bounded support for the custom sampler rack and its built-in module 0x13 (Custom Voice Filter), unblocking PPSA15552 (Dead Cells) startup. Structure layouts come from [shadPS4's ngs2_custom.h](https://raw.githubusercontent.com/shadps4-emu/shadPS4/main/src/core/libraries/ngs2/ngs2_custom.h) (OrbisNgs2CustomVoiceFilterParam, 40 bytes) and live trace correlation from [prosper's hle_audio.cpp](https://raw.githubusercontent.com/mattias800/prosper/main/prosper/src/hle/audio/hle_audio.cpp) (0x40001300 command and parameters like 10000.0f and 0.707f). Routes parameter updates via the 0x40001300 command family into the Biquad filter engine. Filter type 0 clears the destination filter; type 1 configures the RBJ low-pass biquad frequency, Q and level. Other filter types and unmodeled configurations throw. - [sceNgs2VoiceGetState custom sampler](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native, rack 0x4001) - only the size and one field come from a title. PPSA15552 (Dead Cells) calls it on custom sampler voices with a 48-byte buffer at two sites (eboot `0x1740f90`, `0x1741009`) and reads only the 64-bit value at offset 16: the first site keeps it and the second subtracts the kept value, so it is filled with the voice's decoded sample count. Other sizes return `INVALID_OUT_SIZE` as for the other racks. The state flags fill the common voice state; the remaining fields stay zero. shadPS4's `OrbisNgs2CustomSamplerVoiceState` is about 80 bytes and does not match the size this title passes, so the field names and the console's value are unverified -- [sceNgs2CalcWaveformBlock](../../../core/libs/prx/libSceNgs2.native/src/Atrac9.cpp) (libSceNgs2.native) - VAG (0x1c) throws. ATRAC9 keeps AnyPS5's own mapping: it checks the config against the channels and rate (0x804A8431), leaves out the decoder delay from the skip and size, leaves both 0 for zero samples and computes the size in 64 bits, while [prosper reads the PS5 module](https://github.com/mattias800/prosper/blob/40d95db32efe1ad00106a5b9a84e018fffa9ecff/prosper/src/hle/audio/ngs2_waveform.cpp#L348-L396) without the check, with the delay, filling both and modulo 2^32. Linear PCM and float (0x10 to 0x1b), the null checks and the 0x804A8432 of other types follow that reading and [its entry point](https://github.com/mattias800/prosper/blob/40d95db32efe1ad00106a5b9a84e018fffa9ecff/prosper/src/hle/audio/ngs2_waveform.cpp#L444-L466). Closes when: VAG is mapped and ATRAC9 follows the module +- [sceNgs2CalcWaveformBlock](../../../core/libs/prx/libSceNgs2.native/src/Atrac9.cpp) (libSceNgs2.native) - ATRAC9 keeps AnyPS5's own mapping: it checks the config against the channels and rate (0x804A8431), leaves out the decoder delay from the skip and size, leaves both 0 for zero samples and computes the size in 64 bits, while [prosper reads the PS5 module](https://github.com/mattias800/prosper/blob/40d95db32efe1ad00106a5b9a84e018fffa9ecff/prosper/src/hle/audio/ngs2_waveform.cpp#L348-L396) without the check, with the delay, filling both and modulo 2^32. Linear PCM and float (0x10 to 0x1b), VAG (0x1c: whole 28-sample frames of 16 bytes per channel, the frame-rounded sample count and no skip), the null checks and the 0x804A8432 of other types follow that reading and [its entry point](https://github.com/mattias800/prosper/blob/40d95db32efe1ad00106a5b9a84e018fffa9ecff/prosper/src/hle/audio/ngs2_waveform.cpp#L444-L466). Closes when: ATRAC9 follows the module ### Functional diff --git a/docs/dev/debt/libSceNpCppWebApi.md b/docs/dev/debt/libSceNpCppWebApi.md index be7f650ac6..f18925c392 100644 --- a/docs/dev/debt/libSceNpCppWebApi.md +++ b/docs/dev/debt/libSceNpCppWebApi.md @@ -2,10 +2,11 @@ ### Silent stubs -- [sce::Np::CppWebApi::Common](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the `finish` overrides of `TransactionBase<...>` (8 instantiations), `DownStreamTransaction<...>` (2 instantiations) and `Vector>::pushBack` return success without doing work; a failed request never produces a transaction to finish +- [sce::Np::CppWebApi::Common](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the `finish` overrides of `TransactionBase<...>` (13 instantiations), `DownStreamTransaction<...>` (2 instantiations) and `Vector>::pushBack` return success without doing work; a failed request never produces a transaction to finish. `Transaction<...>::setOnFinishedCallback` and `TransactionBase<...>::setResponseInformationOption` (5 instantiations each, the `UserProfile::V1`, `ProfanityFilter::V2` and `CommunicationRestrictionStatus::V3` responses) return success without storing the callback or the option: `start` fails, so neither is ever used. The constructors and destructors of the same five `Transaction<...>` instantiations succeed without keeping state - [sce::Np::CppWebApi::Leaderboards::V1](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - request-body and parameter bookkeeping succeeds without storing state: `GetRankingRequestBody::set{StartSerialRank,Group,Limit,Users,Offset}`, `RecordScoreRequestBody::set{Comment,SmallData,NeedsTmpRank,ComparedDateTime,PcId}`, `User::{setAccountId,setPcId}`, `BoardsApi::ParameterToGetBoardDefinition::{initialize,terminate}`, `RecordApi::ParameterToRecordLargeData::{initialize,terminate}`, `RecordApi::ParameterToRecordScore::{initialize,setxPsnAtomicOperation,terminate}`, `ViewApi::ParameterToGetLargeDataByObjectId::{initialize,terminate}`, `ViewApi::ParameterToGetRanking::{initialize,setgetRankingRequestBody,terminate}` - [sce::Np::CppWebApi::SessionManager::V1](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - parameter bookkeeping succeeds without storing state: `PlayerSessionsApi::ParameterToCreatePlayerSessions::{ctor,dtor,initialize}`, `PlayerSessionsApi::ParameterToGetPlayerSessionInvitations::{ctor,dtor,initialize,setfields,setinvitationInvalidFilter}`, `PlayerSessionsApi::ParameterToJoinPlayerSessionAsPlayer::{ctor,dtor,initialize,terminate}`, `PlayerSessionsApi::ParameterToLeavePlayerSession::{ctor,dtor,initialize}`, `RequestPlayerSession::set{MaxSpectators,InvitableUserType,JoinableUserType,ExclusiveLeaderPrivileges}`, `RequestPlayerSessionPlayer::setPushContexts` - [sce::Np::CppWebApi::TitleCloudStorage::V1](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - `DataApi::ParameterToDownloadData::{initialize,terminate}` succeed without storing state +- [sce::Np::CppWebApi::UserProfile::V1, ProfanityFilter::V2, CommunicationRestrictionStatus::V3](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - parameter bookkeeping succeeds without storing state: `FriendsApi::ParameterToGetFriends`, `PresenceApi::ParameterToGetBasicPresences`, `BlocksApi::ParameterToGetBlockingUsers`, `ProfanityApi::ParameterToFilterProfanity` and `CommunicationRestrictionStatusApi::ParameterToGetCommunicationRestrictionStatus`, each `{ctor,dtor,initialize,terminate}` ### Unknown function info @@ -14,4 +15,5 @@ - [sce::Np::CppWebApi::Common::String](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the 0x20-byte layout (32-bit reference count, buffer at 0x08, buffer size at 0x10, `LibContext*` at 0x18) comes from the module bundled with one title (PPSA21564); other SDK versions are not checked. `append` grows the buffer with the host allocator and the destructor frees it, instead of going through the `LibContext` allocator. `pushBack` into `Vector` stores a deep copy, so a destroyed temporary cannot invalidate the stored element; a title that byte-copies a `String` with a live buffer would still double-free, as nothing increments the reference count - [sce::Np::CppWebApi::Common::Vector](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by PPSA03671, whose dump ships no `libSceNpCppWebApi.prx`. A `Vector` handle is an `IntrusivePtr` over a shared body; bodies created by `pushBack` use a host-side layout with the reference count at 0x30, matching the adopted-body convention measured on PPSA21564. The pointer/deleter constructor adopts a title-provided body released through the stored deleter; `pushBack` on such a body throws, since its element layout is unknown. `begin`, `end`, iterators and `operator[]` throw: the only `Vector`s the title reads back come from failed transactions, which never produce one - [sce::Np::CppWebApi::SessionManager2V1](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by PPSA03671. Offline, the factories, `PlayerSessionsApi` calls and transaction `start`/`getResponse`/`readData` fail with `SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN`; parameter setters, `initialize`, `terminate`, constructors and destructors succeed without keeping state, as no request is ever sent. Response-body accessors (`getSessionId`, `getPlayerSessions`, `getInvitations`, the `IsSet` queries) throw: a failed request never produces a response object +- [sce::Np::CppWebApi::UserProfile::V1, ProfanityFilter::V2, CommunicationRestrictionStatus::V3](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by an Epic Online Services module whose dump ships no `libSceNpCppWebApi.prx`. The reference-count offset 0x00 of `ResponseHeaderBase` and `WebApiFilterRequest` is assumed from the other objects, not measured. Offline, the five API calls, `WebApiFilterRequestFactory::create` and the transactions' `start`/`getResponseHeaders` fail with `SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN`. Response accessors, `ResponseHeaderBase::getHeaderValue` and the `Vector`/`Iterator` members over `IntrusivePtr` and `unsigned long` throw: a failed request never produces a response - [libSceNpCppWebApi](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - unimplemented stubs have their parameter lists, const qualifiers and return types aligned with the demangled symbols confirmed by NID matching in [#8143](https://github.com/boykopovar/AnyPS5/issues/8143). They cover response-body accessors, iterators, and uninstantiated transactions in Leaderboards::V1, SessionManager::V1, InGameCatalog::V5, TitleCloudStorage::V1, and Common templates: each mangled name gives the method and parameter types, but the runtime layout of response objects and errors are unverified on console, so each call continues to throw NotImplemented_nid_no_patch. No call site is recorded for them diff --git a/docs/dev/debt/libSceNpManager.md b/docs/dev/debt/libSceNpManager.md index 96633c6c2b..a484f93b3a 100644 --- a/docs/dev/debt/libSceNpManager.md +++ b/docs/dev/debt/libSceNpManager.md @@ -6,7 +6,7 @@ ### Unknown function info -- [libSceNpManager pending exports](../../../core/libs/prx/libSceNpManager/Export.cpp) - 149 named callable exports are present in the local `libSceNpManager.sprx` export table and the public PS5 3.20 symbol list. Their signatures and behavior are unverified; each declaration uses the no-parameter unknown-signature convention and throws through `NotImplemented_nid_no_patch`. +- [libSceNpManager pending exports](../../../core/libs/prx/libSceNpManager/Export.cpp) - 143 named callable exports are present in the local `libSceNpManager.sprx` export table and the public PS5 3.20 symbol list. Their signatures and behavior are unverified; each declaration uses the no-parameter unknown-signature convention and throws through `NotImplemented_nid_no_patch`. - [sceNpGetSandboxInfo](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager, NID `ZVZ++68nNI8`) - the name hashes to the imported NID (found with nidhunt, confirmed with `tools/nid_names.py`). STAR WARS: Galactic Racer (PPSA28416) imports it; its OnlineSubsystemPS5 calls it with one pointer to a 0x50-byte stack buffer and reads the buffer as a string (discussions/3086). The returned string, the exact parameter struct and console errors are unknown, so every call throws without reading or writing caller memory. A working implementation belongs with that title's bring-up. - [sceNpUnknown_8weiQyIdI3A](../../../core/libs/prx/libSceNpManager/Export.cpp): the original name remains unknown. The native wrapper takes a user ID and a read-only 20-byte online-ID record, validates its first byte and sends the record through IPC. The implementation remains a throwing stub. @@ -18,6 +18,7 @@ ### Functional - [sceNpGetAccountCountry, sceNpGetAccountDateOfBirth, sceNpGetAccountLanguage, sceNpGetParentalControlInfo](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager) - PS4 forms taking an online id, not measured on a console. Signatures and checks follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/81be949167d0060b08f26dc3e055a0b640d129c8/src/core/libraries/np/np_manager.cpp): a null pointer is `INVALID_ARGUMENT`; no user is signed in, so no online id matches one and the rest return `USER_NOT_FOUND` without writing, like `sceNpCheckNpAvailability`. Request ids are not checked +- [sceNpCheckNpAvailabilityA, sceNpGetAccountDateOfBirthA, sceNpGetAccountLanguageA, sceNpGetGamePresenceStatus, sceNpGetGamePresenceStatusA, sceNpGetParentalControlInfoA](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager) - answer as if no PSN account is signed in; the signed-in behaviour is not implemented and none of it is measured on a console. Checks follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/c2814041e546bf75c12756dbec9da911c721ce1c/src/core/libraries/np/np_manager.cpp): a null pointer or user id -1 is `INVALID_ARGUMENT`, then request id 0 is `INVALID_ARGUMENT` and an unknown one `REQUEST_NOT_FOUND`. Other users get `SIGNED_OUT` with nothing written, as from `sceNpGetAccountCountryA`; shadPS4's `sceNpGetAccountDateOfBirthA` returns `USER_NOT_FOUND` for a user it does not know. An async request gets `SIGNED_OUT` from the call; shadPS4 returns 0 and leaves it to `sceNpPollAsync`. The presence queries write `OFFLINE` (0) and return 0 - [sceNpGetUserIdByOnlineId](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager) - the `(online id, user id output)` signature follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/81be949167d0060b08f26dc3e055a0b640d129c8/src/core/libraries/np/np_manager.cpp#L703): a null online id or output returns `SCE_NP_ERROR_INVALID_ARGUMENT`, and a signed-out NP returns `SCE_NP_ERROR_SIGNED_OUT` without writing the output, as `sceNpGetUserIdByAccountId` does - [sceNpCheckPlus](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager) - the `(request id, parameter, result)` signature follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/81be949167d0060b08f26dc3e055a0b640d129c8/src/core/libraries/np/np_manager.cpp#L246): request id 0 or a null parameter or result returns `SCE_NP_ERROR_INVALID_ARGUMENT`, and every other call returns `SCE_NP_ERROR_SIGNED_OUT` without writing the result, as `sceNpCheckPremium` does. The parameter's size and feature fields, which shadPS4 checks, are not read: the errors the console returns for them are not known - [sceNpGetAccountId, sceNpSetGamePresenceOnline](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager) - PS4 forms; the `(online id, account id output)` and `(request id, online id, presence)` signatures follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/81be949167d0060b08f26dc3e055a0b640d129c8/src/core/libraries/np/np_manager.cpp#L1092-L1096). A null online id or output, or request id 0 for `sceNpSetGamePresenceOnline`, returns `SCE_NP_ERROR_INVALID_ARGUMENT`. `sceNpSetGamePresenceOnline` then returns `SCE_NP_ERROR_REQUEST_NOT_FOUND` for a request that was never created or was deleted, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/c1b26558352b863f80142256e4df38585aa7d727/src/core/libraries/np/np_manager.cpp#L1098-L1104) does. Otherwise both return `SCE_NP_ERROR_SIGNED_OUT` without writing, as `sceNpGetAccountIdA` does. The console's result for a signed-out user is not measured diff --git a/docs/dev/debt/libSceNpParty.md b/docs/dev/debt/libSceNpParty.md index 45d68abf16..e88c71d92d 100644 --- a/docs/dev/debt/libSceNpParty.md +++ b/docs/dev/debt/libSceNpParty.md @@ -2,4 +2,7 @@ ### Unknown function info -- [sceNpPartyGetState](../../../core/libs/prx/libSceNpParty/Export.cpp) (libSceNpParty) - the 16-bit state, its `NOT_IN_PARTY` value 2 and the `INVALID_ARGUMENT` (`0x80552504`) error follow shadPS4's [np_party.h](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/np/np_party.h) and [np_error.h](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/np/np_error.h) (PS4); shadPS4 does not check for a null state, and whether the console needs an initialized party library first is not known. Not measured on a PS5 +- [sceNpPartyGetState and sceNpPartyGetStateAsUserA](../../../core/libs/prx/libSceNpParty/Export.cpp) (libSceNpParty) - the 16-bit state, its `NOT_IN_PARTY` value 2 and the `INVALID_ARGUMENT` (`0x80552504`) error follow shadPS4's [np_party.h](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/np/np_party.h) and [np_error.h](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/np/np_error.h) (PS4); shadPS4 does not check for a null state, and whether the console needs an initialized party library first is not known. Not measured on a PS5 +- [sceNpPartyGetId, sceNpPartyGetMembersA, sceNpPartyGetMemberInfoA, sceNpPartyGetMemberSessionInfo, sceNpPartyGetMemberVoiceInfo and sceNpPartySendBinaryMessage](../../../core/libs/prx/libSceNpParty/Export.cpp) (libSceNpParty) - the user is never in a party, so these return `NOT_IN_PARTY` (`0x80552506`, shadPS4's np_error.h) and write nothing, or `INVALID_ARGUMENT` for a null pointer; the signatures are from [Orbital's type database](https://github.com/AlexAltea/orbital/blob/b321fcdbd700cc02da1cb1ad25412781d5ec73c6/tools/ida/db_types.json). Like `sceNpPartyGetState` they do not require `sceNpPartyInitialize`, and the order of the null and party checks on the console is not known. Not measured on a PS5 +- [sceNpPartyInitialize, sceNpPartyTerminate, sceNpPartyCheckCallback and sceNpPartyRegisterHandlerA](../../../core/libs/prx/libSceNpParty/Export.cpp) (libSceNpParty) - an initialized flag with `ALREADY_INITIALIZED` (`0x80552502`) and `NOT_INITIALIZED` (`0x80552503`) from shadPS4's np_error.h; the handlers are accepted but never called, since no party event can happen. Whether the console refuses a null initialize parameter, and which check comes first, is not known +- [sceNpPartyCreate, sceNpPartyCreateA, sceNpPartyGetMemberInfo, sceNpPartyGetMembers, sceNpPartyGetStateAsUser, sceNpPartyGetVoiceChatPriority, sceNpPartyJoin, sceNpPartyLeave, sceNpPartyRegisterHandler, sceNpPartyRegisterPrivateHandler, sceNpPartySetVoiceChatPriority, sceNpPartyShowInvitationList, sceNpPartyShowInvitationListA and sceNpPartyUnregisterPrivateHandler](../../../core/libs/prx/libSceNpParty/Export.cpp) (libSceNpParty) - Orbital has no prototypes for these and no credible ones were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified diff --git a/docs/dev/debt/libSceNpTus.md b/docs/dev/debt/libSceNpTus.md index f21274d8ab..cb413bbf34 100644 --- a/docs/dev/debt/libSceNpTus.md +++ b/docs/dev/debt/libSceNpTus.md @@ -6,8 +6,6 @@ - [Multi-slot variable calls](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - the argument checks of the multi-slot get, set and delete calls are not measured on a console; they follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/c1b26558352b863f80142256e4df38585aa7d727/src/core/libraries/np/np_tus/np_tus.cpp), where they differ between functions: `arrayLen < 1` is `INSUFFICIENT_ARGUMENT` in the NpId and virtual-user delete calls and in `sceNpTusGetMultiSlotVariableVUserAsync`, and `INVALID_ARGUMENT` in the others; a null NpId is accepted except in delete; an empty virtual user ID is `INVALID_ARGUMENT`. RPCS3's [PS3 module](https://github.com/RPCS3/rpcs3/blob/1a9dca3a3a84a18df866e3021918322bd295160a/rpcs3/Emu/Cell/Modules/sceNpTus.cpp) has only the NpId and virtual-user calls; it returns `INVALID_ARGUMENT` for an empty array in all of them, rejects a null target in get and set and does not check for an empty virtual user ID. - [Variable, data, data status and TSS requests](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - the argument checks of the 82 add-and-get, try-and-set, data, data status, multi-user, friends and `sceNpTssGetSmallStorage` calls are not measured on a console; their signatures, checks and order follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/np/np_tus/np_tus.cpp), including its differences between forms: the NpId try-and-set calls return `INVALID_ARGUMENT` for an operation type outside 1-6 where the others return `INVALID_TYPE`, and the NpId and virtual-user multi-slot data status calls return `TOO_MANY_NPID` for more than 64 slots. As for the calls above, the output and structure sizes are not checked, and every request finishes with `SCE_NP_ERROR_SIGNED_OUT` - [sceNpTusSetThreadParam](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTssCreateNpTitleCtx](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature -- [sceNpTssCreateNpTitleCtxA](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature - [sceNpTusCreateTitleCtx](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature - [sceNpTusSetTimeout](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature - [sceNpTusChangeModeForOtherSaveDataOwners](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - unknown signature @@ -32,4 +30,5 @@ ### Functional - [sceNpTusCreateNpTitleCtxA](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - creates a title context without checking sign-in, up to 32 contexts (`TOO_MANY_OBJECTS` beyond); shadPS4 returns the `sceNpGetNpId` error instead. Requests are limited to 256, and the limits are not measured on a console. +- [sceNpTssCreateNpTitleCtx and sceNpTssCreateNpTitleCtxA](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - that TSS title contexts share the TUS context table and its 32-context limit is inferred from [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/615bcba5e21b82dc59588575f7ecbce8da09ca2c/src/core/libraries/np/np_tus/np_tus.cpp#L146-L168), which redirects both to the TUS creators, and from TSS requests coming from `sceNpTusCreateRequest`; nothing is measured on a console. The `A` form is `sceNpTusCreateNpTitleCtxA`. The `NpId` form returns `INSUFFICIENT_ARGUMENT` for a null `npId` and `INVALID_ARGUMENT` for service label `0xFFFFFFFF` before the limit, as shadPS4's [sceNpTusCreateNpTitleCtx](https://github.com/shadps4-emu/shadPS4/blob/615bcba5e21b82dc59588575f7ecbce8da09ca2c/src/core/libraries/np/np_tus/np_tus.cpp#L122-L143) does; the signatures are shadPS4's and fpPS4's, and Orbital's for the `A` form. - [sceNpTusPollAsync, sceNpTusWaitAsync](../../../core/libs/prx/libSceNpTus/Export.cpp) (libSceNpTus) - there is no PSN, so every implemented `*Async` call whose arguments pass the checks starts a request that finishes at once with `SCE_NP_ERROR_SIGNED_OUT`, which poll and wait report and the blocking calls return; shadPS4 returns `SIGNED_OUT` from the `*Async` call itself when no shadNet client is signed in, and RPCS3 returns `INVALID_ONLINE_ID` while offline, marked there as probable behaviour. An abort stays set, so every later operation on the request, blocking ones included, finishes with `ABORTED`; in shadPS4 an abort only ends the operation in progress. Until an operation has started, poll returns 1 and wait returns `INVALID_ARGUMENT` as in shadPS4; RPCS3 [blocks until a result is set](https://github.com/RPCS3/rpcs3/blob/1a9dca3a3a84a18df866e3021918322bd295160a/rpcs3/Emu/NP/np_contexts.cpp#L40-L52). diff --git a/docs/dev/debt/libScePad.md b/docs/dev/debt/libScePad.md index 31435d7262..c8fa44cf9d 100644 --- a/docs/dev/debt/libScePad.md +++ b/docs/dev/debt/libScePad.md @@ -17,6 +17,7 @@ - [scePadVrControllerReadCombinedState](../../../core/libs/prx/libScePad/Export.cpp) - the two handle arguments and 128-byte output are recovered; output field semantics remain incomplete. - [scePadSetVibrationMode](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - modes 1 to 3 and 5 are accepted and stored. On a PS5 (firmware 13.60) the call returns `0x80920005` before `scePadInit`, then rejects a mode of 0 or below with `INVALID_ARG` before it checks the handle, and returns `INVALID_HANDLE` for a bad handle with any higher mode; whether an open handle accepts modes above 3 was not measured, since the payload could not open a pad; 5 is accepted because PPSA25286 passes it on its open handle at start-up; the meanings of modes 3 and 5 are unknown (PPSA21480 calls it with 3 right after `scePadOpen` and aborts on failure; [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Pad/Pad.cs) names only 1 and 2). The stored mode has no other effect - [scePadIsRemoteController](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - the name hashes to NID `fCWdlnmB1Ks`; the signature `(int handle, bool* remote)` comes from PPSA16064, which zeroes a byte, passes its address after `scePadGetControllerInformation` and ignores the result. The error codes follow the other pad calls. Controllers are local devices, so the answer is always false +- [scePadIsDS4Connected, scePadIsBlasterConnected and scePadIsMoveConnected](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - the `bool (int handle)` signatures are recovered; the library reports one DualSense to every title (see Touchpad resolution below), so no DualShock 4, Blaster or Move controller is ever behind a handle and each call answers false for any handle. The console's answer for an invalid handle is not measured - [scePadSetAngularVelocityBiasCorrectionState](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - inferred signature `(int handle, bool enabled)` follows `scePadSetTiltCorrectionState` and `scePadSetAngularVelocityDeadbandState`; an invalid handle returns `PAD_ERROR_INVALID_HANDLE`, disabling is a no-op, and enabling throws because bias correction is not modelled - [scePadGetAccessibilityControllerState](../../../core/libs/prx/libScePad/Export.cpp) (libScePad) - the value a console reports for a DualSense is not measured; the emulated pad, which is not an Access controller, reports 0. The name hashes to NID `zBsgF0q8DIM`. In the OS 10.01 system module the `(int handle, uint32_t* state)` function returns `0x80920005` before `scePadInit`, then `INVALID_ARG` for a NULL `state` and `INVALID_HANDLE` for a bad handle, and otherwise asks the pad driver for a per-device value of the device behind the handle: 0 when no device is behind it, 1 when the driver has no answer, else the driver's value. What the values mean is unknown diff --git a/docs/dev/debt/libScePosixForWebKit.md b/docs/dev/debt/libScePosixForWebKit.md index 681a24a3d5..39e20c0542 100644 --- a/docs/dev/debt/libScePosixForWebKit.md +++ b/docs/dev/debt/libScePosixForWebKit.md @@ -3,7 +3,8 @@ ### Unknown function info - [gethostbyname](../../../core/libs/prx/libScePosixForWebKit/Resolver.cpp) (libScePosixForWebKit) - numeric IPv4 forms, trailing whitespace and 64-bit accumulation follow FreeBSD 11.4 [inet_aton](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libc/inet/inet_addr.c); clearing the resolver error on numeric success follows [fakeaddr](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libc/net/gethostnamadr.c). Exact PS5 behavior is unverified; hostname lookup still uses the host resolver -- [__wrap_access, __wrap_clock_gettime, __wrap_fchown, __wrap_getcwd, __wrap_getenv, __wrap_link, __wrap_lstat, __wrap_mmap, __wrap_readlink, __wrap_statvfs, __wrap_symlink, clock_gettime_np, getenv_np, gethostbyname_r (`scePosixForWebKit_gethostbyname_r`), mmap_np, msetname, pthread_setschedparam_np, setenv_np, sysinfo (`scePosixForWebKit_sysinfo`)](../../../core/libs/prx/libScePosixForWebKit/Export.cpp) (libScePosixForWebKit) - names/NIDs appear in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libScePosixForWebKit.c) and [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv); credible prototypes are unknown. These exports use renamed helpers; defining `gethostbyname_r` would interpose on the host function this library calls itself. All are parameterless import stubs, not ABI claims; callers must not use them until signatures are verified. +- [clock_gettime_np, getenv_np, gethostbyname_r (`scePosixForWebKit_gethostbyname_r`), mmap_np, msetname, pthread_setschedparam_np, setenv_np, sysinfo (`scePosixForWebKit_sysinfo`)](../../../core/libs/prx/libScePosixForWebKit/Export.cpp) (libScePosixForWebKit) - names/NIDs appear in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libScePosixForWebKit.c) and [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv); credible prototypes are unknown. These exports use renamed helpers; defining `gethostbyname_r` would interpose on the host function this library calls itself. All are parameterless import stubs, not ABI claims; callers must not use them until signatures are verified. +- [__wrap_fchown, __wrap_link, __wrap_readlink, __wrap_statvfs, __wrap_symlink](../../../core/libs/prx/libScePosixForWebKit/Export.cpp) (libScePosixForWebKit) - parameterless throwing stubs. Under the `--wrap` convention assumed for the forwarding wrappers (see Functional) each would take the POSIX signature of the function it wraps, but the targets are in libkernel_sys (`fchown`, `link`, `readlink`, `symlink`) and libSceLibcInternal (`statvfs`), which no prx links; libkernel_sys's `fchown`, `readlink` and `symlink` also still throw for some arguments ### Functional @@ -11,3 +12,4 @@ - [arc4random](../../../core/libs/prx/libScePosixForWebKit/Export.cpp) (libScePosixForWebKit) returns four bytes of host OS entropy (`getentropy`, `rand_s` on Windows), as `arc4random_buf` does, uniform over the 32-bit range like FreeBSD 11.4's RC4 keystream in [arc4random.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/arc4random.c). The console's generator and its reseeding are unknown; `arc4random_uniform`, `arc4random_stir` and `arc4random_addrandom` are not in the module's export set and are absent - [fnmatch](../../../core/libs/prx/libScePosixForWebKit/Export.cpp) (libScePosixForWebKit) follows FreeBSD 11 [fnmatch.c](https://github.com/freebsd/freebsd-src/blob/stable/11/lib/libc/gen/fnmatch.c) in the C locale: each byte is one character, `FNM_CASEFOLD` folds ASCII letters only, and bracket ranges compare byte values. `[:class:]` names are not character classes: as in FreeBSD 11 through 14, `[[:digit:]]` is the set of `[`, `:`, `d`, `i`, `g`, `t` followed by a literal `]` (classes were added to `fnmatch.c` after 14). Multibyte characters and locale collation are not modelled. A null pattern or string throws - [getservbyname](../../../core/libs/prx/libScePosixForWebKit/Resolver.cpp) (libScePosixForWebKit) searches a built-in copy of 37 lines of FreeBSD 11.4's [etc/services](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/etc/services) (ftp-data, ftp, ssh, telnet, smtp, domain, tftp, http, pop3, ntp, imap, https, rtsp, submission, imaps, pop3s with their aliases and protocols, in file order) the way [getservent.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/net/getservent.c) searches the file: case-sensitive name or alias, a null protocol takes the first match, the port in network byte order, the result in per-thread storage. The console's `/etc/services` is unknown, so any other service returns null without setting `errno`, as for a name the file lacks. `getaddrinfo` still resolves service names through the host's service database +- [__wrap_access, __wrap_clock_gettime, __wrap_getcwd, __wrap_getenv, __wrap_lstat and __wrap_mmap](../../../core/libs/prx/libScePosixForWebKit/Export.cpp) (libScePosixForWebKit) assume, from the export names alone, the GNU ld [`--wrap`](https://sourceware.org/binutils/docs/ld/Options.html) convention (a reference to `` resolves to `__wrap_`): each takes its target's POSIX signature and forwards to libc's (`access`, `getcwd`, `getenv`) or libkernel's (`clock_gettime`, `lstat`, `mmap`) implementation, which sets `errno`. No PS5 module or WebKit call site was read to confirm that WebKit imports `__wrap_` in place of `` or that the console wrappers only forward. Any sandbox or path handling the console module adds is not modelled, and how the wrappers relate to `getenv_np`, `setenv_np`, `mmap_np` and `clock_gettime_np` (such as a separate environment that `setenv_np` fills) is unknown diff --git a/docs/dev/debt/libSceRnpsAppMgr.md b/docs/dev/debt/libSceRnpsAppMgr.md new file mode 100644 index 0000000000..b430381f91 --- /dev/null +++ b/docs/dev/debt/libSceRnpsAppMgr.md @@ -0,0 +1,5 @@ +# libSceRnpsAppMgr technical debt + +### Unknown function info + +- [sceRnpsAppMgrBlockAppInstall, sceRnpsAppMgrCreateUpdateRequest, sceRnpsAppMgrCreateUrlRequest, sceRnpsAppMgrDestroyBundle, sceRnpsAppMgrDestroyUpdateRequest, sceRnpsAppMgrFinishUrlRequest, sceRnpsAppMgrGetAppInfo, sceRnpsAppMgrGetAppInfoDebugString, sceRnpsAppMgrGetAppInfoList, sceRnpsAppMgrGetEventForShellUI, sceRnpsAppMgrGetUpdateProgress, sceRnpsAppMgrHasEventForShellUI, sceRnpsAppMgrInitialize, sceRnpsAppMgrPushEventForShellUI, sceRnpsAppMgrRecoverUfsImage, sceRnpsAppMgrRegisterShellUI, sceRnpsAppMgrRemoveApp, sceRnpsAppMgrRemoveAppOnSystemShutdown, sceRnpsAppMgrRemoveUfsImageOnSystemShutdown, sceRnpsAppMgrRequestUpdateCheck, sceRnpsAppMgrSetCrlUrl, sceRnpsAppMgrSetEventFlagForClients, sceRnpsAppMgrSetUpdateUrl, sceRnpsAppMgrStartService, sceRnpsAppMgrTerminate, sceRnpsAppMgrUnblockAppInstall and sceRnpsAppMgrUnregisterShellUI](../../../core/libs/prx/libSceRnpsAppMgr/Export.cpp) (libSceRnpsAppMgr) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceRnpsAppMgr.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified diff --git a/docs/dev/debt/libSceScreenShot.md b/docs/dev/debt/libSceScreenShot.md index 2e1f886b38..a109ca5bb9 100644 --- a/docs/dev/debt/libSceScreenShot.md +++ b/docs/dev/debt/libSceScreenShot.md @@ -1,5 +1,11 @@ # libSceScreenShot technical debt +### Silent stubs + +- [sceScreenShotEnableNotification and sceScreenShotDisableNotification](../../../core/libs/prx/libSceScreenShot/Export.cpp) (libSceScreenShot) - return 0 without effect: on the console they ask the system software to show or hide the capture notification, which this project does not display +- [sceScreenShotSetOverlayImage and sceScreenShotSetOverlayImageWithOrigin](../../../core/libs/prx/libSceScreenShot/Export.cpp) (libSceScreenShot) - check the arguments and the file as the PS5 module does, then return 0 without keeping the image, since this project takes no screenshots to overlay +- [sceScreenShotIsDisabled](../../../core/libs/prx/libSceScreenShot/Export.cpp) (libSceScreenShot) - reports one process-wide flag set by sceScreenShotDisable and cleared by sceScreenShotEnable for any id other than -1; on the console the system software answers, and what the id selects is unknown + ### Unknown function info -- [sceScreenShotCapture, sceScreenShotDisable, sceScreenShotDisableNotification, sceScreenShotEnable, sceScreenShotEnableNotification, sceScreenShotGetAppInfo, sceScreenShotGetDrcParam, sceScreenShotIsDisabled, sceScreenShotIsVshScreenCaptureDisabled, sceScreenShotSetDrcParam and sceScreenShotSetParam](../../../core/libs/prx/libSceScreenShot/Export.cpp) (libSceScreenShot) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceScreenShot.c), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. +- [sceScreenShotCapture, sceScreenShotGetAppInfo, sceScreenShotGetDrcParam, sceScreenShotIsVshScreenCaptureDisabled, sceScreenShotSetDrcParam and sceScreenShotSetParam](../../../core/libs/prx/libSceScreenShot/Export.cpp) (libSceScreenShot) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceScreenShot.c), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified. diff --git a/docs/dev/debt/libSceShareUtility.md b/docs/dev/debt/libSceShareUtility.md new file mode 100644 index 0000000000..c953b5f180 --- /dev/null +++ b/docs/dev/debt/libSceShareUtility.md @@ -0,0 +1,5 @@ +# libSceShareUtility technical debt + +### Unknown function info + +- [sceShareUtilityAdvanceFromFileWithContentIdList, sceShareUtilityAdvanceFromFileWithTitleIdList, sceShareUtilityAdvanceGetServiceInfo, sceShareUtilityAdvanceInitialize, sceShareUtilityAdvanceQueryServiceInfo, sceShareUtilityAdvanceQueryServiceInfoA, sceShareUtilityAdvanceResetPlayingContent, sceShareUtilityAdvanceSetPlayingContent, sceShareUtilityAdvanceSetUploadContentData, sceShareUtilityAdvanceTerminate, sceShareUtilityAdvanceValidateContents, sceShareUtilityAdvanceWakeUpShareMenu, sceShareUtilityAdvanceWakeUpShareMenuBroadcast, sceShareUtilityAdvanceWakeUpShareMenuDefault, sceShareUtilityAdvanceWakeUpShareMenuForShareContent, sceShareUtilityInitialize, sceShareUtilityInitializeEx2, sceShareUtilityOpenShareMenu, sceShareUtilityOpenShareMenuBroadcast, sceShareUtilityOpenShareMenuDefault, sceShareUtilityOpenShareMenuForShareContent and sceShareUtilityTerminate](../../../core/libs/prx/libSceShareUtility/Export.cpp) (libSceShareUtility) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceShareUtility.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified diff --git a/docs/dev/debt/libSceSsl.md b/docs/dev/debt/libSceSsl.md index 23190fe3ef..20e9e19b1d 100644 --- a/docs/dev/debt/libSceSsl.md +++ b/docs/dev/debt/libSceSsl.md @@ -4,7 +4,7 @@ - [CA_MGMT_extractKeyBlobEx, CA_MGMT_extractPublicKeyInfo, CA_MGMT_freeKeyBlob, CRYPTO_initAsymmetricKey, CRYPTO_uninitAsymmetricKey, RSA_verifySignature, VLONG_freeVlongQueue, sceSslCheckRecvPending, sceSslCreateSslConnection and sceSslDeleteSslConnection](../../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - the list is the intersection of the export table of the console's libSceSsl (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo. Names and NIDs appear in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSsl.c) and [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv); no credible prototypes were found. They are declared without parameters solely to resolve known imports, not as an ABI claim; callers must not call them until signatures are verified. - [sceSslDisableOption, sceSslDisableOptionInternal, sceSslDisableOptionInternalInsecure, sceSslEnableOption, sceSslEnableOptionInternal, sceSslGetNanoSSLModuleId, sceSslGetPeerCert, sceSslGetSslError, sceSslLoadRootCACert and sceSslSetSslVersion](../../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - the list is the intersection of the export table of the console's libSceSsl (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo. Names and NIDs appear in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSsl.c) and [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv); no credible prototypes were found. They are declared without parameters solely to resolve known imports, not as an ABI claim; callers must not call them until signatures are verified. -- [sceSslRecv, sceSslReuseConnection and sceSslSend](../../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - the list is the intersection of the export table of the console's libSceSsl (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo. Names and NIDs appear in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSsl.c) and [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv). Their parameter shapes follow [SharpProspero's pinned bindings](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Http.cs#L222-L262), and are not measured on a PS5. +- [sceSslReuseConnection](../../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - its name and NID appear in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSsl.c) and [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv); its parameter shape follows [SharpProspero's pinned binding](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Http.cs#L2231-L2235). No console observation or reliable implementation source for session reuse was found. - [sceSslCreateConnection, sceSslDeleteConnection, sceSslSetVerifyCallback, sceSslConnect, sceSslRead, sceSslWrite](../../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - imported by `ffmpeg.prx`. Signatures follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/60977b29103452b9681ae10c7a9011d44965bf16/src/SharpProspero/Interop/Net/Http.cs#L195-L252), which also says the socket stays open after `sceSslDeleteConnection`. The behaviour below was measured on a PS5 on system software 13.40 with a ps5-payload-dev payload (every call printed with its return, a TLS 1.2 test server on the LAN presenting leaves issued by the test fixture root loaded with `sceSslLoadCert`, from an elfldr process rather than a title); what differs for a title process is unknown. An unknown context returns `SSL_ERROR_INVALID_ID` (0x8095F006) from `sceSslCreateConnection`, and every connection call on an unknown or deleted connection id or on a connection whose context was terminated returns `SSL_ERROR_INVALID_ID`. `sceSslCreateConnection` returns `SSL_ERROR_INVALID_VALUE` (0x8095F007) for a null host and does not check the socket: `sceSslConnect` on a descriptor that is not a socket returns `SCE_NET_ERROR_EBADF` (0x80410109). `sceSslConnect` runs a TLS 1.2 client handshake with Mbed TLS 3.6.7 over the host socket that the guest number maps to in the libkernel or libSceNet socket table, and `sceSslRead` and `sceSslWrite` carry application data over it. TLS 1.3 is compiled out: the console offers TLS 1.2 only (its ClientHello carries 12 suites and no supported_versions extension) and a TLS 1.3-only server gets `SSL_ERROR_FATAL_ALERT` (0x8095F010) from it, as from this build. Connection state is allocated from the context's pool and returned to it by `sceSslDeleteConnection`; a connection keeps the pool alive after `sceSslTerm`, whose connections return `SSL_ERROR_INVALID_ID` from then on. The console's pool use for one connection with a two-certificate chain peaked at 80,544 bytes in a 256 KiB pool (5,568 after `sceSslInit`, 12,000 after loading one CA); a 16 KiB pool fails the connect with `SSL_ERROR_OUT_OF_MEMORY` (0x809517D5) there and here. The server's chain is checked against the CAs loaded with `sceSslLoadCert`, the host name the connection was created with (case-insensitively; an IP literal sends no SNI and matches an IP subject alternative name; an empty host sends an empty SNI and fails `CN_CHECK`) and, when enabled, its validity dates, and `sceSslConnect` returns the first failure the connection's verify options select (the verify option entry below lists the bits, codes and initial options); no root bundle is shipped and the console's built-in roots are unknown, so with no CA loaded a connection with the initial options returns `SSL_ERROR_UNKNOWN_CA` (0x8095F00C), as on the console. A client certificate loaded with `sceSslLoadCert` is sent when the server asks for one. A failed verification is final: close_notify is sent, later `sceSslConnect` calls return the same code (also after the `WANT_POLLIN` rounds of a non-blocking socket), and `sceSslRead` and `sceSslWrite` throw, as the console's codes for them were not measured. With a verify callback set it is called once after the handshake, outside the connection's lock, with the context id, the bits of the enabled checks that failed (`KNOWN_CA_CHECK` 0x20, `CN_CHECK` 0x4, the date checks when enabled, 0 when the chain passed), handles to a copy of the peer chain as received (the leaf first, the root last when the server sent it) that the certificate readers of the connection's context accept until the connection is deleted (the handles stay registered in the context until it is terminated; the console frees them at the delete), the chain length and the user argument. A chain fault that no option names (`SSL_ERROR_INVALID_CERT` without a callback) throws when a callback is set, as the bits the console passes for it were not measured. A non-negative return accepts the connection whatever the bits were; a negative return fails the connect with `SSL_ERROR_APPLICATION_VERIFICATION_CALLBACK_CHECK` (0x8095F0FD) and the callback runs again, with the same chain, from every later `sceSslConnect`, `sceSslRead` and `sceSslWrite` of the connection, which return 0x8095F0FD while it keeps refusing and proceed once it accepts. A `sceSslConnect` from inside the callback, or from another thread while it runs, throws. `sceSslRead` and `sceSslWrite` before `sceSslConnect` run the handshake and verification themselves and return its code when it fails. A non-blocking socket that would block returns `SSL_ERROR_WANT_POLLIN` (0x8095F013) or `SSL_ERROR_WANT_POLLOUT` (0x8095F014) from `sceSslConnect`, `sceSslRead` and `sceSslWrite`; the next `sceSslConnect` resumes the handshake, and `sceSslWrite` returns the part it sent before blocking (the console returned 32 KiB of a 64 KiB write). A blocking libSceNet socket whose `SO_RCVTIMEO` expires returns `SSL_ERROR_WANT_POLLIN` as well, and the call can be repeated; `SSL_ERROR_READ_TIMEOUT` (0x80951710) is never returned. libkernel keeps its timeouts above the host socket, so they do not bound SSL reads and writes on a libkernel socket. `sceSslRead` with flags 0 reads until `len` bytes arrived or the stream ended and with flags 1 or 2 returns what the first record delivers. Native flags 3 peek without consuming data; values outside 0 through 3 return `SSL_ERROR_INVALID_VALUE` before connection lookup. A non-blocking read that got some bytes returns them. It returns 0 after the server's close_notify and after a transport that ends without one, every later read 0 again, and `SSL_ERROR_EOF` (0x8095177C) and `SSL_ERROR_SOCKET_CLOSED` (0x8095170D) never from a read. A reset (`ECONNRESET`, `EPIPE`, `WSAECONNRESET`, `WSAECONNABORTED`, `WSAENETRESET`) returns `SCE_NET_ERROR_ECONNRESET` (0x80410136) from `sceSslRead` and `SCE_NET_ERROR_EPIPE` (0x80410120) from `sceSslWrite`, on every later call too; a record that fails its MAC returns 0x80951D55 from `sceSslRead` after the fatal alert goes out. `sceSslWrite` accepts flags 0 only, `SSL_ERROR_INVALID_VALUE` otherwise, sends all of `len`, capped at `INT_MAX`, and returns the count; it keeps writing after the peer's close_notify or a stream that ended without one, as the console does. A fatal alert during the handshake returns `SSL_ERROR_FATAL_ALERT` and ends the transport: later `sceSslConnect` calls return 0, `sceSslRead` 0 and `sceSslWrite` `SSL_ERROR_SOCKET_CLOSED`, as measured. A transport that ends without close_notify or a reset during the handshake returns `SSL_ERROR_SOCKET_CLOSED`, the session is dropped and the next `sceSslConnect` starts over; those two handshake cases and `SSL_ERROR_INVALID_CERT` (0x8095F00A) for a verification failure no option names were not measured. Other handshake and socket failures, a read or write after a failed verification, a write after `sceSslClose`, a null buffer and a read or write from inside the callback throw, as their console codes are unknown. A connection serializes its reads and writes, so a read blocked in one thread holds back a write on the same connection from another. `sceSslDeleteConnection` sends no close_notify and returns `SSL_ERROR_INVALID_ID` when repeated. `sceSslTerm` leaves the context's connections to `sceSslDeleteConnection`, which then returns `SSL_ERROR_INVALID_ID`; a second `sceSslTerm` returns `SSL_ERROR_INVALID_ID` and `sceSslInit` with a pool size of 0 returns `SCE_KERNEL_ERROR_EINVAL` (0x80020016). `sceSslSetVerifyCallback` accepts any callback, null included, and returns 0. The console's first `sceSslConnect` in a process took 23.8 s before its ClientHello left, a one-time initialisation not emulated. `SSL_ERROR_BEFORE_INIT` (0x8095F001) is never returned, as the library keeps no state apart from its contexts - [sceSslClose](../../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - takes the connection id, as in [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/60977b29103452b9681ae10c7a9011d44965bf16/src/SharpProspero/Interop/Net/Http.cs#L195-L252). Measured on 13.40 as the connection entry describes: it sends close_notify once if the handshake completed and returns 0, 0 again when repeated and 0 on a connection that never connected, already failed verification or lost its transport to a fatal alert; a close before the handshake changes nothing, so the connection can still connect. After the close `sceSslConnect` returns 0 and `sceSslRead` 0, as on the console; `sceSslWrite` after it throws, as the console's code was not measured. A close_notify that a non-blocking socket cannot take at once returns `SSL_ERROR_WANT_POLLOUT` (0x8095F014) and the close can be repeated; after a reset it returns `SCE_NET_ERROR_ECONNRESET` (0x80410136) every time. The socket is left to the title; an unknown connection returns `SSL_ERROR_INVALID_ID` (0x8095F006). Whether the console closes the socket is unknown diff --git a/docs/dev/debt/libSceSystemLogger2.md b/docs/dev/debt/libSceSystemLogger2.md new file mode 100644 index 0000000000..3d17ac6069 --- /dev/null +++ b/docs/dev/debt/libSceSystemLogger2.md @@ -0,0 +1,5 @@ +# libSceSystemLogger2 technical debt + +### Unknown function info + +- [sceSystemLogger2GetTimestamp, sceSystemLogger2Initialize, sceSystemLogger2SetEventAsync, sceSystemLogger2SetEventAsyncEx and sceSystemLogger2Terminate](../../../core/libs/prx/libSceSystemLogger2/Export.cpp) (libSceSystemLogger2) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSystemLogger2.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified diff --git a/docs/dev/debt/libSceUlt.md b/docs/dev/debt/libSceUlt.md index 12002369d2..d539ce9772 100644 --- a/docs/dev/debt/libSceUlt.md +++ b/docs/dev/debt/libSceUlt.md @@ -6,6 +6,7 @@ - [libSceUlt ulthreads](../../../core/libs/prx/libSceUlt/Export.cpp) - Runtime option defaults still omit the native header, stack size, worker affinity and priority fields; affinity depends on unresolved process configuration queries. Concurrent join ownership is not modeled: the native runtime distinguishes an ordinary running thread from one with another joiner. Host threads do not use the guest context or worker scheduler. - [libSceUlt queues](../../../core/libs/prx/libSceUlt/Export.cpp) - Queue-data pool destruction and shared slot limits still require comparison with the native node allocator. +- [`sceUltConditionVariableCreate` and `sceUltConditionVariableOptParamInitialize`](../../../core/libs/prx/libSceUlt/Export.cpp) (libSceUlt) are absent from the console export list, where only the underscored variants `_sceUltConditionVariableCreate` and `_sceUltConditionVariableOptParamInitialize` appear; their non-underscored signatures are assumed. Condition variable option parameter initialization only zeroes 128 bytes, and its layout without the common header remains unverified on hardware. - [Ult factory and creation exports](../../../core/libs/prx/libSceUlt/Export.cpp) without a leading underscore are absent from the supplied module; their signatures remain unresolved. Diagnostic signatures and synchronization argument lists are recovered. - [sceUltMutexTryLock, the reader-writer lock and sceUltUlthreadGetSelf, sceUltUlthreadYield and sceUltUlthreadExit](../../../core/libs/prx/libSceUlt/src/ReaderWriterLock.cpp) (libSceUlt) - the signatures follow the PS Vita libult in [RPCS3](https://github.com/RPCS3/rpcs3/blob/13aa88c3c4d66ea8799e987f7d762d90943ab17b/rpcs3/Emu/PSP2/Modules/sceUlt.cpp) (a 256-byte lock and 128-byte option structures), not a PS5 binary. The rules are assumed: readers may take the lock while a writer waits, a thread that holds the write lock gets `ULT_ERROR_STATE` for any further lock instead of waiting on itself, a reader asking for the write lock waits on its own read lock, and the try forms return `ULT_ERROR_BUSY`. Outside an ulthread, `sceUltUlthreadGetSelf`, `sceUltUlthreadYield` and `sceUltUlthreadExit` return `ULT_ERROR_STATE`; inside one they act on the guest thread it runs on, since ulthreads are not scheduled cooperatively here @@ -13,4 +14,3 @@ - [libSceUlt queues](../../../core/libs/prx/libSceUlt/Export.cpp) use preallocated host storage instead of the supplied guest work area; the guest object layouts remain incomplete. Waiting-queue pool thread limits and waiter priority ordering are not implemented. Finalization wakes blocked queue callers with `ULT_ERROR_STATE`. - [sceUltWaitingQueueResourcePoolDestroy](../../../core/libs/prx/libSceUlt/Export.cpp) refuses with `ULT_ERROR_STATE` only when a thread is currently waiting on a pool-backed mutex, semaphore or queue, matching the PS5 libSceUlt disassembly (0x473a in or55417wcDk); bound but idle objects do not block pool destruction. Waiting-queue pool guest work area node accounting is not implemented. -- The following pending declarations have unverified signatures and behavior; they are parameterless import-resolution declarations and throw through `NotImplemented_nid_no_patch`: [`_sceUltConditionVariableOptParamInitialize`](../../../core/libs/prx/libSceUlt/Export.cpp). diff --git a/docs/dev/debt/libSceVdecCore.md b/docs/dev/debt/libSceVdecCore.md new file mode 100644 index 0000000000..a6803142e3 --- /dev/null +++ b/docs/dev/debt/libSceVdecCore.md @@ -0,0 +1,5 @@ +# libSceVdecCore technical debt + +### Unknown function info + +- [sceVdecCoreAnalyzeStream, sceVdecCoreCreateDecoder, sceVdecCoreDeleteDecoder, sceVdecCoreFinalizeComputeResource, sceVdecCoreFlushDecodeOutput, sceVdecCoreGetDecodeOutput, sceVdecCoreInitializeComputeResource, sceVdecCoreMapMemoryBlock, sceVdecCoreQueryComputeResourceInfo, sceVdecCoreQueryFrameBufferInfo, sceVdecCoreQueryInstanceSize, sceVdecCoreResetDecoder, sceVdecCoreSetDecodeInput, sceVdecCoreSetDecodeOutputSw, sceVdecCoreSyncDecode, sceVdecCoreSyncDecodeOutputSw, sceVdecCoreSyncDecodeWptr, sceVdecCoreTrySyncDecode, sceVdecCoreTrySyncDecodeOutputSw and sceVdecCoreTrySyncDecodeWptr](../../../core/libs/prx/libSceVdecCore/Export.cpp) (libSceVdecCore) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceVdecCore.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified diff --git a/docs/dev/debt/libSceVdecsw.md b/docs/dev/debt/libSceVdecsw.md index 0bf65cb433..4818e0bfea 100644 --- a/docs/dev/debt/libSceVdecsw.md +++ b/docs/dev/debt/libSceVdecsw.md @@ -2,10 +2,6 @@ ### Unknown function info -- [libSceVdecsw](../../../core/libs/prx/libSceVdecsw/Export.cpp) (libSceVdecsw) - structure layouts follow shadPS4's `vdecsw.h` and were checked against PPSA05684's movie decoder (`movie-decoder-vdecsw.cpp`), which asserts `pictureCount == 1` at offset 0xb of `OutputInfo` and reads `InputResult` offset 0x10 as the number of frames an access unit produced. The compute, decoder memory and decode input structures and the first 0x28 bytes of the 0x50-byte decoder config are those of libSceVideodec2, and decoding goes through it. The 0x78-byte AVC picture info is filled with Videodec2's leading fields and the cropping fields only. OUTPUT_PENDING (0x81510115), INPUT_QUEUE_EMPTY (0x81510116) and DECODE_PENDING (0x81510117) follow from the codes each call's assert accepts. At most four decoded pictures are queued and the last one is held until `sceVdecswFinalizeDecodeSequence`; the console's queue depth is unknown. `sceVdecswSyncDecodeInput` / `sceVdecswSyncDecodeOutput` retry the TrySync variants until they stop returning DECODE_PENDING / OUTPUT_PENDING; they do not sleep beyond a yield, so another thread must drain outputs when the picture queue is full. `sceVdecswGetHevcPictureInfo` still throws: the 0xb8-byte HEVC picture layout is in shadPS4's `vdecsw.h`, but this wrapper only keeps AVC picture info from Videodec2 +- [libSceVdecsw](../../../core/libs/prx/libSceVdecsw/Export.cpp) (libSceVdecsw) - structure layouts follow shadPS4's `vdecsw.h` and were checked against PPSA05684's movie decoder (`movie-decoder-vdecsw.cpp`), which asserts `pictureCount == 1` at offset 0xb of `OutputInfo` and reads `InputResult` offset 0x10 as the number of frames an access unit produced. The compute, decoder memory and decode input structures and the first 0x28 bytes of the 0x50-byte decoder config are those of libSceVideodec2, and decoding goes through it. The 0x78-byte AVC picture info is filled with Videodec2's leading fields and the cropping fields only. OUTPUT_PENDING (0x81510115), INPUT_QUEUE_EMPTY (0x81510116) and DECODE_PENDING (0x81510117) follow from the codes each call's assert accepts. At most four decoded pictures are queued and the last one is held until `sceVdecswFinalizeDecodeSequence`; the console's queue depth is unknown. `sceVdecswSyncDecodeInput` / `sceVdecswSyncDecodeOutput` retry the TrySync variants until they stop returning DECODE_PENDING / OUTPUT_PENDING; they do not sleep beyond a yield, so another thread must drain outputs when the picture queue is full. The 0xb8-byte HEVC picture info (shadPS4's `vdecsw.h`) carries only the timestamps, coded size, bit depth, IDR and IRAP flags, profile, level and conformance window (in chroma samples, repeated in the frame cropping fields, whose console unit is unknown) - [sceVdecswGetVp9PictureInfo](../../../core/libs/prx/libSceVdecsw/Export.cpp) (libSceVdecsw) - no console behaviour is recorded; the signature and 0x58-byte layout come from [SharpProspero's Vdecsw.cs](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Video/Vdecsw.cs#L892-L952), and shadPS4 has no VP9 getter. It copies the leading `thisSize - 8` bytes of the picture's info, as [shadPS4's `sceVdecswGetPictureInfo`](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/videodec/vdecsw.cpp#L303-L334) does for AVC and HEVC, from Videodec2's VP9 info (`level` is 0 there). An output with no picture returns 0 and writes nothing. Null pointers, `thisSize` below 8, two pictures and a frame buffer no VP9 decoder returned (an H.264 picture too) throw; console error codes are unknown. VP9 decoding goes through Videodec2 and rejects pictures deeper than 10 bits - -### Functional - -- [sceVdecswGetHevcPictureInfo](../../../core/libs/prx/libSceVdecsw/Export.cpp) (libSceVdecsw) - throws until HEVC picture info is filled from the decoder diff --git a/docs/dev/debt/libSceVenc.md b/docs/dev/debt/libSceVenc.md new file mode 100644 index 0000000000..92c6460425 --- /dev/null +++ b/docs/dev/debt/libSceVenc.md @@ -0,0 +1,5 @@ +# libSceVenc technical debt + +### Unknown function info + +- [sceVencCreateEncoder, sceVencDeleteEncoder, sceVencGetConfig, sceVencMapMemory, sceVencQueryHeader, sceVencQueryMemorySize, sceVencQueryPreset, sceVencSetConfig, sceVencSetDecodedFrame, sceVencSetPictureConfig, sceVencSetRateControlConfig, sceVencSetReferenceFrameInvalidationConfig, sceVencStartEncode and sceVencStopEncode](../../../core/libs/prx/libSceVenc/Export.cpp) (libSceVenc) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceVenc.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified diff --git a/docs/dev/debt/libSceVideodec.md b/docs/dev/debt/libSceVideodec.md index c24087ab78..8abac004cf 100644 --- a/docs/dev/debt/libSceVideodec.md +++ b/docs/dev/debt/libSceVideodec.md @@ -2,14 +2,14 @@ ### Unknown function info -- [sceVideodecCreateDecoder, sceVideodecDecode, sceVideodecDeleteDecoder, sceVideodecFlush and sceVideodecReset](../../../core/libs/prx/libSceVideodec/Export.cpp) (libSceVideodec) - the structure layouts and the error codes follow [shadPS4's PS4 library](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/videodec/videodec.h) and its [implementation](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/videodec/videodec.cpp), not measured on a PS5: the console's error for a rejected argument and its own version of the structures are unknown. The decoder runs through libSceVideodec2, which decodes H.264 with FFmpeg, and the picture is copied into the caller's frame buffer with the pitch aligned to 64 and the coded height, as the reference writes it - -- [libSceVideodec](../../../core/libs/prx/libSceVideodec/Export.cpp) (libSceVideodec) - the module's other two exports, `sceVideodecMapMemory` and `sceVideodecQueryResourceInfo`, are not provided, so a title that imports them fails to start +- [sceVideodecCreateDecoder, sceVideodecDecode, sceVideodecDeleteDecoder, sceVideodecFlush, sceVideodecQueryResourceInfo and sceVideodecReset](../../../core/libs/prx/libSceVideodec/Export.cpp) (libSceVideodec) - the structure layouts and the error codes follow [shadPS4's PS4 library](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/videodec/videodec.h) and its [implementation](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/videodec/videodec.cpp), not measured on a PS5: the console's error for a rejected argument and its own version of the structures are unknown. The decoder runs through libSceVideodec2, which decodes H.264 with FFmpeg, and the picture is copied into the caller's frame buffer with the pitch aligned to 64 and the coded height, as the reference writes it ### Functional - [sceVideodecCreateDecoder](../../../core/libs/prx/libSceVideodec/Export.cpp) (libSceVideodec) - the codec type is not checked and the decoder always decodes H.264, as the PS4 library does; the console's codec ids are not documented. A configuration without a maximum frame size is refused with the configuration error, and the resource information the caller passes is not used +- [sceVideodecQueryResourceInfo](../../../core/libs/prx/libSceVideodec/Export.cpp) (libSceVideodec) - the sizes it reports are libSceVideodec2's, not the console's: the CPU memory, CPU-GPU memory and frame buffer sizes and the frame buffer alignment that `sceVideodec2QueryDecoderMemoryInfo` gives for the configuration mapped as `sceVideodecCreateDecoder` maps it. The GPU memory size that query also gives has no field in this structure and is dropped. The memory pointers and `thisSize` are left as the caller passed them, where [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/c2814041e546bf75c12756dbec9da911c721ce1c/src/core/libraries/videodec/videodec.cpp#L137-L187) clears the pointers; what the console writes there is not known. A larger structure is accepted, as by the module's other exports, where shadPS4 requires the exact size, and a configuration without a maximum frame size is refused with the configuration error, where shadPS4 assumes 1920x1080 or 3840x2160 + - [sceVideodecDecode and sceVideodecFlush](../../../core/libs/prx/libSceVideodec/Export.cpp) (libSceVideodec) - the picture information's video usability information (the tick rate, the sample aspect ratio and the colour description) stays zero, as the decoder does not report it, and the crop offsets are the decoded picture's crop halved, the unit the H.264 offsets use. The frame buffer holds the coded picture the decoder reports, so the rows the crop covers are in it and the caller crops them -- [sceVideodecMapMemory and sceVideodecQueryResourceInfo](../../../core/libs/prx/libSceVideodec/Export.cpp) (libSceVideodec) - their names are listed in the PS5 3.20 export table and cross-checked against PS5 13.60. Their signatures and behaviour are unverified; these no-parameter declarations only resolve imports and throw through NotImplemented_nid_no_patch. +- [sceVideodecMapMemory](../../../core/libs/prx/libSceVideodec/Export.cpp) (libSceVideodec) - its name is listed in the PS5 3.20 export table and cross-checked against PS5 13.60. Its signature and behaviour are unverified; this no-parameter declaration only resolves the import and throws through NotImplemented_nid_no_patch. diff --git a/docs/dev/debt/libSceVoice.md b/docs/dev/debt/libSceVoice.md index 9e8d8fc46e..3dcdeae2a9 100644 --- a/docs/dev/debt/libSceVoice.md +++ b/docs/dev/debt/libSceVoice.md @@ -8,19 +8,16 @@ ### Unknown function info - [sceVoiceInit, sceVoiceStart, sceVoiceCreatePort, sceVoiceGetPortInfo](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - a second `sceVoiceInit` returns 0x804E0802, and Start, CreatePort and GetPortInfo return 0x804E0801 before checking any argument while the library is not initialized, as [prosper](https://github.com/mattias800/prosper/blob/ed42e499888d5a86792c3e1b2df3aebd8ca1a681/prosper/src/hle/audio/hle_audio.cpp#L4697-L4713) reads them from the shipped module. The other port and lifecycle calls still throw when the library is not initialized, since prosper gives no module address for them. Whether `sceVoiceInit` checks a null param before its state is unknown, so a null param still throws first, and that `sceVoiceEnd` returns the library to the not-initialized state is assumed. Closes when: the other entry points are read from the module or measured on a PS5 +- [sceVoiceInitHQ, sceVoiceVADSetVersion](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - not measured on a PS5: the signatures and rules follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/voice/voice.cpp). `sceVoiceInitHQ` runs `sceVoiceInit`, as shadPS4's two share one path. `sceVoiceVADSetVersion` accepts versions 0 and 1 and returns `0x804E0805` for any other; nothing uses the version, as no voice activity detection runs - [sceVoicePausePortAll, sceVoiceResumePortAll](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - not measured on a PS5: both return 0x804E0801 while the library is not initialized and 0 otherwise, as in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/c1b26558352b863f80142256e4df38585aa7d727/src/core/libraries/voice/voice.cpp#L356-L437); prosper reads the same code for `sceVoiceStart`, and the PS3 calls in [RPCS3](https://github.com/RPCS3/rpcs3/blob/be69c054eeac57e2e671697a463473d37fe5a7b2/rpcs3/Emu/Cell/Modules/cellVoice.cpp#L462-L543) make the same check. Like `sceVoicePausePort` they change nothing that is reported: [VitaSDK](https://github.com/vitasdk/vita-headers/blob/ae5808f3105c42fa3373ba4ab64ab54e2e3ca848/include/psp2/voice.h#L540-L557) documents that pausing every port keeps the buffered data and the reported state, and nothing here processes port data -- [sceVoiceInitHQ](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature - [sceVoiceGetMemorySize](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature - [sceVoiceDeviceInCustomerData](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature - [sceVoiceQAEnableRecording](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature - [sceVoiceVADAdjustment](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature - [sceVoiceVADPVTAdjustment](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature -- [sceVoiceVADSetVersion](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature -- [sceVoiceSetMuteFlagAll](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature -- [sceVoiceSetBitRate](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature -- [sceVoiceUpdatePort](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature +- [sceVoiceSetMuteFlagAll, sceVoiceSetBitRate, sceVoiceUpdatePort](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - not measured on a PS5; signatures follow [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json), behaviour the Vita's [VitaSDK](https://github.com/vitasdk/vita-headers/blob/ae5808f3105c42fa3373ba4ab64ab54e2e3ca848/include/psp2/voice.h#L250-L557). They return the not-initialized, port-range and missing-port codes the other port calls return. The mute-all flag makes `sceVoiceGetMuteFlag` report every port muted without changing port flags. A non-voice port, a bitrate `sceVoiceCreatePort` rejects, a null param or a different port type throw, since the console's codes for them are unknown - [sceVoiceGetPortAttr](../../../core/libs/prx/libSceVoice/Export.cpp): native initialization, port-range, pointer, attribute-size and port-type validation is implemented. Supported device attributes throw because the host has no device state or voice-activity processing. The former constant-zero voice-activity fallback is removed; failed calls leave output untouched. -- [sceVoiceSetThreadsParams](../../../core/libs/prx/libSceVoice/Export.cpp): the 48-byte mode/priority/affinity layout and initialization, null, started-state and mode validation are recovered. Mode 1 throws because worker scheduling is not implemented; mode 0 is the native no-op. The 32-byte event callback union is recovered, but callback delivery and device-state byte semantics remain unimplemented. +- [sceVoiceSetThreadsParams](../../../core/libs/prx/libSceVoice/Export.cpp): the priorities and affinity masks that mode 1 sets reach no host scheduling, because no voice worker threads run (see sceVoiceStart below). Mode 1 returns 0 for priorities 256-767 with non-zero masks within 0x1fff, the values a PS5 thread takes; the native result for other values is unverified, so they throw. The 32-byte event callback union is recovered, but callback delivery and device-state byte semantics remain unimplemented. - [sceVoiceStart, sceVoiceStop and sceVoiceEnd](../../../core/libs/prx/libSceVoice/Export.cpp) validate initialization, container presence and native mode-dependent memory sizes, but do not partition guest working memory or run the audio workers. Start/stop currently change software state only; backend failures and active-stream shutdown are not reproduced. diff --git a/docs/dev/debt/libSceVorbisDec.md b/docs/dev/debt/libSceVorbisDec.md index f0c2ddc877..45ad9e2d0e 100644 --- a/docs/dev/debt/libSceVorbisDec.md +++ b/docs/dev/debt/libSceVorbisDec.md @@ -2,37 +2,13 @@ ### Unknown function info -- [sceVorbisDecBitrate](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecBitrateInstant](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecClear](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecComment](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecCrossLap](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database +- [sceVorbisDecOpenCallbacks, sceVorbisDecRead and the other libvorbisfile exports](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - the 31 exports named after libvorbisfile functions are taken to be the libvorbisfile API with a `sceVorbisDec` prefix (`Read` is `ov_read`, `CrossLap` is `ov_crosslap`, `HalfrateP` is `ov_halfrate_p`). `ReadFilter`, `ReadFloat` and `Halfrate` exist in libvorbis (`ov_read_filter` since 1.2.2, with the same `vorbisfile.h` signatures through 1.3.7 apart from `const`) but not in Tremor, so times are `double` and `ov_read` takes `bigendianp`, `word` and `sgned`. Guest `long` arguments and results are 64-bit. They run the bundled libvorbis 1.3.7 and libogg 1.3.6; the console's libvorbis version, any Sony changes to its behaviour or error codes, and the signatures themselves are unverified on a PS5. - [sceVorbisDecGetDecodePriority](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database - [sceVorbisDecGetLastDecodeError](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecHalfrate](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecHalfrateP](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecInfo](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecOpenCallbacks](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecPcmSeek](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecPcmSeekLap](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecPcmSeekPage](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecPcmSeekPageLap](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecPcmTell](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecPcmTotal](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecRawSeek](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecRawSeekLap](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecRawTell](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecRawTotal](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecRead](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecReadFilter](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecReadFloat](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecSeekable](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecSerialNumber](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database - [sceVorbisDecSetDecodePriority](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecStreams](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecTimeSeek](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecTimeSeekLap](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecTimeSeekPage](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecTimeSeekPageLap](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecTimeTell](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database -- [sceVorbisDecTimeTotal](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - declared without parameters, signature unknown; name from the NID database + +### Functional + +- [libSceVorbisDec](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - the guest `OggVorbis_File` is never written: the decoder state lives on the host, keyed by the struct's address, so fields a title reads directly keep what it stored, and a struct never opened, cleared or failed to open behaves as libvorbisfile's zeroed struct. `sceVorbisDecInfo` returns a copy of each link's `vorbis_info` with 64-bit `long` fields, refreshed after every call on the struct and valid until `sceVorbisDecClear`. Reopening a struct that is still open frees its old state without calling its close callback; libvorbisfile leaks it. +- [sceVorbisDecClear and sceVorbisDecCrossLap](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - a null `vf` throws, as libvorbisfile dereferences it, except that `sceVorbisDecClear` and `sceVorbisDecCrossLap` with the same pointer twice return 0, and `sceVorbisDecCrossLap` with a null second pointer after a first that is not open returns `OV_EINVAL`, as libvorbisfile does. `sceVorbisDecClear` frees the state before it calls the close callback, as `ov_clear` does, so when that callback throws the struct is already cleared, where libvorbisfile leaves it freed but not zeroed. +- [sceVorbisDecOpenCallbacks and sceVorbisDecBitrate](../../../core/libs/prx/libSceVorbisDec/Export.cpp) (libSceVorbisDec) - on Windows the host `long` is 32-bit: a tell result outside it, or an initial byte count outside it with a non-null `initial`, throws; a sample rate of 2^31 Hz or more is rejected as a bad header; and `sceVorbisDecBitrate` rounds halves up instead of to even. diff --git a/docs/dev/debt/libSceWebBrowserDialog.md b/docs/dev/debt/libSceWebBrowserDialog.md index f86ba5ae8f..c057cc3710 100644 --- a/docs/dev/debt/libSceWebBrowserDialog.md +++ b/docs/dev/debt/libSceWebBrowserDialog.md @@ -3,6 +3,7 @@ ### Silent stubs - [libSceWebBrowserDialog](../../../core/libs/prx/libSceWebBrowserDialog/Export.cpp) - `sceWebBrowserDialogOpen` and `sceWebBrowserDialogOpenForPredeterminedContent` run the common-dialog state machine without displaying an interactive browser, and `sceWebBrowserDialogGetResult` reports that the user cancelled. Minecraft Bedrock (PPSA17221) imports `sceWebBrowserDialogOpenForPredeterminedContent` (#5744). The 424-byte allow-list parameter and its twenty optional address pointers are validated, but address filtering is not modelled without an interactive browser. Main open-parameter validation remains incomplete. +- [sceWebBrowserDialogSetCookie, sceWebBrowserDialogResetCookie](../../../core/libs/prx/libSceWebBrowserDialog/Export.cpp) (libSceWebBrowserDialog) - there is no browser cookie store. Both calls check their parameters in the same order and with the same codes as the PS5 12.00 module, then return 0 without storing or clearing anything. On the console they then forward the request to the browser service. While an opened dialog has not been updated to finished, closed or terminated, they return `0x80B80006` (the console's open-dialog flag). ### Unknown function info - [sceWebBrowserDialogGetEvent, sceWebBrowserDialogNavigate, sceWebBrowserDialogSetZoom](../../../core/libs/prx/libSceWebBrowserDialog/Export.cpp) (libSceWebBrowserDialog) - their names and NIDs are in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceWebBrowserDialog.c); the no-parameter prototypes follow [shadPS4 `webbrowserdialog.cpp`](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/web_browser_dialog/webbrowserdialog.cpp#L23-L85) and are not measured on a console. They are declared without parameters solely to resolve known imports; this is not an ABI claim, and every call throws. diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index b1ebcecbb9..c94e722be8 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -29,7 +29,7 @@ - [sceKernelGetProsperoSystemSwVersion](../../../core/libs/prx/libkernel/System/src/SoftwareVersion.cpp) (libkernel) reports system software version `0x01000000` ("01.000.000"); the console's firmware version is not modeled. The 0x28-byte block (a 64-bit size at 0, the version string at 8 and the packed version at 0x24) follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Kernel/KernelSystem.cs), which Prospero Multi Tools (PPSA99110) and Prospero Explorer (PPSA99109) are built with and which sets the size to 40, and the same fields in [etaHEN](https://github.com/etaHEN/etaHEN/blob/main/Source%20Code/daemon/include/ipc.hpp) and [shsrv](https://github.com/ps5-payload-dev/shsrv/blob/master/sh.c). A size other than 0x28 returns `SCE_KERNEL_ERROR_EINVAL` without writing; whether the console checks the size is unverified, since etaHEN and shsrv declare a 0x30-byte block and do not set it - [sceKernelGetAllowedSdkVersionOnSystem](../../../core/libs/prx/libkernel/System/src/SoftwareVersion.cpp) (libkernel) reports the system software version with the 12 patch bits set, `0x01000fff`, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/98fd0689/src/core/libraries/kernel/kernel.cpp) does on the PS4 and as [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Kernel/KernelSystem.cs) describes it, which Prospero Explorer (PPSA99109) reads it through; a null pointer returns `SCE_KERNEL_ERROR_EINVAL`. The console's value and its errors are unverified - [sceKernelConvertUtcToLocaltime, sceKernelConvertLocaltimeToUtc](../../../core/libs/prx/libkernel/Time/Time.cpp) (libkernel) convert with the host time zone at the given instant, as libc `localtime` does, instead of the console's time zone setting. `west_sec` holds the standard-time offset in seconds east of UTC, despite its name, and `dst_sec` the daylight saving seconds in effect. This follows PPSA99203 ([PS5SX2](https://github.com/Swordpdf/PS5SX2/blob/b4cf55e87c34e8a2a1cd9302df4373e5a94d28f4/ps5/frontend/fe_ps5.cpp#L54-L63)), whose author saw a console write 7200 and 3600 into the 16-byte block; which zone that console was set to is not recorded, so whether `west_sec` includes daylight saving is not settled. [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/23c41333/src/core/libraries/kernel/time.cpp) also stores seconds east. PPSA99203 declares the last argument of `sceKernelConvertUtcToLocaltime` as `uint64_t*`; 32 bits are written. On Windows the standard offset and the daylight saving amount are the C runtime's current `_get_timezone` and `_get_dstbias` for every instant, as libc `localtime_s` uses them, so instants under a zone's earlier rules get today's offsets; a `TZ` variable does not set `_dstbias`, which keeps the value an earlier `_tzset` read from the system (0 when the system zone has no daylight saving), and summer time then adds nothing. In a repeated or skipped local hour, `sceKernelConvertLocaltimeToUtc` takes the offset in effect one standard offset earlier. Its last argument receives the zone's daylight saving amount whether or not it is in effect, found by sampling the host zone over the year around the result, while `dst_sec` is the amount in effect: the libc `mktime` that PPSA99109 bundles (text 0x70ebb-0x70ee5) subtracts that amount when `tm_isdst` is set outside summer time. That `mktime` passes `tm_isdst` as the second argument, which is ignored, so it does not choose the offset in a repeated hour. `sceKernelGettimezone` and the `gettimeofday` time zone still report no offset and no daylight saving. A host time the C runtime cannot convert throws -- [getuid, geteuid, getgid, getegid and issetugid](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) report the root identity (0) and a process that never changed credentials (`issetugid` 0), as a jailbroken process has; the host user and group are not exposed to the guest. The values a title sees on a console are unverified +- [getuid, geteuid, getgid, getegid, getresuid, getresgid and issetugid](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) report the root identity (0) and a process that never changed credentials (`issetugid` 0), as a jailbroken process has; the host user and group are not exposed to the guest. `getresuid` and `getresgid` follow FreeBSD 11.4's `sys_getresuid` and `sys_getresgid`, writing 0 to non-null pointers and leaving errno untouched on success. The values a title sees on a console are unverified - [sceKernelGetAppInfo](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - only `app_id` and the title ID (offset 0x10, up to 9 characters plus a terminator) are written; callers declare the structure with different sizes, so the rest is left alone. `app_id` is 1, the running title's id in libSceSystemService, which is a guess, not something measured. The error codes follow shadPS4: `EPERM` for another process, and 0 without writing anything for a null pointer. - [AMPR object layout and compact gathers](../../../core/libs/prx/libkernel/Apr/include/AprCommandBuffer.hpp) - the 24-byte object and offsets (type 0, cursor 4, count 8, capacity 12, pointer 16) follow [ampr_emu assertions](https://github.com/drakmor/ampr_emu/blob/cfa85df379f6eeeb165d7badf9b648e266fe77b7/include/ampr_emu_command_buffer_types.h#L138-L143) and its [C declaration](https://github.com/drakmor/ampr_emu/blob/cfa85df379f6eeeb165d7badf9b648e266fe77b7/include/ampr.h#L41-L47). AnyPS5 represents the 32-bit type word as low 16-bit type and high 16-bit recording flags. Command encoding is AnyPS5 internal: opcode 29 and three 32-bit words encode the high offset byte, length minus one and low offset word. The 12-byte size reduces HFW's 1 MiB buffer pressure; it is not a claim about console opcode encoding. Existing 40-bit offsets and lengths 1 through 2^32 are retained; console behavior outside those limits is unverified. - [APR resolve ForEach result](../../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libkernel) - `sceKernelAprResolveFilepathsToIdsAndFileSizesForEach` returns the number of paths that resolved, with the per-path status in `results` and the failed ids/sizes set to the invalid id and 0. The [ampr_emu](https://github.com/drakmor/ampr_emu) bridge passes the count of its resolver through and does not show what is counted; counting successes is not confirmed on a title. The guest errno of the failing non-`ForEach` resolves is set through `__error_nid_postfix` @@ -83,7 +83,7 @@ - [select](../../../core/libs/prx/libkernel/Socket/src/Socket.cpp) (libkernel) - a descriptor the socket poller reports as unknown that is below `GuestSockets::FirstDescriptor` is polled on the host instead of throwing (on Windows only a pipe, as above); an invalid host descriptor fails with `EBADF`, and mixed host and socket sets are polled in short alternating slices so neither side waits out the full slice alone. A descriptor that stops being a socket, a kqueue or a pipe during the wait fails the call with `EBADF` when it is closed and is selected again with the rest of the timeout when its number was reused, as FreeBSD 11.4's [`kern_select`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/sys_generic.c#L1129-L1140) looks every descriptor up again in each pass. A close that has already removed the descriptor from the guest's kqueue, pipe or file tables but not yet closed its host descriptor is seen by that new look-up as a descriptor of unknown kind, which throws on Windows and is polled through the host on Linux. Wobbly Life (PPSA29413) selects on file descriptors while streaming its bundles - [__progname](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - unknown data export - [environ](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - data export pointing to a fixed empty environment so titles that import it load; it is not the environment libc's `getenv` reads, so it lists none of those variables and `setenv`/`putenv` changes do not appear in it -- [seteuid, setegid, setuid, setgid, setreuid, setregid, setgroups, getgroups](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - the guest process is assumed to run as root, with real, effective and saved user and group ids 0. As in FreeBSD's `sys_seteuid`, `sys_setegid`, `sys_setuid`, `sys_setgid`, `sys_setreuid` and `sys_setregid`, id 0 (or -1 for an unchanged `setreuid`/`setregid` id) is then accepted without a privilege check and changes no credential, so 0 is returned; any other id would change an id, which is not modelled, and throws. `setgroups` keeps the supplementary group list as `kern_setgroups` does (`NGROUPS_MAX` 1023, an empty list leaves the effective group as the only entry, a first entry other than 0 would change the effective group and throws) and `getgroups` reports it. The ids and privileges a title has on a console are unverified +- [seteuid, setegid, setuid, setgid, setreuid, setregid, setresuid, setresgid, setgroups, getgroups](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - the guest process is assumed to run as root, with real, effective and saved user and group ids 0. As in FreeBSD's `sys_seteuid`, `sys_setegid`, `sys_setuid`, `sys_setgid`, `sys_setreuid`, `sys_setregid`, `sys_setresuid` and `sys_setresgid`, id 0 (or -1 for an unchanged `setreuid`/`setregid`/`setresuid`/`setresgid` id) is then accepted without a privilege check and changes no credential, so 0 is returned; any other id would change an id, which is not modelled, and throws. `setgroups` keeps the supplementary group list as `kern_setgroups` does (`NGROUPS_MAX` 1023, an empty list leaves the effective group as the only entry, a first entry other than 0 would change the effective group and throws) and `getgroups` reports it. The ids and privileges a title has on a console are unverified - [pthread_getname_np](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel) - a thread that has already been joined returns `ESRCH` (3) and its output buffer is left untouched, as measured on a PS5 (firmware 13.60) by the author of this entry; since [#4481](https://github.com/boykopovar/AnyPS5/pull/4481) the joined thread's object is retained, so the posix wrapper answers `ESRCH` for it instead of reading the retained name. A null name buffer is answered with `EFAULT` before that, an order the console table does not measure. The SCE sibling `scePthreadGetname` has no measured joined-thread behaviour on the console and still reads the retained object's name, so it is unchanged, as is the pre-existing dangling case of a thread that ended detached (its object is released at exit; see the scePthreadJoin entry) - [sceKernelGetOperationMode](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - the signature `(int* mode, int* submode)` comes from PPSA12544, which logs both values after the call and ignores the result. A homebrew payload on a PS5 (firmware 13.60) reads mode 2 and submode 0, nothing else is written, and the result register holds no status, so the function is taken to return nothing. Whether a title process reads the same values is unverified. Null outputs throw - [sceCoredumpGetStopInfoCpu](../../../core/libs/prx/libkernel/System/src/Coredump.cpp) has no active coredump context. Native null/size validation is recovered, including both 8-byte and 16-byte records; valid requests return `NOT_IN_COREDUMP_HANDLER` without accessing the output. Active-handler data retrieval remains unimplemented. @@ -217,6 +217,7 @@ - [pause, sigsuspend, sigwait, sigwaitinfo](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) take the pending set of `sigtimedwait` instead of throwing. `pause` waits for a pending signal that is not blocked and whose disposition can be delivered, runs its handler on the calling thread and returns -1 with `EINTR`; an ignored signal is discarded and the wait continues, and it never returns for a signal whose handler cannot run. A signal whose registered handler has `SA_SIGINFO` still throws, and one whose disposition is the default action and that has no host equivalent (every signal except 2, 4, 6, 8, 11 and 15) is neither delivered nor discarded: it stays pending until a wait takes it, where FreeBSD runs the default action, which terminates the process for most of them. A pending `SIGALRM` with the default disposition exits the process with status 142 once it is unblocked, as FreeBSD's default action does. PPSA68091, PPSA99169, PPSA99420 and PPSA97358 import them and fail to load without them. PPSA68091 calls `pause` at one site (0x8F7266), in a loop that waits while a global holds another thread's `errno` address. PPSA99169 only stores the address of `pause` in a table of symbol addresses (0x30DB1). PPSA99420 calls `pause` at 0x3BD80F6 and 0x3C9E220, `sigwait` at 0x3C9E4C7 and `sigwaitinfo` at 0x3C9E588. PPSA97358 calls `sigwait` from QEMU's `sigwait_compat` thread ([compatfd.c](https://github.com/ZiZc3/XPSemu/blob/8eec07b4e31f66dce8e0b05c1ca1281af303e933/util/compatfd.c#L28-L53)), which `qemu_signal_init` ([main-loop.c](https://github.com/ZiZc3/XPSemu/blob/8eec07b4e31f66dce8e0b05c1ca1281af303e933/util/main-loop.c#L110-L147)) starts for `SIGIO`, `SIGALRM` and `SIGBUS` when the main loop is created, so it reaches the wait at start-up, and from the vCPU thread of the dummy accelerator ([dummy-cpus.c](https://github.com/ZiZc3/XPSemu/blob/8eec07b4e31f66dce8e0b05c1ca1281af303e933/accel/dummy-cpus.c#L48-L56)). `sigsuspend` swaps the process-wide mask of `_sigprocmask` for its argument, waits as `pause` does through the same helper, then restores the mask through `_sigprocmask`, and returns -1 with `EINTR`; a null set throws, as the console faults in libthr instead of returning `EFAULT`. All four words of the argument replace the mask and are restored afterwards, as in `_sigprocmask`. Java 25 LAN / DNS (PPSA99869, #5501) imports it and fails to load without it, after `sysctlnametomib`, `fegetenv` and `mincore`. None of the five waits is a cancellation point - [getsid](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) returns the calling process ID for pid 0 and the calling process, and `ESRCH` for other process IDs, assuming the single guest process is its own session leader. PPSA74126 imports it and fails to load without it - [sigaction](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) records the handler, flags and mask of every signal from 1 to 128 and reports them back, following FreeBSD's `sigaction` (`EINVAL` for other signals, and for a handler on `SIGKILL` or `SIGSTOP`); `signal` records its handler with `SA_RESTART`, as FreeBSD's libc does. Only handlers without `SA_SIGINFO` on the signals `signal` already delivers (`SIGINT`, `SIGILL`, `SIGABRT`, `SIGFPE`, `SIGSEGV`, `SIGTERM`) run, the same way `signal` handlers do; their mask and flags are not applied. `SA_SIGINFO` handlers are recorded but never called, since that needs FreeBSD's `siginfo_t` and `ucontext_t`, and neither are the handlers of other signals (`SIGBUS`, `SIGHUP`, ...), whether they are set by `sigaction` or by `signal`, which records them too (every number from 1 to 128, with `SA_RESTART` and an empty mask replacing what `sigaction` stored; `SIGKILL` and `SIGSTOP` accept only the default handler in both calls, as in FreeBSD's `kern_sigaction`). `raise` and `kill` accept every number from 1 to 128 now: a blocked one stays pending on the set of `sigtimedwait`, an unblocked one whose handler is registered runs it on the calling thread, and an unblocked one that has no host equivalent and no handler still fails with `EINVAL`, where FreeBSD runs its default action. `raise` of an `SA_SIGINFO` handler still throws instead of terminating +- [signal](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) records its handler with `SA_RESTART` unless the signal is in the exported `_sigintr` set, as FreeBSD's libc does (`lib/libc/gen/signal.c`). `siginterrupt`, which fills that set, is a libSceLibcInternal export and is not provided: libc does not link libkernel and cannot reach the dispositions, so only a guest that writes the set itself clears `SA_RESTART` this way. - [kill, killpg](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) follow FreeBSD's `kern_kill` and `killpg1` for the only guest process: `EINVAL` for signals outside 0 to 128 and for negative `killpg` process groups, signal 0 only checks that the target exists, and its own pid, 0 and minus its own pid (the guest is taken to lead its own process group) signal it through `raise`, so they share `raise`'s limits above. Any other pid, and -1, which skips the caller, report `ESRCH`, so `EPERM` is never reported - [sceKernelFcntl](../../../core/libs/prx/libkernel/File/src/Open.cpp) (libkernel) runs the POSIX `fcntl` below and returns its guest errno as `SCE_KERNEL_ERROR_*`, the form the other `sceKernel` file exports use. The return values and error codes are unverified on a console - [fcntl](../../../core/libs/prx/libkernel/File/src/DescriptorControl.cpp) (libkernel) follows FreeBSD 11.4's [`kern_fcntl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L479-L799) and [`kern_dup`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L812) on file, directory, pipe and character device descriptors; a socket keeps the libSceNet path (`F_DUPFD`, `F_DUP2FD`, `F_DUPFD_CLOEXEC`, `F_DUP2FD_CLOEXEC`, `F_GETFL`, `F_SETFL` with `O_NONBLOCK`, `F_GETFD` and `F_SETFD`, `EINVAL` for every other command, see the next entry). On Linux and macOS a host socket that is not a guest socket, such as a standard descriptor a service manager connected to its journal, takes the flag handling of a pipe end and `FIONREAD` asks the host for its queued bytes (for a datagram socket Linux counts only the next datagram, where FreeBSD's [`soo_ioctl`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/sys_socket.c#L202-L205) counts every buffered byte); its record locks and read-ahead hints fail with `EBADF`, as FreeBSD fails them on a descriptor without a vnode, and `O_ASYNC` on it throws. The close-on-exec flag and the status flags live in a guest table, the status flags shared by `dup`, `dup2` and `F_DUPFD` as in an open file description; a descriptor the table has not seen, such as 0 to 2, starts from the host's flags, since the console's are not known. `F_GETFL` takes the access mode from the host (the granted access of the handle on Windows), so pipe ends report `O_RDONLY` and `O_WRONLY` where FreeBSD's bidirectional pipes report `O_RDWR`. `F_DUPFD` and `F_DUPFD_CLOEXEC` return the lowest free host descriptor at or above the start, so the bound is the host table (`EMFILE` on Windows, `EINVAL` past `RLIMIT_NOFILE` on Linux), not the console's unknown `RLIMIT_NOFILE` and `kern.maxfilesperproc`. `O_NONBLOCK` switches a host pipe (`PIPE_NOWAIT` on Windows, where an empty read and a full write then report `EAGAIN`) and is only recorded on files, directories and the random device, as their reads never block; on a Windows console or other character device it throws. Changing `O_APPEND` on a Windows file, changing `O_FSYNC` on a file, `O_ASYNC` on a pipe or character device (`SIGIO` is not delivered), `F_GETOWN`, `F_SETOWN` and `F_SETLK_REMOTE` throw; `O_DIRECT`, `F_RDAHEAD` and `F_READAHEAD` are recorded as the hints they are. Record locks (`F_GETLK`, `F_SETLK`, `F_SETLKW` and their forms without `l_sysid`) use the host's POSIX locks on Linux and macOS. `F_GETLK` with `F_UNLCK` asks the host with `F_RDLCK`: as in [`lf_blocks`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_lockf.c#L866) only another owner's overlapping write lock conflicts with either request. On Windows they follow [`kern_fcntl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L607-L752) and [`lf_advlockasync`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_lockf.c#L433-L487) for the one guest process, which owns every lock: `EOVERFLOW` for an `SEEK_CUR` start past `OFF_MAX`, then `EBADF` for `F_RDLCK` without read access or `F_WRLCK` without write access and `EINVAL` for another type, then `EINVAL` and `EOVERFLOW` for the range; as [`lf_blocks`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_lockf.c#L866) never lets locks of one owner conflict, whichever descriptor or thread asks, `F_SETLK` and `F_SETLKW` then succeed without waiting and `F_GETLK` reports `F_UNLCK`, so no lock list is kept and merging, splitting and the release on close have nothing to change. Nothing is held on the host, since Windows byte-range locks are mandatory and would fail the guest's own reads and writes through other descriptors, so other host processes neither see these locks nor are seen; a lock from the end of a directory throws. The return values and error codes are unverified on a console @@ -228,7 +229,7 @@ - [pthread_kill](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) returns the error number like FreeBSD's `pthread_kill`: `EINVAL` for signals outside 0 to 128 and `ESRCH` for a null thread. A signal to the calling thread goes through `raise`, so it shares `raise`'s limits; a signal to another thread is left on the pending set for whichever thread waits for or unblocks it, since it is not delivered to the chosen thread, and a handle that is not a live guest thread returns `ESRCH` without touching it - [select](../../../core/libs/prx/libkernel/Socket/src/Socket.cpp) (libkernel) validates `tv_usec` but saturates a valid timeout beyond the remaining `steady_clock` range at `time_point::max()`, so the wait can continue effectively indefinitely. The console's behavior for a valid timeout outside the host clock's range is unknown. - [inet_ntop, inet_pton](../../../core/libs/prx/libkernel/Socket/src/AddressConversion.cpp) (libkernel) - an `AF_INET6` result of `inet_ntop` is followed by zero bytes up to `size`. In a payload process on a PS5 (firmware 12.70), the text forms, the order of the checks and this padding were measured through both exports. The padding was sampled at sizes from 3 to 65536, above 64 only at 65, 128, 256, 1024, 4096 and 65536; the sizes that were not sampled, sizes above 65536 and a title process are assumed to behave the same -- [_fstatfs](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) fills the FreeBSD 11 `struct statfs` of the PS5 payload SDK with the host volume that holds the file: block size, transfer size, total, free and available blocks, maximum name length and the read-only, `nosuid` and `noexec` flags (on Windows only read-only, and `f_files` and `f_ffree` are 0, as NTFS has no fixed number of files). The filesystem type number and name, mount paths, owner, filesystem id and I/O counters are left 0 or empty, since they would describe a host mount, not a console one. Sockets and pipes return `EINVAL`, as FreeBSD's `getvnode` does; a Windows descriptor that is neither a file nor a pipe throws. Stremio Plus (PPSA74126) reads the free space before a download and only logs the other fields +- [_fstatfs](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) reports the host volume of a file, not a console mount: block sizes, block counts, name length and the read-only, `nosuid` and `noexec` flags (on Windows only read-only, and `f_files` and `f_ffree` are 0). The type, mount names, owner, filesystem id and I/O counters are 0 or empty. Sockets, pipes and kqueues return `EINVAL`, as FreeBSD's `getvnode` does. A character device, including `/dev/random` and `/dev/urandom`, reports FreeBSD 11.4's [`devfs_statfs`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/fs/devfs/devfs_vfsops.c#L225-L237) values on every host: no flags, 512-byte blocks, 2 blocks, nothing free and a 255-byte name limit. A Windows descriptor that is none of these throws. Stremio Plus (PPSA74126) reads the free space before a download and only logs the other fields - [pthread_spin_lock](../../../core/libs/prx/libkernel/Pthread/Posix/Spinlock.cpp) (libkernel) - a lock taken again by the thread that holds it throws here. On a PS5 (firmware 12.70) that call had not returned after 2 s, as FreeBSD 11.4 libthr [`thr_pspinlock.c`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_pspinlock.c) spins forever - [pthread_mutex_getspinloops_np, pthread_mutex_setspinloops_np, pthread_mutex_getyieldloops_np, pthread_mutex_setyieldloops_np and their scePthreadMutex counterparts](../../../core/libs/prx/libkernel/Pthread/src/Mutex.cpp) (libkernel) store and report the counts, but host mutexes do not spin or yield by them. The defaults follow FreeBSD 11.4's [`mutex_init_body`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_mutex.c#L236-L272) without `LIBPTHREAD_SPINLOOPS` and `LIBPTHREAD_YIELDLOOPS`: 2000 spins for an adaptive mutex, 0 otherwise; the console's defaults are unverified. Setting the yield count of a destroyed mutex returns 0 as FreeBSD's [`__pthread_mutex_setyieldloops_np`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_mutex.c#L1125-L1135) does; the console's result is unverified - [pthread_rwlockattr_getpshared, pthread_rwlockattr_setpshared](../../../core/libs/prx/libkernel/Pthread/Posix/Rwlock.cpp) (libkernel) - `pthread_rwlockattr_destroy` clears the attribute here, so a later get, or a set of 0, throws. On a PS5 (firmware 12.70) destroy leaves the attribute set; a get afterwards returned 0 with a stale value and a set of 0 returned 0 diff --git a/docs/dev/debt/libkernel_sys.md b/docs/dev/debt/libkernel_sys.md index 7111e9f679..cb9cee2b32 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -3,11 +3,16 @@ ### Silent stubs - [fchmodat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - `AT_SYMLINK_NOFOLLOW` on a symbolic link (on Windows and Linux) +### Unknown function info + +- [sceKernelGetCompiledSdkVersion](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - which SDK version the console returns is unmeasured. The [PS5 payload SDK](https://github.com/ps5-payload-dev/sdk/blob/a7685ce8e97ce1f8a4f38253a41d54241c9124f1/crt/kernel.c#L257-L275) declares the process parameters with a 32-bit PS4 SDK version at offset `0x10` and a 32-bit PS5 SDK version at `0x14`; this export returns the field at `0x10`, the one [libSceHmd2](../../../core/libs/prx/libSceHmd2/Export.cpp) reads and the one [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/615bcba5e21b82dc59588575f7ecbce8da09ca2c/src/emulator.cpp#L110-L138) (PS4, a single 64-bit `sdk_version` there) reads from the eboot. The `int (int32_t*)` signature and `EINVAL` (`0x80020016`) for a null output follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/615bcba5e21b82dc59588575f7ecbce8da09ca2c/src/core/libraries/kernel/process.cpp#L56-L62). Closes when: measured on a PS5 + ### Functional - [fchdir](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) changes the guest working directory to the descriptor's directory: on Windows through the path recorded when the descriptor was opened, on Linux through the `/proc/self/fd` link target. It returns `EBADF` for a descriptor that is not open, `EINVAL` for a pipe or socket, as FreeBSD `getvnode` does for a descriptor without a vnode, and `ENOTDIR` for any other descriptor that is not a directory. Outside Windows it checks search permission on the directory with `faccessat`, which fails with `EACCES` as FreeBSD `change_dir` does; on Windows that check is not modelled. A directory renamed after it was opened is still reached through the recorded path. Console behaviour is unverified - [openat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `open`. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. As with `open`, `O_NOFOLLOW` and `O_NONBLOCK` are ignored. PPSA99003 opens directory entries this way. Console behaviour is unverified - [fchmodat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `chmod`; any other relative path is joined to the descriptor's host directory the same way as for `openat`, so guest path aliases and case-insensitive name matching do not apply to it and a `..` component is not kept inside the guest root. A flag other than `0` or `AT_SYMLINK_NOFOLLOW` (0x200) returns `EINVAL`; the mode is masked to `07777`. With `AT_SYMLINK_NOFOLLOW` a symbolic link returns success silently on Windows and Linux, since the host cannot change a link's own mode, while a regular file is changed either way. The mode maps to the host file mode, which on Windows is only the CRT's read-only bit. PPSA99621 calls it through `std::filesystem::permissions`. Console behaviour is unverified +- [lchmod](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) throws for a symbolic link on Windows and Linux, whose hosts cannot change a link's own mode; on macOS the link's mode is changed. Anything else goes to `fchmodat(AT_FDCWD, path, mode, AT_SYMLINK_NOFOLLOW)`, as FreeBSD 11.4's [`sys_lchmod`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/vfs_syscalls.c#L2490-L2496) does, so a file or directory is changed as by `chmod`, with `fchmodat`'s path lookup and Windows mode mapping. On Windows any reparse point counts as a link. Console behaviour is unverified - [pathconf](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) looks the path up as FreeBSD 11's `kern_pathconf` does (links followed, `ENAMETOOLONG` for a path of 1024 bytes or more or a component over 255) and returns the constants of FreeBSD 11's UFS `ufs_pathconf` for files and directories alike: `_PC_LINK_MAX` 32767, `_PC_NAME_MAX` 255, `_PC_PATH_MAX` and `_PC_SYMLINK_MAX` 1024, `_PC_PIPE_BUF` 512, `_PC_CHOWN_RESTRICTED` and `_PC_NO_TRUNC` 1, `_PC_FILESIZEBITS` 64, `_PC_REC_MAX_XFER_SIZE` -1, `_PC_PRIO_IO` and `_PC_SYNC_IO` 0. Whether the console's filesystems report the same is not verified. It throws for the names whose UFS value depends on the mount (`_PC_ALLOC_SIZE_MIN`, `_PC_REC_INCR_XFER_SIZE`, `_PC_REC_MIN_XFER_SIZE`, `_PC_MIN_HOLE_SIZE`), the kernel options (`_PC_ACL_EXTENDED`, `_PC_ACL_PATH_MAX`, `_PC_ACL_NFS4`, `_PC_MAC_PRESENT`) or the page size (`_PC_REC_XFER_ALIGN`), and on which UFS and `vop_stdpathconf` disagree (`_PC_MAX_CANON`, `_PC_MAX_INPUT`, `_PC_VDISABLE`, `_PC_ASYNC_IO`); other names fail with `EINVAL` - [mkdirat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `mkdir`. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. On Windows, `mode` is ignored. Console behaviour is unverified - [readlink](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) on Windows reads a symbolic link's target from its reparse data and returns it with `/` separators, only when the target is relative, as `symlink` creates them. A symbolic link with an absolute target returns its print name, or its substitute name when the print name is empty, without a `\??\` or `\\?\` prefix. A directory junction returns the same kind of name when it is a drive path (`X:\...`), using the print name when that is a drive path and the substitute name otherwise. A mounted volume (`\??\Volume{GUID}\`), any other name surrogate and every reparse point without the name surrogate bit (WOF compression, deduplication, cloud files) return `EINVAL`, as FreeBSD does for a file that is not a link and as CPython's `os.readlink`, which reads only symbolic links and junctions, does diff --git a/tools/check_conventions.py b/tools/check_conventions.py index bc985e1480..675981e57b 100644 --- a/tools/check_conventions.py +++ b/tools/check_conventions.py @@ -171,6 +171,7 @@ class Check: core = path.startswith("core/") and "tests" not in PurePosixPath(path).parts source = self.show(path).split("\n") masked = cpp_text("\n".join(source), preserve_comments=True).split("\n") + assembly = asm_exports("\n".join(source)) if core and path.startswith("core/libs/prx/") else {} for number, text in lines: bare = masked[number - 1] if ("//" in bare or "/*" in bare) and not ALLOWED_COMMENT.match(bare): @@ -182,6 +183,7 @@ class Check: if core and path.startswith("core/libs/prx/"): unknowns.extend((nid, name, path, number) for nid, name in source_aliases(text) if "unknown" in name.lower()) symbols.extend((symbol, path, number) for symbol in nid_symbols(text)) + exports.update((name, (path, number)) for name in assembly.get(number, ()) if not name.endswith(("_nid_no_patch", "_nid_no_patch_cut"))) match = EXPORT.search(bare) if match and core and path.startswith("core/libs/prx/") and not match.group(1).endswith(("_nid_no_patch", "_nid_no_patch_cut")): source = source or self.show(path).splitlines() @@ -212,17 +214,18 @@ class Check: self.read(paths) for path in paths: text = self.show(path) - if "APS5_VABI" not in text: + if "APS5_VABI" not in text and "ASM_FUNCTION" not in text: continue source = cpp_text(text).split("\n") + assembly = asm_exports(text) for number, line in enumerate(source): - for match in EXPORT.finditer(line): - name = match.group(1) - if name not in exports: - continue + names = [match.group(1) for match in EXPORT.finditer(line) + if match.group(1) in exports and library(path) != library(exports[match.group(1)][0]) + and function_body(source, number) is not None] + names += [name for name in assembly.get(number + 1, ()) if name in exports and library(path) != library(exports[name][0])] + for name in names: own, own_number = exports[name] - if library(path) != library(own) and function_body(source, number) is not None: - self.report("duplicate-export", own, own_number, f"{name} is also in {library(path)}") + self.report("duplicate-export", own, own_number, f"{name} is also in {library(path)}") def unknown_nids(self, unknowns, symbols): if not unknowns and not symbols: @@ -517,6 +520,16 @@ def symbol_nid(name): return compute_nid(base) if base else None +def asm_exports(text): + tokens = [match for match in CPP_TOKEN.finditer(text) if match.group("comment") is None] + exports = defaultdict(list) + for index, match in enumerate(tokens[:-2]): + name = tokens[index + 2].group()[1:-1] + if match.group() in ("APS5_ASM_FUNCTION", "APS5_SIGNAL_ASM_FUNCTION") and tokens[index + 1].group() == "(" and tokens[index + 2].group("string") is not None and IDENTIFIER.fullmatch(name): + exports[text.count("\n", 0, tokens[index + 2].start()) + 1].append(name) + return exports + + def nid_symbols(text): tokens = [match for match in CPP_TOKEN.finditer(text) if match.group("comment") is None] names = [] diff --git a/tools/hw-oracle/hw_oracle.py b/tools/hw-oracle/hw_oracle.py index ffac30f5c7..f1dc2da856 100644 --- a/tools/hw-oracle/hw_oracle.py +++ b/tools/hw-oracle/hw_oracle.py @@ -1,5 +1,6 @@ import argparse import hashlib +import importlib.util import json import os import operator @@ -17,6 +18,22 @@ CACHE = Path(os.environ.get("HW_ORACLE_CACHE") or Path(os.environ.get("XDG_CACHE ROWS_PER_DISPATCH = 1024 DEFAULT_LDS = 4096 MAX_LDS = 65536 +RUNTIMES = ("hsa", "hip") + + +def runtime(): + selected = os.environ.get("HW_ORACLE_RUNTIME") or ("hip" if os.name == "nt" else "hsa") + if selected not in RUNTIMES: + raise ValueError(f"HW_ORACLE_RUNTIME must be one of {', '.join(RUNTIMES)}") + return selected + + +@cache +def hip_runtime(): + spec = importlib.util.spec_from_file_location("oracle_hip", HERE / "oracle_hip.py") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module def rocm_root(): @@ -78,7 +95,11 @@ def detected_target(binary, visible_devices): def target(): - return os.environ.get("HW_ORACLE_TARGET") or detected_target(oracle(), os.environ.get("ROCR_VISIBLE_DEVICES")) + if os.environ.get("HW_ORACLE_TARGET"): + return os.environ["HW_ORACLE_TARGET"] + if runtime() == "hip": + return hip_runtime().target() + return detected_target(oracle(), os.environ.get("ROCR_VISIBLE_DEVICES")) def assemble(body, work, wave64, *, ieee, denorm32, denorm16, dx10_clamp, round32, round16, fp16_overflow, lds=DEFAULT_LDS): @@ -97,6 +118,9 @@ def assemble(body, work, wave64, *, ieee, denorm32, denorm16, dx10_clamp, round3 ("@FP16_OVERFLOW@", str(int(fp16_overflow))), ("@LDS@", str(lds)), ("@BODY@", body)): text = text.replace(key, value) (work / "k.s").write_text(text) + if runtime() == "hip": + (work / "k.co").write_bytes(hip_runtime().assemble(text.encode("utf-8"), target(), wave64)) + return work / "k.co" subprocess.run([tool("clang"), "-x", "assembler", "-target", "amdgcn-amd-amdhsa", f"-mcpu={target()}", *(["-mwavefrontsize64"] if wave64 else []), "-c", str(work / "k.s"), "-o", str(work / "k.o")], check=True, stdout=sys.stderr) subprocess.run([tool("ld.lld"), "-m", "elf64_amdgpu", "-shared", str(work / "k.o"), "-o", str(work / "k.co")], check=True, stdout=sys.stderr) return work / "k.co" @@ -131,10 +155,14 @@ def run(body, rows, extra=b"", wave64=False, coarse=False, *, ieee, denorm32, de rows_per_dispatch = ROWS_PER_DISPATCH // 2 if wave64 else ROWS_PER_DISPATCH for start in range(0, len(padded), rows_per_dispatch): chunk = padded[start:start + rows_per_dispatch] - (work / "in.bin").write_bytes(b"".join(struct.pack("<4I", *r) for r in chunk) + extra) - (work / "out.bin").unlink(missing_ok=True) - subprocess.run([oracle(), str(code), str(len(chunk)), str(work / "in.bin"), str(work / "out.bin"), "16"], check=True, env=env, stdout=sys.stderr) - data = (work / "out.bin").read_bytes() + payload = b"".join(struct.pack("<4I", *r) for r in chunk) + extra + if runtime() == "hip": + data = hip_runtime().dispatch(Path(code).read_bytes(), len(chunk), payload, 16, coarse) + else: + (work / "in.bin").write_bytes(payload) + (work / "out.bin").unlink(missing_ok=True) + subprocess.run([oracle(), str(code), str(len(chunk)), str(work / "in.bin"), str(work / "out.bin"), "16"], check=True, env=env, stdout=sys.stderr) + data = (work / "out.bin").read_bytes() expected = len(chunk) * 64 if len(data) != expected: raise ValueError(f"Oracle output size: expected {expected} bytes, got {len(data)}") diff --git a/tools/hw-oracle/oracle_hip.py b/tools/hw-oracle/oracle_hip.py new file mode 100644 index 0000000000..596b82a8f7 --- /dev/null +++ b/tools/hw-oracle/oracle_hip.py @@ -0,0 +1,235 @@ +import argparse +import ctypes +import os +import sys +from functools import cache +from pathlib import Path + +HIP_LIBRARIES = ("amdhip64_7.dll", "amdhip64_6.dll", "amdhip64.dll") +COMGR_LIBRARIES = ("amd_comgr_3.dll", "amd_comgr_2.dll", "amd_comgr.dll") +DATA_SOURCE = 0x1 +DATA_LOG = 0x5 +DATA_RELOCATABLE = 0x7 +DATA_EXECUTABLE = 0x8 +ASSEMBLE_ACTIONS = (0x8, 0xA) +LINK_ACTIONS = (0x7, 0x9) +HIP_MEMCPY_HOST_TO_DEVICE = 1 +HIP_MEMCPY_DEVICE_TO_HOST = 2 +DEVICE_PROPERTIES_SIZE = 8192 +DEVICE_NAME_SIZE = 256 + + +class OracleError(Exception): + pass + + +class Handle(ctypes.Structure): + _fields_ = [("handle", ctypes.c_uint64)] + + +def check(status, what): + if status != 0: + raise OracleError(f"{what} failed with status {status}") + + +def load(variable, names): + candidates = (os.environ[variable],) if os.environ.get(variable) else names + errors = [] + for name in candidates: + try: + return ctypes.WinDLL(name) + except OSError as error: + errors.append(f"{name}: {error}") + raise OracleError(f"no library found ({variable} overrides it): " + "; ".join(errors)) + + +@cache +def hip(): + if os.environ.get("ROCR_VISIBLE_DEVICES") is not None and "HIP_VISIBLE_DEVICES" not in os.environ: + os.environ["HIP_VISIBLE_DEVICES"] = os.environ["ROCR_VISIBLE_DEVICES"] + library = load("HW_ORACLE_HIP", HIP_LIBRARIES) + library.hipModuleLoadData.argtypes = [ctypes.POINTER(ctypes.c_void_p), ctypes.c_char_p] + library.hipModuleGetFunction.argtypes = [ctypes.POINTER(ctypes.c_void_p), ctypes.c_void_p, ctypes.c_char_p] + library.hipMemcpy.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t, ctypes.c_int] + library.hipMemset.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_size_t] + library.hipModuleLaunchKernel.argtypes = [ctypes.c_void_p, ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, + ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] + library.hipModuleUnload.argtypes = [ctypes.c_void_p] + library.hipFree.argtypes = [ctypes.c_void_p] + library.hipHostFree.argtypes = [ctypes.c_void_p] + check(library.hipInit(0), "hipInit") + count = ctypes.c_int() + check(library.hipGetDeviceCount(ctypes.byref(count)), "hipGetDeviceCount") + if count.value == 0: + raise OracleError("no HIP device") + return library + + +@cache +def comgr(): + library = load("HW_ORACLE_COMGR", COMGR_LIBRARIES) + library.amd_comgr_create_data.argtypes = [ctypes.c_int, ctypes.POINTER(Handle)] + library.amd_comgr_create_data_set.argtypes = [ctypes.POINTER(Handle)] + library.amd_comgr_create_action_info.argtypes = [ctypes.POINTER(Handle)] + library.amd_comgr_set_data.argtypes = [Handle, ctypes.c_size_t, ctypes.c_char_p] + library.amd_comgr_set_data_name.argtypes = [Handle, ctypes.c_char_p] + library.amd_comgr_data_set_add.argtypes = [Handle, Handle] + library.amd_comgr_action_info_set_isa_name.argtypes = [Handle, ctypes.c_char_p] + library.amd_comgr_action_info_set_option_list.argtypes = [Handle, ctypes.POINTER(ctypes.c_char_p), ctypes.c_size_t] + library.amd_comgr_action_info_set_logging.argtypes = [Handle, ctypes.c_bool] + library.amd_comgr_do_action.argtypes = [ctypes.c_int, Handle, Handle, Handle] + library.amd_comgr_action_data_count.argtypes = [Handle, ctypes.c_int, ctypes.POINTER(ctypes.c_size_t)] + library.amd_comgr_action_data_get_data.argtypes = [Handle, ctypes.c_int, ctypes.c_size_t, ctypes.POINTER(Handle)] + library.amd_comgr_get_data.argtypes = [Handle, ctypes.POINTER(ctypes.c_size_t), ctypes.c_char_p] + library.amd_comgr_release_data.argtypes = [Handle] + library.amd_comgr_destroy_data_set.argtypes = [Handle] + library.amd_comgr_destroy_action_info.argtypes = [Handle] + return library + + +@cache +def target(): + properties = ctypes.create_string_buffer(DEVICE_PROPERTIES_SIZE) + library = hip() + getter = getattr(library, "hipGetDevicePropertiesR0600", None) or library.hipGetDeviceProperties + check(getter(properties, 0), "hipGetDeviceProperties") + start = properties.raw.find(b"gfx", DEVICE_NAME_SIZE) + if start < 0: + raise OracleError("the HIP device properties name no gfx target") + return properties.raw[start:properties.raw.index(b"\0", start)].decode("ascii").split(":")[0] + + +def data_bytes(data_set, kind): + library = comgr() + count = ctypes.c_size_t() + check(library.amd_comgr_action_data_count(data_set, kind, ctypes.byref(count)), "amd_comgr_action_data_count") + chunks = [] + for index in range(count.value): + data = Handle() + check(library.amd_comgr_action_data_get_data(data_set, kind, index, ctypes.byref(data)), "amd_comgr_action_data_get_data") + size = ctypes.c_size_t() + check(library.amd_comgr_get_data(data, ctypes.byref(size), None), "amd_comgr_get_data") + buffer = ctypes.create_string_buffer(size.value) + check(library.amd_comgr_get_data(data, ctypes.byref(size), buffer), "amd_comgr_get_data") + chunks.append(buffer.raw[:size.value]) + library.amd_comgr_release_data(data) + return chunks + + +def data_set(kind, name, payload): + library = comgr() + data = Handle() + check(library.amd_comgr_create_data(kind, ctypes.byref(data)), "amd_comgr_create_data") + check(library.amd_comgr_set_data(data, len(payload), payload), "amd_comgr_set_data") + check(library.amd_comgr_set_data_name(data, name), "amd_comgr_set_data_name") + result = Handle() + check(library.amd_comgr_create_data_set(ctypes.byref(result)), "amd_comgr_create_data_set") + check(library.amd_comgr_data_set_add(result, data), "amd_comgr_data_set_add") + library.amd_comgr_release_data(data) + return result + + +def comgr_step(actions, isa, options, inputs, kind): + library = comgr() + logs = [] + for action in actions: + info = Handle() + check(library.amd_comgr_create_action_info(ctypes.byref(info)), "amd_comgr_create_action_info") + outputs = Handle() + try: + check(library.amd_comgr_action_info_set_isa_name(info, isa.encode("ascii")), "amd_comgr_action_info_set_isa_name") + check(library.amd_comgr_action_info_set_logging(info, True), "amd_comgr_action_info_set_logging") + if options: + check(library.amd_comgr_action_info_set_option_list(info, (ctypes.c_char_p * len(options))(*options), len(options)), + "amd_comgr_action_info_set_option_list") + check(library.amd_comgr_create_data_set(ctypes.byref(outputs)), "amd_comgr_create_data_set") + status = library.amd_comgr_do_action(action, info, inputs, outputs) + result = data_bytes(outputs, kind) + log = b"".join(data_bytes(outputs, DATA_LOG)).decode("utf-8", errors="replace").strip() + library.amd_comgr_destroy_data_set(outputs) + finally: + library.amd_comgr_destroy_action_info(info) + if status == 0 and len(result) == 1: + return result[0] + logs.append(f"action {action:#x} status {status}" + (f": {log}" if log else "")) + raise OracleError("comgr produced no output\n" + "\n".join(logs)) + + +def assemble(source, isa_target, wave64): + isa = f"amdgcn-amd-amdhsa--{isa_target}" + inputs = data_set(DATA_SOURCE, b"k.s", source) + try: + relocatable = comgr_step(ASSEMBLE_ACTIONS, isa, [b"-mwavefrontsize64"] if wave64 else [], inputs, DATA_RELOCATABLE) + finally: + comgr().amd_comgr_destroy_data_set(inputs) + objects = data_set(DATA_RELOCATABLE, b"k.o", relocatable) + try: + return comgr_step(LINK_ACTIONS, isa, [], objects, DATA_EXECUTABLE) + finally: + comgr().amd_comgr_destroy_data_set(objects) + + +def dispatch(code, threads, payload, out_dwords, coarse): + library = hip() + module = ctypes.c_void_p() + check(library.hipModuleLoadData(ctypes.byref(module), code), "hipModuleLoadData") + try: + function = ctypes.c_void_p() + check(library.hipModuleGetFunction(ctypes.byref(function), module, b"probe"), "hipModuleGetFunction") + out_size = threads * out_dwords * 4 + buffers = [] + try: + for size in (len(payload), out_size): + pointer = ctypes.c_void_p() + if coarse: + check(library.hipMalloc(ctypes.byref(pointer), ctypes.c_size_t(size)), "hipMalloc") + else: + check(library.hipHostMalloc(ctypes.byref(pointer), ctypes.c_size_t(size), 0), "hipHostMalloc") + buffers.append(pointer) + device_in, device_out = buffers + check(library.hipMemcpy(device_in, payload, len(payload), HIP_MEMCPY_HOST_TO_DEVICE), "hipMemcpy") + check(library.hipMemset(device_out, 0xee, out_size), "hipMemset") + arguments = (ctypes.c_void_p * 2)(ctypes.addressof(device_in), ctypes.addressof(device_out)) + check(library.hipModuleLaunchKernel(function, 1, 1, 1, min(threads, 1024), 1, 1, 0, None, arguments, None), "hipModuleLaunchKernel") + check(library.hipDeviceSynchronize(), "hipDeviceSynchronize") + output = ctypes.create_string_buffer(out_size) + check(library.hipMemcpy(output, device_out, out_size, HIP_MEMCPY_DEVICE_TO_HOST), "hipMemcpy") + return output.raw + finally: + for pointer in buffers: + (library.hipFree if coarse else library.hipHostFree)(pointer) + finally: + library.hipModuleUnload(module) + + +def main(argv=None): + parser = argparse.ArgumentParser(description="Assemble and run hw-oracle kernels through the HIP runtime and comgr of AMD's Windows driver.") + parser.add_argument("--target", action="store_true", help="print the gfx target of the HIP device") + parser.add_argument("--assemble", nargs=3, metavar=("SOURCE", "OUTPUT", "TARGET"), help="assemble and link SOURCE for TARGET into OUTPUT") + parser.add_argument("--wave64", action="store_true", help="assemble for wave64") + parser.add_argument("dispatch", nargs="*", metavar="CODE THREADS IN OUT OUT_DWORDS", help="run CODE as oracle does") + args = parser.parse_args(argv) + try: + if args.target and not args.assemble and not args.dispatch: + print(target()) + return 0 + if args.assemble and not args.target and not args.dispatch: + source, output, isa_target = args.assemble + Path(output).write_bytes(assemble(Path(source).read_bytes(), isa_target, args.wave64)) + return 0 + if len(args.dispatch) != 5 or args.target or args.assemble or args.wave64: + parser.error("give --target, --assemble SOURCE OUTPUT TARGET, or CODE THREADS IN OUT OUT_DWORDS") + code, threads, inputs, output, out_dwords = args.dispatch + threads, out_dwords = int(threads), int(out_dwords) + if threads not in range(1, 1025) or out_dwords not in range(1, 17): + parser.error("THREADS must be 1 to 1024 and OUT_DWORDS 1 to 16") + result = dispatch(Path(code).read_bytes(), threads, Path(inputs).read_bytes(), out_dwords, os.environ.get("COARSE") is not None) + Path(output).write_bytes(result) + return 0 + except (OSError, OracleError) as error: + print(error, file=sys.stderr) + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/hw-oracle/test_hw_oracle.py b/tools/hw-oracle/test_hw_oracle.py index 58f66cf38c..e234145299 100644 --- a/tools/hw-oracle/test_hw_oracle.py +++ b/tools/hw-oracle/test_hw_oracle.py @@ -10,6 +10,7 @@ import sys import tempfile import unittest from pathlib import Path +import unittest.mock from unittest.mock import patch import hw_oracle @@ -18,6 +19,10 @@ import hw_oracle MODES = dict(ieee=0, denorm32=0, denorm16=3, dx10_clamp=1, round32=0, round16=0, fp16_overflow=0) +def setUpModule(): + os.environ["HW_ORACLE_RUNTIME"] = "hsa" + + class OracleCacheTests(unittest.TestCase): def test_failed_build_is_not_cached_and_can_be_retried(self): for previous in (None, b"old executable"): @@ -642,5 +647,136 @@ class AssemblyTests(unittest.TestCase): self.assertTrue(code.is_file()) +class HipRuntimeTests(unittest.TestCase): + def test_runtime_defaults_to_hip_on_windows_only(self): + environment = {key: value for key, value in os.environ.items() if key != "HW_ORACLE_RUNTIME"} + with patch.dict(os.environ, environment, clear=True): + self.assertEqual(hw_oracle.runtime(), "hip" if os.name == "nt" else "hsa") + for selected in hw_oracle.RUNTIMES: + with patch.dict(os.environ, {"HW_ORACLE_RUNTIME": selected}): + self.assertEqual(hw_oracle.runtime(), selected) + with patch.dict(os.environ, {"HW_ORACLE_RUNTIME": "rocm"}), self.assertRaises(ValueError): + hw_oracle.runtime() + + def test_hip_run_assembles_and_dispatches_in_process(self): + calls = [] + + class Runtime: + @staticmethod + def target(): + calls.append(("target",)) + return "gfx1010" + + @staticmethod + def assemble(source, isa_target, wave64): + calls.append(("assemble", isa_target, wave64, b"v_mov_b32 v10, v4" in source)) + return b"code" + + @staticmethod + def dispatch(code, threads, payload, out_dwords, coarse): + calls.append(("dispatch", code, threads, len(payload), out_dwords, coarse)) + return bytes(threads * out_dwords * 4) + + environment = {key: value for key, value in os.environ.items() if key != "HW_ORACLE_TARGET"} + for wave64, coarse in ((False, False), (True, True)): + calls.clear() + with self.subTest(wave64=wave64), patch.dict(os.environ, {**environment, "HW_ORACLE_RUNTIME": "hip"}, clear=True), patch.object(hw_oracle, "hip_runtime", return_value=Runtime), patch.object(hw_oracle.subprocess, "run", side_effect=AssertionError("subprocess called")), patch.object(hw_oracle, "oracle", side_effect=AssertionError("oracle built")): + results = hw_oracle.run(" v_mov_b32 v10, v4", [(1, 2, 3, 4)], b"xy", wave64, coarse, **MODES) + wave = 64 if wave64 else 32 + self.assertEqual(results, [(0,) * 16]) + self.assertIn(("target",), calls) + self.assertEqual([call for call in calls if call != ("target",)], + [("assemble", "gfx1010", wave64, True), ("dispatch", b"code", wave, wave * 16 + 2, 16, coarse)]) + + def test_hip_output_must_match_the_padded_dispatch_size(self): + class Runtime: + @staticmethod + def assemble(source, isa_target, wave64): + return b"code" + + @staticmethod + def dispatch(code, threads, payload, out_dwords, coarse): + return bytes(threads * out_dwords * 4 - 4) + + with patch.dict(os.environ, {"HW_ORACLE_RUNTIME": "hip", "HW_ORACLE_TARGET": "gfx1010"}), patch.object(hw_oracle, "hip_runtime", return_value=Runtime), self.assertRaises(ValueError): + hw_oracle.run(" s_nop 0", [(0, 0, 0, 0)], **MODES) + + def test_hip_runtime_loads_the_script_next_to_the_tool(self): + self.assertEqual(Path(hw_oracle.hip_runtime().__file__), hw_oracle.HERE / "oracle_hip.py") + + +class HipOracleTests(unittest.TestCase): + def setUp(self): + import oracle_hip + self.oracle_hip = oracle_hip + + def test_cli_rejects_mixed_or_incomplete_requests(self): + for argv in ([], ["--target", "code", "32", "in", "out", "16"], ["--wave64", "code", "32", "in", "out", "16"], + ["code", "32", "in", "out"], ["code", "0", "in", "out", "16"], ["code", "1025", "in", "out", "16"], + ["code", "32", "in", "out", "17"]): + with self.subTest(argv=argv), contextlib.redirect_stderr(io.StringIO()), self.assertRaises(SystemExit) as exit: + self.oracle_hip.main(argv) + self.assertEqual(exit.exception.code, 2) + + def test_comgr_step_tries_each_action_until_one_produces_output(self): + library = unittest.mock.MagicMock() + for name in ("amd_comgr_create_action_info", "amd_comgr_create_data_set", "amd_comgr_action_info_set_isa_name", + "amd_comgr_action_info_set_logging", "amd_comgr_action_info_set_option_list"): + getattr(library, name).return_value = 0 + outputs = {0x8: [], 0xA: [b"object"]} + actions = [] + library.amd_comgr_do_action.side_effect = lambda action, *args: actions.append(action) or (0 if action == 0xA else 1) + + def data_bytes(data_set, kind): + return [b"log"] if kind == self.oracle_hip.DATA_LOG else outputs[actions[-1]] + + isa = "amdgcn-amd-amdhsa--gfx1010" + with patch.object(self.oracle_hip, "comgr", return_value=library), patch.object(self.oracle_hip, "data_bytes", side_effect=data_bytes): + self.assertEqual(self.oracle_hip.comgr_step((0x8, 0xA), isa, [b"-mwavefrontsize64"], self.oracle_hip.Handle(), + self.oracle_hip.DATA_RELOCATABLE), b"object") + self.assertEqual(actions, [0x8, 0xA]) + outputs[0xA] = [] + with self.assertRaises(self.oracle_hip.OracleError) as error: + self.oracle_hip.comgr_step((0x8, 0xA), isa, [], self.oracle_hip.Handle(), self.oracle_hip.DATA_RELOCATABLE) + self.assertIn("action 0x8 status 1: log", str(error.exception)) + self.assertIn("action 0xa status 0: log", str(error.exception)) + + def test_device_selection_follows_rocr_visible_devices(self): + library = unittest.mock.MagicMock() + library.hipInit.return_value = 0 + + def count(pointer): + pointer._obj.value = 1 + return 0 + + library.hipGetDeviceCount.side_effect = count + base = {key: value for key, value in os.environ.items() if key not in ("ROCR_VISIBLE_DEVICES", "HIP_VISIBLE_DEVICES")} + for environment, expected in (({"ROCR_VISIBLE_DEVICES": "1"}, "1"), ({"ROCR_VISIBLE_DEVICES": "1", "HIP_VISIBLE_DEVICES": "0"}, "0")): + self.oracle_hip.hip.cache_clear() + with self.subTest(environment=environment), patch.dict(os.environ, {**base, **environment}, clear=True), \ + patch.object(self.oracle_hip, "load", return_value=library): + self.oracle_hip.hip() + self.assertEqual(os.environ["HIP_VISIBLE_DEVICES"], expected) + self.oracle_hip.hip.cache_clear() + + def test_assembles_both_wave_sizes_with_the_driver_comgr(self): + if os.name != "nt": + self.skipTest("comgr from AMD's Windows driver") + try: + self.oracle_hip.comgr() + except self.oracle_hip.OracleError as error: + self.skipTest(str(error)) + template = (hw_oracle.HERE / "template.s").read_text() + for wave64, mask in ((False, "s8"), (True, "s[8:9]")): + text = template + for key, value in (("@TARGET@", "gfx1010"), ("@WAVE32@", "0" if wave64 else "1"), ("@WAVESIZE@", "64" if wave64 else "32"), + ("@DENORM@", "0"), ("@DENORM16@", "3"), ("@IEEE@", "0"), ("@DX10_CLAMP@", "1"), ("@ROUND32@", "0"), + ("@ROUND16@", "0"), ("@FP16_OVERFLOW@", "0"), ("@LDS@", "4096"), + ("@BODY@", f" v_cmp_lt_f32 {mask}, v4, v5\n v_cndmask_b32 v10, v6, v7, {mask}\n")): + text = text.replace(key, value) + with self.subTest(wave64=wave64): + self.assertEqual(self.oracle_hip.assemble(text.encode(), "gfx1010", wave64)[:4], b"\x7fELF") + + if __name__ == "__main__": unittest.main() diff --git a/tools/nid_names.py b/tools/nid_names.py index 1818f593c9..86610f409c 100644 --- a/tools/nid_names.py +++ b/tools/nid_names.py @@ -184,7 +184,7 @@ def main(argv=None): parser = argparse.ArgumentParser( description="Suggest real names for Unknown NID stubs") parser.add_argument("--db", default=os.environ.get("ANYPS5_NID_DB"), - help="NID database CSV (downloaded to a cache dir by default)") + help="NID database CSV; defaults to $ANYPS5_NID_DB, then to a copy downloaded to a cache dir") parser.add_argument("--nid", action="append", default=None, help="resolve only these NIDs instead of scanning the tree") parser.add_argument("--name", "--symbol", dest="name", action="append", default=None, diff --git a/tools/package_release.py b/tools/package_release.py index 4e0de7b406..c32336f6d8 100644 --- a/tools/package_release.py +++ b/tools/package_release.py @@ -56,6 +56,9 @@ def package(platform, build, output, version): licenses = [] if "libSceAjm.native.prx" in expected: licenses.append((ROOT / "3rdparty/vgmstream/COPYING", "libs/licenses/vgmstream.txt")) + if "libSceVorbisDec.prx" in expected: + licenses.append((ROOT / "3rdparty/ogg/COPYING", "libs/licenses/ogg.txt")) + licenses.append((ROOT / "3rdparty/vorbis/COPYING", "libs/licenses/vorbis.txt")) if expected & {"libSceOpusDec.prx", "libSceOpusCeltDec.prx"}: licenses.append((ROOT / "3rdparty/opus/COPYING", "libs/licenses/opus.txt")) if platform == "windows": diff --git a/tools/pr_overlap.py b/tools/pr_overlap.py index 6699df9ebf..29008a4d9d 100644 --- a/tools/pr_overlap.py +++ b/tools/pr_overlap.py @@ -3,11 +3,14 @@ import json import os import re import subprocess +import sys from concurrent.futures import ThreadPoolExecutor from itertools import combinations from pathlib import Path from urllib.parse import quote, urlencode +from nid_names import compute_nid, source_aliases + PARENT = 'if .parent then "\\(.parent.owner.login)/\\(.parent.name)" else .nameWithOwner end' EXPORT = re.compile(r"^\+.*\bAPS5_VABI\s+(\w+)\s*\(") NID_DISAMBIG = re.compile(r"_nid_disambig[0-9]+$") @@ -121,6 +124,21 @@ def exports(diff): return found +def aliases(diff): + added = "\n".join(line[1:] for line in diff.splitlines() if line.startswith("+") and not line.startswith("+++")) + return {nid for nid, _ in source_aliases(added)} + + +def export_ids(pr): + return {compute_nid(name) for name in pr["exports"]} | pr.get("aliases", set()) + + +def same_exports(pa, pb, shared): + common = (export_ids(pa) & export_ids(pb)).difference(*(export_ids(pr) for pr in shared)) + named = {name for name in pa["exports"] | pb["exports"] if compute_nid(name) in common} + return named | common.difference(compute_nid(name) for name in named) + + def scan(pr): tree, conflicted = merge("refs/pr/base", pr["ref"]) pr["tree"] = None if conflicted else commit(tree) @@ -133,6 +151,7 @@ def scan(pr): pr["added"].add(path) diff = git("diff", "-U0", "--no-color", old, new, "--", "core/libs/prx/*.cpp") pr["exports"] = exports(diff) + pr["aliases"] = aliases(diff) def scan_all(prs, jobs): @@ -187,7 +206,7 @@ def overlaps(prs, a, b): found = {"added": (pa["added"] & pb["added"]).difference(*(pr["added"] for pr in shared))} if pa["author"] != pb["author"]: found["conflicts"] = conflicts(pa, pb) - found["exports"] = (pa["exports"] & pb["exports"]).difference(*(pr["exports"] for pr in shared)) + found["exports"] = same_exports(pa, pb, shared) if not (pa["tree"] and pb["tree"]): uncompared = pa.get("files", set()) & pb.get("files", set()) if uncompared: @@ -252,19 +271,27 @@ def describe_failure(error): return f"pr_overlap: {command} failed with exit code {error.returncode}: {message}" -if __name__ == "__main__": +def main(argv=None): + if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(encoding="utf-8", errors="backslashreplace") + if hasattr(sys.stderr, "reconfigure"): + sys.stderr.reconfigure(encoding="utf-8", errors="backslashreplace") parser = argparse.ArgumentParser(description="report open pull requests that overlap each other") parser.add_argument("--base", default="main") parser.add_argument("--out", type=Path, help="write .md for every open pull request (empty when clean)") parser.add_argument("--branch", help="check a local branch against the open pull requests instead") parser.add_argument("--repo", help="owner/name; defaults to the upstream of the current checkout") parser.add_argument("--jobs", type=int, default=4, help="number of pull request comparisons to run at once") - args = parser.parse_args() + args = parser.parse_args(argv) if bool(args.out) == bool(args.branch): parser.error("pass exactly one of --out or --branch") if args.jobs < 1: parser.error("--jobs must be at least 1") + global REPO + if args.out: + args.out = args.out.resolve() try: + os.chdir(git("rev-parse", "--show-toplevel").strip()) REPO = args.repo or upstream() prs = open_prs(args.base) fetch(args.base, prs) @@ -283,5 +310,10 @@ if __name__ == "__main__": raise SystemExit(describe_failure(error)) if args.branch: print(report(rows[0], blob) or "No overlap with open pull requests.", end="") - raise SystemExit + return 0 write_reports(args.out, rows, blob) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/tests/test_check_conventions_comments.py b/tools/tests/test_check_conventions_comments.py index 95aa949321..6bc843e5d2 100644 --- a/tools/tests/test_check_conventions_comments.py +++ b/tools/tests/test_check_conventions_comments.py @@ -399,7 +399,7 @@ class SilentStubSectionTests(unittest.TestCase): class TestFixtureExportTests(unittest.TestCase): - def check(self, path, text=None): + def check(self, path, text=None, base=None): files = path if text is None else {path: text} checker = Path(__file__).resolve().parents[1] / "check_conventions.py" with tempfile.TemporaryDirectory() as directory: @@ -420,6 +420,8 @@ class TestFixtureExportTests(unittest.TestCase): git("init", "-q") write("core/libs/prx/libkernel/src/Time.cpp", "std::uint64_t APS5_VABI sceKernelGetProcessTime() { return now(); }\n") write("core/libs/prx/libSceNgs2/tests/Render.cpp", "static int APS5_VABI Process(int value) { return value; }\n") + for relative, content in (base or {}).items(): + write(relative, content) git("add", ".") git("commit", "-qm", "feat: add libraries") for relative, content in files.items(): @@ -451,6 +453,63 @@ class TestFixtureExportTests(unittest.TestCase): self.assertIn("core/libs/prx/libSceAudioOut/src/Clock.cpp:1: error: [duplicate-export]", result.stdout) self.assertIn("sceKernelGetProcessTime is also in libkernel", result.stdout) + def test_large_export_set_uses_a_file_backed_pattern(self): + names = [f"ConventionExport{index:08d}LongName" for index in range(1100)] + self.assertGreater(len("|".join(names)), 32767) + source = "\n".join(f"int APS5_VABI {name}(void) {{ return 0; }}" for name in names) + source += "\nstd::uint64_t APS5_VABI sceKernelGetProcessTime() { return now(); }\n" + result = self.check("core/libs/prx/libSceAudioOut/src/Exports.cpp", source) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("core/libs/prx/libSceAudioOut/src/Exports.cpp:1101: error: [duplicate-export]", result.stdout) + self.assertIn("sceKernelGetProcessTime is also in libkernel", result.stdout) + + def test_export_shadowing_an_assembly_export_is_a_duplicate(self): + result = self.check("core/libs/prx/libSceLibcInternal/Export.cpp", + "int APS5_VABI setjmp_nid_postfix(void) {\n NotImplemented_nid_no_patch(__func__);\n return 0;\n}\n", + base={"core/libs/prx/libc/src/JumpBuffer.cpp": 'asm(".text\\n"\n APS5_ASM_FUNCTION("setjmp_nid_postfix")\n " ret\\n");\n'}) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("core/libs/prx/libSceLibcInternal/Export.cpp:1: error: [duplicate-export]", result.stdout) + self.assertIn("setjmp_nid_postfix is also in libc", result.stdout) + + def test_export_shadowing_a_spaced_or_split_assembly_export_is_a_duplicate(self): + for macro, call in (("APS5_ASM_FUNCTION", ' ("setjmp_nid_postfix")'), + ("APS5_ASM_FUNCTION", '(\n "setjmp_nid_postfix")'), + ("APS5_SIGNAL_ASM_FUNCTION", ' (\n "setjmp_nid_postfix")')): + with self.subTest(macro=macro, call=call): + result = self.check("core/libs/prx/libSceLibcInternal/Export.cpp", + "int APS5_VABI setjmp_nid_postfix(void) {\n NotImplemented_nid_no_patch(__func__);\n return 0;\n}\n", + base={"core/libs/prx/libc/src/JumpBuffer.cpp": 'asm(".text\\n"\n ' + macro + call + '\n " ret\\n");\n'}) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("core/libs/prx/libSceLibcInternal/Export.cpp:1: error: [duplicate-export]", result.stdout) + self.assertIn("setjmp_nid_postfix is also in libc", result.stdout) + + def test_assembly_export_in_another_library_is_a_duplicate(self): + result = self.check("core/libs/prx/libSceAudioOut/src/Clock.cpp", + 'asm(".text\\n"\n APS5_SIGNAL_ASM_FUNCTION("sceKernelGetProcessTime")\n " ret\\n");\n') + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("core/libs/prx/libSceAudioOut/src/Clock.cpp:2: error: [duplicate-export]", result.stdout) + self.assertIn("sceKernelGetProcessTime is also in libkernel", result.stdout) + + def test_assembly_export_after_a_raw_string_is_a_duplicate(self): + result = self.check("core/libs/prx/libSceAudioOut/src/Jump.cpp", + 'asm(R"(\n .text\n)" APS5_ASM_FUNCTION("setjmp_nid_postfix") R"(\n ret\n)");\n', + base={"core/libs/prx/libc/src/JumpBuffer.cpp": 'asm(".text\\n"\n APS5_ASM_FUNCTION("setjmp_nid_postfix")\n " ret\\n");\n'}) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("core/libs/prx/libSceAudioOut/src/Jump.cpp:3: error: [duplicate-export]", result.stdout) + self.assertIn("setjmp_nid_postfix is also in libc", result.stdout) + + def test_assembly_export_in_a_block_comment_is_not_an_export(self): + result = self.check("core/libs/prx/libSceLibcInternal/Export.cpp", + "int APS5_VABI setjmp_nid_postfix(void) {\n NotImplemented_nid_no_patch(__func__);\n return 0;\n}\n", + base={"core/libs/prx/libc/src/JumpBuffer.cpp": '/*\n APS5_ASM_FUNCTION("setjmp_nid_postfix")\n*/\n'}) + self.assertNotIn("duplicate-export", result.stdout, result.stdout + result.stderr) + + def test_assembly_name_that_is_not_an_identifier_keeps_the_check(self): + result = self.check("core/libs/prx/libSceAudioOut/src/Clock.cpp", + 'asm(".text\\n"\n APS5_ASM_FUNCTION("x(y")\n " ret\\n");\nstd::uint64_t APS5_VABI sceKernelGetProcessTime() {\n return 1;\n}\n') + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("sceKernelGetProcessTime is also in libkernel", result.stdout) + def test_comment_in_a_shader_is_reported(self): shaders = {} for path in ("core/libs/prx/libSceAgcDriver/Graphics/shaders/Fill.comp", "core/libs/prx/libSceAgcDriver/tests/shaders/Fill.frag", "core/libs/prx/libSceAgcDriver/Graphics/shaders/Fill.glsl"): diff --git a/tools/tests/test_nid_names.py b/tools/tests/test_nid_names.py index 8445352ecd..e317357de5 100644 --- a/tools/tests/test_nid_names.py +++ b/tools/tests/test_nid_names.py @@ -30,6 +30,25 @@ class DatabaseCacheTests(unittest.TestCase): else: self.assertEqual(result.stdout.split(), ["NAME", "NID"]) + def test_environment_variable_selects_the_database_and_help_names_it(self): + script = str(Path(nid_names.__file__)) + helped = subprocess.run([sys.executable, script, "--help"], capture_output=True, text=True, timeout=60) + self.assertEqual(helped.returncode, 0, helped.stderr) + self.assertIn("ANYPS5_NID_DB", " ".join(helped.stdout.split())) + with tempfile.TemporaryDirectory() as directory: + database = Path(directory) / "nid database.csv" + nid = nid_names.compute_nid("sceExample") + database.write_text(f"{nid} sceExample\n", encoding="utf-8") + scratch = str(Path(directory) / "scratch") + Path(scratch).mkdir() + environment = {**os.environ, "ANYPS5_NID_DB": str(database), "TEMP": scratch, "TMP": scratch, "TMPDIR": scratch} + result = subprocess.run([sys.executable, script, "--nid", nid, "--json"], capture_output=True, text=True, + env=environment, timeout=20) + self.assertEqual(list(Path(scratch).iterdir()), []) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertNotIn("downloading", result.stderr) + self.assertIn("sceExample", result.stdout) + def test_interrupted_download_does_not_publish_a_partial_cache(self): with tempfile.TemporaryDirectory() as directory: cache = Path(directory) / "cache" / "aerolib.csv" diff --git a/tools/tests/test_package_release.py b/tools/tests/test_package_release.py index 87c65025f7..fb0ec3cd62 100644 --- a/tools/tests/test_package_release.py +++ b/tools/tests/test_package_release.py @@ -61,6 +61,21 @@ class ReleasePackageTests(unittest.TestCase): with tarfile.open(self.output / "prx-linux-v1.tar.gz") as archive: self.assertEqual(archive.extractfile("libs/licenses/vgmstream.txt").read(), b"decoder license") + def test_vorbis_decoder_licenses_in_archives(self): + (self.source / "core/libs/prx/libSceVorbisDec").mkdir() + (self.build / "core/libs/libs/libSceVorbisDec.prx").write_bytes(b"vorbis") + for name in ("ogg", "vorbis"): + license_file = self.source / f"3rdparty/{name}/COPYING" + license_file.parent.mkdir(parents=True) + license_file.write_bytes(f"{name} license".encode()) + package_release.package("linux", self.build, self.output, "v1") + with zipfile.ZipFile(self.output / "prx-linux-v1.zip") as archive: + self.assertEqual(archive.read("libs/licenses/ogg.txt"), b"ogg license") + self.assertEqual(archive.read("libs/licenses/vorbis.txt"), b"vorbis license") + with tarfile.open(self.output / "prx-linux-v1.tar.gz") as archive: + self.assertEqual(archive.extractfile("libs/licenses/ogg.txt").read(), b"ogg license") + self.assertEqual(archive.extractfile("libs/licenses/vorbis.txt").read(), b"vorbis license") + def test_windows_runtime_follows_the_configured_compiler_directory(self): runtime = self.root / "custom toolchain" / "bin" runtime.mkdir(parents=True) diff --git a/tools/tests/test_pad_timeline.py b/tools/tests/test_pad_timeline.py index cc40a5b7d8..b7a91f3d96 100644 --- a/tools/tests/test_pad_timeline.py +++ b/tools/tests/test_pad_timeline.py @@ -74,6 +74,26 @@ class TimelineTests(unittest.TestCase): with self.assertRaises(ValueError): pad_timeline.compile_timeline(self.document, 1, repeat, gap) + def test_repeated_event_count_at_capacity(self): + rows = pad_timeline.compile_timeline(self.document, 42, 2048, 0).splitlines() + self.assertEqual(rows[0], "APS5_PAD_TIMELINE 1 42 4096") + self.assertEqual(len(rows) - 1, 4096) + self.assertEqual(rows[1:3], ["200 100 18432", "500 200 64"]) + self.assertEqual(rows[2047:2051], [ + "716300 100 18432", "716600 200 64", "717000 100 18432", "717300 200 64", + ]) + self.assertEqual(rows[-2:], ["1433100 100 18432", "1433400 200 64"]) + with self.assertRaisesRegex(ValueError, "1 to 4096 events after repetition"): + pad_timeline.compile_timeline(self.document, 42, 2049, 0) + + def test_repeated_duration_includes_inter_iteration_gaps_only(self): + document = {"version": 1, "events": [{"at_ms": 0, "hold_ms": 5000, "buttons": ["cross"]}]} + self.assertEqual( + pad_timeline.compile_timeline(document, 42, 2, 3590000), + "APS5_PAD_TIMELINE 1 42 2\n0 5000 16384\n3595000 5000 16384\n") + with self.assertRaisesRegex(ValueError, "repeated timeline exceeds one hour"): + pad_timeline.compile_timeline(document, 42, 2, 3590001) + def test_sequence_bounds(self): for sequence in [0, -1, True, 2**64]: with self.assertRaises(ValueError): diff --git a/tools/tests/test_pr_overlap.py b/tools/tests/test_pr_overlap.py index 520620c8a3..4017ab3718 100644 --- a/tools/tests/test_pr_overlap.py +++ b/tools/tests/test_pr_overlap.py @@ -312,6 +312,62 @@ class UnicodePathTests(unittest.TestCase): os.chdir(previous) +class SubdirectoryTests(unittest.TestCase): + @unittest.skipIf(os.name == "nt", "fake gh is a shell script") + def test_branch_report_from_a_subdirectory_matches_the_root(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory).resolve() / "repo" + fake = Path(directory) / "bin" + fake.mkdir() + + def git(*args): + return subprocess.run( + ["git", "-c", "user.name=Overlap Test", "-c", "user.email=test@example.com", + "-c", "commit.gpgsign=false", *args], cwd=root, + capture_output=True, text=True, encoding="utf-8", check=True).stdout.strip() + + filename = "core/libs/prx/libSceExample/Export.cpp" + source = root / filename + source.parent.mkdir(parents=True) + source.write_text("int value = 0;\n", encoding="utf-8") + (root / "tools").mkdir() + (root / "tools" / "tool.py").write_text("print()\n", encoding="utf-8") + git("init", "-q", "-b", "main") + git("add", ".") + git("commit", "-qm", "test: create base") + heads = {} + for branch, value in (("theirs", 1), ("mine", 2)): + git("checkout", "-qb", branch, "main") + source.write_text(f"int value = {value};\nint APS5_VABI sceExample() {{ return {value}; }}\n", encoding="utf-8") + git("add", ".") + git("commit", "-qm", "fix: change value") + heads[branch] = git("rev-parse", "HEAD") + pulls = [{"number": 7, "user": {"login": "someone"}, "head": {"sha": heads["theirs"]}, "body": "Depends on: none"}] + (fake / "pulls.json").write_text(json.dumps(pulls), encoding="utf-8") + (fake / "gh").write_text(f"#!/bin/sh\ncat '{fake / 'pulls.json'}'\n", encoding="utf-8") + (fake / "gh").chmod(0o755) + environment = {**os.environ, "PATH": f"{fake}{os.pathsep}{os.environ['PATH']}", "GIT_CONFIG_COUNT": "1", + "GIT_CONFIG_KEY_0": f"url.{root.as_uri()}.insteadOf", "GIT_CONFIG_VALUE_0": "https://github.com/owner/project.git"} + environment.pop("GITHUB_REPOSITORY", None) + script = Path(pr_overlap.__file__).resolve() + outputs = {} + for subdirectory in (".", "tools", "core/libs/prx"): + result = subprocess.run([sys.executable, str(script), "--repo", "owner/project", "--branch", "mine"], + cwd=root / subdirectory, env=environment, capture_output=True, text=True, + encoding="utf-8", timeout=60) + self.assertEqual(result.returncode, 0, result.stderr) + outputs[subdirectory] = result.stdout + self.assertIn(f"`{filename}` [L1](", outputs["."]) + self.assertIn("| `sceExample` |", outputs["."]) + self.assertEqual(outputs["tools"], outputs["."]) + self.assertEqual(outputs["core/libs/prx"], outputs["."]) + result = subprocess.run([sys.executable, str(script), "--repo", "owner/project", "--out", "reports"], + cwd=root / "tools", env=environment, capture_output=True, text=True, + encoding="utf-8", timeout=60) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertTrue((root / "tools" / "reports" / "7.md").is_file()) + + class CommentStateTests(unittest.TestCase): def setUp(self): if os.name == "nt" or not all(shutil.which(tool) for tool in ("bash", "jq", "sed", "sha256sum")): diff --git a/tools/tests/test_pr_overlap_errors.py b/tools/tests/test_pr_overlap_errors.py index c5bb5e0b8e..f85c7c3267 100644 --- a/tools/tests/test_pr_overlap_errors.py +++ b/tools/tests/test_pr_overlap_errors.py @@ -37,5 +37,33 @@ class ErrorReportTests(unittest.TestCase): self.assertIn(MISSING_REF, str(raised.exception.code)) + def test_main_reconfigures_streams(self): + class Stream: + def __init__(self): + self.encoding = None + self.errors = None + + def reconfigure(self, encoding=None, errors=None): + self.encoding = encoding + self.errors = errors + + def write(self, text): + pass + + def flush(self): + pass + + out, err = Stream(), Stream() + with patch.object(pr_overlap.sys, "stdout", out), patch.object(pr_overlap.sys, "stderr", err): + try: + pr_overlap.main([]) + except SystemExit: + pass + self.assertEqual(out.encoding, "utf-8") + self.assertEqual(out.errors, "backslashreplace") + self.assertEqual(err.encoding, "utf-8") + self.assertEqual(err.errors, "backslashreplace") + + if __name__ == "__main__": unittest.main() diff --git a/tools/tests/test_pr_overlap_exports.py b/tools/tests/test_pr_overlap_exports.py index d326656d8b..d109c06966 100644 --- a/tools/tests/test_pr_overlap_exports.py +++ b/tools/tests/test_pr_overlap_exports.py @@ -53,5 +53,23 @@ class ExportTests(unittest.TestCase): self.assertNotIn("Helper_nid_no_patch", found) + def test_explicit_export_aliases_are_collected(self): + diff = ('+int APS5_VABI placeholder_nid_no_patch() {\n' + '+ NotImplemented_nid_no_patch(__func__);\n' + '+}\n' + '+APS5_EXPORT("Pz262plj20w", placeholder_nid_no_patch);\n' + '-APS5_EXPORT("sKneJjUXWkQ", removed_nid_no_patch);\n') + self.assertEqual(pr_overlap.exports(diff), set()) + self.assertEqual(pr_overlap.aliases(diff), {"Pz262plj20w"}) + self.assertEqual(pr_overlap.aliases(diff.replace('+APS5_EXPORT("Pz262plj20w", placeholder_nid_no_patch);\n', "")), set()) + + def test_explicit_alias_overlaps_named_export_with_the_same_nid(self): + named = {"author": "a", "exports": {"FT_Outline_GetInsideBorder"}, "aliases": set()} + explicit = {"author": "b", "exports": set(), "aliases": {"Pz262plj20w"}} + self.assertEqual(pr_overlap.same_exports(named, explicit, []), {"FT_Outline_GetInsideBorder"}) + self.assertEqual(pr_overlap.same_exports(explicit, dict(explicit, author="c"), []), {"Pz262plj20w"}) + self.assertEqual(pr_overlap.same_exports(named, explicit, [explicit]), set()) + + if __name__ == "__main__": unittest.main() diff --git a/tools/tests/test_unwrap_self.py b/tools/tests/test_unwrap_self.py index e7714f7b75..f30eb5a74f 100644 --- a/tools/tests/test_unwrap_self.py +++ b/tools/tests/test_unwrap_self.py @@ -132,6 +132,27 @@ class UnwrapTest(unittest.TestCase): def tearDown(self): self.temporary.cleanup() + def test_program_table_overlapping_elf_header_preserves_output(self): + for offset in (0, 8, 32, 63): + for previous in (None, b"previous output"): + with self.subTest(offset=offset, previous=previous): + container = bytearray(build_container(default_segments(), default_records(), default_payloads())) + struct.pack_into(" len(data): raise Refused("ELF program header table does not fit the file") headers = []