diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f1da80fadbe..3fac0caf5901 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -101,6 +101,10 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler/tests/test_shader_cache_native_paths.py ${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler/ShaderCacheVersion.cmake ${CMAKE_COMMAND}) set_tests_properties(shader_cache_native_paths PROPERTIES TIMEOUT 20) + add_test(NAME shader_cache_runtime_sources COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_shader_cache_runtime_sources.py + ${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler ${CMAKE_COMMAND}) + set_tests_properties(shader_cache_runtime_sources PROPERTIES TIMEOUT 180) add_test(NAME pr_overlap_encoding COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap_encoding.py) add_test(NAME pr_overlap_dependencies COMMAND ${Python3_EXECUTABLE} @@ -117,6 +121,8 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_unwrap_self.py) add_test(NAME conventions_function_bodies COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_comments.py) + add_test(NAME conventions_debt_length COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_debt_length.py) add_test(NAME test_registration COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_test_registration.py) add_test(NAME check_skips COMMAND ${Python3_EXECUTABLE} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ce636dd89e48..ba9b94305e20 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ - A silent stub is allowed only when it unblocks a title and only affects the UI; add it to [silent stubs](docs/dev/TechnicalDebt.md#silent-stubs). - Missing imports fail at startup with a clear error; don't replace them with fallbacks that keep running. - Implement the general behaviour of a function, not what one title happens to need. -- Don't add replacements for modules that titles ship themselves in `sce_module/`, `sce_modules/` or `prx/`: engine or middleware modules (Cohtml, FMOD, GOG Galaxy) and SDK libraries that only wrap other system libraries (NpCppWebApi over NpWebApi2). The relinker converts and loads the title's own module, and a host library with the same name is left out of `DT_NEEDED`. Only system libraries, which reach the kernel or the hardware, are reimplemented in [core/libs/prx](core/libs/prx). +- Don't add replacements for modules that titles ship themselves in `sce_module/`, `sce_modules/` or `prx/`: engine or middleware modules (Cohtml, FMOD, GOG Galaxy) and SDK libraries that only wrap other system libraries. The relinker converts and loads the title's own module, and a host library with the same name is left out of `DT_NEEDED`. Only system libraries, which reach the kernel or the hardware, are reimplemented in [core/libs/prx](core/libs/prx). A host copy may still exist for a wrapped library that some dumps lack entirely, like libSceNpCppWebApi: the title's own module wins whenever one is shipped, so it is only ever consulted by titles that ship nothing. - Avoid non-standard extensions (`__attribute__`, etc.) where standard C++ is enough. Helper symbols that must not become NIDs use the `_nid_no_patch` or `_nid_no_patch_cut` suffix. - The relinker uses only the C++20 standard library. - Third-party code is added as a submodule under `3rdparty/` and built from source, not found on the system. With `ANYPS5_USE_SYSTEM_DEPS=ON` the packages packaged by the distributions (SDL2, freetype, Vulkan-Headers, SPIRV-Headers, glslang and SPIRV-Tools) come from the system instead; that resolution lives in [cmake/SystemDeps.cmake](cmake/SystemDeps.cmake) and the default build never reads the system. diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index 1ce5b610446b..e34f190e52bf 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -429,6 +429,16 @@ if(BUILD_TESTING) add_test(NAME host_thread_local COMMAND host_thread_local_tests) set_tests_properties(host_thread_local PROPERTIES TIMEOUT 15) + add_test_executable(guest_np_cpp_web_api_intrusive_ptr_tests tests/GuestNpCppWebApiIntrusivePtr.cpp) + target_include_directories(guest_np_cpp_web_api_intrusive_ptr_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_np_cpp_web_api_intrusive_ptr_tests PRIVATE libSceNpCppWebApi libc) + configure_windows_unwind(guest_np_cpp_web_api_intrusive_ptr_tests) + add_test(NAME guest_np_cpp_web_api_intrusive_ptr COMMAND guest_np_cpp_web_api_intrusive_ptr_tests) + if(WIN32) + set_tests_properties(guest_np_cpp_web_api_intrusive_ptr 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) @@ -498,4 +508,24 @@ if(BUILD_TESTING) set_tests_properties(guest_acm PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_voice_chat_tests tests/GuestVoiceChat.cpp) + target_include_directories(guest_voice_chat_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_voice_chat_tests PRIVATE libSceVoiceChat libc) + configure_windows_unwind(guest_voice_chat_tests) + add_test(NAME guest_voice_chat COMMAND guest_voice_chat_tests) + if(WIN32) + set_tests_properties(guest_voice_chat PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + + add_test_executable(guest_np_game_intent_tests tests/GuestNpGameIntent.cpp) + target_include_directories(guest_np_game_intent_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_np_game_intent_tests PRIVATE libSceNpGameIntent libc) + configure_windows_unwind(guest_np_game_intent_tests) + add_test(NAME guest_np_game_intent COMMAND guest_np_game_intent_tests) + if(WIN32) + set_tests_properties(guest_np_game_intent PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/SceTypes.hpp b/core/libs/SceTypes.hpp index ae8ee7386375..5cb12eb095d6 100644 --- a/core/libs/SceTypes.hpp +++ b/core/libs/SceTypes.hpp @@ -1410,6 +1410,11 @@ struct NpGameIntentData { std::uint8_t padding[7]; }; +struct NpGameIntentInitParam { + std::size_t size; + std::uint8_t reserved[32]; +}; + struct NpGameIntentInfo { std::size_t size; std::int32_t user_id; diff --git a/core/libs/nid/src/main.cpp b/core/libs/nid/src/main.cpp index 9965d1cda1c6..bf6878ab4f63 100644 --- a/core/libs/nid/src/main.cpp +++ b/core/libs/nid/src/main.cpp @@ -18,6 +18,8 @@ void _writeFile(const std::string& path, const std::vector& data) std::ofstream f(path, std::ios::binary); if (!f) throw std::runtime_error("cannot write: " + path); f.write(reinterpret_cast(data.data()), static_cast(data.size())); + f.close(); + if (!f) throw std::runtime_error("cannot write: " + path); } } diff --git a/core/libs/prx/libSceAbstractLocal/Export.cpp b/core/libs/prx/libSceAbstractLocal/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAbstractLocal/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAbstractStorage/Export.cpp b/core/libs/prx/libSceAbstractStorage/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAbstractStorage/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAbstractTcs/Export.cpp b/core/libs/prx/libSceAbstractTcs/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAbstractTcs/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAbstractTwitter/Export.cpp b/core/libs/prx/libSceAbstractTwitter/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAbstractTwitter/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAbstractYoutube/Export.cpp b/core/libs/prx/libSceAbstractYoutube/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAbstractYoutube/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAc3Enc/Export.cpp b/core/libs/prx/libSceAc3Enc/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceAc3Enc/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAgc/Cb/include/Sync.hpp b/core/libs/prx/libSceAgc/Cb/include/Sync.hpp index e68396419633..a070c6a5fc8e 100644 --- a/core/libs/prx/libSceAgc/Cb/include/Sync.hpp +++ b/core/libs/prx/libSceAgc/Cb/include/Sync.hpp @@ -7,7 +7,7 @@ struct CommandBuffer; extern "C" { -std::uint32_t* APS5_VABI sceAgcCbMemSemaphore(CommandBuffer* buf, std::uint8_t action, std::uint8_t clientCode, const volatile void* address); +std::uint32_t* APS5_VABI sceAgcCbMemSemaphore(CommandBuffer* buf, const volatile void* address, std::uint32_t operation, std::uint32_t modifier, std::uint32_t value); } #endif diff --git a/core/libs/prx/libSceAgc/Cb/src/Sync.cpp b/core/libs/prx/libSceAgc/Cb/src/Sync.cpp index f738b2b56898..c8c3f80ef375 100644 --- a/core/libs/prx/libSceAgc/Cb/src/Sync.cpp +++ b/core/libs/prx/libSceAgc/Cb/src/Sync.cpp @@ -1,6 +1,5 @@ #include "prx/libSceAgc/Cb/include/Sync.hpp" -#include "prx/libSceAgc/Command/include/Control.hpp" #include "prx/libSceAgc/Command/include/Packet.hpp" #include #include @@ -59,11 +58,13 @@ std::uint32_t* APS5_VABI sceAgcCbReleaseMem(CommandBuffer* buf, std::uint8_t act return packet; } -std::uint32_t* APS5_VABI sceAgcCbMemSemaphore(CommandBuffer* buf, std::uint8_t action, std::uint8_t clientCode, const volatile void* address) { - const std::uint8_t operation = action != 0 ? 7u : 6u; - const std::uint8_t signalType = clientCode & 1u; - const std::uint8_t waitForMailbox = (clientCode >> 1u) & 1u; - return Agc::Command::WriteMemSemaphore(buf, address, waitForMailbox, signalType, operation, __func__); +std::uint32_t* APS5_VABI sceAgcCbMemSemaphore(CommandBuffer* buf, const volatile void* address, std::uint32_t operation, std::uint32_t modifier, std::uint32_t value) { + (void)operation; + (void)modifier; + (void)value; + const auto guestAddress = static_cast(reinterpret_cast(address)); + const auto maskedAddress = guestAddress & ~0xfULL; + return Agc::Command::Emit(buf, 0x39u, {static_cast(maskedAddress), static_cast(maskedAddress >> 32u), 0x80100000u}, __func__); } } diff --git a/core/libs/prx/libSceAgc/tests/Semaphore.cpp b/core/libs/prx/libSceAgc/tests/Semaphore.cpp index 9f083d6093e9..021eb3e7c285 100644 --- a/core/libs/prx/libSceAgc/tests/Semaphore.cpp +++ b/core/libs/prx/libSceAgc/tests/Semaphore.cpp @@ -14,7 +14,7 @@ extern "C" int APS5_VABI sceAgcSetAmmSemaphoreMemory(void* memory, std::uint64_t sizeInBytes); extern "C" int APS5_VABI sceAgcGetSemaphoreLabel(std::uint32_t index, void** label); -extern "C" std::uint32_t* APS5_VABI sceAgcCbMemSemaphore(CommandBuffer* buf, std::uint8_t action, std::uint8_t clientCode, const volatile void* address); +extern "C" std::uint32_t* APS5_VABI sceAgcCbMemSemaphore(CommandBuffer* buf, const volatile void* address, std::uint32_t operation, std::uint32_t modifier, std::uint32_t value); namespace { @@ -145,34 +145,49 @@ void testMemSemaphore() { check(sceAgcGetSemaphoreLabel(0, &label) == 0 && label != nullptr, "failed to get label for memory semaphore test"); const auto guestAddress = reinterpret_cast(label); - for (std::uint8_t action = 0; action <= 1; ++action) { - for (std::uint8_t clientCode = 0; clientCode <= 3; ++clientCode) { - std::array words{}; - words.fill(0xdeadbeefu); - CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0}; - - auto* packet = sceAgcCbMemSemaphore(&buffer, action, clientCode, label); - check(packet == words.data(), "packet address mismatch"); - check(packet[0] == 0xc0023900u, "MEM_SEMAPHORE header opcode mismatch"); - check(packet[1] == static_cast(guestAddress), "MEM_SEMAPHORE address low mismatch"); - check(packet[2] == static_cast(guestAddress >> 32u), "MEM_SEMAPHORE address high mismatch"); - - const std::uint32_t expectedOp = action != 0 ? 7u : 6u; - const std::uint32_t expectedSignal = clientCode & 1u; - const std::uint32_t expectedMailbox = (clientCode >> 1u) & 1u; - const std::uint32_t expectedControl = (expectedOp << 29u) | (expectedSignal << 20u) | (expectedMailbox << 16u); - check(packet[3] == expectedControl, "MEM_SEMAPHORE control word mismatch"); - check(buffer.cursor_up == words.data() + 4, "cursor did not advance by 4 words"); - check(words[4] == 0xdeadbeefu, "words past packet modified"); - } + { + std::array words{}; + words.fill(0xdeadbeefu); + CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0}; + const auto* packet = sceAgcCbMemSemaphore(&buffer, reinterpret_cast(0x1111111122222222ull), 0x33333333u, 0x44444444u, 0); + check(packet == words.data(), "packet address mismatch"); + check(packet[0] == 0xc0023900u, "MEM_SEMAPHORE header opcode mismatch"); + check(packet[1] == 0x22222220u, "MEM_SEMAPHORE address low mismatch"); + check(packet[2] == 0x11111111u, "MEM_SEMAPHORE address high mismatch"); + check(packet[3] == 0x80100000u, "MEM_SEMAPHORE control word mismatch"); + check(buffer.cursor_up == words.data() + 4, "cursor did not advance by 4 words"); + check(words[4] == 0xdeadbeefu, "words past packet modified"); } - std::array words{}; - CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0}; - expectFailure([&] { sceAgcCbMemSemaphore(nullptr, 0, 0, label); }); - expectFailure([&] { sceAgcCbMemSemaphore(&buffer, 0, 0, nullptr); }); - auto* unalignedLabel = reinterpret_cast(guestAddress + 1); - expectFailure([&] { sceAgcCbMemSemaphore(&buffer, 0, 0, unalignedLabel); }); + { + std::array words{}; + words.fill(0xdeadbeefu); + CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0}; + const auto* packet = sceAgcCbMemSemaphore(&buffer, reinterpret_cast(1ull), 2, 3, 4); + check(packet == words.data(), "packet address mismatch"); + check(packet[0] == 0xc0023900u, "MEM_SEMAPHORE header opcode mismatch"); + check(packet[1] == 0u, "MEM_SEMAPHORE address low mismatch"); + check(packet[2] == 0u, "MEM_SEMAPHORE address high mismatch"); + check(packet[3] == 0x80100000u, "MEM_SEMAPHORE control word mismatch"); + check(buffer.cursor_up == words.data() + 4, "cursor did not advance by 4 words"); + check(words[4] == 0xdeadbeefu, "words past packet modified"); + } + + { + std::array words{}; + words.fill(0xdeadbeefu); + CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0}; + const auto* packet = sceAgcCbMemSemaphore(&buffer, label, 0x12345678u, 0x9abcdef0u, 0x55aa55aau); + check(packet == words.data(), "packet address mismatch"); + check(packet[0] == 0xc0023900u, "MEM_SEMAPHORE header opcode mismatch"); + check(packet[1] == static_cast(guestAddress & ~static_cast(0xfu)), "MEM_SEMAPHORE address low mismatch"); + check(packet[2] == static_cast(guestAddress >> 32u), "MEM_SEMAPHORE address high mismatch"); + check(packet[3] == 0x80100000u, "MEM_SEMAPHORE control word mismatch"); + check(buffer.cursor_up == words.data() + 4, "cursor did not advance by 4 words"); + check(words[4] == 0xdeadbeefu, "words past packet modified"); + } + + expectFailure([&] { sceAgcCbMemSemaphore(nullptr, label, 0, 0, 0); }); } } diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 756939ad98a9..ba12cf5293e3 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -83,6 +83,19 @@ add_custom_command( VERBATIM ) +set(agcDispatchArgumentsShaderSource ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/shaders/DispatchArguments.comp) +set(agcDispatchArgumentsShaderSpv ${agcTextureDetileGeneratedDir}/DispatchArguments.spv) +set(agcDispatchArgumentsShaderHeader ${agcTextureDetileGeneratedDir}/DispatchArguments_spv.h) +add_custom_command( + OUTPUT "${agcDispatchArgumentsShaderHeader}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${agcTextureDetileGeneratedDir}" + COMMAND "$" -V --target-env vulkan1.1 -o "${agcDispatchArgumentsShaderSpv}" "${agcDispatchArgumentsShaderSource}" + COMMAND ${CMAKE_COMMAND} -DINPUT=${agcDispatchArgumentsShaderSpv} -DOUTPUT=${agcDispatchArgumentsShaderHeader} + -DSYMBOL=DISPATCH_ARGUMENTS_SPV -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake" + DEPENDS "${agcDispatchArgumentsShaderSource}" glslang-standalone + VERBATIM +) + set(agcSampleCounterShaderSource ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/shaders/SampleCounter.comp) set(agcSampleCounterShaderSpv ${agcTextureDetileGeneratedDir}/SampleCounter.spv) set(agcSampleCounterShaderHeader ${agcTextureDetileGeneratedDir}/SampleCounter_spv.h) @@ -211,12 +224,14 @@ set(agcDriverSources ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/TextureDetiler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/Texture.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/PassHazards.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/BarrierBatch.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/UnitShadow.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/Sampler.cpp ${agcTextureDetileShaderHeader} ${agcCmaskClearShaderHeader} ${agcMeshArgumentsShaderHeader} ${agcTextureDetileImageShaderHeader} + ${agcDispatchArgumentsShaderHeader} ${agcSampleCounterShaderHeader} ${agcSampleDumpsShaderHeader} ${CMAKE_CURRENT_SOURCE_DIR}/Eq/src/Event.cpp @@ -615,6 +630,19 @@ target_link_libraries(agc_scalar_select_mask_tests PRIVATE shader_recompiler_agc set_target_properties(agc_scalar_select_mask_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_scalar_select_mask_tests) +add_test_executable(agc_tessellation_lowering_tests EXCLUDE_FROM_ALL tests/TessellationLowering.cpp) +target_link_libraries(agc_tessellation_lowering_tests PRIVATE shader_recompiler_agc_driver libc) +set_target_properties(agc_tessellation_lowering_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_tessellation_lowering_tests) +if(BUILD_TESTING) + set_target_properties(agc_tessellation_lowering_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + add_test(NAME agc_tessellation_lowering COMMAND agc_tessellation_lowering_tests) + if(WIN32) + set_tests_properties(agc_tessellation_lowering PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + add_test_executable(agc_wave_control_decode_tests EXCLUDE_FROM_ALL tests/WaveControlDecode.cpp) target_include_directories(agc_wave_control_decode_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(agc_wave_control_decode_tests PRIVATE shader_recompiler_agc_driver libc) @@ -691,6 +719,12 @@ target_link_libraries(agc_pass_hazards_tests PRIVATE libc) set_target_properties(agc_pass_hazards_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_pass_hazards_tests) +add_test_executable(agc_barrier_batch_tests EXCLUDE_FROM_ALL tests/BarrierBatch.cpp Graphics/src/BarrierBatch.cpp) +target_include_directories(agc_barrier_batch_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) +target_link_libraries(agc_barrier_batch_tests PRIVATE libc) +set_target_properties(agc_barrier_batch_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_barrier_batch_tests) + add_test_executable(agc_dcc_key_sharing_tests EXCLUDE_FROM_ALL tests/DccKeySharing.cpp) target_include_directories(agc_dcc_key_sharing_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) target_link_libraries(agc_dcc_key_sharing_tests PRIVATE libc) @@ -768,6 +802,11 @@ target_link_libraries(agc_fma_mix_once_tests PRIVATE shader_recompiler_agc_drive set_target_properties(agc_fma_mix_once_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_fma_mix_once_tests) +add_test_executable(agc_f16_single_convert_tests EXCLUDE_FROM_ALL tests/F16SingleConvert.cpp) +target_link_libraries(agc_f16_single_convert_tests PRIVATE shader_recompiler_agc_driver libc) +set_target_properties(agc_f16_single_convert_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_f16_single_convert_tests) + add_test_executable(agc_rect_list_position_invariant_tests EXCLUDE_FROM_ALL tests/RectListPositionInvariant.cpp) target_link_libraries(agc_rect_list_position_invariant_tests PRIVATE shader_recompiler_agc_driver libc) set_target_properties(agc_rect_list_position_invariant_tests PROPERTIES CXX_EXTENSIONS OFF) @@ -937,8 +976,12 @@ configure_windows_unwind(agc_driver_recorder_tests) if(BUILD_TESTING) set_target_properties(agc_driver_recorder_tests PROPERTIES EXCLUDE_FROM_ALL OFF) add_test(NAME agc_recorder_device_selection COMMAND agc_driver_recorder_tests --device-selection-only) + add_test(NAME agc_gpu_timing_complete COMMAND agc_driver_recorder_tests --gpu-timing-complete-only) + add_test(NAME agc_gpu_timing_incomplete COMMAND agc_driver_recorder_tests --gpu-timing-incomplete-only) + set_tests_properties(agc_gpu_timing_complete agc_gpu_timing_incomplete PROPERTIES + ENVIRONMENT "APS5_PROFILE_GPU=1" TIMEOUT 15 SKIP_RETURN_CODE 77) if(WIN32) - set_tests_properties(agc_recorder_device_selection PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(agc_recorder_device_selection agc_gpu_timing_complete agc_gpu_timing_incomplete PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() add_test(NAME agc_pipeline_cache COMMAND agc_driver_recorder_tests --pipeline-cache-only) @@ -1158,6 +1201,14 @@ target_link_libraries(agc_driver_graphics_tests PRIVATE libkernel ) +if(BUILD_TESTING) + add_test(NAME agc_command_batch_fence_failure COMMAND agc_driver_graphics_tests --command-batch-fence-failure) + set_tests_properties(agc_command_batch_fence_failure PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(agc_command_batch_fence_failure PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() set_target_properties(agc_driver_graphics_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_driver_graphics_tests) @@ -1251,6 +1302,8 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() add_test(NAME agc_driver_undithered_alpha_to_coverage_state COMMAND agc_driver_graphics_tests --undithered-alpha-to-coverage-state) + add_test(NAME agc_driver_unused_viewport_depth COMMAND agc_driver_graphics_tests --unused-viewport-depth) + set_tests_properties(agc_driver_unused_viewport_depth PROPERTIES TIMEOUT 30) set_tests_properties(agc_driver_undithered_alpha_to_coverage_state PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") add_test(NAME agc_driver_alpha_to_coverage_state COMMAND agc_driver_graphics_tests --alpha-to-coverage-state) @@ -1352,7 +1405,7 @@ if(BUILD_TESTING) if(WIN32) set_tests_properties(agc_shader_bindless agc_shader_type_zero_image_materialization agc_shader_frontend_pair agc_shader_frontend_pair_off agc_shader_layout_pair agc_shader_layout_pair_off PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() - foreach(agcTest IN ITEMS agc_shared_write_tests agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_spirv_module_tests agc_spirv_bvh_intersect_function_tests agc_dpp_write_tests agc_masked_select_tests agc_constant_bit_test_tests agc_integer_narrowing_tests agc_scalar_select_mask_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_pass_hazards_tests agc_shader_disk_cache_tests agc_shader_device_profile_tests agc_placeholder_images_tests agc_shader_control_flow_tests agc_driver_flip_tests agc_driver_pm4_tests agc_driver_graphics_tests agc_driver_bda_device_tests agc_driver_mesh_tests agc_driver_cmask_fast_clear_tests agc_formatted_buffer_once_tests ${agcExecutionTests} agc_fma_mix_once_tests) + foreach(agcTest IN ITEMS agc_shared_write_tests agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_spirv_module_tests agc_spirv_bvh_intersect_function_tests agc_dpp_write_tests agc_masked_select_tests agc_constant_bit_test_tests agc_integer_narrowing_tests agc_scalar_select_mask_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_pass_hazards_tests agc_barrier_batch_tests agc_shader_disk_cache_tests agc_shader_device_profile_tests agc_placeholder_images_tests agc_shader_control_flow_tests agc_driver_flip_tests agc_driver_pm4_tests agc_driver_graphics_tests agc_driver_bda_device_tests agc_driver_mesh_tests agc_driver_cmask_fast_clear_tests agc_formatted_buffer_once_tests ${agcExecutionTests} agc_fma_mix_once_tests agc_f16_single_convert_tests) if(agcTest STREQUAL "agc_driver_constant_store_tests" OR agcTest STREQUAL "agc_driver_flat_apertures_tests") continue() endif() @@ -1514,9 +1567,12 @@ if(BUILD_TESTING) endif() endforeach() endforeach() - foreach(preparedMode IN ITEMS indirect uncached debug debug_failure deferred deferred_undecodable) + foreach(preparedMode IN ITEMS indirect uncached debug debug_failure deferred deferred_undecodable abi_deferred_compute abi_deferred_pixel abi_deferred_stages abi_deferred_rectangle abi_deferred_undecodable_state abi_deferred_registration_failure abi_deferred_vertex_inputs abi_deferred_rectangle_pairs) set(preparedTest "agc_driver_prepared_shaders_${preparedMode}") - if(preparedMode STREQUAL "indirect") + if(preparedMode MATCHES "^abi_deferred_") + string(REPLACE "_" "-" preparedArgument ${preparedMode}) + add_test(NAME ${preparedTest} COMMAND agc_driver_prepared_shaders_tests --${preparedArgument}) + elseif(preparedMode STREQUAL "indirect") add_test(NAME ${preparedTest} COMMAND agc_driver_prepared_shaders_tests --indirect) elseif(preparedMode STREQUAL "deferred") add_test(NAME ${preparedTest} COMMAND agc_driver_prepared_shaders_tests --deferred) @@ -1529,7 +1585,9 @@ if(BUILD_TESTING) add_test(NAME ${preparedTest} COMMAND agc_driver_prepared_shaders_tests) endif() set_tests_properties(${preparedTest} PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") - if(preparedMode STREQUAL "uncached") + if(preparedMode MATCHES "^abi_deferred_") + set_property(TEST ${preparedTest} APPEND PROPERTY ENVIRONMENT "APS5_NO_FAILURE_MEMO=1") + elseif(preparedMode STREQUAL "uncached") set_property(TEST ${preparedTest} APPEND PROPERTY ENVIRONMENT "APS5_NO_DISPATCH_CACHE=1") elseif(preparedMode STREQUAL "debug" OR preparedMode STREQUAL "debug_failure") set_property(TEST ${preparedTest} APPEND PROPERTY ENVIRONMENT "APS5_PROFILE_DRAW=1" "APS5_NO_UNLOCKED_DEVICE=1") @@ -2000,19 +2058,6 @@ if(BUILD_TESTING) endif() -add_test_executable(agc_constant_select_tests EXCLUDE_FROM_ALL tests/ConstantSelect.cpp) -target_link_libraries(agc_constant_select_tests PRIVATE shader_recompiler_agc_driver libc) -set_target_properties(agc_constant_select_tests PROPERTIES CXX_EXTENSIONS OFF) -configure_windows_unwind(agc_constant_select_tests) -if(BUILD_TESTING) - set_target_properties(agc_constant_select_tests PROPERTIES EXCLUDE_FROM_ALL OFF) - add_test(NAME agc_constant_select COMMAND agc_constant_select_tests) - if(WIN32) - set_tests_properties(agc_constant_select PROPERTIES ENVIRONMENT_MODIFICATION - "PATH=path_list_prepend:$") - endif() -endif() - add_test_executable(agc_constant_select_tests EXCLUDE_FROM_ALL tests/ConstantSelect.cpp) target_link_libraries(agc_constant_select_tests PRIVATE shader_recompiler_agc_driver libc) set_target_properties(agc_constant_select_tests PROPERTIES CXX_EXTENSIONS OFF) diff --git a/core/libs/prx/libSceAgcDriver/Execution/Metal/MetalDrawAhead.mm b/core/libs/prx/libSceAgcDriver/Execution/Metal/MetalDrawAhead.mm index d44540fa483c..1c07178a3a9f 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/Metal/MetalDrawAhead.mm +++ b/core/libs/prx/libSceAgcDriver/Execution/Metal/MetalDrawAhead.mm @@ -10,6 +10,7 @@ #include "prx/libSceAgcDriver/Execution/include/ShaderMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" #include "RdnaDecoder/RdnaInstructionDecoder.hpp" +#include #include #include #include @@ -23,10 +24,15 @@ std::uint32_t PositionExportLayout(std::span code, std::uint32_t paClVsOutCntl) { const auto bits = paClVsOutCntl & Graphics::PositionExportLayoutBits; if (bits == 0) return 0; - const auto decoded = RdnaInstructionDecoder{}.Decode(code); - const bool used = std::any_of(decoded.instructions.begin(), decoded.instructions.end(), [](const auto& instruction) { - return instruction.family == RdnaInstructionFamily::EXP && instruction.exportTarget >= 0x0du && instruction.exportTarget <= 0x0fu && instruction.exportEnableMask != 0; - }); + bool used = true; + try { + const auto decoded = RdnaInstructionDecoder{}.Decode(code); + used = std::any_of(decoded.instructions.begin(), decoded.instructions.end(), [](const auto& instruction) { + return instruction.family == RdnaInstructionFamily::EXP && instruction.exportTarget >= 0x0du && instruction.exportTarget <= 0x0fu && instruction.exportEnableMask != 0; + }); + } catch (const std::invalid_argument& error) { + if (std::strstr(error.what(), "opcode is not implemented") == nullptr) throw; + } return used ? bits : 0; } diff --git a/core/libs/prx/libSceAgcDriver/Execution/Metal/MetalPacketExecution.mm b/core/libs/prx/libSceAgcDriver/Execution/Metal/MetalPacketExecution.mm index e9940bf1b864..3ac37aac1bd5 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/Metal/MetalPacketExecution.mm +++ b/core/libs/prx/libSceAgcDriver/Execution/Metal/MetalPacketExecution.mm @@ -228,10 +228,15 @@ bool reportSkipCount(std::uint64_t count) { std::uint32_t PositionExportLayout(std::span code, std::uint32_t paClVsOutCntl) { const auto bits = paClVsOutCntl & Graphics::PositionExportLayoutBits; if (bits == 0) return 0; - const auto decoded = RdnaInstructionDecoder{}.Decode(code); - const bool used = std::any_of(decoded.instructions.begin(), decoded.instructions.end(), [](const auto& instruction) { - return instruction.family == RdnaInstructionFamily::EXP && instruction.exportTarget >= 0x0du && instruction.exportTarget <= 0x0fu && instruction.exportEnableMask != 0; - }); + bool used = true; + try { + const auto decoded = RdnaInstructionDecoder{}.Decode(code); + used = std::any_of(decoded.instructions.begin(), decoded.instructions.end(), [](const auto& instruction) { + return instruction.family == RdnaInstructionFamily::EXP && instruction.exportTarget >= 0x0du && instruction.exportTarget <= 0x0fu && instruction.exportEnableMask != 0; + }); + } catch (const std::invalid_argument& error) { + if (std::strstr(error.what(), "opcode is not implemented") == nullptr) throw; + } return used ? bits : 0; } @@ -944,7 +949,7 @@ bool reportSkipCount(std::uint64_t count) { } bool MetalDriver::Impl::PendingLabelSatisfies(std::span packet) const { - const auto address = static_cast(packet[2]) | (static_cast(packet[3]) << 32u); + const auto address = Pm4::WaitAddress(packet); const auto bytes = Pm4::WaitAwaitedBytes(packet); for (auto label = pendingLabels.rbegin(); label != pendingLabels.rend(); ++label) { if (address + bytes <= label->address || label->address + label->size <= address) continue; @@ -1164,7 +1169,7 @@ bool reportSkipCount(std::uint64_t count) { // ours the guest CPU waits on before it writes this one still reach it. A value our own // pending work may still write drains first, so the wait never passes on the bytes from // before that work. ANYPS5_METAL_GPU_WAITS=0 always drains first, as before. - const auto address = static_cast(packet[2]) | (static_cast(packet[3]) << 32u); + const auto address = Pm4::WaitAddress(packet); const auto awaitedEnd = address + Pm4::WaitAwaitedBytes(packet); const bool oursPending = timeline.MayHaveWritten(address, awaitedEnd) || std::any_of(pendingLabels.begin(), pendingLabels.end(), [&](const PendingLabel& label) { diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp index 31f580396430..74207caed600 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp @@ -40,7 +40,7 @@ struct PreparedGraphicsStage { PreparedShaders::Entry entry; }; -std::vector PrepareGraphicsStages(const DrawDecode& decoded, const ShaderRecompiler::SpirvTarget& target); +std::vector PrepareGraphicsStages(const DrawDecode& decoded, const ShaderRecompiler::SpirvTarget& target, bool deferUnprepared = false); struct DrawRecipeRecord { std::vector> stages; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp index f64410a1980d..83a5fbfd02fb 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp @@ -169,6 +169,16 @@ class Driver { static void countIndirect(int path, double readMs); static int indirectArgumentsPath(std::span packet, std::uint64_t arguments); void dispatchIndirect(QueueState& queue, std::span packet, const Submission& submission); + enum class AheadArguments { None, Matched, Differed }; + struct IndirectSource { + std::uint64_t arguments = 0; + int path = IndirectGpu; + std::uint32_t queue = 0; + AheadArguments ahead = AheadArguments::None; + }; + static IndirectSource& indirectSource(); + static std::string describeIndirectSource(); + std::array readDispatchArguments(std::uint32_t queue, std::uint64_t arguments, std::uint32_t initiator); static bool& sampledRead(); static bool writeEvidenceEnabled(); static bool writerKeyedEvidence(); @@ -243,7 +253,7 @@ class Driver { std::chrono::steady_clock::time_point resolvedAt{}; }; struct GroupCaptureStats { - std::uint64_t runs = 0, resolved = 0, resolvedHits = 0, adopted = 0, addressMismatches = 0, keyMismatches = 0, failures = 0, stale = 0, labelOverlaps = 0, writtenBefore = 0, deviceRejects = 0; + std::uint64_t runs = 0, resolved = 0, resolvedHits = 0, adopted = 0, addressMismatches = 0, keyMismatches = 0, failures = 0, stale = 0, labelOverlaps = 0, writtenBefore = 0, deviceRejects = 0, argumentMismatches = 0; double resolveMs = 0, resolveMaxMs = 0, ageMs = 0, ageMaxMs = 0; std::chrono::steady_clock::time_point reported = std::chrono::steady_clock::now(); }; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp index f7fd525817e9..417591cbcf51 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp @@ -218,7 +218,7 @@ void SubmitLibraryPrebuild(std::function task); void AwaitLibraryPrebuilds(); void CloseRegistrationPreparation(); -void ResolvePreparedGraphics(const ShaderSnapshot& front, const std::shared_ptr& fragment, std::uint32_t primitiveType, const ShaderRecompiler::SpirvTarget& target); +void ResolvePreparedGraphics(const ShaderSnapshot& front, const std::shared_ptr& fragment, std::uint32_t primitiveType, const ShaderRecompiler::SpirvTarget& target, bool deferUnprepared = false); ShaderRecompiler::RectListShaders PreparedRectangle(const ShaderSnapshot& snapshot, std::uint64_t vertexId, std::uint64_t fragmentId); ShaderRecompiler::RectListShaders DrawRectangle(const ShaderSnapshot& front, const std::shared_ptr& fragment, std::uint64_t vertexId, std::uint64_t fragmentId, const ShaderRecompiler::SpirvTarget& target); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp index c8522dc93866..980c4734b8df 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp @@ -107,6 +107,8 @@ bool WaitSatisfiedUnchecked(std::span packet); // holds will store), without reading memory. bool WaitComparesValue(std::span packet, std::uint64_t value); std::size_t WaitAwaitedBytes(std::span packet); +// The awaited address with the low bits the hardware ignores for the compare width masked off. +std::uint64_t WaitAddress(std::span packet); std::size_t ConditionalWords(std::span packet); std::uint32_t ReadCondition(std::span packet); // A label write (RELEASE_MEM with a data select, WRITE_DATA to memory): the destination and the diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/DrawDispatch.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/DrawDispatch.cpp index 6fb75d515aab..961734aaaf54 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/DrawDispatch.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/DrawDispatch.cpp @@ -141,6 +141,7 @@ DrawDispatchState DecodeDrawDispatch(const QueueState& queue, const DriverDetail product.state = Graphics::DecodeState(queue, vertexWorkgroup); DecodeDrawPrograms(product.state, queue, shaders, nullPixelProgramAddress, false, true, product.programs, product.roles); product.pixel = Graphics::DecodePixelStageInfo(queue.context, Graphics::ExportMappings(product.state), Graphics::PixelProgramSkipped(queue)); + Graphics::NormalizeUnusedViewportDepth(product.state, product.pixel); // The kept pixel ancillary word carries the primitive class (upstream 18d9874a0); the driver path sets it in DrawDecode. if (product.pixel.ancillary) product.pixel.rasterPrimitive = Graphics::RasterPrimitiveOf(product.state); return product; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp index d47e7d5b6090..7bd917ba118b 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp @@ -63,11 +63,10 @@ void Driver::dispatch(QueueState& queue, std::span packet, // The parked packet may differ from the entry packet (e.g. indirect arguments resolved ahead). decoded = DecodeComputeDispatch(queue, packet, snapshot.header, noteShaderRead); compute = decoded.compute; - } else if (indirectArguments != 0 && matchesFillKernel(std::span(snapshot.code).subspan(codeOffset), userData, compute)) { - - recordQueuedLabelsBeforeRead(submission.queue); + } else if (indirectArguments != 0 && !resolving && matchesFillKernel(std::span(snapshot.code).subspan(codeOffset), userData, compute)) { + indirectSource().path = IndirectFillKernel; const auto readStart = std::chrono::steady_clock::now(); - resolved = Pm4::ReadDispatchArguments(indirectArguments, packet[4]); + resolved = readDispatchArguments(submission.queue, indirectArguments, packet[4]); countIndirect(IndirectFillKernel, std::chrono::duration(std::chrono::steady_clock::now() - readStart).count()); packet = resolved; indirectArguments = 0; @@ -347,7 +346,7 @@ void Driver::dispatch(QueueState& queue, std::span packet, } char where[64]; std::snprintf(where, sizeof(where), "compute shader 0x%llx: ", static_cast(address)); - throw std::runtime_error(where + std::string(error.what())); + throw std::runtime_error(where + std::string(error.what()) + describeIndirectSource()); }; phaseTiming.Phase(PhaseSnapshots); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp index f268e7dfaebb..f1d0091178b6 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp @@ -70,7 +70,7 @@ bool OverlapsRange(std::span regions, cons } bool WrittenByRun(std::span wait, const std::vector& labels) { - const std::uint64_t awaited = wait[2] | (static_cast(wait[3]) << 32u); + const std::uint64_t awaited = Pm4::WaitAddress(wait); const auto bytes = Pm4::WaitAwaitedBytes(wait); for (auto it = labels.rbegin(); it != labels.rend(); ++it) { if (it->address != awaited || it->size < bytes) continue; @@ -209,10 +209,10 @@ void Driver::reportGroupCapture(std::uint32_t queue) { auto& stats = groupCaptureStats(); const auto now = std::chrono::steady_clock::now(); if (now - stats.reported < std::chrono::seconds(10)) return; - AgcDriver::ProfilePrint_nid_no_patch("[groupcap] queue 0x%x (10 s): %llu runs resolved %llu dispatches (%llu cache hits) in %.1f ms (max %.2f ms); %llu adopted %.2f ms after the resolve on average, max %.2f ms; mismatches: address %llu key %llu; %llu resolve failures, %llu stale, %llu label overlaps, %llu read an earlier dispatch's output, %llu rejected by the device\n", + AgcDriver::ProfilePrint_nid_no_patch("[groupcap] queue 0x%x (10 s): %llu runs resolved %llu dispatches (%llu cache hits) in %.1f ms (max %.2f ms); %llu adopted %.2f ms after the resolve on average, max %.2f ms; mismatches: address %llu key %llu; %llu resolve failures, %llu stale, %llu label overlaps, %llu read an earlier dispatch's output, %llu rejected by the device, %llu dropped because the argument re-read differed\n", queue, static_cast(stats.runs), static_cast(stats.resolved), static_cast(stats.resolvedHits), stats.resolveMs, stats.resolveMaxMs, static_cast(stats.adopted), stats.adopted != 0 ? stats.ageMs / static_cast(stats.adopted) : 0.0, stats.ageMaxMs, static_cast(stats.addressMismatches), static_cast(stats.keyMismatches), - static_cast(stats.failures), static_cast(stats.stale), static_cast(stats.labelOverlaps), static_cast(stats.writtenBefore), static_cast(stats.deviceRejects)); + static_cast(stats.failures), static_cast(stats.stale), static_cast(stats.labelOverlaps), static_cast(stats.writtenBefore), static_cast(stats.deviceRejects), static_cast(stats.argumentMismatches)); stats = GroupCaptureStats{}; stats.reported = now; } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/IndirectDispatch.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/IndirectDispatch.cpp index f567a93e2eb1..2806f9a0df0b 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/IndirectDispatch.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/IndirectDispatch.cpp @@ -1,10 +1,32 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Dispatch/IndirectDispatch.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" +#include #include namespace AgcDriver::DriverDetail { +namespace { + +const char* IndirectPathName(int path) { + switch (path) { + case IndirectGpu: return "gpu-side"; + case IndirectPendingImage: return "pending image results"; + case IndirectCopiedWrite: return "pending label or copied write"; + case IndirectNotImported: return "not imported"; + case IndirectThreadDimensions: return "thread dimensions"; + case IndirectFillKernel: return "fill kernel"; + case IndirectMisaligned: return "misaligned"; + case IndirectDisabled: return "disabled"; + case IndirectWorkgroupMemory: return "device memory LDS"; + default: return "unknown"; + } +} + +} + void Driver::countIndirect(int path, double readMs) { static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; if (!profile || path < 0 || path >= IndirectPaths) return; @@ -31,23 +53,56 @@ int Driver::indirectArgumentsPath(std::span packet, std::ui return IndirectGpu; } +Driver::IndirectSource& Driver::indirectSource() { + static thread_local IndirectSource source; + return source; +} + +std::string Driver::describeIndirectSource() { + const auto& source = indirectSource(); + if (source.arguments == 0) return {}; + const char* ahead = source.ahead == AheadArguments::Matched ? "adopted, its counts matched the re-read" : source.ahead == AheadArguments::Differed ? "dropped, its counts differed from the re-read" : "none"; + char text[256]; + std::snprintf(text, sizeof(text), "; DISPATCH_INDIRECT on queue 0x%x, arguments at 0x%llx, driver path %s, read-ahead %s", source.queue, static_cast(source.arguments), IndirectPathName(source.path), ahead); + return text; +} + +std::array Driver::readDispatchArguments(std::uint32_t queue, std::uint64_t arguments, std::uint32_t initiator) { + recordQueuedLabelsBeforeRead(queue); + if (!resolvingAhead()) { + GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Indirect); + std::lock_guard gpuLock(GuestMemory::GpuMutex()); + Graphics::FinishLeaseWork(); + } + return Pm4::ReadDispatchArguments(arguments, initiator); +} + void Driver::dispatchIndirect(QueueState& queue, std::span packet, const Submission& submission) { const auto arguments = Pm4::DispatchArgumentAddress(packet, queue); const auto initiator = packet.back(); const int path = indirectArgumentsPath(packet, arguments); + struct Scope { + IndirectSource& source; + IndirectSource previous; + ~Scope() { source = previous; } + } scope{indirectSource(), indirectSource()}; + scope.source = IndirectSource{arguments, path, submission.queue, AheadArguments::None}; if (path != IndirectGpu) { + const auto readStart = std::chrono::steady_clock::now(); + const auto direct = readDispatchArguments(submission.queue, arguments, initiator); + countIndirect(path, std::chrono::duration(std::chrono::steady_clock::now() - readStart).count()); if (!resolvingAhead()) { - const auto& resolved = resolvedAhead(); + auto& resolved = resolvedAhead(); if (const auto found = resolved.find(currentPacketOffset()); found != resolved.end() && found->second.indirectArguments == 0 && found->second.packet[0] == 0xc0031500u) { - const auto direct = found->second.packet; - dispatch(queue, direct, submission); - return; + if (found->second.packet == direct) { + scope.source.ahead = AheadArguments::Matched; + } else { + resolved.erase(found); + ++groupCaptureStats().argumentMismatches; + scope.source.ahead = AheadArguments::Differed; + } } } - recordQueuedLabelsBeforeRead(submission.queue); - const auto readStart = std::chrono::steady_clock::now(); - const auto direct = Pm4::ReadDispatchArguments(arguments, initiator); - countIndirect(path, std::chrono::duration(std::chrono::steady_clock::now() - readStart).count()); dispatch(queue, direct, submission); return; } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp index 7d95fc5730f6..0d8486331fd5 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp @@ -34,6 +34,7 @@ std::shared_ptr Driver::decodeDraw(const QueueState& queue, const Su product->state = Graphics::DecodeState(queue, vertexWorkgroup); DecodeGraphicsPrograms(*product, queue, *submission.shaders, false, true); product->pixel = Graphics::DecodePixelStageInfo(queue.context, Graphics::ExportMappings(product->state), Graphics::PixelProgramSkipped(queue)); + Graphics::NormalizeUnusedViewportDepth(product->state, product->pixel); product->pixel.targetExportPacking = Graphics::ExportPackings(product->state); product->pixel.dualSourceBlend = product->state.dualSourceBlend; product->pixel.reversedBlend = product->state.reversedBlend; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/IndirectDraw.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/IndirectDraw.cpp index e52db4829bc3..d3049765aa27 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/IndirectDraw.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/IndirectDraw.cpp @@ -1,5 +1,6 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" #include @@ -28,6 +29,7 @@ std::optional Driver::ClassifyIndirectDraw(const Sha using Path = Graphics::IndirectDrawPath; const auto support = localDevice->DrawIndirectSupport(); const auto fail = [&](Path reason) { if (!indirectCpu) indirectCpu = reason; }; + if (drawParameters.indexed && !GuestMemory::Accessible(reinterpret_cast(drawParameters.indexAddress), static_cast(drawParameters.indexCount) * drawParameters.indexSize)) fail(Path::IndexRange); constexpr std::uint32_t NoLocation = 0x280u; const bool meshPath = graphics.stages.mesh.has_value() && !graphics.stages.tessellation && !graphics.rectList; const bool meshGpuSide = meshPath && drawParameters.indexed && indirect.count == 1 && !indirect.countIndirect && indirect.baseVertexLocation == NoLocation && indirect.startInstanceLocation == NoLocation && (!indirect.drawIndexEnabled || indirect.drawIndexLocation == NoLocation); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp index 1c1dc62fea90..6c6d497a6cde 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp @@ -36,7 +36,7 @@ bool keepsDrawPipeline(std::span packet, std::uint32_t head return packet.size() >= 5 && !DrawPipeline::Queue0().Overlaps(packet[1] | (static_cast(packet[2]) << 32u), static_cast(packet[4]) * 8); case 0x3c: case 0x93: { if (packet.size() < 5) return false; - const std::uint64_t awaited = packet[2] | (static_cast(packet[3]) << 32u); + const std::uint64_t awaited = Pm4::WaitAddress(packet); const auto bytes = Pm4::WaitAwaitedBytes(packet); if (DrawPipeline::Queue0().Overlaps(awaited, bytes)) return false; GuestMemory::CheckRange(reinterpret_cast(awaited), bytes, bytes); @@ -209,14 +209,24 @@ void Driver::execute(const Submission& submission) { continue; } if (pipelined && !predicated && (opcode == 0x3c || opcode == 0x93) && header != RenderingWaitPacketHeader && orderedWaits && DrawPipeline::Queue0().Busy()) { - const std::uint64_t awaited = packet[2] | (static_cast(packet[3]) << 32u); - if (const auto value = DrawPipeline::Queue0().PendingLabel(awaited, Pm4::WaitAwaitedBytes(packet)); value.has_value() && Pm4::WaitComparesValue(packet, *value)) { + const auto awaitedBytes = Pm4::WaitAwaitedBytes(packet); + const std::uint64_t awaited = Pm4::WaitAddress(packet); + const auto value = DrawPipeline::Queue0().PendingLabel(awaited, awaitedBytes); + if (value.has_value() && Pm4::WaitComparesValue(packet, *value)) { ++waitOutcomes().fromRecorderSameQueue; gateOpened() = true; recent.Record(cursor); cursor = nextCursor; continue; } + if (!DrawPipeline::Queue0().Overlaps(awaited, awaitedBytes) && Pm4::WaitSatisfiedUnchecked(packet)) { + bumpEpoch(&EpochBumps::waits); + ++waitOutcomes().atEntry; + gateOpened() = true; + recent.Record(cursor); + cursor = nextCursor; + continue; + } } if (pipelined && DrawPipeline::Queue0().Busy() && !keepsDrawPipeline(packet, header, opcode)) DrawPipeline::Queue0().Drain(DrawPipeline::DrainReason::Packet, header == FlipPacketHeader ? 0xffu : opcode); if (pipelined && predicated && DrawPipeline::Queue0().Busy() && DrawPipeline::Queue0().Overlaps(queue.predication.address, queue.predication.operation == 3 ? 8 : 4)) DrawPipeline::Queue0().Drain(DrawPipeline::DrainReason::Packet, opcode); 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 d34af3102a67..d7dac242f48b 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -28,9 +28,11 @@ #include #include #include +#include #include #include #include +#include #ifdef __GLIBC__ #include #endif @@ -262,6 +264,7 @@ void ShaderPreparationTransaction::Commit() { destination.rectangleProgress.swap(change.prepared.rectangleProgress); destination.fragments.swap(change.prepared.fragments); std::swap(destination.rectangleRequested, change.prepared.rectangleRequested); + destination.deferred = destination.deferred || change.prepared.deferred; } } committed = true; @@ -745,6 +748,29 @@ std::vector PrepareRegistered(const ShaderSnapshot& snap return plan != nullptr ? PrepareRegistered(*plan) : std::vector{}; } +void DeferPipelineStage(PreparedShaderState& prepared, const ShaderSnapshot& snapshot, const char* reason) { + if (!prepared.deferred) APS5_LOG_ERR("Shader 0x%llx could not be prepared when the title created its pipeline (%s); preparing it when it is used", static_cast(snapshot.codeAddress), reason); + prepared.deferred = true; +} + +std::optional FailedRegistration(const ShaderSnapshot& snapshot) { + std::unique_lock lock(snapshot.prepared->mutex); + try { + AwaitRegisteredPreparation(*snapshot.prepared, lock); + } catch (const std::exception& error) { + return error.what(); + } + return std::nullopt; +} + +bool DeferFailedRegistration(const ShaderSnapshot& snapshot) { + const auto failure = FailedRegistration(snapshot); + if (!failure) return false; + std::lock_guard lock(snapshot.prepared->mutex); + DeferPipelineStage(*snapshot.prepared, snapshot, failure->c_str()); + return true; +} + #ifdef __GLIBC__ void trimFreedMemory() { static std::atomic last{0}; @@ -998,7 +1024,7 @@ unsigned RegistrationPreparationThreads() { return RegistrationPreparePool::LiveWorkers(); } -std::vector PrepareGraphicsStages(const DrawDecode& decoded, const ShaderRecompiler::SpirvTarget& target) { +std::vector PrepareGraphicsStages(const DrawDecode& decoded, const ShaderRecompiler::SpirvTarget& target, bool deferUnprepared) { ShaderPreparationTransaction transaction; require(decoded.programs.size() == decoded.roles.size(), "graphics ABI program roles are incomplete"); for (const auto& program : decoded.programs) require(program.snapshot != nullptr, "graphics ABI has no registered shader snapshot"); @@ -1031,11 +1057,22 @@ std::vector PrepareGraphicsStages(const DrawDecode& decod const auto& program = decoded.programs[index]; const bool fragment = program.binary.stage == ShaderRecompiler::ShaderStage::Fragment; const auto wave = fragment ? decoded.state.stages.fragmentWaveSize : decoded.state.stages.vertexWaveSize; - std::optional vertex; - if (!fragment) vertex = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, nullptr, true, 8u - program.firstUserSgpr); pushOffset = Graphics::StagePushOffset(pushOffset, program.binary.stage, fixedSlots); const auto offset = pushOffset; const auto room = (fixedSlots && fragment ? Graphics::PipelinePushSlotBytes : capacity) - pushOffset % Graphics::PipelinePushSlotBytes; + if (deferUnprepared && DeferFailedRegistration(*program.snapshot)) { + prepared.push_back({program.snapshot, {program.codeOffset, nullptr}}); + continue; + } + std::optional vertex; + try { + if (!fragment) vertex = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, nullptr, true, 8u - program.firstUserSgpr); + } catch (const std::exception& error) { + if (!deferUnprepared) throw; + DeferPipelineStage(transaction.Edit(*program.snapshot), *program.snapshot, error.what()); + prepared.push_back({program.snapshot, {program.codeOffset, nullptr}}); + continue; + } 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); @@ -1048,7 +1085,14 @@ std::vector PrepareGraphicsStages(const DrawDecode& decod } if (handle == nullptr) { PerformanceTimer timing("Shader.PrepareArtifact"); - handle = PrepareShaderWithDiagnostics(request); + try { + handle = PrepareShaderWithDiagnostics(request); + } catch (const std::exception& error) { + if (!deferUnprepared) throw; + DeferPipelineStage(transaction.Edit(*program.snapshot), *program.snapshot, error.what()); + prepared.push_back({program.snapshot, {program.codeOffset, nullptr}}); + continue; + } } const auto bytes = handle->artifact->bindings.pushConstantSizeBytes; require(bytes <= room, "prepared graphics stages exceed the push constant block"); @@ -1068,6 +1112,7 @@ void Driver::ResolveGraphicsStagesAbi(std::span stages, std QueueState state{}; std::shared_ptr registry; std::shared_ptr owner; + std::vector> selectedStages; { std::lock_guard lock(mutex); registry = shaders; @@ -1077,7 +1122,7 @@ void Driver::ResolveGraphicsStagesAbi(std::span stages, std require(registry != nullptr && registry->contains(address), "graphics ABI refers to an unregistered shader"); const auto selected = RegisteredHeader(*registry, shader); require(selected != nullptr, "graphics ABI refers to a replaced shader header"); - if (owner == nullptr) owner = selected; + selectedStages.push_back(selected); const auto& snapshot = *selected; require(snapshot.registeredState != nullptr, "registered shader state is missing"); const auto registered = DecodeRegisteredState(snapshot, {}); @@ -1095,6 +1140,13 @@ void Driver::ResolveGraphicsStagesAbi(std::span stages, std for (const auto reg : primitive) state.userConfig.insert_or_assign(reg.offset, reg.value); const auto localDevice = device.Load(); require(localDevice != nullptr, "graphics ABI device is missing"); + for (const auto& selected : selectedStages) { + if (!DeferFailedRegistration(*selected) && owner == nullptr) owner = selected; + } + if (owner == nullptr) { + transaction.Commit(); + return; + } struct GraphicsAbiKeyStorage {}; auto& key = HostThreadLocal, GraphicsAbiKeyStorage>(); BuildRegisteredAbiKey(state, *localDevice, key); @@ -1104,23 +1156,30 @@ void Driver::ResolveGraphicsStagesAbi(std::span stages, std for (const auto& stage : abi.stages) { const auto snapshot = stage.lock(); require(snapshot != nullptr, "prepared graphics ABI lost a registered stage"); - if (snapshot->type != 1) ResolvePreparedGraphics(*snapshot, {}, primitiveType == state.userConfig.end() ? 0u : primitiveType->second, localDevice->Target()); + if (snapshot->type != 1) ResolvePreparedGraphics(*snapshot, {}, primitiveType == state.userConfig.end() ? 0u : primitiveType->second, localDevice->Target(), true); } transaction.Commit(); return; } DrawDecode decoded{}; - decoded.state.stages = Graphics::DecodeShaderStages(state); - DecodeGraphicsPrograms(decoded, state, *registry, true, false); - auto prepared = PrepareGraphicsStages(decoded, localDevice->Target()); + try { + decoded.state.stages = Graphics::DecodeShaderStages(state); + DecodeGraphicsPrograms(decoded, state, *registry, true, false); + } catch (const std::exception& error) { + for (const auto& selected : selectedStages) DeferPipelineStage(transaction.Edit(*selected), *selected, error.what()); + transaction.Commit(); + return; + } + auto prepared = PrepareGraphicsStages(decoded, localDevice->Target(), true); for (auto& stage : prepared) { + if (stage.entry.handle == nullptr) continue; const auto& entries = transaction.Read(*stage.snapshot).entries; const auto duplicate = std::ranges::any_of(entries, [&](const auto& existing) { return existing.codeOffset == stage.entry.codeOffset && existing.handle->artifact == stage.entry.handle->artifact; }); if (!duplicate) transaction.Edit(*stage.snapshot).entries.push_back(std::move(stage.entry)); } for (const auto& stage : prepared) { if (stage.snapshot->type == 1) continue; - ResolvePreparedGraphics(*stage.snapshot, {}, primitiveType == state.userConfig.end() ? 0u : primitiveType->second, localDevice->Target()); + ResolvePreparedGraphics(*stage.snapshot, {}, primitiveType == state.userConfig.end() ? 0u : primitiveType->second, localDevice->Target(), true); } PreparedShaderState::GraphicsAbi abi{key, registry, {}}; for (const auto& stage : prepared) abi.stages.push_back(stage.snapshot); @@ -1159,20 +1218,31 @@ void Driver::ResolveShaderAbi(const Shader* shader, std::span, RegisteredAbiKeyStorage>(); BuildRegisteredAbiKey(state, *localDevice, key); + std::optional deferral; const auto& registeredAbis = transaction.Read(*snapshot).registeredAbis; if (std::ranges::find(registeredAbis, key) != registeredAbis.end()) { transaction.Commit(); return; } - auto entries = PrepareRegistered(*snapshot, *localDevice, state, false); + std::vector entries; + try { + entries = PrepareRegistered(*snapshot, *localDevice, state, false); + } catch (const std::exception& error) { + deferral = error.what(); + } auto& prepared = transaction.Edit(*snapshot); for (auto& entry : entries) { const auto duplicate = std::ranges::any_of(prepared.entries, [&](const auto& existing) { return existing.handle->artifact == entry.handle->artifact; }); if (!duplicate) prepared.entries.push_back(std::move(entry)); } + if (deferral) DeferPipelineStage(prepared, *snapshot, deferral->c_str()); prepared.registeredAbis.push_back(key); transaction.Commit(); } @@ -1181,7 +1251,7 @@ void Driver::ResolveGraphicsAbi(const Shader* vertex, const Shader* pixel, std:: PerformanceContext timingContext(FrameTiming::Preparation()); PerformanceTimer timing("Shader.ResolveGraphicsAbi"); CheckFailure(); - if (primitiveType != 0 && primitiveType != 7 && primitiveType != 17) return; + if ((primitiveType != 0 && primitiveType != 7 && primitiveType != 17) || pixel == nullptr) return; ShaderPreparationTransaction transaction; require(vertex != nullptr && pixel != nullptr, "rectangle ABI requires vertex and fragment shaders"); std::shared_ptr front; @@ -1203,7 +1273,7 @@ void Driver::ResolveGraphicsAbi(const Shader* vertex, const Shader* pixel, std:: require(front != fragment, "rectangle stages refer to the same shader"); const auto localDevice = device.Load(); require(localDevice != nullptr, "shader registration device is missing"); - ResolvePreparedGraphics(*front, fragment, primitiveType, localDevice->Target()); + ResolvePreparedGraphics(*front, fragment, primitiveType, localDevice->Target(), true); transaction.Commit(); } @@ -1217,15 +1287,19 @@ void Driver::SettleShaderPreparations(std::span stages) { if (auto snapshot = RegisteredHeader(*shaders, shader)) snapshots.push_back(std::move(snapshot)); } } - for (const auto& snapshot : snapshots) { - std::unique_lock lock(snapshot->prepared->mutex); - AwaitRegisteredPreparation(*snapshot->prepared, lock); - } + for (const auto& snapshot : snapshots) static_cast(FailedRegistration(*snapshot)); } -void ResolvePreparedGraphics(const ShaderSnapshot& front, const std::shared_ptr& fragment, std::uint32_t primitiveType, const ShaderRecompiler::SpirvTarget& target) { +void ResolvePreparedGraphics(const ShaderSnapshot& front, const std::shared_ptr& fragment, std::uint32_t primitiveType, const ShaderRecompiler::SpirvTarget& target, bool deferUnprepared) { ShaderPreparationTransaction transaction; require(fragment.get() != &front, "rectangle stages refer to the same shader"); + if (deferUnprepared) { + const bool frontFailed = DeferFailedRegistration(front); + if ((fragment != nullptr && DeferFailedRegistration(*fragment)) || frontFailed) { + transaction.Commit(); + return; + } + } std::vector> fragments; { const auto& current = transaction.Read(front); @@ -1251,27 +1325,37 @@ void ResolvePreparedGraphics(const ShaderSnapshot& front, const std::shared_ptr< for (const auto& pixel : fragments) { const auto& current = transaction.Read(front); const auto& pixelPrepared = transaction.Read(*pixel); - require(!current.entries.empty() && !pixelPrepared.entries.empty(), "rectangle ABI has missing stage artifacts"); + if ((current.entries.empty() && current.deferred) || (pixelPrepared.entries.empty() && pixelPrepared.deferred)) continue; + if (current.entries.empty() || pixelPrepared.entries.empty()) { + require(deferUnprepared || pixel != fragment, "rectangle ABI has missing stage artifacts"); + continue; + } const auto progress = std::ranges::find_if(current.rectangleProgress, [&](const auto& entry) { return entry.fragment.lock() == pixel; }); const auto vertexCount = progress == current.rectangleProgress.end() ? 0u : progress->vertexCount; const auto fragmentCount = progress == current.rectangleProgress.end() ? 0u : progress->fragmentCount; if (vertexCount == current.entries.size() && fragmentCount == pixelPrepared.entries.size()) continue; auto& prepared = transaction.Edit(front); std::vector rectangles; - for (std::size_t vertexIndex = 0; vertexIndex < prepared.entries.size(); ++vertexIndex) { - const auto& vertexEntry = prepared.entries[vertexIndex]; - for (std::size_t fragmentIndex = vertexIndex < vertexCount ? fragmentCount : 0u; fragmentIndex < pixelPrepared.entries.size(); ++fragmentIndex) { - const auto& fragmentEntry = pixelPrepared.entries[fragmentIndex]; - const auto& vertexArtifact = ShaderRecompiler::GetPreparedArtifact(*vertexEntry.handle); - const auto& fragmentArtifact = ShaderRecompiler::GetPreparedArtifact(*fragmentEntry.handle); - const auto exists = [&](const auto& entry) { return entry.vertexId == vertexArtifact.variantId && entry.fragmentId == fragmentArtifact.variantId; }; - if (std::ranges::any_of(prepared.rectangles, exists) || std::ranges::any_of(rectangles, exists)) continue; - ShaderRecompiler::RecompileResult vertexResult; - ShaderRecompiler::RecompileResult fragmentResult; - static_cast(vertexResult) = vertexArtifact; - static_cast(fragmentResult) = fragmentArtifact; - rectangles.push_back({vertexArtifact.variantId, fragmentArtifact.variantId, std::make_shared(ShaderRecompiler::BuildRectListShaders(vertexResult, fragmentResult, target))}); + try { + for (std::size_t vertexIndex = 0; vertexIndex < prepared.entries.size(); ++vertexIndex) { + const auto& vertexEntry = prepared.entries[vertexIndex]; + for (std::size_t fragmentIndex = vertexIndex < vertexCount ? fragmentCount : 0u; fragmentIndex < pixelPrepared.entries.size(); ++fragmentIndex) { + const auto& fragmentEntry = pixelPrepared.entries[fragmentIndex]; + const auto& vertexArtifact = ShaderRecompiler::GetPreparedArtifact(*vertexEntry.handle); + const auto& fragmentArtifact = ShaderRecompiler::GetPreparedArtifact(*fragmentEntry.handle); + const auto exists = [&](const auto& entry) { return entry.vertexId == vertexArtifact.variantId && entry.fragmentId == fragmentArtifact.variantId; }; + if (std::ranges::any_of(prepared.rectangles, exists) || std::ranges::any_of(rectangles, exists)) continue; + ShaderRecompiler::RecompileResult vertexResult; + ShaderRecompiler::RecompileResult fragmentResult; + static_cast(vertexResult) = vertexArtifact; + static_cast(fragmentResult) = fragmentArtifact; + rectangles.push_back({vertexArtifact.variantId, fragmentArtifact.variantId, std::make_shared(ShaderRecompiler::BuildRectListShaders(vertexResult, fragmentResult, target))}); + } } + } catch (const std::exception& error) { + if (!deferUnprepared && pixel == fragment) throw; + if (deferUnprepared) DeferPipelineStage(transaction.Edit(*pixel), *pixel, error.what()); + continue; } prepared.rectangles.insert(prepared.rectangles.end(), std::make_move_iterator(rectangles.begin()), std::make_move_iterator(rectangles.end())); std::erase_if(prepared.rectangleProgress, [&](const auto& entry) { return entry.fragment.expired() || entry.fragment.lock() == pixel; }); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/LabelHistory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/LabelHistory.cpp index 696612f79475..9e3de640fb08 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/LabelHistory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/LabelHistory.cpp @@ -50,7 +50,7 @@ void Driver::traceLabel(std::span packet, std::uint32_t que const char* kind = nullptr; if (opcode == 0x49 && packet.size() >= 7) { target = packet[3] | (static_cast(packet[4]) << 32u); kind = "RELEASE_MEM"; } else if (opcode == 0x37 && packet.size() >= 5) { target = packet[2] | (static_cast(packet[3]) << 32u); kind = "WRITE_DATA"; } - else if ((opcode == 0x3c || opcode == 0x93) && packet.size() >= 7) { target = packet[2] | (static_cast(packet[3]) << 32u); kind = "WAIT_REG_MEM"; } + else if ((opcode == 0x3c || opcode == 0x93) && packet.size() >= 7) { target = Pm4::WaitAddress(packet); kind = "WAIT_REG_MEM"; } else if (opcode == 0x40 && packet.size() >= 6) { target = packet[4] | (static_cast(packet[5]) << 32u); kind = "COPY_DATA"; } std::uint64_t length = 4; if (opcode == 0x50 && packet.size() >= 7) { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp index 3dc99dfb55b3..03330e057f4f 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp @@ -110,11 +110,11 @@ void Driver::waitMemory(std::span packet, std::uint32_t que GuestMemory::TagGpuLockThread(queue); auto& outcomes = waitOutcomes(); - const std::uint64_t awaited = packet[2] | (static_cast(packet[3]) << 32u); const std::size_t awaitedBytes = Pm4::WaitAwaitedBytes(packet); + const std::uint64_t awaited = Pm4::WaitAddress(packet); static const bool traceGpu = std::getenv("APS5_TRACE_GPU") != nullptr; - if (traceGpu) std::fprintf(stderr, "[gpu] %.1f queue 0x%x waits 0x%llx == 0x%x (now 0x%x)\n", TraceMs(), queue, static_cast(packet[2] | (static_cast(packet[3]) << 32u)), packet[4], - *reinterpret_cast(packet[2] | (static_cast(packet[3]) << 32u))); + if (traceGpu) std::fprintf(stderr, "[gpu] %.1f queue 0x%x waits 0x%llx == 0x%x (now 0x%x)\n", TraceMs(), queue, static_cast(awaited), packet[4], + *reinterpret_cast(awaited)); struct WaitTrace { bool enabled; std::uint32_t queue; std::uint64_t address; std::chrono::steady_clock::time_point begin; ~WaitTrace() { @@ -122,7 +122,7 @@ void Driver::waitMemory(std::span packet, std::uint32_t que const auto ms = std::chrono::duration(std::chrono::steady_clock::now() - begin).count(); if (ms >= 0.5) std::fprintf(stderr, "[gpu] %.1f queue 0x%x wait on 0x%llx done after %.1f ms\n", TraceMs(), queue, static_cast(address), ms); } - } waitTrace{traceGpu, queue, packet[2] | (static_cast(packet[3]) << 32u), std::chrono::steady_clock::now()}; + } waitTrace{traceGpu, queue, awaited, std::chrono::steady_clock::now()}; bool warned = false; struct EpochPoint { @@ -369,7 +369,7 @@ void Driver::waitMemory(std::span packet, std::uint32_t que static std::uint64_t timeouts = 0; const std::lock_guard lock(reportedMutex); if (++timeouts % 20 == 0) std::fprintf(stderr, "[gpu] %llu GPU waits have timed out\n", static_cast(timeouts)); - firstReport = reported.insert(packet[2] | (static_cast(packet[3]) << 32u)).second; + firstReport = reported.insert(awaited).second; } if (std::getenv("APS5_SKIP_FAILED_SHADERS") != nullptr) { if (firstReport) { @@ -382,10 +382,10 @@ void Driver::waitMemory(std::span packet, std::uint32_t que const bool wide = ((packet[0] >> 8u) & 0xffu) == 0x93u; const std::uint64_t reference = wide ? packet[4] | (static_cast(packet[5]) << 32u) : packet[4]; const std::uint64_t mask = wide ? packet[6] | (static_cast(packet[7]) << 32u) : packet[5]; - const std::uint64_t current = wide ? *reinterpret_cast(awaited) : *reinterpret_cast(awaited); + const std::uint64_t current = awaitedBytes == 8 ? *reinterpret_cast(awaited) : *reinterpret_cast(awaited); std::fprintf(stderr, "[gpu] queue 0x%x WAIT_REG_MEM%s%s at 0x%llx timed out after %dms (function %u ref 0x%llx mask 0x%llx value 0x%llx)\n", queue, wide ? "_64" : "", requireMemory ? " (landing)" : "", static_cast(awaited), waitTimeoutMs(), packet[1] & 7u, static_cast(reference), static_cast(mask), static_cast(current)); - const auto address = packet[2] | (static_cast(packet[3]) << 32u); + const auto address = awaited; for (const auto& record : writeHistory()) { if (record.length != 0 && record.target <= address && address < record.target + std::max(record.length, 4)) std::fprintf(stderr, "[gpu] earlier write by queue 0x%x opcode 0x%x at 0x%llx+0x%llx\n", record.queue, record.opcode, static_cast(record.target), static_cast(record.length)); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp index 275901bb9ae8..cc3747747a14 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp @@ -883,6 +883,23 @@ struct WriteTracker { } } + void stampWatchedPart(std::uint64_t address, std::uint64_t end, std::uint32_t stampGeneration) { +#ifdef _WIN32 + address = std::max(address, base); + end = std::min(end, base + size); +#else + end = std::min(end, LeafCount * LeafBlocks * WriteBlockBytes); +#endif + if (address >= end) return; + for (auto block = blockOf(address); block <= blockOf(end - 1); ++block) { + const auto first = std::max(address, blockBegin(block)); + const auto stop = std::min(end, blockBegin(block) + WriteBlockBytes); + if (!covers(first, 1) && !covers(stop - 1, 1)) continue; + stamp(block, stampGeneration, StampKind::Driver); + noteDriverStore(block, address, end, stampGeneration, false); + } + } + void noteDriverStore(std::uint64_t block, std::uint64_t address, std::uint64_t end, std::uint32_t stampGeneration, bool complete) { const auto begin = blockBegin(block); const auto from = static_cast(std::max(address, begin) - begin); @@ -1390,6 +1407,7 @@ std::uint64_t storeOwn(std::uint64_t address, std::size_t bytes, const std::func ++tracker.generation; if (!tracker.watched || !tracker.covers(stored.first, static_cast(stored.second - stored.first))) { tracker.noteUnstampedStore(stored.first, stored.second); + if (tracker.watched) tracker.stampWatchedPart(stored.first, stored.second, tracker.generation); return 0; } for (auto block = tracker.blockOf(stored.first); block <= tracker.blockOf(stored.second - 1); ++block) { @@ -1438,6 +1456,7 @@ static std::uint64_t markWritten(std::uint64_t address, std::size_t bytes, bool ++tracker.generation; if (!tracker.watched || !tracker.covers(address, bytes)) { tracker.noteUnstampedStore(address, address + bytes); + if (tracker.watched) tracker.stampWatchedPart(address, address + bytes, tracker.generation); return 0; } #ifndef _WIN32 diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp index 26c8dcc2cb80..beb60f630d22 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp @@ -456,7 +456,6 @@ void Validate(std::span packet, std::uint32_t queue) { size(opcode == 0x3c ? 7 : 9); require((packet[1] & 0x10u) != 0, "register-space WAIT_REG_MEM is not implemented"); require((packet[1] & 7u) <= 6u, "invalid WAIT_REG_MEM compare function"); - require((packet[2] & (opcode == 0x3c ? 3u : 7u)) == 0, "misaligned WAIT_REG_MEM address"); break; } case 0x45: { @@ -548,20 +547,31 @@ bool waitCompares(std::span packet, bool wide, std::uint64_ } +std::size_t WaitAwaitedBytes(std::span packet) { + const bool wide = ((packet[0] >> 8u) & 0xffu) == 0x93u; + return wide && (packet.size() < 8 || packet[7] != 0) ? 8 : 4; +} + +std::uint64_t WaitAddress(std::span packet) { + return address(packet[2] & (WaitAwaitedBytes(packet) == 8 ? ~7u : ~3u), packet[3]); +} + bool WaitSatisfied(std::span packet) { const bool wide = ((packet[0] >> 8u) & 0xffu) == 0x93u; - const auto source = address(packet[2], packet[3]); + const auto awaitedBytes = WaitAwaitedBytes(packet); + const auto source = WaitAddress(packet); std::uint64_t value = 0; // Named for the [hooksync] attribution (the read goes through the flush hook). const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::Wait); - GuestMemory::Read(source, std::as_writable_bytes(std::span(&value, 1)).first(wide ? 8 : 4), wide ? 8 : 4); + GuestMemory::Read(source, std::as_writable_bytes(std::span(&value, 1)).first(awaitedBytes), awaitedBytes); return waitCompares(packet, wide, value); } bool WaitSatisfiedUnchecked(std::span packet) { const bool wide = ((packet[0] >> 8u) & 0xffu) == 0x93u; - const auto source = address(packet[2], packet[3]); - const std::uint64_t value = wide ? *reinterpret_cast(source) : *reinterpret_cast(source); + const auto awaitedBytes = WaitAwaitedBytes(packet); + const auto source = WaitAddress(packet); + const std::uint64_t value = awaitedBytes == 8 ? *reinterpret_cast(source) : *reinterpret_cast(source); return waitCompares(packet, wide, value); } @@ -570,11 +580,6 @@ bool WaitComparesValue(std::span packet, std::uint64_t valu return waitCompares(packet, wide, value); } -std::size_t WaitAwaitedBytes(std::span packet) { - const bool wide = ((packet[0] >> 8u) & 0xffu) == 0x93u; - return wide && (packet.size() < 8 || packet[7] != 0) ? 8 : 4; -} - std::size_t ConditionalWords(std::span packet) { require(packet.size() == 5 && ((packet[0] >> 8u) & 0xffu) == 0x22u, "expected COND_EXEC packet"); return packet[4] & ConditionalWordsMask; @@ -767,13 +772,10 @@ DrawParameters resolveIndirectDraw(std::span packet, const draw.firstVertex = offset->second; indirect.indxOffset = offset->second; } else { - // The CP clamps firstIndex + indexCount to INDEX_BUFFER_SIZE: the declared range is the - // hardware's view of the index buffer, so that is what gets bound. const auto indexSize = indexSizeOf(queue); require(queue.indexBufferSize != 0, "INDEX_BUFFER_SIZE has not been set"); const auto bytes = static_cast(queue.indexBufferSize) * indexSize; require(bytes <= std::numeric_limits::max() && bytes <= std::numeric_limits::max() - queue.indexBase, "index range size overflow"); - GuestMemory::CheckRange(reinterpret_cast(queue.indexBase), static_cast(bytes), indexSize); draw.indexAddress = queue.indexBase; draw.indexCount = queue.indexBufferSize; draw.indexSize = indexSize; @@ -971,7 +973,7 @@ void Execute(std::span packet, QueueState& queue) { if (!warned && std::chrono::steady_clock::now() - start > std::chrono::seconds(5)) { warned = true; std::fprintf(stderr, "[gpu] WAIT_REG_MEM at 0x%llx still waiting after 5s (function %u ref 0x%x mask 0x%x)\n", - static_cast(address(packet[2], packet[3])), packet[1] & 7u, packet[4], packet[5]); + static_cast(WaitAddress(packet)), packet[1] & 7u, packet[4], packet[5]); } } return; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index 8d32347a9f36..cce32d9847f7 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -3301,6 +3301,8 @@ struct RecordedDispatch { // Whether the data refresh recorded anything. bool refreshed = false; bool resourceKeyMatched = false; + std::uint64_t cpuArguments = 0; + int cpuReason = 0; }; VkDevice VulkanDevice::Device() const { @@ -3558,15 +3560,16 @@ void VulkanDevice::decideIndirect(RecordedDispatch& record, IndirectOutcome& out } timer.phase(PhaseDecide); if (outcome.cpuReason == 0) return; - // The read waits for the producing batch through the flush hook, as the driver's resolve - // did for every indirect dispatch before; the counts then go through the direct checks. const auto readStart = std::chrono::steady_clock::now(); + Graphics::FinishLeaseWork(); std::array groups{}; GuestMemory::Read(arguments, std::as_writable_bytes(std::span(groups)), 4); outcome.argumentReadMs = std::chrono::duration(std::chrono::steady_clock::now() - readStart).count(); record.x = groups[0]; record.y = groups[1]; record.z = groups[2]; + record.cpuArguments = arguments; + record.cpuReason = outcome.cpuReason; // Timed before `arguments` is cleared, so the read counts as an indirect phase. timer.phase(PhaseArgumentRead); // From here on the phases of a CPU-resolved indirect dispatch are charged as direct ones. @@ -3664,6 +3667,16 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) { recorder.EndGpuTiming(timing); ++d.preBarriersRecorded; } + VkBuffer indirectBuffer = argumentImport != nullptr ? argumentImport->buffer : VK_NULL_HANDLE; + VkDeviceSize indirectOffset = argumentImport != nullptr ? arguments - argumentImport->base : 0; + if (argumentImport != nullptr && argumentImport->address != 0) { + auto checked = std::make_shared(context, 12, VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); + if (recorder.RecordDispatchArguments(commands, argumentImport->address + (arguments - argumentImport->base), checked->DeviceAddress())) { + indirectBuffer = checked->Handle(); + indirectOffset = 0; + recorder.Keep(std::move(checked)); + } + } context.Resolved(&Graphics::DeviceFunctions::cmdBindPipeline, "vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, record.objects->pipeline); resources.Bind(commands, VK_PIPELINE_BIND_POINT_COMPUTE, record.objects->layout); if (const auto dwords = record.shader->program->workgroupMemoryDwords; dwords != 0) { @@ -3676,20 +3689,22 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) { } const auto gpuTiming = recorder.BeginGpuTiming(record.programAddress != 0 ? record.programAddress : record.shader->program->variantId); if (argumentImport != nullptr) { - context.Resolved(&Graphics::DeviceFunctions::cmdDispatchIndirect, "vkCmdDispatchIndirect")(commands, argumentImport->buffer, arguments - argumentImport->base); + context.Resolved(&Graphics::DeviceFunctions::cmdDispatchIndirect, "vkCmdDispatchIndirect")(commands, indirectBuffer, indirectOffset); } else { const auto* limit = state->properties.limits.maxComputeWorkGroupCount; if (record.x <= limit[0] && record.y <= limit[1] && record.z <= limit[2]) { context.Resolved(&Graphics::DeviceFunctions::cmdDispatch, "vkCmdDispatch")(commands, record.x, record.y, record.z); } else { + char counted[128] = ""; + if (record.cpuArguments != 0) std::snprintf(counted, sizeof(counted), " (read on the CPU from the indirect arguments at 0x%llx, cpuReason %d)", static_cast(record.cpuArguments), record.cpuReason); if (record.shader->program->workgroupMemoryDwords != 0) { - char text[256]; - std::snprintf(text, sizeof(text), "Vulkan dispatch: workgroup count %ux%ux%u exceeds device limits %ux%ux%u and a split aliases the device-memory LDS slots, whose base is computed from gl_NumWorkGroups of a single chunk", record.x, record.y, record.z, limit[0], limit[1], limit[2]); + char text[384]; + std::snprintf(text, sizeof(text), "Vulkan dispatch: workgroup count %ux%ux%u%s exceeds device limits %ux%ux%u and a split aliases the device-memory LDS slots, whose base is computed from gl_NumWorkGroups of a single chunk", record.x, record.y, record.z, counted, limit[0], limit[1], limit[2]); throw std::runtime_error(text); } if (state->properties.vendorID == 0x10de) { - char text[192]; - std::snprintf(text, sizeof(text), "Vulkan dispatch: workgroup count %ux%ux%u exceeds device limits %ux%ux%u and NVIDIA drops workgroups of a vkCmdDispatchBase split", record.x, record.y, record.z, limit[0], limit[1], limit[2]); + char text[320]; + std::snprintf(text, sizeof(text), "Vulkan dispatch: workgroup count %ux%ux%u%s exceeds device limits %ux%ux%u and NVIDIA drops workgroups of a vkCmdDispatchBase split", record.x, record.y, record.z, counted, limit[0], limit[1], limit[2]); throw std::runtime_error(text); } const auto dispatchBase = context.Function("vkCmdDispatchBase"); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/BarrierBatch.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/BarrierBatch.hpp new file mode 100644 index 000000000000..3cd1257efdee --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/BarrierBatch.hpp @@ -0,0 +1,25 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_BARRIERBATCH_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_BARRIERBATCH_HPP + +#ifndef VK_NO_PROTOTYPES +#define VK_NO_PROTOTYPES +#endif +#include + +namespace AgcDriver::Graphics { + +struct BarrierMask { + VkPipelineStageFlags srcStage = 0; + VkPipelineStageFlags dstStage = 0; + VkAccessFlags srcAccess = 0; + VkAccessFlags dstAccess = 0; + bool changesLayout = false; + bool transfersQueue = false; +}; + +bool Covers(const BarrierMask& outer, const BarrierMask& inner); +bool OrderedByTrailing(VkAccessFlags covered, const BarrierMask& wanted); + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp index b2b4d09787dd..5e5a16a603c7 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp @@ -80,6 +80,7 @@ class BufferPool { }; struct Slab { std::vector available; + std::size_t blocks = 0; std::size_t emptyBlocks = 0; }; static std::uint64_t SlabKey(std::uint32_t memoryType, std::size_t capacity, bool addressable); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp index 6c4332713100..84225bba343b 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp @@ -31,7 +31,7 @@ struct DrawInputCopy { }; DrawInputCopy CopyDrawInput(const Context& context, Recorder* recorder, std::uint64_t address, std::size_t bytes, std::size_t alignment, Recorder::SnapshotUse use); DrawInputCopy CopyBoundedIndexInput(const Context& context, const Pm4::DrawParameters& draw); -DrawInputCopy CopyZeroPaddedDrawInput(const Context& context, std::uint64_t address, std::size_t bytes, std::size_t validBytes); +DrawInputCopy CopyZeroPaddedDrawInput(const Context& context, std::uint64_t address, std::size_t bytes, std::size_t validBytes, std::size_t stride = 0); void KeepDrawInput(Recorder* recorder, std::uint64_t address, const DrawInputCopy& copy, Recorder::SnapshotUse use, std::uint32_t derived); const HostImport* InPlaceDrawInput(const Context& context, std::uint64_t address, std::size_t bytes, std::size_t alignment); @@ -91,7 +91,7 @@ bool DrawRecipes(); // draw index, the vertex ranges, the device features, APS5_NO_GPU_INDIRECT_DRAW) or under the device // lock in Draw (the memory state of the records). `readMs` is the CPU read's time (its sync); // `rewritten` counts a GPU-side draw whose records were copied and patched with a constant. -enum class IndirectDrawPath : std::uint8_t { Gpu, NotFolded, FetchUnknown, NonVertexPath, IndxOffset, DrawIndex, VertexRange, FeatureGap, PendingImage, PendingLabelOrCopy, NotImported, Disabled, Count }; +enum class IndirectDrawPath : std::uint8_t { Gpu, NotFolded, FetchUnknown, NonVertexPath, IndxOffset, DrawIndex, VertexRange, FeatureGap, PendingImage, PendingLabelOrCopy, NotImported, Disabled, IndexRange, Count }; void CountIndirectDraw(IndirectDrawPath path, double readMs, bool rewritten = false); std::array(IndirectDrawPath::Count)> IndirectDrawCounts(); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp index 23d0232df7b7..b518c805f650 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp @@ -125,6 +125,7 @@ std::uint64_t ImageMirrorSerial(const Context& context, std::uint64_t address, s // prints the [address-sync] leases line every 10 s from LeaseCounters (cumulative). bool SyncLeaseWork(); void CountLeaseOutcome(bool synced, std::uint64_t batchSerial); +void FinishLeaseWork(); struct LeaseStats { std::uint64_t deferred = 0; std::uint64_t synced = 0; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp index a58de74ce446..06cc134e4251 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp @@ -1,6 +1,7 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_RECORDER_HPP #define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_RECORDER_HPP +#include "prx/libSceAgcDriver/Graphics/include/BarrierBatch.hpp" #include "prx/libSceAgcDriver/Graphics/include/Context.hpp" #include "prx/libSceAgcDriver/Graphics/include/PassHazards.hpp" #include @@ -159,6 +160,7 @@ class Recorder { static ReadStatistics ReadCounts(); // Whether the OPEN (unsubmitted) batch writes the range: a wait on such a range must submit it. bool OpenWriteOverlaps(std::uint64_t address, std::size_t bytes) const; + std::uint64_t PendingWriterSerial(std::uint64_t address, std::size_t bytes) const; // What SyncThrough(address, bytes) would wait for, for the [hooksync] attribution: the newest // batch whose noted range overlaps the access (the open batch counts first, with the serial it // will get), whether that batch's fence has already signaled (its GPU work is done, so the @@ -179,6 +181,7 @@ class Recorder { // ever submitted); WaitSerial(serial) then covers all recorded work. std::uint64_t SubmitAndEpoch(); bool HasTimeline() const { return timeline != VK_NULL_HANDLE; } + bool RecordDispatchArguments(VkCommandBuffer commands, VkDeviceAddress source, VkDeviceAddress arguments); // Waits until every batch up to `serial` completed on the GPU. Called WITHOUT GuestMemory::GpuMutex: // it touches only the immutable timeline semaphore (the caller keeps the device alive). Requires // HasTimeline(); the debug switch APS5_NO_TIMELINE makes the device create the recorder without one. @@ -461,6 +464,8 @@ class Recorder { // (or APS5_NO_BARRIER_ELISION=1) records every leading barrier, as before. static bool MergeBarriers(); static void CountMerged(CommandClass which); + static bool BarrierBatch(); + static bool ElideCovered(CommandClass which, const BarrierMask& wanted, VkAccessFlags covered); // Hazard tracker, counting mode (APS5_BARRIER_VALIDATE=1): every command tells the tracker // what it reads and writes (guest ranges through host imports, images) with its stage before // it is recorded; the tracker simulates barriers emitted only on a hazard against the accesses @@ -692,6 +697,9 @@ class Recorder { VkPipelineLayout dumpLayout = VK_NULL_HANDLE; VkPipeline dumpPipeline = VK_NULL_HANDLE; std::shared_ptr segmentPools; + int dispatchArgumentState = 0; + VkPipelineLayout dispatchArgumentLayout = VK_NULL_HANDLE; + VkPipeline dispatchArgumentPipeline = VK_NULL_HANDLE; // BeginGpuTiming on the open batch without Commands() (RecordStore times its own run, which // Commands() would close). std::uint32_t beginTiming(std::uint64_t key); @@ -744,7 +752,7 @@ class Recorder { void removeLabels(Batch& batch); void release(Batch& batch) noexcept; static bool overlaps(const Batch& batch, std::uint64_t address, std::uint64_t end); - static std::optional readOverlap(const Batch& batch, std::uint64_t address, std::uint64_t end); + static std::optional readOverlap(const Batch& batch, std::uint64_t address, std::uint64_t end, std::optional only = std::nullopt); static std::vector> notedReads(const Batch& batch); bool signaled(const Batch& batch) const; // Rebuilds the lock-free snapshot of pending writes from open, inFlight and finishing. diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp index 4ecb7b1bc86e..a394542d52ef 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp @@ -162,6 +162,7 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup = false); // The rasterizer's sample positions in [0, 1) of a pixel, rasterSamples of them: the custom pattern, or // the standard 2x/4x/8x pattern (the D3D/Vulkan standard, which is also Metal's default). std::array SampleLocations(const State& state); +void NormalizeUnusedViewportDepth(State& state, const ShaderRecompiler::ShaderPixelStageInfo& pixel); std::array ExportMappings(const State& state); std::array ExportPackings(const State& state); ShaderRecompiler::RasterPrimitive RasterPrimitiveOf(const State& state); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/shaders/DispatchArguments.comp b/core/libs/prx/libSceAgcDriver/Graphics/shaders/DispatchArguments.comp new file mode 100644 index 000000000000..ed0b2525613c --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/shaders/DispatchArguments.comp @@ -0,0 +1,23 @@ +#version 450 +#extension GL_EXT_buffer_reference : require + +layout(local_size_x = 1) in; +layout(buffer_reference, std430, buffer_reference_align = 4) buffer Words { uint words[]; }; +layout(push_constant) uniform Parameters { + Words source; + Words arguments; + uint maxX; + uint maxY; + uint maxZ; + uint unused; +} parameters; + +void main() { + uint x = parameters.source.words[0]; + uint y = parameters.source.words[1]; + uint z = parameters.source.words[2]; + bool valid = x <= parameters.maxX && y <= parameters.maxY && z <= parameters.maxZ; + parameters.arguments.words[0] = valid ? x : 0u; + parameters.arguments.words[1] = valid ? y : 0u; + parameters.arguments.words[2] = valid ? z : 0u; +} diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/BarrierBatch.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/BarrierBatch.cpp new file mode 100644 index 000000000000..0b7197ddc6fe --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/BarrierBatch.cpp @@ -0,0 +1,27 @@ +#include "prx/libSceAgcDriver/Graphics/include/BarrierBatch.hpp" + +namespace AgcDriver::Graphics { + +namespace { + +bool StageCovers(VkPipelineStageFlags outer, VkPipelineStageFlags inner) { + auto rest = inner & ~outer; + if ((outer & VK_PIPELINE_STAGE_ALL_COMMANDS_BIT) != 0) rest &= VK_PIPELINE_STAGE_HOST_BIT; + return rest == 0; +} + +} + +bool Covers(const BarrierMask& outer, const BarrierMask& inner) { + if (inner.changesLayout || inner.transfersQueue) return false; + return StageCovers(outer.srcStage, inner.srcStage) && StageCovers(outer.dstStage, inner.dstStage) && (inner.srcAccess & ~outer.srcAccess) == 0 && (inner.dstAccess & ~outer.dstAccess) == 0; +} + +bool OrderedByTrailing(VkAccessFlags covered, const BarrierMask& wanted) { + if (wanted.changesLayout || wanted.transfersQueue || (wanted.dstStage & VK_PIPELINE_STAGE_HOST_BIT) != 0) return false; + constexpr VkAccessFlags shaderAndTransfer = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; + const VkAccessFlags needed = shaderAndTransfer | wanted.dstAccess; + return (covered & needed) == needed; +} + +} diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp index 3d8a5d55591d..821310e84246 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp @@ -103,9 +103,11 @@ SlabSlot BufferPool::TakeSlot(const Context& context, std::uint32_t memoryType, for (std::uint32_t index = slots; index-- != 0;) block->free.push_back(index); std::lock_guard lock(slabMutex); auto& slab = slabs[key]; + if (slab.blocks == slab.available.capacity()) slab.available.reserve(std::max(1, slab.available.capacity() * 2)); auto& inserted = *block; slabBlocks.emplace(inserted.memory, std::move(block)); slab.available.push_back(&inserted); + ++slab.blocks; ++slab.emptyBlocks; return takeFrom(*slab.available.back(), slab); } @@ -131,6 +133,7 @@ void BufferPool::PutSlot(VkDeviceMemory memory, VkDeviceSize offset) noexcept { return; } slab.available.erase(std::find(slab.available.begin(), slab.available.end(), &block)); + --slab.blocks; released = std::move(found->second); slabBlocks.erase(found); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp index 8d1ee9507dd4..745aa2e81b67 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp @@ -354,9 +354,9 @@ DccKeys readDccKeys(std::uint64_t metaAddress, std::size_t count, bool& memoized return DccKeys::Mixed; } PublishShadow(metaAddress, count, PublishScope::Whole, PublishReason::Keys); - if (auto* recorder = Recorder::Active()) { + if (auto* recorder = Recorder::Active(); recorder != nullptr && recorder->PendingWriteOverlaps(metaAddress, count)) { Recorder::CountSync(2); - recorder->Sync(); + recorder->SyncThrough(metaAddress, count); } } const auto* keys = reinterpret_cast(metaAddress); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 50e54699bab6..0a8b33945cdd 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -382,7 +382,7 @@ constexpr std::array DrawPhaseNames{"validate", "vertex enum SyncReason : std::size_t { SyncNone, SyncNotResident, SyncNoRecorder, SyncDisabled, SyncCopiedWrites, SyncLease, SyncCount }; constexpr std::array SyncReasonNames{"none", "non-resident target", "no recorder", "disabled", "copied writes", "lease"}; constexpr std::size_t IndirectPathCount = static_cast(IndirectDrawPath::Count); -constexpr std::array IndirectDrawPathNames{"gpu-side", "patched SGPR not folded", "fetch offset unknown", "non-vertex path", "GE_INDX_OFFSET", "draw index", "vertex range too large", "feature gap", "pending image results", "pending label or copied write", "not imported", "disabled"}; +constexpr std::array IndirectDrawPathNames{"gpu-side", "patched SGPR not folded", "fetch offset unknown", "non-vertex path", "GE_INDX_OFFSET", "draw index", "vertex range too large", "feature gap", "pending image results", "pending label or copied write", "not imported", "disabled", "index range not fully readable"}; // What became of one draw, for the totals. struct DrawOutcome { @@ -967,14 +967,20 @@ DrawInputCopy CopyDrawInput(const Context& context, Recorder* recorder, std::uin return copy; } -DrawInputCopy CopyZeroPaddedDrawInput(const Context& context, std::uint64_t address, std::size_t bytes, std::size_t validBytes) { +DrawInputCopy CopyZeroPaddedDrawInput(const Context& context, std::uint64_t address, std::size_t bytes, std::size_t validBytes, std::size_t stride) { Require(validBytes <= bytes, "the valid bytes of a vertex fetch exceed the fetch"); GuestMemory::FlushGpuWrites(address, bytes); DrawInputCopy copy; copy.buffer = std::make_shared(context, bytes, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); const auto span = copy.buffer->Bytes(); - GuestMemory::Read(address, span.subspan(0, validBytes), 1); - std::fill(span.begin() + static_cast(validBytes), span.end(), std::byte{0}); + auto readable = validBytes; + if (stride != 0 && !GuestMemory::Accessible(reinterpret_cast(address), validBytes)) { + const auto ranges = GuestMemory::CommittedRanges(address, validBytes); + const std::size_t prefix = !ranges.empty() && ranges.front().first == address ? static_cast(ranges.front().second - address) : 0; + if (validBytes - prefix < stride) readable = prefix; + } + GuestMemory::Read(address, span.subspan(0, readable), 1); + std::fill(span.begin() + static_cast(readable), span.end(), std::byte{0}); return copy; } @@ -1251,7 +1257,7 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P for (const auto f : soloFetches) { const auto begin = fetches[f].begin; const auto bytes = static_cast(fetches[f].end - begin); - auto copy = CopyZeroPaddedDrawInput(context, begin, bytes, fetchValid[f]); + auto copy = CopyZeroPaddedDrawInput(context, begin, bytes, fetchValid[f], fetches[f].stride); soloHandles[f] = copy.buffer->Handle(); inputs.vertexBuffers.push_back(std::move(copy.buffer)); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp index 7ba2d0dd83c7..d73dfce3a149 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp @@ -1842,6 +1842,17 @@ void CountLeaseOutcome(bool synced, std::uint64_t batchSerial) { AgcDriver::ProfilePrint_nid_no_patch("[address-sync] leases: %llu released at completion, %llu synced at once; %llu pin-contention waits by guest threads (%llu finished the lease batch, %llu drained the recorder, %llu cache-only drops) %.1f s; BDA table cache %llu hits / %llu misses (%zu tables held); snapshot compares: %llu skipped (live-backed region), %llu made\n", static_cast(stats.deferred), static_cast(stats.synced), static_cast(stats.contentionWaits), static_cast(stats.contentionSyncs), static_cast(stats.contentionDrains), static_cast(stats.cacheDrops), stats.contentionMs / 1000, static_cast(table.hits), static_cast(table.misses), table.held, static_cast(snapshots.skipped.load(std::memory_order_relaxed)), static_cast(snapshots.checked.load(std::memory_order_relaxed))); } +void FinishLeaseWork() { + GuestMemory::AssertGpuLockHeld("FinishLeaseWork"); + const auto& state = Leases(); + auto* recorder = Recorder::Active(); + const auto target = state.newestLeaseSerial; + if (recorder == nullptr || recorder != state.leaseRecorder || target <= recorder->CompletedThrough()) return; + if (target >= recorder->OpenSerial()) recorder->Submit(); + Recorder::AnnounceSyncSite(__builtin_return_address(0)); + recorder->FinishUpTo(target); +} + LeaseStats LeaseCounters() { auto& state = Leases(); std::lock_guard lock(state.mutex); @@ -3377,15 +3388,19 @@ void GuestBufferMemory::recordGpuCopies(std::span copies, bool ad recorder->FlushKeyStoresOverlapping(region->begin, static_cast(region->end - region->begin)); recorder->FlushStoresOverlapping(region->begin, static_cast(region->end - region->begin)); } - const auto commands = recorder->Commands(); + VkAccessFlags covered = 0; + const auto commands = recorder->Commands(&covered); const auto timing = recorder->BeginGpuTiming(StagingCopyInKey); if (Recorder::BarrierValidate()) { std::vector> reads; for (const auto* region : copies) reads.emplace_back(region->begin, region->end); recorder->NoteAccess(Recorder::CommandClass::StagingIn, Recorder::Access{reads, {}, {}, VK_PIPELINE_STAGE_TRANSFER_BIT}); } - RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); - Recorder::CountBarriers(Recorder::CommandClass::StagingIn); + constexpr BarrierMask leading{VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT}; + if (!Recorder::ElideCovered(Recorder::CommandClass::StagingIn, leading, covered)) { + RecordMemoryBarrier(context, commands, leading.srcStage, leading.dstStage, leading.srcAccess, leading.dstAccess); + Recorder::CountBarriers(Recorder::CommandClass::StagingIn); + } bool stagedAny = false; std::uint64_t copiedBytes = 0; for (auto* region : copies) { @@ -3496,11 +3511,15 @@ void GuestBufferMemory::RecordCopyBacks(Recorder& recorder) { const auto to = std::min(end, region.end); if (from >= to) continue; if (!recording) { - commands = recorder.Commands(); + VkAccessFlags covered = 0; + commands = recorder.Commands(&covered); timing = recorder.BeginGpuTiming(StagingCopyBackKey); // The shader's stores into the buffer (whichever stage made them) precede the copy. - RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); - Recorder::CountBarriers(Recorder::CommandClass::StagingOut); + constexpr BarrierMask leading{VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT}; + if (!Recorder::ElideCovered(Recorder::CommandClass::StagingOut, leading, covered)) { + RecordMemoryBarrier(context, commands, leading.srcStage, leading.dstStage, leading.srcAccess, leading.dstAccess); + Recorder::CountBarriers(Recorder::CommandClass::StagingOut); + } recording = true; } // Exactly the sub-range the shader may write, in place in the import: what the shader diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp index 1f7c2c59c78e..172af7efd43b 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp @@ -13,6 +13,7 @@ #include "prx/libSceAgcDriver/Execution/include/Pm4Opcodes.hpp" #include "prx/libc/include/CpuTopology.hpp" #include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libSceAgcDriver/Graphics/shaders/DispatchArguments_spv.h" #include #include #include @@ -504,6 +505,8 @@ void ReleaseDeferredKeeps() { // Lock-free state of the active recorder's open batch, read by the queue workers between packets // and inside WAIT_REG_MEM polls (see the static readers in Recorder.hpp). Written under the mutex. constexpr std::int64_t NoPendingLabel = std::numeric_limits::min(); +constexpr BarrierMask PassEndBarrier{VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT}; +constexpr BarrierMask LabelLeadingBarrier{VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT}; std::atomic pendingLabelSince{NoPendingLabel}; std::atomic writeGeneration{0}; std::atomic publishGeneration{0}; @@ -1171,6 +1174,8 @@ Recorder::~Recorder() { context.Function("vkDestroyPipelineLayout")(context.device, dumpLayout, nullptr); sampleCounter.reset(); } + if (dispatchArgumentPipeline != VK_NULL_HANDLE) context.Function("vkDestroyPipeline")(context.device, dispatchArgumentPipeline, nullptr); + if (dispatchArgumentLayout != VK_NULL_HANDLE) context.Function("vkDestroyPipelineLayout")(context.device, dispatchArgumentLayout, nullptr); for (auto& [commands, fence] : spare) { context.Function("vkFreeCommandBuffers")(context.device, context.pool, 1, &commands); context.Function("vkDestroyFence")(context.device, fence, nullptr); @@ -1417,7 +1422,7 @@ void Recorder::endOpenRenderPass(bool barrier) { // The pass's attachment and shader writes are visible to everything recorded after it (the // host sees them at the batch's fence). if (barrier) { - recordBarrier(open->commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); + recordBarrier(open->commands, PassEndBarrier.srcStage, PassEndBarrier.dstStage, PassEndBarrier.srcAccess, PassEndBarrier.dstAccess); CountBarriers(CommandClass::Draw); } if (pass.afterPass) pass.afterPass(open->commands); @@ -1630,6 +1635,7 @@ bool Recorder::closeStoreRun(bool atSubmit) { // A label says the work before it is done: the queued key stores (results of that work) // land ahead of every store of the run, and the pass a draw left open ends. if (!open->keyStores.empty()) recordKeyStores(true); + const bool passEndLast = open->renderPass.open && !open->renderPass.afterPass; if (open->renderPass.open) endOpenRenderPass(); const auto commands = open->commands; run.timing = beginTiming(ClassKey(CommandClass::LabelRun)); @@ -1640,9 +1646,15 @@ bool Recorder::closeStoreRun(bool atSubmit) { } // At Submit every result of the batch reaches the host before the stores (the title's // pollers read results after the label); in place, later work sees the stores as well. - if (atSubmit) recordBarrier(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_READ_BIT); - else recordBarrier(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT); std::uint32_t barriers = 2; + if (atSubmit) { + recordBarrier(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_READ_BIT); + } else if (passEndLast && BarrierBatch() && Covers(PassEndBarrier, LabelLeadingBarrier)) { + CountMerged(CommandClass::LabelRun); + barriers = 1; + } else { + recordBarrier(commands, LabelLeadingBarrier.srcStage, LabelLeadingBarrier.dstStage, LabelLeadingBarrier.srcAccess, LabelLeadingBarrier.dstAccess); + } std::uint64_t storedBytes = 0; const auto update = function(cmdUpdateBuffer, "vkCmdUpdateBuffer"); std::vector writes; @@ -1826,6 +1838,17 @@ bool Recorder::MergeBarriers() { return merge; } +bool Recorder::BarrierBatch() { + static const bool batch = MergeBarriers() && std::getenv("APS5_NO_BARRIER_BATCH") == nullptr; + return batch; +} + +bool Recorder::ElideCovered(CommandClass which, const BarrierMask& wanted, VkAccessFlags covered) { + if (!BarrierBatch() || !OrderedByTrailing(covered, wanted)) return false; + CountMerged(which); + return true; +} + void Recorder::CountMerged(CommandClass which) { if (!DrawOrGpuProfiled()) return; classMerged[static_cast(which)].fetch_add(1, std::memory_order_relaxed); @@ -1942,6 +1965,52 @@ void Recorder::EndGpuTiming(std::uint32_t index, std::uint64_t bytes) { context.Function("vkCmdWriteTimestamp")(open->commands, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, open->queries, index * 2 + 1); } +bool Recorder::RecordDispatchArguments(VkCommandBuffer commands, VkDeviceAddress source, VkDeviceAddress arguments) { + constexpr std::uint32_t PushBytes = 32; + if (dispatchArgumentState == 0) { + dispatchArgumentState = -1; + VkShaderModule module = VK_NULL_HANDLE; + try { + const VkPushConstantRange push{VK_SHADER_STAGE_COMPUTE_BIT, 0, PushBytes}; + VkPipelineLayoutCreateInfo layoutInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + layoutInfo.pushConstantRangeCount = 1; + layoutInfo.pPushConstantRanges = &push; + Check(context.Function("vkCreatePipelineLayout")(context.device, &layoutInfo, nullptr, &dispatchArgumentLayout), "vkCreatePipelineLayout dispatch arguments"); + VkShaderModuleCreateInfo moduleInfo{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; + moduleInfo.codeSize = sizeof(DISPATCH_ARGUMENTS_SPV); + moduleInfo.pCode = DISPATCH_ARGUMENTS_SPV; + Check(context.Function("vkCreateShaderModule")(context.device, &moduleInfo, nullptr, &module), "vkCreateShaderModule dispatch arguments"); + VkComputePipelineCreateInfo pipelineInfo{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO}; + pipelineInfo.stage = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO}; + pipelineInfo.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT; + pipelineInfo.stage.module = module; + pipelineInfo.stage.pName = "main"; + pipelineInfo.layout = dispatchArgumentLayout; + Check(context.Function("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &dispatchArgumentPipeline), "vkCreateComputePipelines dispatch arguments"); + context.Function("vkDestroyShaderModule")(context.device, module, nullptr); + dispatchArgumentState = 1; + } catch (const std::exception& error) { + if (module != VK_NULL_HANDLE) context.Function("vkDestroyShaderModule")(context.device, module, nullptr); + std::fprintf(stderr, "[gpu] indirect dispatches read their group counts unchecked: %s\n", error.what()); + } + } + if (dispatchArgumentState < 0) return false; + recordBarrier(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); + function(cmdBindPipeline, "vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, dispatchArgumentPipeline); + const auto* limit = context.limits.maxComputeWorkGroupCount; + struct { + VkDeviceAddress source; + VkDeviceAddress arguments; + std::array limits; + } parameters{source, arguments, {limit[0], limit[1], limit[2], 0u}}; + static_assert(sizeof(parameters) == PushBytes); + function(cmdPushConstants, "vkCmdPushConstants")(commands, dispatchArgumentLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, PushBytes, ¶meters); + function(cmdDispatch, "vkCmdDispatch")(commands, 1, 1, 1); + recordBarrier(commands, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_INDIRECT_COMMAND_READ_BIT); + CountBarriers(CommandClass::DispatchLeading, 2); + return true; +} + void Recorder::CountSamples() { GuestMemory::AssertGpuLockHeld("Recorder::CountSamples"); gpuSampleCounter(); @@ -2748,6 +2817,17 @@ bool Recorder::OpenWriteOverlaps(std::uint64_t address, std::size_t bytes) const return bytes != 0 && open != nullptr && overlaps(*open, address, address + bytes); } +std::uint64_t Recorder::PendingWriterSerial(std::uint64_t address, std::size_t bytes) const { + if (bytes == 0) return 0; + const auto end = address + bytes; + const auto writes = [&](const Batch& batch) { return overlaps(batch, address, end) || readOverlap(batch, address, end, ReadKind::AddressBased).has_value(); }; + if (open != nullptr && writes(*open)) return OpenSerial(); + for (auto it = inFlight.rbegin(); it != inFlight.rend(); ++it) { + if (writes(**it)) return (*it)->serial; + } + return 0; +} + bool Recorder::signaled(const Batch& batch) const { return batch.submitted && fenceStatus(batch.fence) == VK_SUCCESS; } @@ -2840,8 +2920,9 @@ void Recorder::NotePendingReadList(std::shared_ptr ranges, ReadK open->readLists.push_back({std::move(ranges), open->reads.size(), kind}); } -std::optional Recorder::readOverlap(const Batch& batch, std::uint64_t address, std::uint64_t end) { +std::optional Recorder::readOverlap(const Batch& batch, std::uint64_t address, std::uint64_t end, std::optional only) { const auto listOverlaps = [&](const Batch::ReadListNote& note) { + if (only.has_value() && note.kind != *only) return false; const auto& list = *note.ranges; const auto found = std::partition_point(list.begin(), list.end(), [&](const auto& range) { return range.second <= address; }); return found != list.end() && found->first < end; @@ -2852,7 +2933,7 @@ std::optional Recorder::readOverlap(const Batch& batch, std: if (listOverlaps(batch.readLists[next])) return batch.readLists[next].kind; } const auto& read = batch.reads[index]; - if (address < read.end && read.begin < end) return read.kind; + if ((!only.has_value() || read.kind == *only) && address < read.end && read.begin < end) return read.kind; } for (; next < batch.readLists.size(); ++next) { if (listOverlaps(batch.readLists[next])) return batch.readLists[next].kind; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp index bb49ff42688b..26bf4ac81e13 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp @@ -333,7 +333,9 @@ CommandBatch::CommandBatch(const Context& context) : context(context) { allocation.commandBufferCount = 1; Check(context.Function("vkAllocateCommandBuffers")(context.device, &allocation, &commands), "vkAllocateCommandBuffers"); VkFenceCreateInfo info{VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; - Check(context.Function("vkCreateFence")(context.device, &info, nullptr, &fence), "vkCreateFence"); + VkFence created = VK_NULL_HANDLE; + Check(context.Function("vkCreateFence")(context.device, &info, nullptr, &created), "vkCreateFence"); + fence = created; VkCommandBufferBeginInfo begin{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; Check(context.Function("vkBeginCommandBuffer")(commands, &begin), "vkBeginCommandBuffer"); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp index 1d02d8aa1e86..0bd36dd79869 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp @@ -36,6 +36,7 @@ struct Module { std::map decorations; std::map, std::uint32_t> builtins; std::map, std::uint32_t> offsets; + std::set> perPrimitiveMembers; std::map variables; std::map constants; std::set interface; @@ -357,6 +358,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi auto& fields = instruction[3] == spv::DecorationBuiltIn ? module.builtins : module.offsets; Require(fields.emplace(std::make_pair(instruction[1], instruction[2]), instruction[4]).second, "duplicate SPIR-V member decoration"); } + if (instruction[3] == spv::DecorationPerPrimitiveEXT) module.perPrimitiveMembers.emplace(instruction[1], instruction[2]); break; case spv::OpTypeBool: case spv::OpTypeInt: @@ -448,7 +450,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi const auto length = module.constants.find(outer[3]); Require(length != module.constants.end() && length->second != 0, "invalid per-vertex interface array length"); if (mesh) { - const bool primitive = decoration.perPrimitive || (decoration.builtin && (*decoration.builtin == spv::BuiltInPrimitiveTriangleIndicesEXT || *decoration.builtin == spv::BuiltInCullPrimitiveEXT)); + const bool primitive = decoration.perPrimitive || module.perPrimitiveMembers.contains({outer[2], 0u}) || (decoration.builtin && (*decoration.builtin == spv::BuiltInPrimitiveLineIndicesEXT || *decoration.builtin == spv::BuiltInPrimitiveTriangleIndicesEXT || *decoration.builtin == spv::BuiltInCullPrimitiveEXT)); Require(length->second == (primitive ? state.stages.mesh->maxPrimitives : state.stages.mesh->maxVertices), "mesh interface array disagrees with output limits"); } else if (geometry) { Require(length->second == 3u, "geometry interface array must hold the three vertices of a triangle"); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index c218281d799f..5c2c8fbf7216 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -16,6 +16,14 @@ #include namespace AgcDriver::Graphics { + +void NormalizeUnusedViewportDepth(State& state, const ShaderRecompiler::ShaderPixelStageInfo& pixel) { + if (state.depth || pixel.posZ) return; + if (!std::isfinite(state.viewport.minDepth) || !std::isfinite(state.viewport.maxDepth)) return; + state.viewport.minDepth = std::clamp(state.viewport.minDepth, 0.0f, 1.0f); + state.viewport.maxDepth = std::clamp(state.viewport.maxDepth, 0.0f, 1.0f); +} + namespace { std::uint32_t read(const Registers& registers, std::uint32_t offset, RegisterBank bank = RegisterBank::Context) { @@ -91,7 +99,7 @@ VkConservativeRasterizationModeEXT decodeConservativeRasterization(const QueueSt const auto primitive = read(queue.userConfig, 0x242, RegisterBank::UserConfig); requireConservativeTriangles(primitive == 4u || primitive == 5u || primitive == 6u || primitive == 21u, primitive == 1u ? "points" : primitive == 2u || primitive == 3u ? "lines" : primitive == 7u || primitive == 17u ? "rectangles" : "other primitive types", "VGT_PRIMITIVE_TYPE", primitive); } - Require((read(cx, 0x1b3) & 0x44u) == 0, "conservative rasterization with centroid interpolation is unsupported"); + Require((read(cx, 0x1b3) & 0x44u) == 0 || (read(cx, 0x2f8) & 7u) == 0, "conservative rasterization with centroid interpolation of more than one rasterized sample is unsupported"); return VK_CONSERVATIVE_RASTERIZATION_MODE_OVERESTIMATE_EXT; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index 0461810da7f3..4e0fa2148c17 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -1032,28 +1032,48 @@ namespace { class PendingList { public: using iterator = std::vector::iterator; + using Ranges = std::vector>; iterator begin() { return textures.begin(); } iterator end() { return textures.end(); } void push_back(StorageTexture* texture) { textures.push_back(texture); - ++version; + changed(); } iterator erase(iterator it) { - ++version; - return textures.erase(it); + const auto next = textures.erase(it); + changed(); + return next; } void remove(const StorageTexture* texture) { - if (std::erase(textures, texture) != 0) ++version; + if (std::erase(textures, texture) != 0) changed(); } bool MayOverlap(std::uint64_t address, std::size_t bytes) { - if (textures.empty() || bytes == 0) return false; if (indexed != version) rebuild(); + return !textures.empty() && Overlaps(ranges, address, bytes); + } + bool SurelyClear(std::uint64_t address, std::size_t bytes) const { + const auto current = published.load(std::memory_order_acquire); + return current != nullptr && current->version == changes.load(std::memory_order_acquire) && !Overlaps(current->ranges, address, bytes); + } + +private: + struct Snapshot { + std::uint64_t version = 0; + Ranges ranges; + }; + + static bool Overlaps(const Ranges& ranges, std::uint64_t address, std::size_t bytes) { + if (bytes == 0) return false; const auto end = bytes > std::numeric_limits::max() - address ? std::numeric_limits::max() : address + bytes; const auto it = std::upper_bound(ranges.begin(), ranges.end(), address, [](std::uint64_t value, const std::pair& range) { return value < range.second; }); return it != ranges.end() && it->first < end; } -private: + void changed() { + ++version; + changes.store(version, std::memory_order_release); + } + void rebuild() { ranges.clear(); for (const auto* texture : textures) ranges.emplace_back(texture->Descriptor().baseAddress, texture->Descriptor().baseAddress + texture->GuestBytes()); @@ -1065,11 +1085,14 @@ class PendingList { } ranges.resize(merged); indexed = version; + published.store(std::make_shared(Snapshot{version, ranges}), std::memory_order_release); } std::vector textures; - std::vector> ranges; + Ranges ranges; std::uint64_t version = 0; std::uint64_t indexed = ~std::uint64_t{0}; + std::atomic changes{0}; + std::atomic> published; }; // Storage images whose results have not reached guest memory yet. @@ -2766,7 +2789,9 @@ std::uint64_t StorageTexture::writeBackWindows(const HostImport& import, std::sp toRead.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; toRead.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; toRead.newLayout = VK_IMAGE_LAYOUT_GENERAL; - context.Function("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 1, &importReady, 0, nullptr, 1, &toRead); + const BarrierMask ready{VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, importReady.srcAccessMask | toRead.srcAccessMask, importReady.dstAccessMask | toRead.dstAccessMask, toRead.oldLayout != toRead.newLayout, toRead.srcQueueFamilyIndex != toRead.dstQueueFamilyIndex}; + const bool ordered = recorder != nullptr && seeds.empty() && padding.copies.empty() && Recorder::ElideCovered(Recorder::CommandClass::StorageWriteBack, ready, covered); + if (!ordered) context.Function("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 1, &importReady, 0, nullptr, 1, &toRead); for (std::size_t i = 0; i < windows.size(); ++i) { const auto& window = windows[i]; auto retile = window.window; @@ -3046,6 +3071,20 @@ bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, cons for (const auto& texture : flush) std::fprintf(stderr, "[flush] image 0x%llx+0x%llx for %s 0x%llx+0x%zx\n", static_cast(texture->descriptor.baseAddress), static_cast(texture->guestBytes), reason, static_cast(address), bytes); } std::lock_guard gpu(GuestMemory::GpuMutex()); + if (auto* recorder = Recorder::Active(); recorder != nullptr && !Recorder::InCompletion() && recorder->PendingWriterSerial(address, bytes) != 0) { + try { + recorder->Reap(); + if (const auto writer = recorder->PendingWriterSerial(address, bytes); writer == recorder->OpenSerial()) { + Recorder::CountSync(recorder->OpenWriteOverlaps(address, bytes) ? 1 : 3); + recorder->Sync(); + } else if (writer != 0) { + recorder->FinishUpTo(writer); + } + } catch (...) { + for (const auto& texture : flush) texture->reconcilePending(); + throw; + } + } std::exception_ptr failure; const auto previousReason = std::exchange(flushReason, reason); for (const auto& texture : flush) { @@ -3168,6 +3207,7 @@ std::vector> StorageTexture::overlappingPending( // FlushPending's listing; the images stay alive across the caller's scan as there. std::vector> overlapping; auto& pending = Pending(); + if (pending.textures.SurelyClear(address, bytes)) return overlapping; std::lock_guard lock(pending.mutex); if (!pending.textures.MayOverlap(address, bytes)) return overlapping; const auto* exempt = refreshing; @@ -4441,7 +4481,8 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { overhang = covered >= end; } } - if (unregisteredDrop && !overhang && !RegisteredReadableCovers(descriptor.baseAddress, static_cast(guestBytes))) { + const bool registered = std::all_of(keep.begin(), keep.end(), [](const auto& range) { return RegisteredReadableCovers(range.first, static_cast(range.second - range.first)); }); + if (unregisteredDrop && !overhang && !registered) { static std::atomic reports{0}; if (reports.fetch_add(1) < 4) std::fprintf(stderr, "[gpu] storage image 0x%llx+0x%llx: memory no longer registered; %zu pending ranges dropped\n", static_cast(descriptor.baseAddress), static_cast(guestBytes), keep.size()); unregisteredDropped.fetch_add(1, std::memory_order_relaxed); @@ -4457,7 +4498,17 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { // stale unit (dropped, or kept for the CPU) still differs from them, so `original` vouches for // the content again only where it did before, or once every unit is stored. const bool wasValid = originalValid; - if (!wasValid) GuestMemory::ReadCommitted(descriptor.baseAddress, original); + if (!wasValid) { + if (registered) { + GuestMemory::ReadCommitted(descriptor.baseAddress, original); + } else { + for (const auto& [from, to] : keep) { + const auto first = std::max(descriptor.baseAddress, from); + const auto last = std::min(descriptor.baseAddress + guestBytes, to); + if (first < last) GuestMemory::ReadCommitted(first, std::span(original).subspan(static_cast(first - descriptor.baseAddress), static_cast(last - first))); + } + } + } DeviceBuffer linear(context, static_cast(sliceLinearBytes * arrayLayers), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); DeviceBuffer tiled(context, original.size(), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); Buffer host(context, original.size(), VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); diff --git a/core/libs/prx/libSceAgcDriver/Submit/include/CommandBuffer.hpp b/core/libs/prx/libSceAgcDriver/Submit/include/CommandBuffer.hpp index ef4b9e689444..52fb4cb31d91 100644 --- a/core/libs/prx/libSceAgcDriver/Submit/include/CommandBuffer.hpp +++ b/core/libs/prx/libSceAgcDriver/Submit/include/CommandBuffer.hpp @@ -1,4 +1,16 @@ #ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_SUBMIT_INCLUDE_COMMANDBUFFER_HPP #define CORE_LIBS_PRX_LIBSCEAGCDRIVER_SUBMIT_INCLUDE_COMMANDBUFFER_HPP +#include "SceTypes.hpp" +#include + +struct Packet; + +extern "C" { + +int APS5_VABI sceAgcDriverSubmitCommandBuffer(void* queue_context, const Packet* packet); +int APS5_VABI sceAgcDriverSubmitMultiCommandBuffers(void* queue_context, uint32_t* const* command_buffers, const uint32_t* sizes_in_dwords, uint32_t count); + +} + #endif diff --git a/core/libs/prx/libSceAgcDriver/Submit/src/CommandBuffer.cpp b/core/libs/prx/libSceAgcDriver/Submit/src/CommandBuffer.cpp index 646827ca1138..7b326f7f9d92 100644 --- a/core/libs/prx/libSceAgcDriver/Submit/src/CommandBuffer.cpp +++ b/core/libs/prx/libSceAgcDriver/Submit/src/CommandBuffer.cpp @@ -1,26 +1,35 @@ #include "prx/libSceAgcDriver/Submit/include/CommandBuffer.hpp" +#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include #include #include +#include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" extern "C" { int APS5_VABI sceAgcDriverSubmitCommandBuffer(void* queue_context, const Packet* packet) { - (void)queue_context; - (void)packet; - NotImplemented_nid_no_patch(__func__); - return 0; + if (!queue_context || !packet) return EINVAL; + const auto queue = *reinterpret_cast(static_cast(queue_context) + 4); + if (queue != 0 && (queue < 0x20 || queue >= 0x58)) { + throw std::runtime_error(std::string(__func__) + ": unsupported queue"); + } + AgcDriver::Submit(packet, queue); + return 0; } int APS5_VABI sceAgcDriverSubmitMultiCommandBuffers(void* queue_context, uint32_t* const* command_buffers, const uint32_t* sizes_in_dwords, uint32_t count) { - (void)queue_context; - (void)command_buffers; - (void)sizes_in_dwords; - (void)count; - NotImplemented_nid_no_patch(__func__); - return 0; + if (count == 0) return 0; + if (!queue_context || !command_buffers || !sizes_in_dwords) return EINVAL; + const auto queue = *reinterpret_cast(static_cast(queue_context) + 4); + if (queue != 0 && (queue < 0x20 || queue >= 0x58)) { + throw std::runtime_error(std::string(__func__) + ": unsupported queue"); + } + return AgcDriver::SubmitMany(command_buffers, sizes_in_dwords, count, queue); } } diff --git a/core/libs/prx/libSceAgcDriver/tests/BarrierBatch.cpp b/core/libs/prx/libSceAgcDriver/tests/BarrierBatch.cpp new file mode 100644 index 000000000000..440bf1bde39f --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/BarrierBatch.cpp @@ -0,0 +1,113 @@ +#include "prx/libSceAgcDriver/Graphics/include/BarrierBatch.hpp" +#include + +using namespace AgcDriver::Graphics; + +namespace { + +int failures = 0; + +void Expect(bool condition, const char* what) { + if (condition) return; + std::fprintf(stderr, "FAIL: %s\n", what); + ++failures; +} + +constexpr VkPipelineStageFlags All = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; +constexpr VkPipelineStageFlags Transfer = VK_PIPELINE_STAGE_TRANSFER_BIT; +constexpr VkPipelineStageFlags Compute = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; +constexpr VkPipelineStageFlags Host = VK_PIPELINE_STAGE_HOST_BIT; +constexpr VkAccessFlags SW = VK_ACCESS_SHADER_WRITE_BIT; +constexpr VkAccessFlags SR = VK_ACCESS_SHADER_READ_BIT; +constexpr VkAccessFlags TW = VK_ACCESS_TRANSFER_WRITE_BIT; +constexpr VkAccessFlags TR = VK_ACCESS_TRANSFER_READ_BIT; +constexpr VkAccessFlags HW = VK_ACCESS_HOST_WRITE_BIT; +constexpr VkAccessFlags HR = VK_ACCESS_HOST_READ_BIT; +constexpr VkAccessFlags CAW = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; +constexpr VkAccessFlags DSW = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; +constexpr VkAccessFlags Full = SR | SW | TR | TW; + +const BarrierMask PassEnd{All, All, SW | CAW | DSW | TW, Full}; +const BarrierMask LabelLeading{All, Transfer, SW | TW | CAW, TW}; +const BarrierMask StagingInLeading{All | Host, Transfer, SW | TW | HW, TR}; +const BarrierMask StagingOutLeading{All, Transfer, SW, TR}; +const BarrierMask WriteBackReady{All | Host, Compute, SR | SW | TR | TW | HW | CAW, SR | SW}; + +void testCoversStages() { + Expect(Covers(PassEnd, LabelLeading), "a pass end covers the label run's leading barrier"); + Expect(!Covers(LabelLeading, PassEnd), "the leading barrier does not cover the pass end"); + Expect(Covers(PassEnd, PassEnd), "a barrier covers itself"); + Expect(Covers(BarrierMask{}, BarrierMask{}), "the empty barrier covers the empty one"); + Expect(Covers(PassEnd, StagingOutLeading), "ALL_COMMANDS holds a transfer destination and a shader write"); + Expect(!Covers(PassEnd, StagingInLeading), "ALL_COMMANDS does not hold the host stage or a host write"); + Expect(Covers(StagingInLeading, StagingOutLeading), "a wider source covers the narrower one"); + const BarrierMask computeOnly{Compute, Compute, SW, SR}; + Expect(!Covers(computeOnly, PassEnd), "a compute-only barrier does not cover ALL_COMMANDS"); + Expect(!Covers(computeOnly, StagingOutLeading), "a compute destination does not hold a transfer destination"); +} + +void testCoversAccess() { + const BarrierMask noColor{All, All, SW | TW, Full}; + Expect(!Covers(noColor, LabelLeading), "a source without the colour attachment write does not cover a barrier that asks for it"); + const BarrierMask readOnlyDestination{All, All, SW | CAW | DSW | TW, SR | SW}; + Expect(!Covers(readOnlyDestination, LabelLeading), "a destination without the transfer write does not cover a barrier that asks for it"); + const BarrierMask same{All, Transfer, SW | TW | CAW, TW}; + Expect(Covers(same, LabelLeading) && Covers(LabelLeading, same), "equal barriers cover each other"); +} + +void testCoversImage() { + BarrierMask transition = LabelLeading; + transition.changesLayout = true; + Expect(!Covers(PassEnd, transition), "a barrier that changes a layout is never covered by a memory barrier"); + BarrierMask transfer = LabelLeading; + transfer.transfersQueue = true; + Expect(!Covers(PassEnd, transfer), "a queue family ownership transfer is never covered"); + BarrierMask sameLayout = LabelLeading; + Expect(Covers(PassEnd, sameLayout), "an image barrier that keeps the layout and the queue is covered like a memory barrier"); +} + +void testOrderedByTrailing() { + constexpr VkAccessFlags dispatched = Full | HR; + constexpr VkAccessFlags filled = Full | VK_ACCESS_INDIRECT_COMMAND_READ_BIT | HR; + Expect(OrderedByTrailing(dispatched, StagingInLeading), "a dispatch's trailing barrier orders the staging copy-in"); + Expect(OrderedByTrailing(dispatched, StagingOutLeading), "a dispatch's trailing barrier orders the staging copy-out"); + Expect(OrderedByTrailing(filled, WriteBackReady), "a fill's trailing barrier orders the write-back's import-ready barrier"); + Expect(OrderedByTrailing(Full, LabelLeading), "the pass end's destination accesses order the label run"); + Expect(!OrderedByTrailing(0, StagingInLeading), "an intervening command clears the covered accesses, so the barrier is kept"); + Expect(!OrderedByTrailing(0, StagingOutLeading) && !OrderedByTrailing(0, WriteBackReady), "no trailing barrier, no elision"); + Expect(!OrderedByTrailing(SR | SW, StagingInLeading), "a shader-only trailing barrier does not order a transfer read"); + Expect(!OrderedByTrailing(SR | SW | TW, StagingInLeading), "a trailing barrier without the transfer read is kept"); + constexpr VkAccessFlags stagedIn = SR | VK_ACCESS_UNIFORM_READ_BIT | SW; + Expect(!OrderedByTrailing(stagedIn, StagingInLeading), "the staging copy-in's own trailing barrier does not order the next copy-in"); + Expect(!OrderedByTrailing(Full, BarrierMask{All, Transfer, SW, TR | VK_ACCESS_INDIRECT_COMMAND_READ_BIT}), "a destination access the trailing barrier lacks is kept"); +} + +void testHostAndImage() { + BarrierMask toHost = StagingOutLeading; + toHost.dstStage |= Host; + toHost.dstAccess |= HR; + Expect(!OrderedByTrailing(Full | HR, toHost), "a barrier that makes writes visible to the host is kept"); + BarrierMask ready = WriteBackReady; + Expect(OrderedByTrailing(Full | HR, ready), "an image barrier that keeps its layout is elided like a memory barrier"); + ready.changesLayout = true; + Expect(!OrderedByTrailing(Full | HR, ready), "an image barrier that changes the layout is kept"); + ready.changesLayout = false; + ready.transfersQueue = true; + Expect(!OrderedByTrailing(Full | HR, ready), "a queue family transfer is kept"); +} + +} + +int main() { + testCoversStages(); + testCoversAccess(); + testCoversImage(); + testOrderedByTrailing(); + testHostAndImage(); + if (failures != 0) { + std::fprintf(stderr, "%d barrier batch checks failed\n", failures); + return 1; + } + std::printf("barrier batch tests passed\n"); + return 0; +} diff --git a/core/libs/prx/libSceAgcDriver/tests/F16SingleConvert.cpp b/core/libs/prx/libSceAgcDriver/tests/F16SingleConvert.cpp new file mode 100644 index 000000000000..affffdd32ccf --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/F16SingleConvert.cpp @@ -0,0 +1,93 @@ +#include "BdaAbi.hpp" +#include "Recompiler.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using namespace ShaderRecompiler; + +void Check(bool condition, const std::string& message) { + if (!condition) throw std::runtime_error(message); +} + +SpirvTarget Target(bool nativeFloat16) { + static constexpr std::array plain{ + spv::CapabilityInt64, spv::CapabilityFloat64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess, spv::CapabilityClipDistance, + }; + static constexpr std::array native{ + spv::CapabilityInt64, spv::CapabilityFloat64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess, spv::CapabilityClipDistance, + spv::CapabilityFloat16, spv::CapabilityRoundingModeRTE, spv::CapabilityDenormPreserve, + }; + static constexpr std::array extensions{ + "SPV_EXT_descriptor_indexing", "SPV_KHR_8bit_storage", "SPV_KHR_float_controls", "SPV_KHR_physical_storage_buffer", + }; + if (nativeFloat16) return {0x00401000u, 0x00010300u, 32, BdaAbi::Version, native, extensions, false, {1024, 1024, 64}, 1024, 32768, {}, {}}; + return {0x00401000u, 0x00010300u, 32, BdaAbi::Version, plain, extensions, false, {1024, 1024, 64}, 1024, 32768, {}, {}}; +} + +std::size_t ZeroPairs(std::span words) { + Check(words.size() >= 5 && words[0] == spv::MagicNumber, "f16 single convert: missing SPIR-V module"); + std::unordered_set floats; + std::unordered_set pairs; + std::unordered_set zeros; + std::size_t count = 0; + for (std::size_t offset = 5; offset < words.size();) { + const auto length = words[offset] >> spv::WordCountShift; + Check(length != 0 && length <= words.size() - offset, "f16 single convert: malformed SPIR-V instruction"); + const auto opcode = words[offset] & spv::OpCodeMask; + if (opcode == spv::OpTypeFloat && length == 3u && words[offset + 2u] == 32u) floats.insert(words[offset + 1u]); + if (opcode == spv::OpTypeVector && length == 4u && floats.contains(words[offset + 2u]) && words[offset + 3u] == 2u) pairs.insert(words[offset + 1u]); + if (opcode == spv::OpConstant && length == 4u && floats.contains(words[offset + 1u]) && words[offset + 3u] == 0u) zeros.insert(words[offset + 2u]); + if (opcode == spv::OpConstantNull && length == 3u && floats.contains(words[offset + 1u])) zeros.insert(words[offset + 2u]); + if (opcode == spv::OpCompositeConstruct && length == 5u && pairs.contains(words[offset + 1u]) && zeros.contains(words[offset + 4u])) ++count; + offset += length; + } + return count; +} + +std::size_t Compile(bool nativeFloat16) { + std::vector code{0xe0300000u, 0x80010100u, 0xbf8c3f70u}; + for (std::uint32_t op = 0; op < 4u; ++op) code.push_back(0x7e04a900u | (256u + (op == 0u ? 1u : 2u))); + code.insert(code.end(), {0xe0700000u, 0x80010200u, 0xbf810000u}); + const std::span words(code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(words)}}}; + const std::array userData{0u, 0u, 0u, 0u, 0x10000000u, 0u, 0x1000u, 0x31016facu}; + RecompileRequest request{}; + request.shader = {ShaderStage::Compute, reinterpret_cast(code.data()), words, 0, {}}; + request.target = Target(nativeFloat16); + request.layout = {0, 0, 0, 128}; + request.context.waveSize = 32; + request.context.userDataBaseRegister = 0; + request.context.userData = userData; + request.context.compute = ShaderComputeStageInfo{{32u, 1u, 1u}, 0u, {false, false, false}, false, 1u}; + request.context.memory = memory; + request.useCache = false; + return ZeroPairs(Recompile(request).spirv.Words()); +} + +} + +int main() { + try { + for (const bool nativeFloat16 : {false, true}) { + const auto pairs = Compile(nativeFloat16); + Check(pairs == 0u, std::string("f16 single convert: four v_rcp_f16 emitted ") + std::to_string(pairs) + " (x, 0.0) f32 pairs to convert one f16 each" + (nativeFloat16 ? " with native f16" : " without native f16")); + } + std::puts("f16 single convert tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 04634d6432b4..0a197cfe1b13 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -2,6 +2,7 @@ #include "GraphicsTests.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" #include "prx/libSceAgcDriver/Graphics/include/Pipeline.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp" #include "prx/libSceAgcDriver/Graphics/include/VertexInput.hpp" @@ -23,6 +24,11 @@ #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/GuestHeap.hpp" #include +#ifdef _WIN32 +#include +#else +#include +#endif #include #include #include @@ -30,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -2631,6 +2638,52 @@ void quadPixelMaskTests() { Require(AgcDriver::Graphics::DecodePixelStageInfo(queue.context, {}).quadPixelMask == 0xfu, "full sample masks did not cover the whole quad"); } +void UnusedViewportDepthTests() { + auto queue = makeState(); + queue.context[0x204] = 0; + queue.context[0x113] = std::bit_cast(1.0f); + queue.context[0x114] = 0; + const auto decoded = AgcDriver::Graphics::DecodeState(queue); + Require(!decoded.depth && decoded.viewport.minDepth == -1 && decoded.viewport.maxDepth == 1, "unused depth fixture changed"); + AgcDriver::Graphics::Context context{}; + context.limits.maxViewportDimensions[0] = context.limits.maxViewportDimensions[1] = 16384; + context.limits.viewportBoundsRange[0] = -32768; + context.limits.viewportBoundsRange[1] = 32767; + ShaderRecompiler::ShaderPixelStageInfo pixel{}; + pixel.posX = pixel.posY = pixel.posW = true; + auto color = decoded; + AgcDriver::Graphics::NormalizeUnusedViewportDepth(color, pixel); + AgcDriver::Graphics::ValidateViewport(context, color.viewport); + Require(color.viewport.minDepth == 0 && color.viewport.maxDepth == 1, "unused viewport depth is not representable"); + Require(color.viewport.x == decoded.viewport.x && color.viewport.y == decoded.viewport.y && color.viewport.width == decoded.viewport.width && color.viewport.height == decoded.viewport.height && color.negativeOneToOne == decoded.negativeOneToOne && color.depthClamp == decoded.depthClamp, "unused depth changed coverage or clipping"); + auto observed = decoded; + pixel.posZ = true; + AgcDriver::Graphics::NormalizeUnusedViewportDepth(observed, pixel); + Require(observed.viewport.minDepth == -1, "fragment position depth was changed"); + expectFailure([&] { AgcDriver::Graphics::ValidateViewport(context, observed.viewport); }, "viewport depth outside"); + pixel.posZ = false; + auto attached = decoded; + attached.depth.emplace(); + AgcDriver::Graphics::NormalizeUnusedViewportDepth(attached, pixel); + Require(attached.viewport.minDepth == -1, "attached depth was changed"); + expectFailure([&] { AgcDriver::Graphics::ValidateViewport(context, attached.viewport); }, "viewport depth outside"); + auto reversed = decoded; + reversed.viewport.minDepth = 2; + reversed.viewport.maxDepth = -1; + AgcDriver::Graphics::NormalizeUnusedViewportDepth(reversed, pixel); + AgcDriver::Graphics::ValidateViewport(context, reversed.viewport); + Require(reversed.viewport.minDepth == 1 && reversed.viewport.maxDepth == 0, "reversed unused depth is not representable"); + auto bounded = decoded; + bounded.viewport.minDepth = 0.25f; + bounded.viewport.maxDepth = 0.75f; + AgcDriver::Graphics::NormalizeUnusedViewportDepth(bounded, pixel); + Require(bounded.viewport.minDepth == 0.25f && bounded.viewport.maxDepth == 0.75f, "an in-range viewport changed"); + auto invalid = decoded; + invalid.viewport.minDepth = std::numeric_limits::infinity(); + AgcDriver::Graphics::NormalizeUnusedViewportDepth(invalid, pixel); + expectFailure([&] { AgcDriver::Graphics::ValidateViewport(context, invalid.viewport); }, "non-finite viewport"); +} + void DepthClipTests() { auto queue = makeState(); const auto direct = AgcDriver::Graphics::DecodeState(queue); @@ -2789,36 +2842,18 @@ struct LiveCounter { } }; -struct LiveCounter { - std::atomic value{0}; - LiveCounter() = default; - LiveCounter(const LiveCounter& other) : value(other.value.load()) {} - LiveCounter& operator=(const LiveCounter& other) { - value.store(other.value.load()); - return *this; - } - operator std::int64_t() const { return value.load(); } - LiveCounter& operator++() { - ++value; - return *this; - } - LiveCounter& operator--() { - --value; - return *this; - } - LiveCounter& operator+=(std::int64_t count) { - value += count; - return *this; - } - LiveCounter& operator-=(std::int64_t count) { - value -= count; - return *this; - } -}; - struct MockVulkan { std::uint64_t next = 1; LiveCounter live; + VkResult commandAllocationResult = VK_SUCCESS; + VkResult fenceCreationResult = VK_SUCCESS; + VkResult commandBeginResult = VK_SUCCESS; + VkFence failedFenceOutput = VK_NULL_HANDLE; + std::uint32_t freedCommandBuffers = 0; + std::uint32_t fenceCreateCalls = 0; + std::uint32_t fenceDestroyCalls = 0; + std::uint32_t invalidFenceDestroys = 0; + std::uint32_t commandBeginCalls = 0; std::map bufferSizes; std::map bufferUsage; std::map allocationFlags; @@ -3195,6 +3230,117 @@ AgcDriver::Graphics::Context mockContext() { return context; } +struct CommandBatchFailureProbe { + VkResult allocation = VK_SUCCESS; + VkResult creation = VK_SUCCESS; + VkResult begin = VK_SUCCESS; + VkFence failedFence = VK_NULL_HANDLE; + std::uint32_t freed = 0; + std::uint32_t creates = 0; + std::uint32_t destroys = 0; + std::uint32_t invalidDestroys = 0; + std::uint32_t begins = 0; +}; + +CommandBatchFailureProbe batchProbe; + +VKAPI_ATTR VkResult VKAPI_CALL batchAllocateCommands(VkDevice device, const VkCommandBufferAllocateInfo* info, VkCommandBuffer* commands) { + if (batchProbe.allocation != VK_SUCCESS) { + std::fill_n(commands, info->commandBufferCount, VK_NULL_HANDLE); + return batchProbe.allocation; + } + return mockAllocateCommandBuffers(device, info, commands); +} + +VKAPI_ATTR VkResult VKAPI_CALL batchCreateFence(VkDevice device, const VkFenceCreateInfo* info, const VkAllocationCallbacks* allocator, VkFence* fence) { + ++batchProbe.creates; + if (batchProbe.creation != VK_SUCCESS) { + *fence = batchProbe.failedFence; + return batchProbe.creation; + } + return mockCreateFence(device, info, allocator, fence); +} + +VKAPI_ATTR void VKAPI_CALL batchFreeCommands(VkDevice device, VkCommandPool pool, std::uint32_t count, const VkCommandBuffer* commands) { + batchProbe.freed += count; + mockFreeCommandBuffers(device, pool, count, commands); +} + +VKAPI_ATTR void VKAPI_CALL batchDestroyFence(VkDevice device, VkFence fence, const VkAllocationCallbacks* allocator) { + ++batchProbe.destroys; + if (fence != VK_NULL_HANDLE && fence == batchProbe.failedFence) { + ++batchProbe.invalidDestroys; + return; + } + mockDestroyFence(device, fence, allocator); +} + +VKAPI_ATTR VkResult VKAPI_CALL batchBeginCommands(VkCommandBuffer commands, const VkCommandBufferBeginInfo* info) { + ++batchProbe.begins; + if (batchProbe.begin != VK_SUCCESS) return batchProbe.begin; + return mockBeginCommandBuffer(commands, info); +} + +VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL batchFailureProc(VkDevice device, const char* name) { + if (std::strcmp(name, "vkAllocateCommandBuffers") == 0) return reinterpret_cast(batchAllocateCommands); + if (std::strcmp(name, "vkCreateFence") == 0) return reinterpret_cast(batchCreateFence); + if (std::strcmp(name, "vkFreeCommandBuffers") == 0) return reinterpret_cast(batchFreeCommands); + if (std::strcmp(name, "vkDestroyFence") == 0) return reinterpret_cast(batchDestroyFence); + if (std::strcmp(name, "vkBeginCommandBuffer") == 0) return reinterpret_cast(batchBeginCommands); + return mockProc(device, name); +} + +void commandBatchFenceFailureTests() { + using namespace AgcDriver::Graphics; + const std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + std::uint32_t invalidDestroys = 0; + for (const auto result : {VK_ERROR_OUT_OF_HOST_MEMORY, VK_ERROR_OUT_OF_DEVICE_MEMORY}) { + for (const bool nonnull : {false, true}) { + mock = MockVulkan{}; + batchProbe = {}; + batchProbe.creation = result; + batchProbe.failedFence = nonnull ? reinterpret_cast(std::uintptr_t{0xdeadbeef}) : VK_NULL_HANDLE; + auto context = mockContext(); + context.deviceProc = batchFailureProc; + bool failed = false; + try { + CommandBatch batch(context); + } catch (const std::runtime_error& error) { + const std::string message = error.what(); + failed = message.find("vkCreateFence") != std::string::npos && message.find("Vulkan result " + std::to_string(result)) != std::string::npos; + } + Require(failed && batchProbe.creates == 1 && batchProbe.begins == 0, "fence creation failure lost its error or continued construction"); + Require(mock.live == 0 && batchProbe.freed == 1, "fence creation failure leaked the allocated command buffer"); + invalidDestroys += batchProbe.invalidDestroys; + std::printf("command batch fence result=%d nonnull=%d invalid destroys=%u\n", result, nonnull, batchProbe.invalidDestroys); + const auto destroyed = batchProbe.destroys; + const auto invalid = batchProbe.invalidDestroys; + batchProbe.creation = VK_SUCCESS; + { + CommandBatch recovered(context); + Require(recovered.Handle() != VK_NULL_HANDLE, "command batch did not recover after fence creation failed"); + } + Require(mock.live == 0 && batchProbe.freed == 2 && batchProbe.destroys == destroyed + 1 && batchProbe.invalidDestroys == invalid, "recovered command batch mishandled its owned objects"); + } + } + for (const bool allocated : {false, true}) { + mock = MockVulkan{}; + batchProbe = {}; + batchProbe.allocation = allocated ? VK_SUCCESS : VK_ERROR_OUT_OF_HOST_MEMORY; + batchProbe.begin = VK_ERROR_OUT_OF_HOST_MEMORY; + auto context = mockContext(); + context.deviceProc = batchFailureProc; + bool failed = false; + try { + CommandBatch batch(context); + } catch (const std::runtime_error& error) { + failed = std::string(error.what()).find(allocated ? "vkBeginCommandBuffer" : "vkAllocateCommandBuffers") != std::string::npos; + } + Require(failed && mock.live == 0 && batchProbe.freed == (allocated ? 1u : 0u) && batchProbe.destroys == (allocated ? 1u : 0u) && batchProbe.invalidDestroys == 0, "command batch constructor failure mishandled its owned objects"); + } + Require(invalidDestroys == 0, "command batch destroyed undefined failed fence outputs"); +} + void addressSpaceTeardownTests() { #ifdef _WIN32 _putenv_s("APS5_NO_LEASE_MIRROR", "1"); @@ -5562,7 +5708,11 @@ void ConservativeRasterizationTests() { expectFailure([&] { AgcDriver::Graphics::DecodeState(tessellation); }, "conservative rasterization of lines is unsupported (VGT_GS_OUT_PRIM_TYPE=0x1)"); for (const auto inputs : {0x6u, 0x42u}) { queue.context[0x1b3] = inputs; - expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "conservative rasterization with centroid interpolation"); + Require(AgcDriver::Graphics::DecodeState(queue).conservativeRasterization == VK_CONSERVATIVE_RASTERIZATION_MODE_OVERESTIMATE_EXT, "OVER_RAST_ENABLE rejected centroid inputs " + std::to_string(inputs) + " of a single rasterized sample"); + auto multisampled = queue; + multisampled.context[0x2f8] = 0x00200002; + multisampled.context[0x31d] = (1u << 12u) | (1u << 15u); + expectFailure([&] { AgcDriver::Graphics::DecodeState(multisampled); }, "conservative rasterization with centroid interpolation of more than one rasterized sample is unsupported"); } queue.context[0x1b3] = 2; for (const auto control : {0x6003u, 0x6020u, 0x6401u, 0xe06001u, 0x1u}) { @@ -6471,6 +6621,39 @@ void vertexZeroFillTests() { Require(std::all_of(bytes.begin() + static_cast(valid), bytes.end(), [](std::byte value) { return value == std::byte{0}; }), "a vertex fetch past the record count did not read as zeros"); } +void vertexOverhangTests() { + using AgcDriver::Graphics::CopyZeroPaddedDrawInput; + constexpr std::size_t page = 65536; + constexpr std::size_t stride = 44; + constexpr std::size_t overhang = 8; +#ifdef _WIN32 + auto* block = static_cast(VirtualAlloc(nullptr, page * 2, MEM_RESERVE, PAGE_NOACCESS)); + Require(block != nullptr && VirtualAlloc(block, page, MEM_COMMIT, PAGE_READWRITE) != nullptr, "cannot reserve the guest pages of the overhang test"); +#else + void* mapped = mmap(nullptr, page * 2, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(mapped != MAP_FAILED && mprotect(mapped, page, PROT_READ | PROT_WRITE) == 0, "cannot reserve the guest pages of the overhang test"); + auto* block = static_cast(mapped); +#endif + constexpr std::size_t offset = 0x650; + const auto readable = page - offset; + std::fill(block + offset, block + page, std::byte{0x7a}); + const auto address = reinterpret_cast(block) + offset; + { + const auto copy = CopyZeroPaddedDrawInput(mockContext(), address, readable + overhang, readable + overhang, stride); + const auto bytes = copy.buffer->Bytes(); + Require(bytes.size() == readable + overhang, "an overhanging vertex fetch changed its size"); + Require(std::all_of(bytes.begin(), bytes.begin() + static_cast(readable), [](std::byte value) { return value == std::byte{0x7a}; }), "an overhanging vertex fetch did not copy the readable bytes"); + Require(std::all_of(bytes.begin() + static_cast(readable), bytes.end(), [](std::byte value) { return value == std::byte{0}; }), "an overhanging vertex fetch did not zero its last record"); + } + expectFailure([&] { CopyZeroPaddedDrawInput(mockContext(), address, readable + stride, readable + stride, stride); }, "is not readable"); + expectFailure([&] { CopyZeroPaddedDrawInput(mockContext(), address, readable + overhang, readable + overhang, 0); }, "is not readable"); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + munmap(block, page * 2); +#endif +} + void vertexZeroFillMergeTests() { using AgcDriver::Graphics::PlanVertexCopies; using AgcDriver::Graphics::SoloZeroPaddedFetchIndices; @@ -6599,10 +6782,19 @@ int main(int argc, char** argv) { setenv("APS5_HEAP_MIRROR_MIB", "4", 1); #endif try { + if (argc == 2 && std::string_view(argv[1]) == "--unused-viewport-depth") { + UnusedViewportDepthTests(); + DepthClipTests(); + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--unused-stencil-reference") { stencilReferenceUnusedTests(); return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--command-batch-fence-failure") { + commandBatchFenceFailureTests(); + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--pipeline-library-clear-waits-for-build") { pipelineLibraryClearWaitsForBuildTests(); std::cout << "pipeline library clear waits for active part builds\n"; @@ -6674,6 +6866,7 @@ int main(int argc, char** argv) { hardwareScreenOffsetTests(); srgb8TargetTests(); DepthClipTests(); + UnusedViewportDepthTests(); DepthStencilTests(); OneDimensionalColorTests(); htileDepthClearTests(); @@ -6750,6 +6943,7 @@ int main(int argc, char** argv) { storeAtLabelTests(); nullVertexDescriptorTests(); vertexZeroFillTests(); + vertexOverhangTests(); vertexZeroFillMergeTests(); parallelCompareTests(); pixelParameterSlotTests(); diff --git a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp index 8af80ee42e78..a1936dda40fb 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp @@ -498,7 +498,13 @@ void testIndexAttributesIndirect() { check(sameDraw(indirectFromRecord, AgcDriver::Pm4::ResolveDraw(indirectDraw, direct)) && indirectFromRecord.indexAddress == indexAddress && indirectFromRecord.indexCount == 12 && indirectFromRecord.indexSize == 4, "an indexed indirect draw after INDEX_ATTRIBUTES_INDIRECT differs from the same draw after the direct packets"); records[6] = 0x7fffffffu; execute(loaded, packet); - expectFailure([&] { AgcDriver::Pm4::ResolveDraw(indirectDraw, loaded); }, "guest"); + const auto largeIndirect = AgcDriver::Pm4::ResolveDraw(indirectDraw, loaded); + check(largeIndirect.indexAddress == indexAddress && largeIndirect.indexCount == records[6] && largeIndirect.indexSize == 4, "an indirect draw did not preserve its declared index limit for record resolution"); + loaded.indexBase = std::numeric_limits::max() - 3u; + expectFailure([&] { AgcDriver::Pm4::ResolveDraw(indirectDraw, loaded); }, "index range size overflow"); + loaded.indexBase = indexAddress + 1u; + expectFailure([&] { AgcDriver::Pm4::ResolveDraw(indirectDraw, loaded); }, "misaligned index base"); + loaded.indexBase = indexAddress; records[6] = 12; static std::size_t flushed = 0; diff --git a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp index b9df0dc7491b..cd71a87f1520 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp @@ -353,6 +353,14 @@ class Device { bool NullDescriptors() const { return nullDescriptors; } const Context& GetContext() const { return context; } bool Graphics() const { return graphics; } + bool Timestamps() const { + std::uint32_t count = 0; + const auto queues = function("vkGetPhysicalDeviceQueueFamilyProperties"); + queues(context.physical, &count, nullptr); + std::vector families(count); + queues(context.physical, &count, families.data()); + return families.at(context.queueFamily).timestampValidBits != 0; + } void WaitQueue() const { Check(context.Function("vkQueueWaitIdle")(context.queue), "vkQueueWaitIdle"); } private: @@ -774,6 +782,44 @@ void afterRecordedWorkTests(const Device& device, Recorder& recorder) { Require(seen == 1, "an action ran before the completion label recorded ahead of it"); } +int gpuTimingCompletionTests(const Device& device, bool incomplete) { + const auto& context = device.GetContext(); + if (!device.Timestamps()) { + std::cout << "skipped, queue timestamp queries unavailable\n"; + return 77; + } + Require(Recorder::GpuTimingEnabled(), "GPU timing completion test requires APS5_PROFILE_GPU"); + std::unique_lock gpu(GpuMutex()); + int completed = 0; + Recorder recorder(context); + recorder.Activate(); + for (int round = 0; round < 3; ++round) { + const bool missingEnd = incomplete && round == 1; + const auto first = recorder.BeginGpuTiming(0x12340); + Require(first != Recorder::NoTiming, "first GPU timing range was not allocated"); + if (!missingEnd) recorder.EndGpuTiming(first); + const auto second = recorder.BeginGpuTiming(0x12341); + Require(second != Recorder::NoTiming && second != first, "second GPU timing range was not allocated"); + recorder.EndGpuTiming(second); + recorder.OnComplete([&] { ++completed; }); + const auto serial = recorder.Submissions() + 1; + recorder.Submit(); + recorder.Sync(); + Require(completed == round + 1 && recorder.Idle(), "GPU timing prevented batch completion"); + std::size_t missing = 0; + const auto batches = recorder.CompletedBatches(serial - 1, serial, missing); + Require(missing == 0 && batches.size() == 1 && batches.front().serial == serial, "GPU timing batch completion record is missing"); + const auto& batch = batches.front(); + if (missingEnd) { + Require(batch.gpuStartNs == 0 && batch.gpuEndNs == 0, "incomplete query batch published GPU timings"); + } else { + Require(batch.gpuStartNs > 0 && batch.gpuEndNs >= batch.gpuStartNs, "complete query batch lost GPU timings"); + } + } + std::cout << "GPU timing batch completion checked, incomplete=" << incomplete << '\n'; + return 0; +} + void batchStampTests(Recorder& recorder) { Require(recorder.Idle(), "batch stamps: the recorder is busy"); double previousEnd = 0; @@ -5950,6 +5996,9 @@ int main(int argc, char** argv) { return 77; } Device& device = *created; + if (argc == 2 && (std::string_view(argv[1]) == "--gpu-timing-complete-only" || std::string_view(argv[1]) == "--gpu-timing-incomplete-only")) { + return gpuTimingCompletionTests(device, std::string_view(argv[1]) == "--gpu-timing-incomplete-only"); + } if (argc == 2 && std::string_view(argv[1]) == "--import-watch-only") { importWatchTests(device); return 0; diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index f247546ab42c..5fb457887034 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -4536,6 +4536,20 @@ int main(int argc, char** argv) { expectFailure([&] { static_cast(ValidateAndOptimizeSpirv(first.spirv, 0x00400000u, 0x00010600u)); }, "unsupported Vulkan/SPIR-V target", "incompatible target accepted"); expectFailure([&] { static_cast(ValidateAndOptimizeSpirv(first.spirv, 0x00405000u, 0x00010600u)); }, "unsupported Vulkan target", "unknown Vulkan target accepted"); #endif + const auto readOnly = [&](const char* name) { + std::optional id; + bool decorated = false; + for (std::size_t at = 5; at < first.spirv.size() && (first.spirv[at] >> 16u) != 0u; at += first.spirv[at] >> 16u) { + const auto opcode = first.spirv[at] & 0xffffu; + if (opcode == spv::OpName && std::strcmp(reinterpret_cast(&first.spirv[at + 2u]), name) == 0) id = first.spirv[at + 1u]; + } + for (std::size_t at = 5; id && at < first.spirv.size() && (first.spirv[at] >> 16u) != 0u; at += first.spirv[at] >> 16u) { + decorated = decorated || ((first.spirv[at] & 0xffffu) == spv::OpDecorate && first.spirv[at + 1u] == *id && first.spirv[at + 2u] == spv::DecorationNonWritable); + } + return std::pair{id.has_value(), decorated}; + }; + require(readOnly("flattened_srt") == std::pair{true, true}, "the flattened SRT is not decorated NonWritable"); + for (const auto* table : {"bda_pagetable", "table_map"}) require(!readOnly(table).first || readOnly(table).second, "a read-only BDA page table or image table map is not decorated NonWritable"); payload = 0x40000000u; AgcDriver::ShaderMemory updatedMemory({}); const auto updatedCapture = updatedMemory.Capture(request); diff --git a/core/libs/prx/libSceAgcDriver/tests/SpirvModule.cpp b/core/libs/prx/libSceAgcDriver/tests/SpirvModule.cpp index 791036b03795..ce8c173969d7 100644 --- a/core/libs/prx/libSceAgcDriver/tests/SpirvModule.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/SpirvModule.cpp @@ -373,6 +373,64 @@ void testIdsBeyondTheBoundAreRejected() { check(rejected, "a module whose ids reach its bound was specialized"); } +void testBlockBallotsOfOnePredicateShareOneBallot() { + SpirvModule module; + const auto typeBool = module.Type(spv::OpTypeBool); + const auto typeU32 = module.Type(spv::OpTypeInt, 32u, 0u); + const auto typeBallot = module.Type(spv::OpTypeVector, typeU32, 4u); + const auto typeVoid = module.Type(spv::OpTypeVoid); + const auto typeFunction = module.Type(spv::OpTypeFunction, typeVoid); + const auto scope = module.Constant(spv::OpConstant, typeU32, static_cast(spv::ScopeSubgroup)); + const auto truth = module.Constant(spv::OpConstantTrue, typeBool); + const auto other = module.Constant(spv::OpConstantFalse, typeBool); + const auto helper = module.AllocateId(); + const auto main = module.AllocateId(); + const auto first = module.AllocateId(); + const auto repeated = module.AllocateId(); + const auto distinct = module.AllocateId(); + const auto inHelper = module.AllocateId(); + const auto afterHelper = module.AllocateId(); + const auto afterCall = module.AllocateId(); + const auto afterLabel = module.AllocateId(); + const auto next = module.AllocateId(); + module.AddFunction(spv::OpFunction, typeVoid, main, spv::FunctionControlMaskNone, typeFunction); + module.AddFunction(spv::OpLabel, module.AllocateId()); + module.AddSubgroupBallot(typeBallot, first, scope, truth); + module.AddSubgroupBallot(typeBallot, repeated, scope, truth); + module.AddSubgroupBallot(typeBallot, distinct, scope, other); + module.BeginHelperFunction(); + module.AddFunction(spv::OpFunction, typeVoid, helper, spv::FunctionControlMaskNone, typeFunction); + module.AddFunction(spv::OpLabel, module.AllocateId()); + module.AddSubgroupBallot(typeBallot, inHelper, scope, truth); + module.AddFunction(spv::OpReturn); + module.AddFunction(spv::OpFunctionEnd); + module.EndHelperFunction(); + module.AddSubgroupBallot(typeBallot, afterHelper, scope, truth); + module.AddFunction(spv::OpFunctionCall, typeVoid, module.AllocateId(), helper); + module.AddSubgroupBallot(typeBallot, afterCall, scope, truth); + module.AddFunction(spv::OpBranch, next); + module.AddFunction(spv::OpLabel, next); + module.AddSubgroupBallot(typeBallot, afterLabel, scope, truth); + module.AddFunction(spv::OpReturn); + module.AddFunction(spv::OpFunctionEnd); + const auto words = module.Finalize(); + std::map ballotSources; + std::map copySources; + for (std::size_t offset = 5; offset < words.size(); offset += words[offset] >> spv::WordCountShift) { + if ((words[offset] >> spv::WordCountShift) == 0u) break; + const auto opcode = words[offset] & 0xffffu; + if (opcode == spv::OpGroupNonUniformBallot) ballotSources[words[offset + 2u]] = words[offset + 4u]; + if (opcode == spv::OpCopyObject) copySources[words[offset + 2u]] = words[offset + 3u]; + } + check(ballotSources.size() == 5u, "a block did not ballot each predicate exactly once"); + check(copySources.size() == 2u && copySources.contains(repeated) && copySources.contains(afterHelper), "a repeated ballot in one block was not a copy of the first"); + check(copySources[repeated] == first && copySources[afterHelper] == first, "a repeated ballot did not copy the block's first ballot"); + check(ballotSources[distinct] == other, "a ballot of another predicate was reused"); + check(ballotSources.contains(inHelper), "a helper function reused a ballot of the function it interrupted"); + check(ballotSources.contains(afterCall), "a ballot after a function call reused one from before the call"); + check(ballotSources.contains(afterLabel), "a ballot in a later block reused one from an earlier block"); +} + int main() { testUnusedHelperFunctions(); testFoldingLeavesNoDeadBlocksOrConstants(); @@ -388,6 +446,7 @@ int main() { testModulesAreIndependent(); testIdsStayUniqueBeyondCacheCapacity(); testEmittedWordsAreStable(); + testBlockBallotsOfOnePredicateShareOneBallot(); if (failures != 0) { std::fprintf(stderr, "%d check(s) failed\n", failures); return 1; diff --git a/core/libs/prx/libSceAgcDriver/tests/SpirvOptimizer.cpp b/core/libs/prx/libSceAgcDriver/tests/SpirvOptimizer.cpp index 52e11768a621..24b46ca1f06a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/SpirvOptimizer.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/SpirvOptimizer.cpp @@ -535,6 +535,97 @@ OpDecorate %bytes Binding 1 else check(adds >= blocks && adds <= blocks * 4, "control-flow optimization lost arithmetic or retained redundant values"); } +std::size_t repeatedBlockBallots(const std::vector& words) { + std::size_t repeats = 0; + std::set> seen; + for (std::size_t offset = 5; offset < words.size(); offset += words[offset] >> spv::WordCountShift) { + const auto opcode = words[offset] & spv::OpCodeMask; + if (opcode == spv::OpLabel || opcode == spv::OpFunctionCall) seen.clear(); + if (opcode == spv::OpGroupNonUniformBallot && !seen.insert({words[offset + 1], words[offset + 3], words[offset + 4]}).second) ++repeats; + } + return repeats; +} + +std::string ballotModuleSource(const std::string& body) { + return R"(OpCapability Shader +OpCapability GroupNonUniformBallot +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 32 1 1 +OpDecorate %runtime ArrayStride 16 +OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +OpDecorate %buffer DescriptorSet 0 +OpDecorate %buffer Binding 0 +%void = OpTypeVoid +%function = OpTypeFunction %void +%bool = OpTypeBool +%uint = OpTypeInt 32 0 +%uvec4 = OpTypeVector %uint 4 +%runtime = OpTypeRuntimeArray %uvec4 +%block = OpTypeStruct %runtime +%blockPointer = OpTypePointer StorageBuffer %block +%elementPointer = OpTypePointer StorageBuffer %uvec4 +%buffer = OpVariable %blockPointer StorageBuffer +%zero = OpConstant %uint 0 +%one = OpConstant %uint 1 +%two = OpConstant %uint 2 +%three = OpConstant %uint 3 +%subgroup = OpConstant %uint 3 +%helper = OpFunction %void None %function +%helperEntry = OpLabel +OpReturn +OpFunctionEnd +%main = OpFunction %void None %function +%entry = OpLabel +%slot0 = OpAccessChain %elementPointer %buffer %zero %zero +%slot1 = OpAccessChain %elementPointer %buffer %zero %one +%slot2 = OpAccessChain %elementPointer %buffer %zero %two +%slot3 = OpAccessChain %elementPointer %buffer %zero %three +%loaded = OpLoad %uvec4 %slot0 +%value = OpCompositeExtract %uint %loaded 0 +%predicate = OpINotEqual %bool %value %zero +%other = OpIEqual %bool %value %one +)" + body + "OpReturn\nOpFunctionEnd\n"; +} + +void testBallotsMergedIntoOneBlockShareOneBallot(bool optimizationEnabled) { + const auto merged = assemble(ballotModuleSource(R"(%first = OpGroupNonUniformBallot %uvec4 %subgroup %predicate +OpStore %slot1 %first +OpBranch %next +%next = OpLabel +%second = OpGroupNonUniformBallot %uvec4 %subgroup %predicate +OpStore %slot2 %second +)")); + const auto output = ShaderRecompiler::ValidateAndOptimizeSpirv(merged, Vulkan11, Spirv13, false); + check(repeatedBlockBallots(output) == 0, "a block merged by the optimizer ballots one predicate twice"); + check(opcodeCount(output, spv::OpStore) == 2, "ballot collapse lost a store"); + if (optimizationEnabled) check(opcodeCount(output, spv::OpGroupNonUniformBallot) == 1, "blocks merged by the optimizer kept both ballots"); + + const auto oneBlock = assemble(ballotModuleSource(R"(%first = OpGroupNonUniformBallot %uvec4 %subgroup %predicate +%different = OpGroupNonUniformBallot %uvec4 %subgroup %other +%second = OpGroupNonUniformBallot %uvec4 %subgroup %predicate +OpStore %slot1 %first +OpStore %slot2 %different +OpStore %slot3 %second +)")); + for (const bool optimize : {false, true}) { + const auto collapsed = ShaderRecompiler::ValidateAndOptimizeSpirv(oneBlock, Vulkan11, Spirv13, false, optimize); + const auto label = std::string(optimize ? "optimized" : "unoptimized"); + check(repeatedBlockBallots(collapsed) == 0, label + ": one block ballots one predicate twice"); + check(opcodeCount(collapsed, spv::OpGroupNonUniformBallot) == 2, label + ": a different predicate lost its ballot"); + } + + const auto called = assemble(ballotModuleSource(R"(%first = OpGroupNonUniformBallot %uvec4 %subgroup %predicate +OpStore %slot1 %first +%call = OpFunctionCall %void %helper +%second = OpGroupNonUniformBallot %uvec4 %subgroup %predicate +OpStore %slot2 %second +)")); + const auto acrossCall = ShaderRecompiler::ValidateAndOptimizeSpirv(called, Vulkan11, Spirv13, false, false); + check(opcodeCount(acrossCall, spv::OpGroupNonUniformBallot) == 2, "a ballot was shared across a function call"); +} + } int main() { @@ -549,6 +640,7 @@ int main() { testSpecializedDeadContinue(); testLongControlFlow(optimizationEnabled, false); testLongControlFlow(optimizationEnabled, true); + testBallotsMergedIntoOneBlockShareOneBallot(optimizationEnabled); if (failures != 0) { std::fprintf(stderr, "%d optimizer check(s) failed\n", failures); return 1; diff --git a/core/libs/prx/libSceAgcDriver/tests/Structurizer.cpp b/core/libs/prx/libSceAgcDriver/tests/Structurizer.cpp index ca8b02d46541..c3f0359632d7 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Structurizer.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Structurizer.cpp @@ -1,6 +1,7 @@ #include "ControlFlow/Structurizer.hpp" #include #include +#include #include #include #include @@ -251,6 +252,17 @@ void requireSameStructuredExecutions(const std::vector>& successors, const char* name) { + if (std::getenv("APS5_FORCE_STRUCTURIZER_FALLBACK") != nullptr) return; + auto graph = makeGraph(successors); + Structurizer{}.Structurize(graph); + for (const auto& block : graph.blocks) { + if (block.terminator.condition == BranchCondition::GotoVariable || block.terminator.gotoValue >= 0) { + throw std::runtime_error(std::string(name) + ": block " + std::to_string(block.id) + " routes through a goto variable instead of a structured selection"); + } + } +} + void requireGrowthBudgetRefusal(ControlFlowGraph graph, const char* name) { const std::string growthBudget = "CFG shared merge splitting exceeded growth budget"; try { @@ -423,6 +435,9 @@ int main() { requireStructuredBranches(elseReturn, "an early return in one arm of a selection whose other arm reaches the same merge"); requireDominatedMerges(elseReturn, "an early return in one arm of a selection whose other arm reaches the same merge"); requireOriginalBranches(elseReturn, elseReturnSuccessors, "an early return in one arm of a selection whose other arm reaches the same merge"); + const std::vector> branchingReturn{{5, 1}, {3, 2}, {13}, {9, 4}, {13}, {10, 6}, {8, 7}, {13}, {13, 9}, {13}, {12, 11}, {12}, {13}, {}}; + requireSameStructuredExecutions(branchingReturn, "an arm whose returns branch once a tail shared across arms is cloned"); + requireNoRouteVariables(branchingReturn, "an arm whose returns branch once a tail shared across arms is cloned"); std::vector> diamonds; for (std::uint32_t diamond = 0; diamond < 50u; ++diamond) { const auto head = diamond * 3u; diff --git a/core/libs/prx/libSceAgcDriver/tests/TessellationLowering.cpp b/core/libs/prx/libSceAgcDriver/tests/TessellationLowering.cpp new file mode 100644 index 000000000000..287ca9bfd85d --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/TessellationLowering.cpp @@ -0,0 +1,197 @@ +#include "IntermediateRepresentation/IrBuilder.hpp" +#include "IntermediateRepresentation/IrProgram.hpp" +#include "Optimization/TessellationLowering.hpp" +#include +#include +#include +#include +#include + +using namespace ShaderRecompiler; + +namespace { + +constexpr std::uint32_t InputControlPoints = 3u; +constexpr std::uint32_t LocalStride = 32u; +constexpr std::uint32_t PatchStride = InputControlPoints * LocalStride; +constexpr std::uint32_t ReadPc = 0x40u; + +bool IsKind(const IrValue* value, TessellationAttribute kind) { + return value->HasImmediate() && value->Type() == IrType::U32 && value->ImmediateU32() == static_cast(kind); +} + +struct Hull { + IrProgram program; + IrBuilder builder{program}; + IrBlock* block = nullptr; + + Hull() { + program.Resources().stage = IrShaderStage::TessellationControl; + program.Resources().memoryInfo.push_back(MemoryInfo{.kind = ResourceKind::Lds}); + static_cast(newBlock()); + } + + IrBlock& newBlock() { + auto& created = program.CreateBlock(); + program.BlockOrder().push_back(&created); + BlockInfo info; + info.id = created.Id(); + program.Metadata().blockInfo.push_back(info); + if (block == nullptr) program.SetEntryBlock(created); + block = &created; + builder.SetInsertionPoint(created); + return created; + } + + IrValue& invocation() { + return builder.Emit(IrOpcode::GetBuiltin, IrType::U32, {&builder.Constant(static_cast(StageInputKind::InvocationId)), &builder.Constant(0u)}); + } + + IrValue& relativePatch() { + return builder.Emit(IrOpcode::TessellationBase, IrType::U32, {&builder.Constant(static_cast(TessellationBaseKind::RelativeIndex))}); + } + + IrValue& computed(std::uint32_t limit, std::uint32_t multiple) { + auto& index = builder.Emit(IrOpcode::UMin32, IrType::U32, {&invocation(), &builder.Constant(limit)}); + return builder.IMul(index, builder.Constant(multiple)); + } + + IrValue& firstInvocation() { + return builder.Emit(IrOpcode::ULessThan32, IrType::Bool, {&invocation(), &builder.Constant(1u)}); + } + + IrValue& select(std::uint32_t firstLimit, std::uint32_t firstMultiple, IrValue& second) { + return builder.Emit(IrOpcode::SelectU32, IrType::U32, {&firstInvocation(), &computed(firstLimit, firstMultiple), &second}); + } + + IrValue& inPatch(IrValue& offset) { + return builder.IAdd(builder.IMul(relativePatch(), builder.Constant(PatchStride)), offset); + } + + IrValue& read(IrValue& address) { + auto& loaded = builder.Emit(IrOpcode::LoadSharedU32, IrType::U32, {&address, &builder.ConstantBool(true)}, MemoryFlags{0u, ReadPc}); + static_cast(builder.Emit(IrOpcode::ReferenceU32, IrType::Void, {&loaded})); + return loaded; + } + + IrValue& merge(std::uint32_t firstMultiple, std::uint32_t secondMultiple) { + IrBlock& entry = *block; + IrBlock& first = newBlock(); + IrBlock& second = newBlock(); + IrBlock& together = newBlock(); + builder.SetInsertionPoint(entry); + builder.BranchConditional(firstInvocation(), first, second); + builder.SetInsertionPoint(first); + auto& left = computed(1u, firstMultiple); + builder.Branch(together); + builder.SetInsertionPoint(second); + auto& right = computed(1u, secondMultiple); + builder.Branch(together); + builder.SetInsertionPoint(together); + auto& merged = builder.Emit(IrOpcode::Phi, IrType::U32, {}); + merged.AddPhiOperand(&first, &left); + merged.AddPhiOperand(&second, &right); + return merged; + } +}; + +struct Outcome { + bool lowered = false; + std::string reason; + const IrValue* emitted = nullptr; +}; + +const ShaderTessellationInputInfo Tessellation{.inputControlPoints = InputControlPoints, .outputControlPoints = InputControlPoints, .patchesPerGroup = 1u}; + +Outcome Lower(IrProgram& program) { + Outcome outcome; + try { + TessellationLowering{}.Lower(program, Tessellation); + } catch (const std::exception& error) { + outcome.reason = error.what(); + return outcome; + } + outcome.lowered = true; + for (IrBlock* block : program.BlockOrder()) { + for (IrValue* inst : block->Instructions()) { + if (inst->Opcode() == IrOpcode::GetTessellationAttribute && IsKind(inst->Argument(0), TessellationAttribute::ControlInput)) { + outcome.emitted = inst->Argument(1)->Resolve(); + } + } + } + return outcome; +} + +bool LoweredAt(IrProgram& program, IrValue& address) { + const Outcome outcome = Lower(program); + if (!outcome.lowered) std::fprintf(stderr, "refused: %s\n", outcome.reason.c_str()); + return outcome.lowered && outcome.emitted == address.Resolve(); +} + +bool RefusedBecause(IrProgram& program, const char* reason) { + const Outcome outcome = Lower(program); + if (outcome.lowered) std::fprintf(stderr, "lowered instead of refusing: %s\n", reason); + return !outcome.lowered && outcome.reason.find(reason) != std::string::npos; +} + +bool run(const char* name, const std::function& body) { + bool passed = false; + try { + passed = body(); + } catch (const std::exception& error) { + std::fprintf(stderr, "%s: %s\n", name, error.what()); + } + if (!passed) std::fprintf(stderr, "failed: %s\n", name); + return passed; +} + +} + +int main() { + bool passed = true; + + passed &= run("a hull read at a computed control-point index keeps the program's address", [] { + Hull hull; + auto& address = hull.inPatch(hull.computed(2u, LocalStride)); + hull.read(address); + return LoweredAt(hull.program, address); + }); + + passed &= run("a hull read at a control point selected from data keeps the program's address", [] { + Hull hull; + auto& address = hull.inPatch(hull.select(2u, LocalStride, hull.computed(1u, LocalStride))); + hull.read(address); + return LoweredAt(hull.program, address); + }); + + passed &= run("a hull read at a control point merged by a phi keeps the program's address", [] { + Hull hull; + auto& address = hull.inPatch(hull.merge(LocalStride, LocalStride)); + hull.read(address); + return LoweredAt(hull.program, address); + }); + + passed &= run("a selected control point at half a slot is refused", [] { + Hull hull; + auto& address = hull.inPatch(hull.select(2u, LocalStride, hull.computed(1u, LocalStride / 2u))); + hull.read(address); + return RefusedBecause(hull.program, "not a whole number of control points"); + }); + + passed &= run("a control point merged by a phi at half a slot is refused", [] { + Hull hull; + auto& address = hull.inPatch(hull.merge(LocalStride, LocalStride / 2u)); + hull.read(address); + return RefusedBecause(hull.program, "not a whole number of control points"); + }); + + passed &= run("a selected control point at two different offsets is refused", [] { + Hull hull; + auto& offset = hull.builder.IAdd(hull.computed(1u, LocalStride), hull.builder.Constant(LocalStride)); + auto& address = hull.inPatch(hull.select(2u, LocalStride, offset)); + hull.read(address); + return RefusedBecause(hull.program, "not a whole number of control points"); + }); + + return passed ? 0 : 1; +} diff --git a/core/libs/prx/libSceAgcDriver/tests/VertexBarrier.cpp b/core/libs/prx/libSceAgcDriver/tests/VertexBarrier.cpp index 8d591fe05165..a854e742071e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/VertexBarrier.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/VertexBarrier.cpp @@ -11,6 +11,8 @@ namespace { constexpr std::array BarrierExport{0xbf8a0000u, 0xf80008cfu, 0u, 0xbf810000u}; constexpr std::array BarrierLdsExport{0xd8340000u, 0u, 0xbf8a0000u, 0xf80008cfu, 0u, 0xbf810000u}; +constexpr std::array LeadingBarrierLds{0xbf8a0000u, 0xd8340000u, 0u, 0xf80008cfu, 0u, 0xbf810000u}; +constexpr std::array ScalarBarrierLds{0xbe8003c1u, 0xbf8a0000u, 0xd8340000u, 0u, 0xf80008cfu, 0u, 0xbf810000u}; ShaderRecompiler::RecompileRequest vertexRequest(std::span code) { using namespace ShaderRecompiler; @@ -39,6 +41,13 @@ void verifyBarrierWithoutMemory() { if (hasControlBarrier(result)) throw std::runtime_error("a vertex program's s_barrier became a workgroup OpControlBarrier, which the Vertex execution model does not allow"); } +void verifyBarrierBeforeLds() { + for (const auto code : {std::span(LeadingBarrierLds), std::span(ScalarBarrierLds)}) { + const auto result = ShaderRecompiler::Recompile(vertexRequest(code)); + if (hasControlBarrier(result)) throw std::runtime_error("an s_barrier before every memory access of a vertex program became a workgroup OpControlBarrier"); + } +} + void verifyBarrierWithLds() { try { (void)ShaderRecompiler::Recompile(vertexRequest(BarrierLdsExport)); @@ -54,6 +63,7 @@ void verifyBarrierWithLds() { int main() { try { verifyBarrierWithoutMemory(); + verifyBarrierBeforeLds(); verifyBarrierWithLds(); std::cout << "vertex barrier tests passed\n"; return 0; diff --git a/core/libs/prx/libSceAgcDriver/tests/WaitShutdown.cpp b/core/libs/prx/libSceAgcDriver/tests/WaitShutdown.cpp index 72c88cbc478b..ee2a3fb2212a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/WaitShutdown.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/WaitShutdown.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Submit/include/Acb.hpp" +#include "prx/libSceAgcDriver/Submit/include/CommandBuffer.hpp" #include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" #include "prx/libc/include/Shutdown.hpp" @@ -50,6 +51,46 @@ void Submit(std::uint32_t queue, const std::vector& words) { Require(result == 0, "shutdown wait submission failed"); } +void CheckCommandBufferSubmission() { + struct MockQueueContext { + std::uint32_t unused = 0; + std::uint32_t queue_id = 0; + }; + MockQueueContext graphicsCtx{0, 0}; + MockQueueContext computeCtx{0, 0x20}; + MockQueueContext invalidCtx{0, 1}; + + Packet emptyPacket{nullptr, 0, 0, {}}; + Require(sceAgcDriverSubmitCommandBuffer(nullptr, &emptyPacket) == 22, "null context submit must return EINVAL"); + Require(sceAgcDriverSubmitCommandBuffer(&graphicsCtx, nullptr) == 22, "null packet submit must return EINVAL"); + Require(sceAgcDriverSubmitMultiCommandBuffers(nullptr, nullptr, nullptr, 0) == 0, "count 0 multi submit must return 0"); + + std::uint32_t* cmdBufs[] = {nullptr}; + const std::uint32_t cmdSizes[] = {0}; + Require(sceAgcDriverSubmitMultiCommandBuffers(nullptr, cmdBufs, cmdSizes, 1) == 22, "null context multi submit must return EINVAL"); + Require(sceAgcDriverSubmitMultiCommandBuffers(&graphicsCtx, nullptr, cmdSizes, 1) == 22, "null cmdBufs multi submit must return EINVAL"); + Require(sceAgcDriverSubmitMultiCommandBuffers(&graphicsCtx, cmdBufs, nullptr, 1) == 22, "null cmdSizes multi submit must return EINVAL"); + Require(sceAgcDriverSubmitMultiCommandBuffers(&graphicsCtx, cmdBufs, cmdSizes, 0) == 0, "empty multi submission failed"); + Require(sceAgcDriverSubmitMultiCommandBuffers(&graphicsCtx, cmdBufs, cmdSizes, 1) == 0, "graphics context multi submit failed"); + Require(sceAgcDriverSubmitMultiCommandBuffers(&computeCtx, cmdBufs, cmdSizes, 1) == 0, "compute context multi submit failed"); + + bool submitRejected = false; + try { + sceAgcDriverSubmitCommandBuffer(&invalidCtx, &emptyPacket); + } catch (const std::runtime_error&) { + submitRejected = true; + } + Require(submitRejected, "unsupported queue context was not rejected"); + + bool multiRejected = false; + try { + sceAgcDriverSubmitMultiCommandBuffers(&invalidCtx, cmdBufs, cmdSizes, 1); + } catch (const std::runtime_error&) { + multiRejected = true; + } + Require(multiRejected, "unsupported queue context for multi submit was not rejected"); +} + void CheckShutdownCancellation() { static std::array states{}; constexpr std::array queues{0, 0x20, 0x28}; @@ -92,6 +133,7 @@ int main(int argc, char** argv) { return 1; } try { + CheckCommandBufferSubmission(); CheckShutdownCancellation(); std::puts("unsatisfied label waits cancelled cleanly at shutdown"); return 0; diff --git a/core/libs/prx/libSceAgcDriver/tests/WriteTracking.cpp b/core/libs/prx/libSceAgcDriver/tests/WriteTracking.cpp index 56bfe785d0a4..39498e9d8ed2 100644 --- a/core/libs/prx/libSceAgcDriver/tests/WriteTracking.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/WriteTracking.cpp @@ -290,6 +290,39 @@ void CheckNewestStamp() { Require(NewestStamp(base + 3 * Block - 8, Block + 8) > partial, "a CPU store into the watched part of a partly watched range did not advance its stamp"); } +void CheckPartlyWatchedStore() { + void* memory = AllocateWatched(3 * Block); + const auto base = reinterpret_cast(memory); + const auto stored = base + Block; + const auto unwatched = base + 2 * Block; + std::memset(memory, 0x11, 3 * Block); + BumpCollectEpoch(); + Unwatch(unwatched, Block); + const auto before = CollectWrites(base, 2 * Block); + Require(before != 0 && UnchangedSince(base, 2 * Block, before), "the watched blocks were not collected"); + Require(MarkWritten(unwatched - 64, 128) == 0, "a driver store into partly watched memory is trusted"); + Require(!UnchangedSince(stored, Block, before), "a driver store into partly watched memory left its watched block unchanged"); + Require(StoredOver(unwatched - 64, 64, before), "a driver store into partly watched memory is not seen over its watched bytes"); + Require(!StoredOver(stored, Block - 64, before), "a driver store into partly watched memory is seen over watched bytes it did not store"); + Require(UnchangedSince(base, Block, before), "a driver store into partly watched memory stamped a block it did not store into"); + const auto beforeOwn = TrackerGeneration(); + Require(StoreOwnBytes(unwatched - 64, 128, [&] { std::memset(reinterpret_cast(unwatched - 64), 0x22, 128); }) == 0, "an own store into partly watched memory is trusted"); + Require(!UnchangedSince(stored, Block, beforeOwn), "an own store into partly watched memory left its watched block unchanged"); + Require(UnchangedSince(base, Block, beforeOwn), "an own store into partly watched memory stamped a block it did not store into"); +#ifndef _WIN32 + void* split = AllocateWatched(Block); + const auto splitBase = reinterpret_cast(split); + const auto watchedHalf = splitBase + Block / 2; + std::memset(split, 0x11, Block); + Unwatch(splitBase, Block / 2); + const auto beforeSplit = CollectWrites(watchedHalf, Block / 2); + Require(beforeSplit != 0 && UnchangedSince(watchedHalf, Block / 2, beforeSplit), "the watched half of a split block was not collected"); + Require(MarkWritten(splitBase + 64, 128) == 0 && UnchangedSince(watchedHalf, Block / 2, beforeSplit), "a driver store into the unwatched half of a split block stamped its watched half"); + Require(MarkWritten(watchedHalf - 64, 128) == 0, "a driver store across the halves of a split block is trusted"); + Require(!UnchangedSince(watchedHalf, Block / 2, beforeSplit), "a driver store across the halves of a split block left its watched half unchanged"); +#endif +} + #ifdef _WIN32 void CheckPrivateMappingReuse() { void* memory = AllocateWatched(3 * Block); @@ -450,6 +483,7 @@ int main() { CheckOwnStore(); CheckUnwatch(); CheckNewestStamp(); + CheckPartlyWatchedStore(); #ifdef _WIN32 CheckPrivateMappingReuse(); #endif diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ArrayLayerTarget.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ArrayLayerTarget.cpp index 18dcef360cc5..f9e388295ebc 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ArrayLayerTarget.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ArrayLayerTarget.cpp @@ -81,7 +81,7 @@ std::string Hex(std::uint32_t value) { class GuestBlock { public: - GuestBlock() { + explicit GuestBlock(std::size_t registeredBytes = SurfaceBytes, std::size_t registeredOffset = 0) : registeredOffset(registeredOffset) { #ifdef _WIN32 block = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(SurfaceBytes, LayerBytes)); if (block != nullptr) GuestArena::GuestArenaCommit_nid_postfix(block, SurfaceBytes, PAGE_READWRITE, SurfaceBytes); @@ -97,11 +97,11 @@ class GuestBlock { } #endif Require(block != nullptr, "array layer target: cannot allocate the guest block"); - GuestAllocations::Mutation().Add(block, SurfaceBytes, true, true, true); + GuestAllocations::Mutation().Add(block + registeredOffset, registeredBytes, true, true, true); } ~GuestBlock() { - GuestAllocations::Mutation().Remove(block); + if (registered) GuestAllocations::Mutation().Remove(block + registeredOffset); #ifdef _WIN32 GuestArena::GuestArenaReset_nid_postfix(block, SurfaceBytes); GuestArena::GuestArenaRelease_nid_postfix(block, SurfaceBytes); @@ -115,9 +115,15 @@ class GuestBlock { GuestBlock& operator=(const GuestBlock&) = delete; std::uint8_t* Data() { return block; } + void Unregister() { + GuestAllocations::Mutation().Remove(block + registeredOffset); + registered = false; + } private: std::uint8_t* block = nullptr; + std::size_t registeredOffset = 0; + bool registered = true; }; std::array SurfaceDescriptor(std::uint64_t address, std::uint32_t swizzleMode, std::uint32_t type, std::uint32_t lastLayer) { @@ -309,6 +315,45 @@ void SampleSliceZero(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std: AgcDriver::Graphics::ClearCachedTextures(device.Device()); } +void UnregisteredUnusedLayer(AgcDriver::VulkanDevice& device, std::uint32_t slice) { + GuestBlock block(LayerBytes, slice * LayerBytes); + auto* texels = block.Data(); + const auto surface = AddressOf(texels); + const auto active = surface + slice * LayerBytes; + std::fill_n(reinterpret_cast(texels), SurfaceBytes / 4u, Initial); + AgcDriver::GuestMemory::CollectWrites(surface, SurfaceBytes); + AgcDriver::GuestMemory::BumpCollectEpoch(); + DrawSlice(device, surface, slice, TileR64KBX, false); + Require(StorageTexture::FindPending(surface, SurfaceBytes) != nullptr, "the registered slice was not pending before its CPU write-back"); + StorageTexture::FlushPending(active, LayerBytes, nullptr, "test"); + device.WaitIdle(); + const auto* first = reinterpret_cast(active); + const auto* last = reinterpret_cast(active + LayerBytes); + const auto unchanged = [](std::uint32_t value) { return value == Initial; }; + const auto drawn = std::count(first, last, std::bit_cast(Drawn)); + const bool untouched = std::all_of(reinterpret_cast(texels), first, unchanged) && std::all_of(last, reinterpret_cast(texels + SurfaceBytes), unchanged); + AgcDriver::Graphics::ClearCachedTextures(device.Device()); + Require(drawn == ScissorWidth * Side, "CPU write-back discarded the registered slice because an unused array slice was unregistered"); + Require(untouched, "CPU write-back modified an unregistered unused slice"); +} + +void RetiredArrayLayer(AgcDriver::VulkanDevice& device) { + GuestBlock block(LayerBytes); + auto* texels = block.Data(); + const auto surface = AddressOf(texels); + std::fill_n(reinterpret_cast(texels), SurfaceBytes / 4u, Initial); + AgcDriver::GuestMemory::CollectWrites(surface, SurfaceBytes); + AgcDriver::GuestMemory::BumpCollectEpoch(); + DrawSlice(device, surface, 0u, TileR64KBX, false); + Require(StorageTexture::FindPending(surface, SurfaceBytes) != nullptr, "the retired slice was not pending before it was unregistered"); + block.Unregister(); + StorageTexture::FlushPending(surface, LayerBytes, nullptr, "test"); + device.WaitIdle(); + const bool untouched = std::all_of(reinterpret_cast(texels), reinterpret_cast(texels + SurfaceBytes), [](std::uint32_t value) { return value == Initial; }); + AgcDriver::Graphics::ClearCachedTextures(device.Device()); + Require(untouched, "CPU write-back stored results over a retired allocation"); +} + } int main() { @@ -319,6 +364,9 @@ int main() { Run(*device, block.Data(), TileR64KBX); Run(*device, block.Data(), static_cast(AgcDriver::Graphics::ColorTileMode::D64KBX)); for (std::uint32_t slice = 1; slice < Layers; ++slice) Run(*device, block.Data(), TileR64KBX, true, slice); + UnregisteredUnusedLayer(*device, 0u); + UnregisteredUnusedLayer(*device, Layers - 1u); + RetiredArrayLayer(*device); std::puts("array layer target tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F64Arithmetic.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F64Arithmetic.cpp index 38ff1defd32e..520e49b4a901 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/F64Arithmetic.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F64Arithmetic.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -154,15 +155,13 @@ void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, con Require(actual == expected, std::string(Ieee ? "f64 arithmetic, IEEE mode: lane " : "f64 arithmetic: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); } -void Run(AgcDriver::VulkanDevice& device) { - for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); +void Run(AgcDriver::VulkanDevice& device, std::span code, std::optional mode) { Output.fill(0xdeadbeefu); std::vector userData(8, 0u); const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); std::copy(input.begin(), input.end(), userData.begin()); std::copy(output.begin(), output.end(), userData.begin() + 4); - const std::span code(Code); const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; ShaderRecompiler::RecompileRequest request{ @@ -172,7 +171,7 @@ void Run(AgcDriver::VulkanDevice& device) { {0, 0, 0, 128} }; request.useCache = false; - if (Ieee) request.context.floatMode = ShaderRecompiler::ShaderFloatMode{0xf0u, true, true, false}; + request.context.floatMode = mode; const auto result = ShaderRecompiler::Recompile(request); device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); device.WaitIdle(); @@ -190,6 +189,80 @@ void Check() { } } +std::uint64_t MinMaxReference(std::uint64_t lhs, std::uint64_t rhs, bool maximum, bool ieee) { + constexpr std::uint64_t magnitude = 0x7fffffffffffffffull; + constexpr std::uint64_t infinity = 0x7ff0000000000000ull; + constexpr std::uint64_t quiet = 0x0008000000000000ull; + const bool lhsNan = (lhs & magnitude) > infinity; + const bool rhsNan = (rhs & magnitude) > infinity; + if (ieee && lhsNan && (lhs & quiet) == 0u) return lhs | quiet; + if (ieee && rhsNan && (rhs & quiet) == 0u) return rhs | quiet; + if (rhsNan) return lhs; + if (lhsNan) return rhs; + if ((lhs & magnitude) == 0u && (rhs & magnitude) == 0u) return maximum ? lhs & rhs : lhs | rhs; + return maximum ? (std::bit_cast(lhs) > std::bit_cast(rhs) ? lhs : rhs) : (std::bit_cast(lhs) < std::bit_cast(rhs) ? lhs : rhs); +} + +void CheckMinMaxDenormals(AgcDriver::VulkanDevice& device) { + constexpr std::array, Threads> rows{{ + {0x0000000000000001ull, 0x0000000000000000ull}, {0x0000000000000000ull, 0x0000000000000001ull}, + {0x8000000000000001ull, 0x0000000000000000ull}, {0x0000000000000000ull, 0x8000000000000001ull}, + {0x0000000000000001ull, 0x8000000000000001ull}, {0x8000000000000001ull, 0x0000000000000001ull}, + {0x000fffffffffffffull, 0x800fffffffffffffull}, {0x800fffffffffffffull, 0x000fffffffffffffull}, + {0x000fffffffffffffull, 0x0010000000000000ull}, {0x0010000000000000ull, 0x000fffffffffffffull}, + {0x800fffffffffffffull, 0x8010000000000000ull}, {0x8010000000000000ull, 0x800fffffffffffffull}, + {0x0000000000000001ull, 0x000fffffffffffffull}, {0x000fffffffffffffull, 0x0000000000000001ull}, + {0x8000000000000001ull, 0x800fffffffffffffull}, {0x800fffffffffffffull, 0x8000000000000001ull}, + {0x7ff8000000000123ull, 0x0000000000000001ull}, {0x0000000000000001ull, 0x7ff8000000000123ull}, + {0xfff8000000000123ull, 0x800fffffffffffffull}, {0x800fffffffffffffull, 0xfff8000000000123ull}, + {0x7ff0000000000123ull, 0x0000000000000001ull}, {0x0000000000000001ull, 0x7ff0000000000123ull}, + {0xfff0000000000123ull, 0x800fffffffffffffull}, {0x800fffffffffffffull, 0xfff0000000000123ull}, + {0x7ff0000000000000ull, 0x000fffffffffffffull}, {0x000fffffffffffffull, 0x7ff0000000000000ull}, + {0xfff0000000000000ull, 0x800fffffffffffffull}, {0x800fffffffffffffull, 0xfff0000000000000ull}, + {0x0000000000000000ull, 0x8000000000000000ull}, {0x8000000000000000ull, 0x0000000000000000ull}, + {0x0010000000000000ull, 0x8010000000000000ull}, {0x8010000000000000ull, 0x0010000000000000ull}, + }}; + constexpr std::array, 4> modifiers{{{0u, 0u}, {1u, 2u}, {2u, 1u}, {3u, 3u}}}; + for (const auto& modifier : modifiers) { + alignas(256) auto code = Code; + for (const auto word : {13u, 15u}) { + code[word] |= modifier[0] << 8u; + code[word + 1u] |= modifier[1] << 29u; + } + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + for (std::uint32_t source = 0; source < 2u; ++source) { + Input[tid * Inputs + source * 2u] = static_cast(rows[tid][source]); + Input[tid * Inputs + source * 2u + 1u] = static_cast(rows[tid][source] >> 32u); + } + } + for (const int denormals : {-1, 0, 2, 3}) { + for (const bool ieee : {false, true}) { + if (denormals < 0 && ieee) continue; + std::optional mode; + if (denormals >= 0) mode = ShaderRecompiler::ShaderFloatMode{0x30u | (static_cast(denormals) << 6u), true, ieee, false}; + Run(device, code, mode); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + auto pair = rows[tid]; + for (std::uint32_t source = 0; source < 2u; ++source) { + if (((modifier[0] >> source) & 1u) != 0u) pair[source] &= 0x7fffffffffffffffull; + if (((modifier[1] >> source) & 1u) != 0u) pair[source] ^= 0x8000000000000000ull; + if ((denormals == 0 || denormals == 2) && (pair[source] & 0x7ff0000000000000ull) == 0u) pair[source] &= 0x8000000000000000ull; + } + for (std::uint32_t operation = 0; operation < 2u; ++operation) { + const auto expected = MinMaxReference(pair[0], pair[1], operation != 0u, ieee); + for (std::uint32_t word = 0; word < 2u; ++word) { + const auto actual = Output[tid * Results + 8u + operation * 2u + word]; + const auto expectedWord = static_cast(expected >> (word * 32u)); + Require(actual == expectedWord, "f64 min/max denormal mode " + std::to_string(denormals) + " IEEE " + std::to_string(ieee) + " abs " + std::to_string(modifier[0]) + " neg " + std::to_string(modifier[1]) + " lane " + std::to_string(tid) + " " + Names[8u + operation * 2u + word] + " is " + Hex(actual) + ", expected " + Hex(expectedWord)); + } + } + } + } + } + } + std::puts("f64 min/max denormal input cases passed"); +} + } int main() { @@ -202,9 +275,11 @@ int main() { } for (const bool ieee : {false, true}) { Ieee = ieee; - Run(*device); + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Run(*device, Code, ieee ? std::optional{ShaderRecompiler::ShaderFloatMode{0xf0u, true, true, false}} : std::nullopt); Check(); } + CheckMinMaxDenormals(*device); std::puts("f64 arithmetic tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlipStoresSmallResults.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlipStoresSmallResults.cpp index bbac636c998a..34f0951dc6f5 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/FlipStoresSmallResults.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlipStoresSmallResults.cpp @@ -131,7 +131,7 @@ int main() { auto words = Frame(texels); Packet packet{words.data(), static_cast(words.size()), 0, {}}; AgcDriver::Submit(&packet, 0); - const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10); while ((output->ready.load() == 0 || !stored()) && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(1)); const bool flipped = output->ready.load() == 1; const bool seen = stored(); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/IndirectArgumentsPendingProducer.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/IndirectArgumentsPendingProducer.cpp new file mode 100644 index 000000000000..012363965a34 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/IndirectArgumentsPendingProducer.cpp @@ -0,0 +1,365 @@ +#include "VulkanTestDevice.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" +#include +#include "prx/libSceAgcDriver/Submit/include/Acb.hpp" +#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, const std::string& message) { + if (!condition) throw std::runtime_error(message); +} + +alignas(256) std::array AddressWriterCode{0x7e000202u, 0x7e020203u, 0x7e040204u, 0x7e060280u, 0xdc7c8000u, 0x00000003u, 0xbf810000u}; +alignas(256) std::array CounterCode{0xf4080200u, 0xfa000000u, 0xbf8cc07fu, 0x7e000281u, 0xe0c80000u, 0x80020000u, 0xbf810000u}; +alignas(256) std::array SpinCode{0xf4080200u, 0xfa000000u, 0xbe840381u, 0x9304ff04u, 0x0019660du, 0x8004ff04u, 0x3c6ef35fu, 0x80828102u, 0xbf078002u, 0xbf85fff9u, 0xbf8cc07fu, 0x7e000204u, 0xe0700000u, 0x80020000u, 0xbf810000u}; + +constexpr std::uint32_t Groups = 5; +constexpr std::array Stale{3u, 288u, 0xffffffffu}; +constexpr std::uint32_t SpinIterations = 1u << 20u; +constexpr std::uint32_t SpinDispatches = 16; +constexpr std::uint32_t ThreadDimensions = 0x8061u; +constexpr std::uint32_t GroupCounts = 0x8041u; +constexpr std::uint32_t Compute = 0x20u; +constexpr std::uint32_t OtherCompute = 0x28u; +constexpr std::uint32_t Graphics = 0u; +constexpr std::size_t GuestBytes = 1u << 20u; + +struct Slot { + alignas(256) std::array arguments{}; + alignas(256) std::array counter{}; + alignas(256) std::array counterDescriptor{}; + alignas(256) std::array spin{}; + alignas(256) std::array spinDescriptor{}; + alignas(256) std::uint32_t label = 0; + alignas(256) std::uint32_t done = 0; +}; + +enum class Order { SeparateSubmissions, OneSubmission, LabelWaitInOneSubmission, LabelWaitBetweenSubmissions, LabelWaitAcrossQueues, ArgumentLabelInEarlierBatch }; + +struct Variant { + const char* name; + std::uint32_t initiator; + Order order; +}; + +constexpr std::array Variants{{ + {"thread dimensions, separate submissions", ThreadDimensions, Order::SeparateSubmissions}, + {"thread dimensions, one submission", ThreadDimensions, Order::OneSubmission}, + {"thread dimensions, label wait in one submission", ThreadDimensions, Order::LabelWaitInOneSubmission}, + {"thread dimensions, label wait between submissions", ThreadDimensions, Order::LabelWaitBetweenSubmissions}, + {"thread dimensions, label wait across compute queues", ThreadDimensions, Order::LabelWaitAcrossQueues}, + {"group counts read on the CPU behind an argument label of an earlier graphics batch", GroupCounts, Order::ArgumentLabelInEarlierBatch}, +}}; + +std::uint32_t Low(const volatile void* pointer) { + return static_cast(reinterpret_cast(pointer)); +} + +std::uint32_t High(const volatile void* pointer) { + return static_cast(static_cast(reinterpret_cast(pointer)) >> 32u); +} + +std::array Descriptor(const void* pointer, std::uint32_t bytes) { + return {Low(pointer), High(pointer) & 0xffffu, bytes, 0x31016facu}; +} + +std::uint32_t Load(const std::uint32_t& value) { + return reinterpret_cast(value); +} + +void SetShaderRegister(std::vector& words, std::uint32_t offset, std::uint32_t value) { + words.insert(words.end(), {0xc0017600u, offset, value}); +} + +void Program(std::vector& words, const void* code, std::initializer_list userData) { + const auto address = static_cast(reinterpret_cast(code)); + SetShaderRegister(words, 0x20c, static_cast(address >> 8u)); + SetShaderRegister(words, 0x20d, static_cast(address >> 40u) & 0xffu); + for (const std::uint32_t offset : {0x207u, 0x208u, 0x209u}) SetShaderRegister(words, offset, 1); + SetShaderRegister(words, 0x212, 0); + SetShaderRegister(words, 0x213, static_cast(userData.size()) << 1u); + std::uint32_t offset = 0x240; + for (const auto value : userData) SetShaderRegister(words, offset++, value); +} + +void WriteData(std::vector& words, std::uint32_t* target, std::uint32_t value) { + words.insert(words.end(), {0xc0033700u, 0x00100200u, Low(target), High(target), value}); +} + +void WaitEqual(std::vector& words, std::uint32_t* target, std::uint32_t value) { + words.insert(words.end(), {0xc0053c00u, 0x13u, Low(target), High(target), value, 0xffffffffu, 0x19u}); +} + +void Spin(std::vector& words, Slot& slot, std::uint32_t dispatches) { + for (std::uint32_t index = 0; index < dispatches; ++index) { + Program(words, SpinCode.data(), {Low(slot.spinDescriptor.data()), High(slot.spinDescriptor.data()), SpinIterations}); + words.insert(words.end(), {0xc0031500u, 1, 1, 1, GroupCounts}); + } +} + +void Produce(std::vector& words, Slot& slot) { + Program(words, AddressWriterCode.data(), {Low(slot.arguments.data()), High(slot.arguments.data()), Groups, 1u, 1u}); + words.insert(words.end(), {0xc0031500u, 1, 1, 1, GroupCounts}); +} + +void Consume(std::vector& words, Slot& slot, std::uint32_t initiator) { + Program(words, CounterCode.data(), {Low(slot.counterDescriptor.data()), High(slot.counterDescriptor.data())}); + words.insert(words.end(), {0xc0021102u, 1u, Low(slot.arguments.data()), High(slot.arguments.data())}); + words.insert(words.end(), {0xc0011600u, 0u, initiator}); +} + +void Submit(std::uint32_t queue, std::vector& words) { + Packet packet{words.data(), static_cast(words.size()), 0, {}}; + if (queue == Graphics) Require(sceAgcDriverSubmitDcb(&packet) == 0, "graphics submit failed"); + else Require(sceAgcDriverSubmitAcb(queue, &packet) == 0, "compute submit failed"); +} + +ShaderRecompiler::RecompileResult Recompile(AgcDriver::VulkanDevice& device, Slot& slot, std::span code, std::initializer_list userData) { + const std::array memory{{ + {reinterpret_cast(code.data()), std::as_bytes(code)}, + {reinterpret_cast(slot.counterDescriptor.data()), std::as_bytes(std::span(slot.counterDescriptor))}, + }}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{1, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderRecompiler::ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, std::span(userData.begin(), userData.size()), compute, std::nullopt, std::nullopt, memory}, + device.ComputeTarget(32), + {0, 0, 0, 128} + }; + request.useCache = false; + return ShaderRecompiler::Recompile(request); +} + +class ProducerSubmissionGate { +public: + explicit ProducerSubmissionGate(AgcDriver::VulkanDevice& device) : device(device) { +#ifdef _WIN32 + constexpr const char* libraryName = "vulkan-1.dll"; +#else + constexpr const char* libraryName = "libvulkan.so.1"; +#endif + library.reset(SDL_LoadObject(libraryName)); + Require(library != nullptr, "cannot load Vulkan for the producer submission gate"); + resolve = reinterpret_cast(SDL_LoadFunction(library.get(), "vkGetDeviceProcAddr")); + Require(resolve != nullptr, "cannot resolve vkGetDeviceProcAddr for the producer submission gate"); + const auto createEvent = function("vkCreateEvent"); + destroyEvent = function("vkDestroyEvent"); + setEvent = function("vkSetEvent"); + waitEvents = function("vkCmdWaitEvents"); + const VkEventCreateInfo info{VK_STRUCTURE_TYPE_EVENT_CREATE_INFO}; + Require(createEvent(device.Device(), &info, nullptr, &event) == VK_SUCCESS, "cannot create the producer submission gate"); + } + + ~ProducerSubmissionGate() { + if (worker.joinable()) { + worker.request_stop(); + worker.join(); + } + setEvent(device.Device(), event); + try { + device.WaitIdle(); + } catch (...) { + } + destroyEvent(device.Device(), event, nullptr); + } + + void Record(AgcDriver::Graphics::Recorder& recorder) { + waitEvents(recorder.Commands(), 1, &event, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, nullptr, 0, nullptr, 0, nullptr); + } + + void ReleaseAfterSubmission(AgcDriver::Graphics::Recorder& recorder, std::uint64_t serial) { + worker = std::jthread([this, &recorder, serial](std::stop_token stop) { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (!stop.stop_requested() && recorder.NewestSubmitted() < serial && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::microseconds(100)); + producerSubmitted = recorder.NewestSubmitted() >= serial; + releaseResult = setEvent(device.Device(), event); + if (!producerSubmitted && !stop.stop_requested()) std::fprintf(stderr, "pending producer gate: watchdog expired with producer batch %llu unsubmitted (vkSetEvent %d)\n", static_cast(serial), static_cast(releaseResult)); + }); + } + + void Finish() { + worker.join(); + Require(releaseResult == VK_SUCCESS, "cannot release the producer submission gate"); + Require(producerSubmitted, "the CPU argument read did not submit the later leased producer before waiting for its earlier argument label"); + device.WaitIdle(); + } + +private: + template + TFunction function(const char* name) const { + const auto function = reinterpret_cast(resolve(device.Device(), name)); + Require(function != nullptr, std::string("cannot resolve ") + name + " for the producer submission gate"); + return function; + } + + AgcDriver::VulkanDevice& device; + std::unique_ptr library{nullptr, &SDL_UnloadObject}; + PFN_vkGetDeviceProcAddr resolve = nullptr; + PFN_vkDestroyEvent destroyEvent = nullptr; + PFN_vkSetEvent setEvent = nullptr; + PFN_vkCmdWaitEvents waitEvents = nullptr; + VkEvent event = VK_NULL_HANDLE; + std::jthread worker; + bool producerSubmitted = false; + VkResult releaseResult = VK_NOT_READY; +}; + +void RunArgumentLabel(Slot& slot) { + AgcDriverShutdown_nid_postfix(); + slot.arguments = {3, 1, 1, 0}; + const auto device = OpenVulkanTestDevice(); + Require(device != nullptr, "cannot open the argument-label test device"); + const auto producer = Recompile(*device, slot, AddressWriterCode, {Low(slot.arguments.data()), High(slot.arguments.data()), Groups, 1, 1}); + const auto consumer = Recompile(*device, slot, CounterCode, {Low(slot.counterDescriptor.data()), High(slot.counterDescriptor.data())}); + const auto preparedProducer = device->PrepareDispatch(producer, {}, 1); + const auto preparedConsumer = device->PrepareDispatch(consumer, {}, 0); + std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); + auto* recorder = AgcDriver::Graphics::Recorder::Active(); + Require(recorder != nullptr, "the argument-label test has no active recorder"); + ProducerSubmissionGate gate(*device); + gate.Record(*recorder); + const std::uint32_t labelValue = 2; + const auto labelAddress = reinterpret_cast(&slot.arguments[1]); + Require(device->WriteLabelOnGpu(labelAddress, std::as_bytes(std::span(&labelValue, 1)), 1, Graphics, false) == 0, "the argument label was not recorded on the GPU"); + device->SubmitRecorded(false); + const auto producerSerial = recorder->OpenSerial(); + gate.ReleaseAfterSubmission(*recorder, producerSerial); + const auto deferredBefore = AgcDriver::Graphics::LeaseCounters().deferred; + device->Dispatch(producer, 1, 1, 1, {}, reinterpret_cast(AddressWriterCode.data()), preparedProducer); + Require(AgcDriver::Graphics::LeaseCounters().deferred > deferredBefore, "the address-based producer did not defer its registry lease"); + Require(recorder->Submissions() < producerSerial && recorder->Recording(), "the producer was submitted before the CPU argument decision"); + Require(recorder->PendingLabel(labelAddress, 4, 0).has_value(), "the earlier argument label retired before the CPU argument decision"); + const auto outcome = device->DispatchIndirect(consumer, reinterpret_cast(slot.arguments.data()), {}, reinterpret_cast(CounterCode.data()), preparedConsumer); + Require(outcome.cpuReason == 2, "the indirect dispatch did not take the pending-label CPU fallback (reason " + std::to_string(outcome.cpuReason) + ")"); + std::fprintf(stderr, "pending producer CPU fallback: cpuReason %d\n", outcome.cpuReason); + gate.Finish(); + slot.done = 1; +} + +void Run(Slot& slot, const Variant& variant) { + slot.arguments = {Stale[0], Stale[1], Stale[2], 0}; + slot.counter = {}; + slot.counterDescriptor = Descriptor(slot.counter.data(), sizeof(slot.counter)); + slot.spinDescriptor = Descriptor(slot.spin.data(), sizeof(slot.spin)); + slot.label = 0; + slot.done = 0; + std::vector first; + std::vector second; + switch (variant.order) { + case Order::SeparateSubmissions: + Spin(first, slot, SpinDispatches); + Produce(first, slot); + Consume(second, slot, variant.initiator); + WriteData(second, &slot.done, 1); + Submit(Compute, first); + Submit(Compute, second); + break; + case Order::OneSubmission: + Spin(first, slot, SpinDispatches); + Produce(first, slot); + Consume(first, slot, variant.initiator); + WriteData(first, &slot.done, 1); + Submit(Compute, first); + break; + case Order::LabelWaitInOneSubmission: + Spin(first, slot, SpinDispatches); + Produce(first, slot); + WriteData(first, &slot.label, 1); + WaitEqual(first, &slot.label, 1); + Consume(first, slot, variant.initiator); + WriteData(first, &slot.done, 1); + Submit(Compute, first); + break; + case Order::LabelWaitBetweenSubmissions: + Spin(first, slot, SpinDispatches); + Produce(first, slot); + WriteData(first, &slot.label, 1); + WaitEqual(second, &slot.label, 1); + Consume(second, slot, variant.initiator); + WriteData(second, &slot.done, 1); + Submit(Compute, first); + Submit(Compute, second); + break; + case Order::LabelWaitAcrossQueues: + Spin(first, slot, SpinDispatches); + Produce(first, slot); + WriteData(first, &slot.label, 1); + WaitEqual(second, &slot.label, 1); + Consume(second, slot, variant.initiator); + WriteData(second, &slot.done, 1); + Submit(OtherCompute, first); + Submit(Compute, second); + break; + case Order::ArgumentLabelInEarlierBatch: + RunArgumentLabel(slot); + break; + } + AgcDriverWaitIdle_nid_postfix(); + const auto start = std::chrono::steady_clock::now(); + while (Load(slot.done) == 0) { + Require(std::chrono::steady_clock::now() - start < std::chrono::seconds(10), std::string(variant.name) + ": the consumer's completion label did not land"); + std::this_thread::sleep_for(std::chrono::microseconds(100)); + } + Require(Load(slot.arguments[0]) == Groups && Load(slot.arguments[1]) == 1 && Load(slot.arguments[2]) == 1, std::string(variant.name) + ": the producer's address-based store did not write the indirect arguments"); + Require(Load(slot.counter[0]) == Groups, std::string(variant.name) + ": the indirect dispatch ran " + std::to_string(Load(slot.counter[0])) + " threads, expected the " + std::to_string(Groups) + " its producer's address-based store wrote"); +} + +} + +int main(int argc, char** argv) { + try { + auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + device.reset(); +#ifdef _WIN32 + auto* block = static_cast(VirtualAlloc(nullptr, GuestBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + auto* block = static_cast(std::aligned_alloc(GuestBytes, GuestBytes)); +#endif + Require(block != nullptr, "cannot allocate guest memory"); + GuestAllocations::Mutation().Add(block, GuestBytes, true, true, true); + auto* slots = new (block) Slot[Variants.size()]; + for (std::size_t index = 0; index < Variants.size(); ++index) { + if (argc > 1 && std::to_string(index) != argv[1]) continue; + Run(slots[index], Variants[index]); + std::cout << Variants[index].name << ": passed\n"; + } + std::cout << "indirect arguments pending producer tests passed\n"; + AgcDriverShutdown_nid_postfix(); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + try { + AgcDriverShutdown_nid_postfix(); + } catch (const std::exception& shutdownError) { + std::cerr << shutdownError.what() << '\n'; + } + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/IndirectDispatchLimit.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/IndirectDispatchLimit.cpp new file mode 100644 index 000000000000..54aba72adc0f --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/IndirectDispatchLimit.cpp @@ -0,0 +1,103 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::size_t BlockBytes = 65536; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array StoreCode{0x34020082u, 0x7e040287u, 0xe0701000u, 0x80010201u, 0xbf810000u}; + +std::array BufferDescriptor(const void* data, std::uint32_t count) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), count * 4u, 0x31016facu}; +} + +std::optional> OverLimit(const std::uint32_t* limit) { + for (std::size_t axis = 0; axis < 3; ++axis) { + if (limit[axis] == 0xffffffffu) continue; + std::array groups{1u, 1u, 1u}; + groups[axis] = limit[axis] + 1u; + return groups; + } + return std::nullopt; +} + +int Run(AgcDriver::VulkanDevice& device, std::uint32_t* block, std::array groups) { + Output.fill(0xdeadbeefu); + std::memcpy(block, groups.data(), sizeof(groups)); + std::vector userData(8, 0u); + const auto output = BufferDescriptor(Output.data(), Threads); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(StoreCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {64, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + const auto outcome = device.DispatchIndirect(result, reinterpret_cast(block), {}, reinterpret_cast(code.data())); + device.WaitIdle(); + return outcome.cpuReason; +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + auto* block = static_cast(::operator new(BlockBytes, std::align_val_t{BlockBytes})); + std::memset(block, 0, BlockBytes); + auto* mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix(); + GuestAllocations::GuestAllocationsAdd_nid_postfix(mutation, block, BlockBytes, true, true, true); + GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation); + int failures = 0; + try { + const auto over = OverLimit(device->WorkGroupCountLimit()); + const int invalidPath = over ? Run(*device, block, *over) : 0; + const auto overText = over ? std::to_string((*over)[0]) + "x" + std::to_string((*over)[1]) + "x" + std::to_string((*over)[2]) : std::string(); + if (!over) { + std::puts("skipped, every group count is within the device limits"); + } else if (invalidPath != 0) { + std::printf("skipped, the group counts were read on the CPU (reason %d): the device cannot import the block\n", invalidPath); + } else { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Require(Output[tid] == 0xdeadbeefu, "indirect dispatch limit: thread " + std::to_string(tid) + " ran with " + overText + " groups"); + Require(Run(*device, block, {1u, 1u, 1u}) == 0, "indirect dispatch limit: valid counts left the GPU path"); + for (std::uint32_t tid = 0; tid < Threads; ++tid) Require(Output[tid] == 7u, "indirect dispatch limit: thread " + std::to_string(tid) + " stored " + std::to_string(Output[tid]) + " with one group, expected 7"); + } + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + failures = 1; + } + mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix(); + GuestAllocations::GuestAllocationsRemove_nid_postfix(mutation, block); + GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation); + ::operator delete(block, std::align_val_t{BlockBytes}); + if (failures != 0) return 1; + std::puts("indirect dispatch limit tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/InterpolationModes.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/InterpolationModes.cpp index cb363ba335c0..9c332e8aaaab 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/InterpolationModes.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/InterpolationModes.cpp @@ -6,9 +6,13 @@ #include "VulkanTestDevice.hpp" #include #include +#include +#include #include #include #include +#include +#include #include namespace { @@ -65,15 +69,19 @@ constexpr std::array Rows{{ constexpr std::uint32_t Words = 12; constexpr std::uint32_t Sentinel = 0xdeadbeefu; -alignas(256) std::array Data{}; +constexpr std::uint32_t RandomRows = 2048; +constexpr std::uint32_t RandomModes = 32; +constexpr std::uint32_t RandomWords = 9 + RandomModes * 3; +alignas(256) std::array Data{}; -RecompileResult Compile(std::uint32_t mode) { +RecompileResult Compile(std::span modes, bool integer, bool raw, std::uint32_t words) { IrProgram program; const ShaderStageInputInfo inputs{}; SpirvEmitterState state(program, inputs); + state.integerInterpolation = integer; auto& module = state.module; module.EmitCapability(spv::CapabilityShader); - module.EmitCapability(spv::CapabilityFloat64); + module.EmitCapability(integer ? spv::CapabilityInt64 : spv::CapabilityFloat64); module.EmitCapability(spv::CapabilitySignedZeroInfNanPreserve); module.EmitExtension("SPV_KHR_float_controls"); module.AddMemoryModel(spv::AddressingModelLogical, spv::MemoryModelGLSL450); @@ -92,14 +100,15 @@ RecompileResult Compile(std::uint32_t mode) { const auto voidType = module.Type(spv::OpTypeVoid); module.EmitEntryPoint(spv::ExecutionModelGLCompute, main, "main", {invocation}); module.AddExecutionMode(main, spv::ExecutionModeLocalSize, static_cast(Rows.size()), 1u, 1u); - for (const auto width : {32u, 64u}) module.AddExecutionMode(main, spv::ExecutionModeSignedZeroInfNanPreserve, width); + module.AddExecutionMode(main, spv::ExecutionModeSignedZeroInfNanPreserve, 32u); + if (!integer) module.AddExecutionMode(main, spv::ExecutionModeSignedZeroInfNanPreserve, 64u); module.AddFunction(spv::OpFunction, voidType, main, spv::FunctionControlMaskNone, module.Type(spv::OpTypeFunction, voidType)); module.AddFunction(spv::OpLabel, module.AllocateId()); const auto id = module.AllocateId(); module.AddFunction(spv::OpLoad, TypeU32Vector(state, 3u), id, invocation); const auto lane = module.AllocateId(); module.AddFunction(spv::OpCompositeExtract, u32, lane, id, 0u); - const auto rowBase = Binary(state, spv::OpIMul, u32, lane, ConstantU32(state, Words)); + const auto rowBase = Binary(state, spv::OpIMul, u32, lane, ConstantU32(state, words)); const auto address = [&](std::uint32_t offset) { const auto pointer = module.AllocateId(); module.AddFunction(spv::OpAccessChain, element, pointer, buffer, ConstantU32(state, 0u), Binary(state, spv::OpIAdd, u32, rowBase, ConstantU32(state, offset))); @@ -113,14 +122,18 @@ RecompileResult Compile(std::uint32_t mode) { IrProgram constants; IrBuilder builder(constants); builder.SetInsertionPoint(constants.CreateBlock()); - const auto* flags = &builder.Constant(mode); - for (std::uint32_t op = 0; op < 3u; ++op) { - const auto delta = load(op * 3u); - const auto coordinate = load(op * 3u + 1u); - const auto base = load(op * 3u + 2u); - const auto value = op == 1u ? EmitFPInterpolateF16(state, delta, coordinate, base, flags) : EmitFPInterpolateF32(state, delta, coordinate, base, flags); - const auto result = op == 1u ? EmitConvertF16F32(state, value) : Unary(state, spv::OpBitcast, u32, value); - module.AddFunction(spv::OpStore, address(9u + op), result); + std::array operands{}; + for (std::uint32_t index = 0; index < operands.size(); ++index) operands[index] = load(index); + for (std::uint32_t slot = 0; slot < modes.size(); ++slot) { + const auto* flags = &builder.Constant(modes[slot]); + for (std::uint32_t op = 0; op < 3u; ++op) { + const auto delta = operands[op * 3u]; + const auto coordinate = operands[op * 3u + 1u]; + const auto base = operands[op * 3u + 2u]; + const auto value = op == 1u ? EmitFPInterpolateF16(state, delta, coordinate, base, flags) : EmitFPInterpolateF32(state, delta, coordinate, base, flags); + const auto result = op == 1u && !raw ? EmitConvertF16F32(state, value) : Unary(state, spv::OpBitcast, u32, value); + module.AddFunction(spv::OpStore, address(9u + slot * 3u + op), result); + } } module.AddFunction(spv::OpReturn); module.AddFunction(spv::OpFunctionEnd); @@ -132,10 +145,11 @@ RecompileResult Compile(std::uint32_t mode) { return result; } -void Run(AgcDriver::VulkanDevice& device, std::uint32_t mode) { +void Run(AgcDriver::VulkanDevice& device, std::uint32_t mode, bool integer) { Data.fill(Sentinel); for (std::uint32_t row = 0; row < Rows.size(); ++row) std::copy(Rows[row].input.begin(), Rows[row].input.end(), Data.begin() + row * Words); - device.Dispatch(Compile(ModeFlags[mode]), 1u, 1u, 1u); + const std::array modes{ModeFlags[mode]}; + device.Dispatch(Compile(modes, integer, false, Words), 1u, 1u, 1u); device.WaitIdle(); for (std::uint32_t row = 0; row < Rows.size(); ++row) { for (std::uint32_t input = 0; input < 9u; ++input) Require(Data[row * Words + input] == Rows[row].input[input], "interpolation modes changed an input"); @@ -143,7 +157,7 @@ void Run(AgcDriver::VulkanDevice& device, std::uint32_t mode) { const auto actual = Data[row * Words + 9u + op]; const auto expected = Rows[row].expected[mode][op]; char message[200]; - std::snprintf(message, sizeof(message), "%s, %s, row %u: got 0x%08x, expected 0x%08x", OpNames[op], ModeNames[mode], row, actual, expected); + std::snprintf(message, sizeof(message), "%s (%s), %s, row %u: got 0x%08x, expected 0x%08x", OpNames[op], integer ? "integer" : "Float64", ModeNames[mode], row, actual, expected); Require(actual == expected, message); } } @@ -152,17 +166,155 @@ void Run(AgcDriver::VulkanDevice& device, std::uint32_t mode) { } +std::uint32_t Flush(std::uint32_t bits, bool flush, std::uint32_t smallestNormal) { + const auto magnitude = bits & 0x7fffffffu; + return flush && magnitude != 0u && magnitude < smallestNormal ? bits & 0x80000000u : bits; +} + +bool NonFinite(std::uint32_t bits) { return (bits & 0x7f800000u) == 0x7f800000u; } +bool Nan(std::uint32_t bits) { return (bits & 0x7fffffffu) > 0x7f800000u; } +bool Infinite(std::uint32_t bits) { return (bits & 0x7fffffffu) == 0x7f800000u; } +bool Zero(std::uint32_t bits) { return (bits & 0x7fffffffu) == 0u; } + +std::uint32_t Reference(std::uint32_t delta, std::uint32_t coordinate, std::uint32_t base, bool half, std::uint32_t mode) { + const bool quiet = half || (mode & InterpolationQuiet) != 0u; + const bool flush32 = (mode & InterpolationFlush32) != 0u; + const bool flush16 = (mode & InterpolationFlush16) != 0u; + const std::uint32_t rounding = half ? (mode >> InterpolationRound16Shift) & 3u : 0u; + const auto d = Flush(delta, flush16, 0x38800000u); + const auto c = Flush(coordinate, flush32, 0x00800000u); + const auto b = half ? Flush(base, flush32, 0x00800000u) : Flush(base, flush16, 0x38800000u); + const double fd = std::bit_cast(d); + const double fc = std::bit_cast(c); + const double fb = std::bit_cast(b); + std::uint32_t result = 0; + if (NonFinite(d) || NonFinite(c) || NonFinite(b)) { + const double unbounded = fd * fc + fb; + result = std::isnan(unbounded) ? 0xffc00000u : (std::signbit(unbounded) ? 0xff800000u : 0x7f800000u); + const auto quieted = [&](std::uint32_t bits) { return quiet ? bits | 0x00400000u : bits; }; + const bool invalid = (Infinite(d) && Zero(c)) || (Zero(d) && Infinite(c)); + if (Nan(b) && !invalid) result = quieted(b); + if (Nan(c)) result = quieted(c); + if (Nan(d)) result = quieted(d); + } else { + const double product = fd * fc; + double sum = product + fb; + const double back = sum - product; + const double error = (product - (sum - back)) + (fb - back); + if (error != 0.0 && (std::bit_cast(sum) & 1u) == 0u) sum = std::nextafter(sum, error > 0.0 ? std::numeric_limits::infinity() : -std::numeric_limits::infinity()); + if (sum == 0.0 && rounding == 2u && !(product == 0.0 && !std::signbit(product) && fb == 0.0 && !std::signbit(fb))) sum = -0.0; + double rounded = sum; + if (sum != 0.0) { + const int precision = half ? 11 : 24; + const int smallest = half ? -24 : -149; + const int step = std::max(std::ilogb(sum) - (precision - 1), smallest); + const double scaled = std::ldexp(sum, -step); + const double integral = rounding == 1u ? std::ceil(scaled) : rounding == 2u ? std::floor(scaled) : rounding == 3u ? std::trunc(scaled) : std::nearbyint(scaled); + rounded = std::ldexp(integral, step); + } + if (half) { + const double magnitude = std::fabs(rounded); + const bool negative = std::signbit(rounded); + if (rounding == 0u && magnitude > 65536.0) rounded = std::copysign(65536.0, rounded); + if (rounding != 0u && magnitude > 65504.0) { + const bool larger = (rounding == 1u && !negative) || (rounding == 2u && negative); + rounded = std::copysign(larger ? 65536.0 : 65504.0, rounded); + } + } + result = std::bit_cast(static_cast(rounded)); + } + return half ? Flush(result, flush16, 0x38800000u) : Flush(result, flush32, 0x00800000u); +} + +struct Generator { + std::uint64_t state; + std::uint64_t Next() { + state += 0x9e3779b97f4a7c15ull; + auto value = state; + value = (value ^ (value >> 30u)) * 0xbf58476d1ce4e5b9ull; + value = (value ^ (value >> 27u)) * 0x94d049bb133111ebull; + return value ^ (value >> 31u); + } + std::uint32_t Pick(std::uint32_t count) { return static_cast(Next() % count); } + std::uint32_t Half() { + constexpr std::array special{0x0000u, 0x8000u, 0x0001u, 0x8001u, 0x03ffu, 0x0400u, 0x3c00u, 0xbc00u, 0x7bffu, 0xfbffu, 0x7c00u, 0xfc00u}; + const auto bits = Pick(4u) == 0u ? special[Pick(static_cast(special.size()))] : static_cast(Next() & 0xffffu); + const auto sign = (bits & 0x8000u) << 16u; + const auto exponent = (bits >> 10u) & 0x1fu; + const auto fraction = bits & 0x3ffu; + if (exponent == 31u) return sign | 0x7f800000u | (fraction << 13u); + if (exponent != 0u) return sign | ((exponent + 112u) << 23u) | (fraction << 13u); + return sign | std::bit_cast(static_cast(fraction) * 0x1p-24f); + } + std::uint32_t Single() { + constexpr std::array special{0x00000000u, 0x80000000u, 0x00000001u, 0x807fffffu, 0x00800000u, 0x3f800000u, 0xbf800000u, 0x7f7fffffu, 0xff7fffffu, 0x7f800000u, 0xff800000u, 0x7fc00000u, 0xffa00001u, 0x33800000u}; + switch (Pick(4u)) { + case 0: return special[Pick(static_cast(special.size()))]; + case 1: return static_cast(Next()); + default: return (Pick(2u) << 31u) | ((Pick(254u) + 1u) << 23u) | static_cast(Next() & 0x7fffffu); + } + } + std::uint32_t Cancelling(std::uint32_t delta, std::uint32_t coordinate, bool half) { + const double product = static_cast(std::bit_cast(delta)) * static_cast(std::bit_cast(coordinate)); + if (!std::isfinite(product)) return half ? Single() : Half(); + auto bits = std::bit_cast(static_cast(-product)); + if (Pick(2u) == 0u) bits += Pick(5u) - 2u; + if (half) return bits; + const auto value = std::bit_cast(bits); + if (!std::isfinite(value) || std::fabs(value) > 65504.0f) return Half(); + const auto exponent = std::max(std::ilogb(value) - 10, -24); + return std::bit_cast(static_cast(std::ldexp(std::nearbyint(std::ldexp(value, -exponent)), exponent))); + } +}; + +void RunRandom(AgcDriver::VulkanDevice& device, bool integer) { + std::array modes{}; + for (std::uint32_t slot = 0; slot < RandomModes; ++slot) modes[slot] = ModeFlags[slot % ModeFlags.size()] | ((slot / ModeFlags.size()) << InterpolationRound16Shift); + Generator generator{0x6b5f00000000ull}; + Data.fill(Sentinel); + for (std::uint32_t row = 0; row < RandomRows; ++row) { + for (std::uint32_t op = 0; op < 3u; ++op) { + const bool half = op == 1u; + const auto delta = generator.Half(); + const auto coordinate = generator.Single(); + const auto base = generator.Pick(3u) == 0u ? generator.Cancelling(delta, coordinate, half) : half ? generator.Single() : generator.Half(); + Data[row * RandomWords + op * 3u] = delta; + Data[row * RandomWords + op * 3u + 1u] = coordinate; + Data[row * RandomWords + op * 3u + 2u] = base; + } + } + device.Dispatch(Compile(modes, integer, true, RandomWords), RandomRows / static_cast(Rows.size()), 1u, 1u); + device.WaitIdle(); + for (std::uint32_t row = 0; row < RandomRows; ++row) { + for (std::uint32_t slot = 0; slot < RandomModes; ++slot) { + for (std::uint32_t op = 0; op < 3u; ++op) { + const auto* input = &Data[row * RandomWords + op * 3u]; + const auto expected = Reference(input[0], input[1], input[2], op == 1u, modes[slot]); + const auto actual = Data[row * RandomWords + 9u + slot * 3u + op]; + char message[240]; + std::snprintf(message, sizeof(message), "%s (%s), mode 0x%x, random row %u: 0x%08x * 0x%08x + 0x%08x gave 0x%08x, reference 0x%08x", + OpNames[op], integer ? "integer" : "Float64", modes[slot], row, input[0], input[1], input[2], actual, expected); + Require(actual == expected, message); + } + } + } + for (std::size_t index = RandomRows * RandomWords; index < Data.size(); ++index) Require(Data[index] == Sentinel, "random interpolation rows wrote beyond the rows"); +} + int main() { try { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; const auto target = device->Target(); - if (std::find(target.supportedCapabilities.begin(), target.supportedCapabilities.end(), spv::CapabilityFloat64) == target.supportedCapabilities.end()) { - std::puts("skipped, interpolation modes require Float64"); - return VulkanTestSkipped; + const bool float64 = std::find(target.supportedCapabilities.begin(), target.supportedCapabilities.end(), spv::CapabilityFloat64) != target.supportedCapabilities.end(); + for (std::uint32_t mode = 0; mode < ModeFlags.size(); ++mode) { + Run(*device, mode, true); + if (float64) Run(*device, mode, false); } - for (std::uint32_t mode = 0; mode < ModeFlags.size(); ++mode) Run(*device, mode); - std::puts("interpolation modes passed: 32 hardware rows, 3 ops, 8 float modes"); + RunRandom(*device, true); + if (float64) RunRandom(*device, false); + std::puts(float64 ? "interpolation modes passed: 32 hardware rows and 2048 random rows against the reference in 32 float modes, 3 ops, integer and Float64 paths" + : "interpolation modes passed: 32 hardware rows and 2048 random rows against the reference in 32 float modes, 3 ops, integer path (the device has no Float64)"); return 0; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/MeshProvokingLayer.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/MeshProvokingLayer.cpp new file mode 100644 index 000000000000..0431a9279e29 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/MeshProvokingLayer.cpp @@ -0,0 +1,259 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Pipeline.hpp" +#include "prx/libSceAgcDriver/Graphics/include/State.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureSwizzleEquations.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::ColorTileMode; +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Width = 32; +constexpr std::uint32_t Height = 16; +constexpr std::uint32_t Layers = 2; +constexpr std::uint8_t Kept = 0x40; +constexpr std::uint32_t LayerExport = (1u << 18u) | (1u << 21u); +constexpr std::uint32_t ShaderViewportIndexLayerCapability = 5254; + +alignas(256) constexpr std::array GeometryCode{ + 0x8f6a9003, 0x94fe6ac1, 0xbf88000b, 0xd7650006, 0x000100c1, 0xd7660006, 0x00020cc1, 0x93ebff03, + 0x00040018, 0xd7460006, 0x04190c6b, 0x340c0c82, 0xd8340000, 0x00000506, 0xbf8cc07f, 0xbefe04c1, + 0xbf8a0000, 0x938dff02, 0x00090016, 0x9382ff03, 0x00040018, 0x938cff03, 0x00080008, 0xd7650009, + 0x000100c1, 0xd7660009, 0x000212c1, 0xd746000a, 0x04250c02, 0x7da8120c, 0xbf88002e, 0x361600ff, + 0x0000ffff, 0x2c180090, 0x361a02ff, 0x0000ffff, 0xd8d80000, 0x0b00000b, 0xd8d80000, 0x0c00000c, + 0xd8d80000, 0x0d00000d, 0xbf8cc07f, 0xe0382000, 0x8002100b, 0xe0382010, 0x8002140b, 0xe0382000, + 0x8002180c, 0xe0382010, 0x80021c0c, 0xe0382000, 0x8002200d, 0xe0382010, 0x8002240d, 0x161c1483, + 0x161e14ff, 0x00000060, 0xbf8c3f70, 0xdb7c0400, 0x0000100f, 0xdb7c0410, 0x0000140f, 0xdb7c0420, + 0x0000180f, 0xdb7c0430, 0x00001c0f, 0xdb7c0440, 0x0000200f, 0xdb7c0450, 0x0000240f, 0x4a501c81, + 0x4a521c82, 0x3450508a, 0x34525294, 0xd772002a, 0x04a6510e, 0xbf8cc07f, 0xbefe04c1, 0xbf8a0000, + 0x930e830d, 0xbf078002, 0xbf850003, 0x8f0f8c0d, 0x887c0f0e, 0xbf900009, 0x7da8140d, 0xbf880002, + 0xf8000941, 0x0000002a, 0xbefe04c1, 0x7da8140e, 0xbf88000c, 0x34561485, 0xdbfc0400, 0x2c00002b, + 0xdbfc0410, 0x3000002b, 0xbf8cc07f, 0xf80000cf, 0x2f2e2d2c, 0xf80008d4, 0x00330000, 0xf800020f, + 0x33323130, 0xbf810000, +}; + +alignas(256) constexpr std::array PixelCode{ + 0x7e0e02f2, 0xf800180f, 0x07070707, 0xbf810000, +}; + +alignas(256) constexpr std::array LayerFieldPixelCode{ + 0x2c020090, 0x7e020d01, 0x100202f0, 0xf800180f, 0x01010101, 0xbf810000, +}; + +struct Vertex { + std::array position; + std::array color; + std::uint32_t layer; +}; + +alignas(256) std::array Vertices{}; + +void BuildQuad() { + const std::array, 6> corners{{{-1.0f, -1.0f}, {1.0f, -1.0f}, {-1.0f, 1.0f}, {1.0f, -1.0f}, {1.0f, 1.0f}, {-1.0f, 1.0f}}}; + for (std::uint32_t k = 0; k < corners.size(); ++k) Vertices[k] = {{corners[k][0], corners[k][1], 0.5f, 1.0f}, {1.0f, 1.0f, 1.0f}, k % 3u == 2u ? 1u : 0u}; +} + +std::array BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu}; +} + +AgcDriver::Graphics::ColorTarget LayeredTarget(std::byte* memory) { + AgcDriver::Graphics::ColorTarget color{reinterpret_cast(memory), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, 0, 0xe4u}; + color.tileMode = ColorTileMode::RenderTarget; + color.depth = Layers; + color.depthSlice = 0; + color.layers = Layers; + color.bytes = AgcDriver::Graphics::DescribeSurface(AgcDriver::Graphics::SurfaceForTarget(color)).guestBytes; + return color; +} + +class GuestVolume { +public: + explicit GuestVolume(std::size_t bytes) { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(65536, bytes)); +#endif + Require(block != nullptr, "cannot allocate the layered target"); + GuestAllocations::Mutation().Add(block, bytes, true, true, true); + std::memset(block, Kept, bytes); + } + + ~GuestVolume() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + } + + GuestVolume(const GuestVolume&) = delete; + GuestVolume& operator=(const GuestVolume&) = delete; + std::byte* Data() const { return block; } + +private: + std::byte* block = nullptr; +}; + +void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& color, bool last, std::span pixelCode, bool layerField) { + const auto target = device.Target(); + const auto vertexBuffer = BufferDescriptor(Vertices.data(), sizeof(Vertex), static_cast(Vertices.size())); + const AgcDriver::Pm4::DrawParameters draw{0, static_cast(Vertices.size()), 0, 1, 0, false}; + ShaderRecompiler::ShaderVertexStageInfo vertexInfo{}; + vertexInfo.paClVsOutCntl = LayerExport; + std::vector userData(12u, 0u); + std::copy(vertexBuffer.begin(), vertexBuffer.end(), userData.begin() + 8); + const ShaderRecompiler::MeshConfiguration mesh{4u, 4u, 12u, 12u, 4u, 64u, 1024u, last ? 2u : 0u, 4u}; + const std::array geometryMemory{{{reinterpret_cast(GeometryCode.data()), std::as_bytes(std::span(GeometryCode))}}}; + ShaderRecompiler::RecompileRequest geometry{ + {ShaderStage::Mesh, reinterpret_cast(GeometryCode.data()), GeometryCode, 0, {}}, + {64, 0, userData, std::nullopt, std::nullopt, vertexInfo, geometryMemory}, + target, + {0, 0, 0, ShaderRecompiler::MeshDrawPushOffsetBytes} + }; + geometry.graphics = ShaderRecompiler::GraphicsCompileContext{0, {}, mesh, std::nullopt, {draw.indexAddress, draw.indexCount, draw.indexSize, draw.instanceCount}}; + geometry.useCache = false; + const auto geometryResult = ShaderRecompiler::Recompile(geometry); + const auto geometryPush = static_cast(geometryResult.pushConstants.size()); + ShaderRecompiler::ShaderPixelStageInfo pixel{}; + pixel.targetOutputMode[0] = 9; + pixel.targetExportMapping.fill(0xe4u); + if (layerField) { + pixel.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::Ancillary); + pixel.ancillary = true; + pixel.layerExported = true; + } + const std::vector pixelUserData(8, 0u); + const std::array pixelMemory{{{reinterpret_cast(pixelCode.data()), std::as_bytes(pixelCode)}}}; + ShaderRecompiler::RecompileRequest fragment{ + {ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, + {64, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, + target, + PixelPushLayout(geometryPush, target) + }; + fragment.useCache = false; + const auto pixelResult = ShaderRecompiler::Recompile(fragment); + const std::array shaders{{ + {ShaderStage::Mesh, &geometryResult, 0}, + {ShaderStage::Fragment, &pixelResult, PixelPushOffset(geometryPush, target)} + }}; + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Geometry, 0x20u, 64, 64, mesh, std::nullopt}; + state.provokingVertexMode = last ? VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT : VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT; + state.color = color; + state.colors = {state.color}; + state.hasColorTarget = true; + state.renderExtent = {Width, Height}; + state.renderLayers = Layers; + state.layerExports = LayerExport; + state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + state.viewport = {0, static_cast(Height), static_cast(Width), -static_cast(Height), 0, 1}; + state.negativeOneToOne = false; + state.scissor = {{0, 0}, {Width, Height}}; + state.cullMode = VK_CULL_MODE_NONE; + state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + state.blend.colorWriteMask = 15; + state.blends = {state.blend}; + state.blendConstants = {}; + device.Draw(state, draw, shaders); + device.WaitIdle(); + AgcDriver::Graphics::StorageTexture::FlushAllPending("mesh provoking layer test"); + device.WaitIdle(); +} + +std::uint64_t BlockOffset(const AgcDriver::Graphics::TextureSwizzleEquation& equation, std::uint32_t x, std::uint32_t y, std::uint32_t z) { + std::uint64_t offset = 0; + for (std::uint32_t bit = 0; bit < equation.bits.size(); ++bit) { + const auto mask = equation.bits[bit]; + const auto selected = (x & (mask & 0xfffu)) ^ ((y << 12u) & (mask & 0xfff000u)) ^ ((z << 24u) & (mask & 0xff000000u)); + offset |= static_cast(std::popcount(selected) & 1u) << bit; + } + return offset; +} + +void Check(const std::byte* memory, const AgcDriver::Graphics::ColorTarget& color, std::uint32_t drawnLayer, std::uint8_t drawn, const std::string& what) { + using namespace AgcDriver::Graphics; + const auto geometry = DescribeSurface(SurfaceForTarget(color)); + const auto tileMode = ColorTextureTileMode(color.tileMode); + const auto thin = ThinBlockLayout(tileMode, 4); + const auto& mip = geometry.mips.at(0); + const auto* equation = FindTextureSwizzleEquation(static_cast(color.tileMode), 4); + Require(equation != nullptr, "missing the target's swizzle equation"); + for (std::uint32_t layer = 0; layer < Layers; ++layer) { + const auto* base = memory + geometry.GuestLayerOffset(layer); + const auto expected = layer == drawnLayer ? drawn : Kept; + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + const auto* texel = base + (static_cast(y / thin[2]) * mip.blocksPerRow + x / thin[1]) * thin[0] + BlockOffset(*equation, x, y, layer); + for (std::uint32_t channel = 0; channel < 4; ++channel) { + const auto actual = std::to_integer(texel[channel]); + Require(actual == expected || (layer == drawnLayer && expected == 128 && actual == 127), what + ": layer " + std::to_string(layer) + " texel (" + std::to_string(x) + ", " + std::to_string(y) + ") channel " + std::to_string(channel) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected) + (layer == drawnLayer ? " (drawn)" : " (untouched)")); + } + } + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const auto target = device->Target(); + if (!target.mesh.has_value() || std::find(target.supportedCapabilities.begin(), target.supportedCapabilities.end(), ShaderViewportIndexLayerCapability) == target.supportedCapabilities.end()) { + std::puts("skipped, the device has no VK_EXT_mesh_shader or VK_EXT_shader_viewport_index_layer"); + return VulkanTestSkipped; + } + if (device->DeviceName().starts_with("llvmpipe")) { + std::puts("skipped, the device has no mesh shader compiler that handles these programs (llvmpipe)"); + return VulkanTestSkipped; + } + if (!device->ProvokingVertexLast()) { + std::puts("skipped, the device has no last provoking vertex mode"); + return VulkanTestSkipped; + } + BuildQuad(); + const auto bytes = (LayeredTarget(nullptr).bytes + 65535u) & ~std::size_t{65535u}; + for (const bool layerField : {false, true}) { + for (const bool last : {false, true}) { + const GuestVolume memory(bytes); + const auto color = LayeredTarget(memory.Data()); + Draw(*device, color, last, layerField ? std::span(LayerFieldPixelCode) : std::span(PixelCode), layerField); + const auto drawn = static_cast(layerField ? (last ? 128u : 0u) : 255u); + Check(memory.Data(), color, last ? 1u : 0u, drawn, std::string(last ? "last provoking vertex: the layer of each triangle's third vertex" : "first provoking vertex: the layer of each triangle's first vertex") + (layerField ? ", read by the pixel shader" : "")); + } + } + std::puts("mesh provoking layer tests passed: first mode draws each triangle into its first vertex's layer and last mode into its third vertex's"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/MixPrecision.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/MixPrecision.cpp index d8c1506e367b..d5870380d6ae 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/MixPrecision.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/MixPrecision.cpp @@ -17,7 +17,6 @@ namespace { using AgcDriver::Graphics::Require; using ShaderRecompiler::ShaderStage; -constexpr std::uint32_t Float64Capability = 10; constexpr std::uint32_t Threads = 64; constexpr std::uint32_t Inputs = 4; constexpr std::uint32_t Results = 16; @@ -241,11 +240,6 @@ int main() { try { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; - const auto capabilities = device->Target().supportedCapabilities; - if (std::find(capabilities.begin(), capabilities.end(), Float64Capability) == capabilities.end()) { - std::puts("skipped, v_fma_mixlo_f16 and v_fma_mixhi_f16 are computed in f64 and the device has no shaderFloat64"); - return VulkanTestSkipped; - } FillInput(); Run(*device, MixCode); Check(); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp index bec235513948..6ffd06cb0c51 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp @@ -5,7 +5,9 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp" #include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/ShaderPreparation.hpp" +#include "prx/libSceAgcDriver/Execution/include/ShaderPreparationScope.hpp" #include "prx/libSceAgcDriver/Submit/include/Acb.hpp" #include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" #include @@ -28,6 +30,11 @@ #include #include #include +#ifdef _WIN32 +#include +#else +#include +#endif namespace { @@ -604,11 +611,427 @@ void DeferredUndecodableRegistration() { ExpectFailure([] { AgcDriverShutdown_nid_postfix(); }, "unsupported MIMG opcode"); } +class StderrCapture { +public: + StderrCapture() { + std::fflush(stderr); + file = std::tmpfile(); + Require(file != nullptr, "cannot open a temporary file for stderr"); +#ifdef _WIN32 + saved = _dup(_fileno(stderr)); + _dup2(_fileno(file), _fileno(stderr)); +#else + saved = dup(fileno(stderr)); + dup2(fileno(file), fileno(stderr)); +#endif + } + std::string Finish() { + std::fflush(stderr); +#ifdef _WIN32 + _dup2(saved, _fileno(stderr)); + _close(saved); +#else + dup2(saved, fileno(stderr)); + close(saved); +#endif + std::rewind(file); + std::string text; + char buffer[4096]; + for (std::size_t read = 0; (read = std::fread(buffer, 1, sizeof(buffer), file)) != 0;) text.append(buffer, read); + std::fclose(file); + std::fwrite(text.data(), 1, text.size(), stderr); + return text; + } + +private: + std::FILE* file = nullptr; + int saved = -1; +}; + +template +std::string Logged(TAction action) { + StderrCapture capture; + try { + action(); + } catch (...) { + static_cast(capture.Finish()); + throw; + } + return capture.Finish(); +} + +std::size_t Deferrals(const std::string& log) { + constexpr std::string_view line = "could not be prepared when the title created its pipeline"; + std::size_t count = 0; + for (auto found = log.find(line); found != std::string::npos; found = log.find(line, found + line.size())) ++count; + return count; +} + +struct AbiHeader { + Shader shader{}; + std::array registers{}; + std::array context{}; + ShaderSpecialRegs specials{}; + ShaderUserData users{}; +}; + +std::uint32_t ProgramLow(const void* code) { + return static_cast(reinterpret_cast(code) >> 8u); +} + +std::uint32_t ProgramHigh(const void* code) { + return static_cast(reinterpret_cast(code) >> 40u); +} + +template +void DescribeAbi(AbiHeader& header, const std::array& code, std::uint8_t type, std::initializer_list registers, std::initializer_list context) { + header.shader.file_header = 0x34333231u; + header.shader.version = 0x18; + header.shader.header_size = sizeof(AbiHeader); + header.shader.shader_size = sizeof(code); + header.shader.code = code.data(); + header.shader.type = type; + header.shader.user_data = &header.users; + std::copy(registers.begin(), registers.end(), header.registers.begin()); + header.shader.sh_registers = header.registers.data(); + header.shader.num_sh_registers = static_cast(registers.size()); + std::copy(context.begin(), context.end(), header.context.begin()); + header.shader.cx_registers = header.context.data(); + header.shader.num_cx_registers = static_cast(context.size()); +} + +alignas(256) constexpr std::array UserDataCaller{ + 0x7e020280u, 0xbe940383u, 0xbe8e0300u, 0xbe8f0301u, 0xbe8e210eu, 0xbe8e0302u, 0xbe8f0303u, 0xbe8e210eu, + 0x80948114u, 0xbf078014u, 0xbf85fff7u, 0x34040082u, 0xe0701000u, 0x80010102u, 0xbf810000u, 0xbf9f0000u, + 0x30306c73u, 0x00000040u, 0x00000008u}; +alignas(256) constexpr std::array AddFiveCallee{0x4a020285u, 0xbe80200eu}; +alignas(256) constexpr std::array AddSixtyFourCallee{0x4a0202c0u, 0xbe80200eu}; +alignas(256) std::array CallerOutput{}; +alignas(256) constexpr std::array WaterfallStoreCode{ + 0x7e020280u, 0x7e040280u, 0x7e080280u, 0x7e0a0280u, 0x7e0c0280u, 0x7e0e0280u, 0xbe86047eu, 0xf40c0200u, + 0xfa000000u, 0xd4c10000u, 0x00020083u, 0xbf8cc07fu, 0xf0200f08u, 0x00020401u, 0x8afe007eu, 0xbf89fff7u, + 0xbefe0406u, 0xbf810000u}; +alignas(256) constexpr std::array WaterfallPixelCode{ + 0xc80a0002u, 0x7e000280u, 0x7e020280u, 0xbe86047eu, 0xf40c0200u, 0xfa000000u, 0xd4c10000u, 0x00020483u, + 0xbf8cc07fu, 0xf0000f08u, 0x00020400u, 0x8afe007eu, 0xbf89fff7u, 0xbefe0406u, 0xbf8c3f70u, 0xf800180fu, + 0x07060504u, 0xbf810000u}; +alignas(256) constexpr std::array WaterfallVertexCode{ + 0x7e020280u, 0x7e040280u, 0xbe86037eu, 0xf40c0404u, 0xfa000000u, 0xd4c10008u, 0x00020083u, 0xbf8cc07fu, + 0xf0000f08u, 0x00040401u, 0x8a7e087eu, 0xbf89fff7u, 0xbefe0306u, 0xbf8a0000u, 0xbf8c3f70u, 0xf80008cfu, + 0x07060504u, 0xbf810000u}; +alignas(256) constexpr std::array OtherWaterfallVertexCode = WaterfallVertexCode; +alignas(256) constexpr std::array BarrierFreeWaterfallVertexCode{ + 0x7e020280u, 0x7e040280u, 0xbe86037eu, 0xf40c0404u, 0xfa000000u, 0xd4c10008u, 0x00020083u, 0xbf8cc07fu, + 0xf0000f08u, 0x00040401u, 0x8a7e087eu, 0xbf89fff7u, 0xbefe0306u, 0xbf800000u, 0xbf8c3f70u, 0xf80008cfu, + 0x07060504u, 0xbf810000u}; +alignas(256) constexpr std::array FrontCode{0x7e0002ffu, 0, 0x7e0202ffu, 0, 0x7e0402ffu, 0, 0x7e0602ffu, 0x3f800000u, 0xf80008cfu, 0x03020100u, 0xbf810000u}; +alignas(256) constexpr std::array UndecodablePixelCode{0xf1949f05u, 0x00060018u, 0xbf810000u}; +alignas(256) constexpr std::array PassthroughPixelCode{0xc8020002u, 0xf800180fu, 0x00000000u, 0xbf810000u}; + +void DescribeWaterfallPixel(AbiHeader& pixel) { + DescribeAbi(pixel, WaterfallPixelCode, 1, {{0x8, ProgramLow(WaterfallPixelCode.data())}, {0x9, ProgramHigh(WaterfallPixelCode.data())}, {0xa, 0}, {0xb, 2u << 1u}}, {{0x1b6, 1}, {0x1b3, 2}, {0x1b4, 2}, {0x191, 0}, {0x1c5, 4}}); +} + +void DescribeFront(AbiHeader& vertex) { + DescribeAbi(vertex, FrontCode, 2, {{0xc8, ProgramLow(FrontCode.data())}, {0xc9, ProgramHigh(FrontCode.data())}, {0x8b, 0}, {0x8a, 0}, {0xca, 0}, {0xcb, 0}, {0xcc, 0}}, {{0x2d5, 0x2000}}); + vertex.shader.specials = &vertex.specials; + vertex.specials.dispatch_modifier = 0x8000; +} + +void DescribeWaterfallVertex(AbiHeader& vertex, const std::array& code) { + DescribeAbi(vertex, code, 2, {{0xc8, ProgramLow(code.data())}, {0xc9, ProgramHigh(code.data())}, {0x8a, 0}, {0x8b, 2u << 1u}}, {{0x1ff, 64}, {0x2ab, 4}, {0x2ce, 0}}); + vertex.shader.specials = &vertex.specials; + vertex.specials = {{0x25b, 0x8040}, {0x2d5, 0x00402000}, 0, {}, {}, {0x29b, 2}, {0x260, 0}}; +} + +struct FetchingHeader { + AbiHeader abi; + std::array directOffsets{}; + ShaderSemantic semantic{}; +}; + +void DescribeStaticFetch(FetchingHeader& vertex) { + vertex.abi.shader.header_size = sizeof(FetchingHeader); + for (auto& reg : vertex.abi.registers) { + if (reg.offset == 0x8b) reg.value = 4u << 1u; + } + vertex.directOffsets.fill(0xffffu); + vertex.directOffsets[8] = 0; + vertex.directOffsets[10] = 2; + vertex.abi.users.direct_resource_offset = vertex.directOffsets.data(); + vertex.abi.users.direct_resource_count = static_cast(vertex.directOffsets.size()); + vertex.semantic.size_in_elements = 4; + vertex.semantic.static_vb_index = 1; + vertex.abi.shader.input_semantics = &vertex.semantic; + vertex.abi.shader.num_input_semantics = 1; +} + +void AbiDeferredCompute() { + static AbiHeader caller; + DescribeAbi(caller, UserDataCaller, 0, {{0x20c, ProgramLow(UserDataCaller.data())}, {0x20d, ProgramHigh(UserDataCaller.data())}, {0x207, 64}, {0x208, 1}, {0x209, 1}, {0x212, 0}, {0x213, 8u << 1u}}, {}); + caller.shader.specials = &caller.specials; + caller.specials.dispatch_modifier = 0x8000u; + AgcDriverRegisterShader_nid_postfix(&caller.shader); + const auto before = ShaderRecompiler::FrontEndBuilds(); + AgcDriverResolveShaderAbi_nid_postfix(&caller.shader, {}, {}); + const auto attempted = ShaderRecompiler::FrontEndBuilds(); + AgcDriverResolveShaderAbi_nid_postfix(&caller.shader, {}, {}); + Require(attempted > before && ShaderRecompiler::FrontEndBuilds() == attempted, "a compute static ABI deferred to its dispatch was prepared again for the same state"); + const auto first = reinterpret_cast(AddFiveCallee.data()); + const auto second = reinterpret_cast(AddSixtyFourCallee.data()); + const auto output = reinterpret_cast(CallerOutput.data()); + const std::array users{static_cast(first), static_cast(first >> 32u), static_cast(second), static_cast(second >> 32u), + static_cast(output), static_cast((output >> 32u) & 0xffffu), static_cast(sizeof(CallerOutput)), 0x31016facu}; + std::vector commands; + for (std::size_t index = 0; index < caller.shader.num_sh_registers; ++index) commands.insert(commands.end(), {0xc0017600u, caller.registers[index].offset, caller.registers[index].value}); + for (std::uint32_t index = 0; index < users.size(); ++index) commands.insert(commands.end(), {0xc0017600u, 0x240u + index, users[index]}); + commands.insert(commands.end(), {0xc0031500u, 1, 1, 1, 0x8041u}); + CallerOutput.fill(0xcafebabeu); + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + sceAgcDriverSubmitAcb(0x20, &packet); + AgcDriverWaitIdle_nid_postfix(); + AgcDriver::GuestMemory::FlushGpuWrites(output, sizeof(CallerOutput)); + Require(std::ranges::all_of(CallerOutput, [](std::uint32_t lane) { return lane == 3u * (5u + 64u); }), "a compute shader its static ABI deferred did not run its user-data callees at its dispatch"); + AgcDriverShutdown_nid_postfix(); +} + +void AbiDeferredRegistrationFailure() { + static AbiHeader store; + DescribeAbi(store, WaterfallStoreCode, 0, {{0x20c, ProgramLow(WaterfallStoreCode.data())}, {0x20d, ProgramHigh(WaterfallStoreCode.data())}, {0x207, 64}, {0x208, 1}, {0x209, 1}, {0x212, 0}, {0x213, 2u << 1u}}, {}); + store.shader.specials = &store.specials; + static AbiHeader front; + DescribeFront(front); + static AbiHeader pixel; + DescribeWaterfallPixel(pixel); + pixel.context[0] = {0x1b6, 0}; + static AbiHeader vertex; + DescribeWaterfallVertex(vertex, BarrierFreeWaterfallVertexCode); + static AbiHeader interpolated; + DescribeWaterfallPixel(interpolated); + for (auto* header : {&store, &front, &pixel, &vertex, &interpolated}) AgcDriverRegisterShader_nid_postfix(&header->shader); + const std::array primitive{{{0x242, 4}}}; + const std::array frontStages{&front.shader, &pixel.shader}; + const std::array vertexStages{&vertex.shader, &interpolated.shader}; + const auto resolve = [&] { + AgcDriver::ShaderPreparationScope scope({&front.shader, &pixel.shader, &vertex.shader}); + AgcDriverResolveShaderAbi_nid_postfix(&store.shader, {}, {}); + AgcDriverResolveShaderAbi_nid_postfix(&pixel.shader, {}, {}); + AgcDriverResolveGraphicsAbi_nid_postfix(&front.shader, &pixel.shader, 17); + AgcDriverResolveGraphicsStagesAbi_nid_postfix(frontStages, {}, primitive); + AgcDriverResolveGraphicsStagesAbi_nid_postfix(vertexStages, {}, primitive); + AgcDriverResolveGraphicsAbi_nid_postfix(&vertex.shader, &interpolated.shader, 17); + scope.Commit(); + }; + auto log = Logged(resolve); + Require(Deferrals(log) == 3 && log.find("is not a valid runtime value") != std::string::npos, "stages whose registration preparation failed were not deferred once each, with the stored reason, when the title created their pipelines"); + log = Logged(resolve); + Require(Deferrals(log) == 0, "stages whose registration preparation failed were logged again for the same pipelines"); + std::vector commands; + for (std::size_t index = 0; index < store.shader.num_sh_registers; ++index) commands.insert(commands.end(), {0xc0017600u, store.registers[index].offset, store.registers[index].value}); + commands.insert(commands.end(), {0xc0031500u, 1, 1, 1, 0x8041u}); + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + sceAgcDriverSubmitAcb(0x20, &packet); + ExpectFailure([] { AgcDriverWaitIdle_nid_postfix(); }, "is not a valid runtime value"); + ExpectFailure([] { AgcDriverShutdown_nid_postfix(); }, "is not a valid runtime value"); +} + +void AbiDeferredPixel() { + static AbiHeader pixel; + DescribeWaterfallPixel(pixel); + AgcDriverRegisterShader_nid_postfix(&pixel.shader); + const auto before = ShaderRecompiler::FrontEndBuilds(); + auto log = Logged([&] { AgcDriverResolveShaderAbi_nid_postfix(&pixel.shader, {}, {}); }); + const auto attempted = ShaderRecompiler::FrontEndBuilds(); + log += Logged([&] { AgcDriverResolveShaderAbi_nid_postfix(&pixel.shader, {}, {}); }); + Require(attempted > before && ShaderRecompiler::FrontEndBuilds() == attempted, "a pixel static ABI deferred to its draw was prepared again for the same state"); + Require(Deferrals(log) == 1 && log.find("is not a valid runtime value") != std::string::npos, "a pixel shader its static ABI cannot prepare was not logged once with its reason"); + { + AgcDriver::ShaderPreparationScope scope({&pixel.shader}); + const std::array interpolant{{{0x191, 0x20}}}; + log = Logged([&] { AgcDriverResolveShaderAbi_nid_postfix(&pixel.shader, interpolant, {}); }); + scope.Commit(); + } + Require(ShaderRecompiler::FrontEndBuilds() > attempted && Deferrals(log) == 0, "a deferred pixel shader was not prepared once more for another state, or was logged again"); + static AbiHeader front; + DescribeFront(front); + AgcDriverRegisterShader_nid_postfix(&front.shader); + std::vector commands; + for (const auto* header : {&front, &pixel}) { + for (std::size_t index = 0; index < header->shader.num_sh_registers; ++index) commands.insert(commands.end(), {0xc0017600u, header->registers[index].offset, header->registers[index].value}); + for (std::size_t index = 0; index < header->shader.num_cx_registers; ++index) commands.insert(commands.end(), {0xc0016900u, header->context[index].offset, header->context[index].value}); + } + commands.insert(commands.end(), {0xc0016900u, 0x8e, 0xfu, 0xc0016900u, 0x8f, 0xfu, 0xc0016900u, 0xd, 0x00100010u, 0xc0016900u, 0x204, 0x80000u, 0xc0016900u, 0x1c3, 4, 0xc0017900u, 0x242, 4, 0xc0012d00u, 3, 2}); + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + Require(sceAgcDriverSubmitDcb(&packet) == 0, "the draw of a deferred pixel shader was not submitted"); + ExpectFailure([] { AgcDriverWaitIdle_nid_postfix(); }, "is not a valid runtime value"); + ExpectFailure([&] { AgcDriverResolveShaderAbi_nid_postfix(&pixel.shader, {}, {}); }, "is not a valid runtime value"); + ExpectFailure([] { AgcDriverShutdown_nid_postfix(); }, "is not a valid runtime value"); +} + +void AbiDeferredStages() { + static AbiHeader nested; + static AbiHeader direct; + DescribeWaterfallVertex(nested, WaterfallVertexCode); + DescribeWaterfallVertex(direct, OtherWaterfallVertexCode); + AgcDriverRegisterShader_nid_postfix(&nested.shader); + AgcDriverRegisterShader_nid_postfix(&direct.shader); + const std::array primitive{{{0x242, 4}}}; + const auto before = ShaderRecompiler::FrontEndBuilds(); + std::string log; + { + AgcDriver::ShaderPreparationScope scope({&nested.shader}); + log = Logged([&] { AgcDriverResolveShaderAbi_nid_postfix(&nested.shader, {}, primitive); }); + scope.Commit(); + } + const auto nestedAttempt = ShaderRecompiler::FrontEndBuilds(); + Require(nestedAttempt > before && Deferrals(log) == 1, "a vertex shader the static ABI resolves through its graphics stages was not deferred to its draw"); + log = Logged([&] { AgcDriverResolveShaderAbi_nid_postfix(&nested.shader, {}, primitive); }); + Require(ShaderRecompiler::FrontEndBuilds() == nestedAttempt && Deferrals(log) == 0, "a deferred vertex static ABI was prepared or logged again for the same state"); + const std::array stages{&direct.shader}; + log = Logged([&] { AgcDriverResolveGraphicsStagesAbi_nid_postfix(stages, {}, primitive); }); + const auto directAttempt = ShaderRecompiler::FrontEndBuilds(); + Require(directAttempt > nestedAttempt && Deferrals(log) == 1, "a graphics stage its ABI cannot prepare was not deferred to its draw"); + log = Logged([&] { AgcDriverResolveGraphicsStagesAbi_nid_postfix(stages, {}, primitive); }); + Require(ShaderRecompiler::FrontEndBuilds() == directAttempt && Deferrals(log) == 0, "a deferred graphics stage ABI was prepared or logged again for the same state"); + AgcDriverShutdown_nid_postfix(); +} + +void AbiDeferredRectangle() { + static AbiHeader front; + static AbiHeader undecodable; + static AbiHeader pixel; + DescribeFront(front); + DescribeAbi(undecodable, UndecodablePixelCode, 1, {{0x8, ProgramLow(UndecodablePixelCode.data())}, {0x9, ProgramHigh(UndecodablePixelCode.data())}, {0xa, 0}, {0xb, 0}}, {}); + DescribeWaterfallPixel(pixel); + for (auto* header : {&front, &undecodable, &pixel}) AgcDriverRegisterShader_nid_postfix(&header->shader); + AgcDriverResolveGraphicsAbi_nid_postfix(&front.shader, &undecodable.shader, 17); + AgcDriverResolveShaderAbi_nid_postfix(&pixel.shader, {}, {}); + { + AgcDriver::ShaderPreparationScope scope({&front.shader, &pixel.shader}); + AgcDriverResolveGraphicsAbi_nid_postfix(&front.shader, &pixel.shader, 17); + scope.Commit(); + } + AgcDriverShutdown_nid_postfix(); +} + +void AbiDeferredUndecodableState() { + static AbiHeader front; + DescribeFront(front); + front.context[0] = {0x2ce, 3}; + front.specials = {{0x25b, 0x8040}, {0x2d5, 0x2030}, 0x8000, {}, {}, {0x29b, 1}, {0x262, 0}}; + AgcDriverRegisterShader_nid_postfix(&front.shader); + const std::array stages{&front.shader}; + const std::array context{front.specials.vgt_shader_stages_en, front.specials.vgt_gs_out_prim_type}; + const std::array primitive{front.specials.ge_cntl, front.specials.ge_user_vgpr_en, ShaderRegister{0x242, 4}}; + const auto before = ShaderRecompiler::FrontEndBuilds(); + auto log = Logged([&] { AgcDriverResolveGraphicsStagesAbi_nid_postfix(stages, context, primitive); }); + Require(Deferrals(log) == 1 && log.find("VGT_SHADER_STAGES_EN=0x2030: unsupported geometry input or output assembly") != std::string::npos, "a graphics stage whose pipeline state does not decode was not deferred with the decode's reason"); + log = Logged([&] { AgcDriverResolveGraphicsStagesAbi_nid_postfix(stages, context, primitive); }); + Require(ShaderRecompiler::FrontEndBuilds() == before && Deferrals(log) == 0, "a graphics stage deferred for a pipeline state that does not decode was prepared or logged again"); + std::vector commands; + for (std::size_t index = 0; index < front.shader.num_sh_registers; ++index) commands.insert(commands.end(), {0xc0017600u, front.registers[index].offset, front.registers[index].value}); + commands.insert(commands.end(), {0xc0016900u, 0x2d5, 0x2000u, 0xc0017600u, 0x8, 0, 0xc0016900u, 0xd, 0x00100010u, 0xc0016900u, 0x204, 0x80000u, 0xc0016900u, 0x1c3, 4, 0xc0017900u, 0x242, 4, 0xc0012d00u, 3, 2}); + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + log = Logged([&] { + Require(sceAgcDriverSubmitDcb(&packet) == 0, "the draw of a graphics stage deferred at its pipeline ABI was not submitted"); + AgcDriverWaitIdle_nid_postfix(); + }); + Require(ShaderRecompiler::FrontEndBuilds() > before && log.find("has no artifact prepared at registration") == std::string::npos, "a graphics stage deferred at its pipeline ABI was not prepared at its first draw, or was reported there as missing a registration artifact"); + AgcDriverShutdown_nid_postfix(); +} + +void AbiDeferredVertexInputs() { + static FetchingHeader fetching; + DescribeWaterfallVertex(fetching.abi, WaterfallVertexCode); + DescribeStaticFetch(fetching); + AgcDriverRegisterShader_nid_postfix(&fetching.abi.shader); + const std::array stages{&fetching.abi.shader}; + const std::array primitive{{{0x242, 4}}}; + const auto resolve = [&] { + AgcDriver::ShaderPreparationScope scope({&fetching.abi.shader}); + AgcDriverResolveGraphicsStagesAbi_nid_postfix(stages, {}, primitive); + scope.Commit(); + }; + const auto before = ShaderRecompiler::FrontEndBuilds(); + auto log = Logged(resolve); + Require(Deferrals(log) == 1 && log.find("statically bound vertex attributes are not implemented") != std::string::npos, "a graphics stage whose vertex inputs its pipeline ABI cannot decode was not deferred with the decode's reason"); + log = Logged(resolve); + Require(ShaderRecompiler::FrontEndBuilds() == before && Deferrals(log) == 0, "a graphics stage deferred for vertex inputs that do not decode was prepared or logged again"); + alignas(16) static std::array tables{}; + const auto address = reinterpret_cast(tables.data()); + std::vector commands; + for (std::size_t index = 0; index < fetching.abi.shader.num_sh_registers; ++index) commands.insert(commands.end(), {0xc0017600u, fetching.abi.registers[index].offset, fetching.abi.registers[index].value}); + for (std::uint32_t index = 0; index < 4; ++index) commands.insert(commands.end(), {0xc0017600u, 0x8cu + index, static_cast(index % 2u == 0u ? address : address >> 32u)}); + for (std::size_t index = 0; index < fetching.abi.shader.num_cx_registers; ++index) commands.insert(commands.end(), {0xc0016900u, fetching.abi.context[index].offset, fetching.abi.context[index].value}); + for (const auto reg : {fetching.abi.specials.vgt_shader_stages_en, fetching.abi.specials.vgt_gs_out_prim_type}) commands.insert(commands.end(), {0xc0016900u, reg.offset, reg.value}); + for (const auto reg : {fetching.abi.specials.ge_cntl, fetching.abi.specials.ge_user_vgpr_en}) commands.insert(commands.end(), {0xc0017900u, reg.offset, reg.value}); + commands.insert(commands.end(), {0xc0017600u, 0x8, 0, 0xc0016900u, 0xd, 0x00100010u, 0xc0016900u, 0x204, 0x80000u, 0xc0016900u, 0x1c3, 4, 0xc0017900u, 0x242, 4, 0xc0012d00u, 3, 2}); + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + Require(sceAgcDriverSubmitDcb(&packet) == 0, "the draw of a graphics stage deferred for its vertex inputs was not submitted"); + ExpectFailure([] { AgcDriverWaitIdle_nid_postfix(); }, "statically bound vertex attributes are not implemented"); + ExpectFailure([] { AgcDriverShutdown_nid_postfix(); }, "statically bound vertex attributes are not implemented"); +} + +void AbiDeferredRectanglePairs() { + static AbiHeader front; + DescribeFront(front); + static AbiHeader pixel; + DescribeAbi(pixel, PassthroughPixelCode, 1, {{0x8, ProgramLow(PassthroughPixelCode.data())}, {0x9, ProgramHigh(PassthroughPixelCode.data())}, {0xa, 0}, {0xb, 0}}, {{0x1b6, 1}, {0x1b3, 2}, {0x1b4, 2}, {0x191, 0x420}, {0x1c5, 4}}); + static AbiHeader unresolved; + DescribeWaterfallPixel(unresolved); + for (auto* header : {&front, &pixel, &unresolved}) AgcDriverRegisterShader_nid_postfix(&header->shader); + const std::array passthrough{pixel.context[3]}; + const auto resolve = [&] { + AgcDriver::ShaderPreparationScope scope({&front.shader, &pixel.shader}); + AgcDriverResolveGraphicsAbi_nid_postfix(&front.shader, nullptr, 17); + AgcDriverResolveGraphicsAbi_nid_postfix(&front.shader, &pixel.shader, 17); + AgcDriverResolveGraphicsAbi_nid_postfix(&front.shader, &unresolved.shader, 17); + AgcDriverResolveShaderAbi_nid_postfix(&pixel.shader, passthrough, {}); + AgcDriverResolveGraphicsAbi_nid_postfix(&front.shader, &pixel.shader, 17); + scope.Commit(); + }; + auto log = Logged(resolve); + Require(Deferrals(log) == 1 && log.find("custom per-vertex interpolation is unsupported") != std::string::npos, "a rectangle pair its pipeline ABI cannot build was not deferred once with the builder's reason"); + log = Logged(resolve); + Require(Deferrals(log) == 0, "a rectangle pair deferred at its pipeline ABI was logged again"); + const auto draw = [&](bool withPixel) { + std::vector commands; + for (const auto* header : {&front, &pixel}) { + if (header == &pixel && !withPixel) continue; + for (std::size_t index = 0; index < header->shader.num_sh_registers; ++index) commands.insert(commands.end(), {0xc0017600u, header->registers[index].offset, header->registers[index].value}); + for (std::size_t index = 0; index < header->shader.num_cx_registers; ++index) commands.insert(commands.end(), {0xc0016900u, header->context[index].offset, header->context[index].value}); + } + if (!withPixel) commands.insert(commands.end(), {0xc0017600u, 0x8, 0}); + commands.insert(commands.end(), {0xc0016900u, 0x8e, 0xfu, 0xc0016900u, 0x8f, 0xfu, 0xc0016900u, 0xd, 0x00100010u, 0xc0016900u, 0x204, 0x80000u, 0xc0016900u, 0x1c3, 4, 0xc0017900u, 0x242, 17, 0xc0012d00u, 3, 2}); + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + Require(sceAgcDriverSubmitDcb(&packet) == 0, "a rect-list draw was not submitted"); + AgcDriverWaitIdle_nid_postfix(); + }; + draw(false); + ExpectFailure([&] { draw(true); }, "custom per-vertex interpolation is unsupported"); + ExpectFailure([] { AgcDriverShutdown_nid_postfix(); }, "custom per-vertex interpolation is unsupported"); +} + } int main(int argc, char** argv) { try { - Require(argc == 1 || (argc == 2 && (std::string_view(argv[1]) == "--indirect" || std::string_view(argv[1]) == "--fail-before-registration" || std::string_view(argv[1]) == "--deferred" || std::string_view(argv[1]) == "--deferred-undecodable")), "invalid test arguments"); + const std::string_view abiMode = argc == 2 ? argv[1] : ""; + const bool abiDeferred = abiMode == "--abi-deferred-compute" || abiMode == "--abi-deferred-pixel" || abiMode == "--abi-deferred-stages" || abiMode == "--abi-deferred-rectangle" || abiMode == "--abi-deferred-undecodable-state" || abiMode == "--abi-deferred-registration-failure" || abiMode == "--abi-deferred-vertex-inputs" || abiMode == "--abi-deferred-rectangle-pairs"; + Require(argc == 1 || abiDeferred || (argc == 2 && (std::string_view(argv[1]) == "--indirect" || std::string_view(argv[1]) == "--fail-before-registration" || std::string_view(argv[1]) == "--deferred" || std::string_view(argv[1]) == "--deferred-undecodable")), "invalid test arguments"); + if (abiDeferred) { + if (!OpenVulkanTestDevice()) return VulkanTestSkipped; + if (abiMode == "--abi-deferred-compute") AbiDeferredCompute(); + else if (abiMode == "--abi-deferred-pixel") AbiDeferredPixel(); + else if (abiMode == "--abi-deferred-stages") AbiDeferredStages(); + else if (abiMode == "--abi-deferred-undecodable-state") AbiDeferredUndecodableState(); + else if (abiMode == "--abi-deferred-registration-failure") AbiDeferredRegistrationFailure(); + else if (abiMode == "--abi-deferred-vertex-inputs") AbiDeferredVertexInputs(); + else if (abiMode == "--abi-deferred-rectangle-pairs") AbiDeferredRectanglePairs(); + else AbiDeferredRectangle(); + std::cout << "shader ABI deferral tests passed: " << abiMode << '\n'; + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--deferred") { if (!OpenVulkanTestDevice()) return VulkanTestSkipped; DeferredRegistration(); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/StorageFlushOrdering.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/StorageFlushOrdering.cpp new file mode 100644 index 000000000000..ec90020045fa --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/StorageFlushOrdering.cpp @@ -0,0 +1,173 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/GuestWriteWatch.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::size_t BlockBytes = 1u << 20u; +constexpr std::uint32_t Width = 256; +constexpr std::uint32_t Height = 128; +constexpr std::size_t ImageBytes = Width * Height * 4u; +constexpr std::uint32_t Format32UInt = 20; +constexpr std::uint32_t Type2D = 9; +constexpr std::uint32_t Initial = 0x3f800000u; +constexpr std::uint32_t Stored = 0x11111111u; +constexpr std::uint32_t Overwritten = 0x22222222u; +constexpr std::size_t OverwrittenDword = 0x100u / 4u; +constexpr std::size_t FarDword = 100u * Width; + +alignas(256) constexpr std::array StoreImageCode{0x7e000280u, 0x7e020280u, 0x7e0402ffu, Stored, 0xf0200108u, 0x00010200u, 0x7e0202ffu, 0x00000064u, 0xf0200108u, 0x00010200u, 0xbf810000u}; +alignas(256) constexpr std::array StoreGlobalCode{0x7e000208u, 0x7e020209u, 0x7e0402ffu, Overwritten, 0xdc708100u, 0x007d0200u, 0xbf810000u}; + +class GuestBlock { +public: + explicit GuestBlock(bool watched) : watched(watched) { +#ifdef _WIN32 + block = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(BlockBytes, 65536)); + if (block != nullptr) GuestArena::GuestArenaCommit_nid_postfix(block, BlockBytes, PAGE_READWRITE, BlockBytes); +#else + constexpr std::uintptr_t alignment = 65536; + void* mapped = mmap(nullptr, BlockBytes + alignment, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (mapped != MAP_FAILED) { + const auto begin = reinterpret_cast(mapped); + const auto aligned = (begin + alignment - 1) & ~(alignment - 1); + if (aligned != begin) munmap(mapped, aligned - begin); + if (aligned + BlockBytes != begin + BlockBytes + alignment) munmap(reinterpret_cast(aligned + BlockBytes), begin + alignment - aligned); + block = reinterpret_cast(aligned); + if (watched) GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(block, BlockBytes); + } +#endif + Require(block != nullptr, "storage flush ordering: cannot allocate the guest block"); + Require(!watched || AgcDriver::GuestMemory::Watched(Address(), BlockBytes), "storage flush ordering: the guest block is not write-watched"); + std::fill(Words(), Words() + BlockBytes / 4u, Initial); + GuestAllocations::Mutation().Add(block, BlockBytes, true, true, true); + AgcDriver::GuestMemory::CollectWritesUncached(Address(), BlockBytes); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + GuestArena::GuestArenaReset_nid_postfix(block, BlockBytes); + GuestArena::GuestArenaRelease_nid_postfix(block, BlockBytes); +#else + munmap(block, BlockBytes); + if (watched) GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(block, BlockBytes); +#endif + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint32_t* Words() const { return reinterpret_cast(block); } + std::uint64_t Address() const { return static_cast(reinterpret_cast(block)); } + +private: + bool watched; + std::uint8_t* block = nullptr; +}; + +ShaderRecompiler::RecompileResult Compile(std::span code, std::span userData, const ShaderRecompiler::SpirvTarget& target) { + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{64u, 1u, 1u}, 0u, {false, false, false}, false, 1u}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {64u, 0, userData, compute, std::nullopt, std::nullopt, memory}, + target, + {0, 0, 0, 128} + }; + request.useCache = false; + return ShaderRecompiler::Recompile(request); +} + +std::vector ImageUserData(std::uint64_t base) { + std::vector userData(12, 0u); + userData[4] = static_cast(base >> 8u); + userData[5] = static_cast((base >> 40u) & 0xffu) | (Format32UInt << 20u) | (((Width - 1u) & 3u) << 30u); + userData[6] = ((Width - 1u) >> 2u) | ((Height - 1u) << 14u); + userData[7] = 0xfacu | (Type2D << 28u); + return userData; +} + +std::vector GlobalUserData(std::uint64_t base) { + std::vector userData(10, 0u); + userData[8] = static_cast(base); + userData[9] = static_cast(base >> 32u); + return userData; +} + +bool AddressBasedStorePending(std::uint64_t image, bool open) { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + const auto* recorder = AgcDriver::Graphics::Recorder::Active(); + const auto read = recorder != nullptr ? recorder->DescribePendingRead(image + ImageBytes, 4u) : std::nullopt; + return read.has_value() && read->kind == AgcDriver::Graphics::Recorder::ReadKind::AddressBased && read->open == open; +} + +void Run(AgcDriver::VulkanDevice& device, const GuestBlock& guest, std::size_t offset, bool submit, const char* name) { + const auto* words = guest.Words() + offset / 4u; + const auto image = guest.Address() + offset; + const auto target = device.Target(); + const auto storeImage = Compile(StoreImageCode, ImageUserData(image), target); + const auto storeGlobal = Compile(StoreGlobalCode, GlobalUserData(image), target); + device.Dispatch(storeImage, 1u, 1u, 1u, {}, reinterpret_cast(StoreImageCode.data())); + device.Dispatch(storeGlobal, 1u, 1u, 1u, {}, reinterpret_cast(StoreGlobalCode.data())); + if (submit) { + std::uint64_t serial = 0; + { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + serial = device.SubmitAndEpoch(); + } + device.WaitRecorded(serial); + } + const std::string what = std::string("storage flush ordering, ") + name + ": "; + Require(AddressBasedStorePending(image, !submit), what + "the address-based store was not pending in the expected batch at the flush"); + Require(AgcDriver::Graphics::StorageTexture::FlushPending(image, ImageBytes, nullptr, "test"), what + "the image store left no results pending"); + device.WaitIdle(); + Require(words[FarDword] == Stored, what + "the pending image's texel away from the newer write was not stored"); + Require(words[OverwrittenDword] == Overwritten, what + "the pending image's store put its older texels over a newer address-based store"); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const bool watched = AgcDriver::GuestMemory::WriteWatched(); + GuestBlock guest(watched); + if (!watched) std::puts("guest memory has no write watch: the writer-in-flight case is skipped"); + else if (!device->CanWaitUnlocked()) std::puts("the recorder cannot wait for the GPU without running completions: the writer-in-flight case is skipped"); + else Run(*device, guest, 0, true, "writer in flight"); + Run(*device, guest, BlockBytes / 2u, false, "writer in the open batch"); + std::puts("storage flush ordering tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcResourceRegistration/Export.cpp b/core/libs/prx/libSceAgcResourceRegistration/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAgcResourceRegistration/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAgcVshDebug/Export.cpp b/core/libs/prx/libSceAgcVshDebug/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAgcVshDebug/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAjm.native/src/Ajm.cpp b/core/libs/prx/libSceAjm.native/src/Ajm.cpp index 105587e0aef9..10d7437a9a3d 100644 --- a/core/libs/prx/libSceAjm.native/src/Ajm.cpp +++ b/core/libs/prx/libSceAjm.native/src/Ajm.cpp @@ -46,6 +46,7 @@ constexpr std::int32_t AJM_RESULT_FATAL = static_cast(0x80000000u) constexpr std::uint32_t CODEC_MP3 = 0; constexpr std::uint32_t CODEC_AT9 = 1; constexpr std::uint32_t CODEC_M4AAC = 2; +constexpr std::uint32_t CODEC_PCM = 23; constexpr std::uint32_t CODEC_OPUS = 24; constexpr std::uint32_t M4AAC_CONFIG_ADTS = 1; @@ -162,6 +163,9 @@ struct Instance { std::uint32_t aacFrameSamples = 0; std::uint32_t aacDelayFrames = 0; bool aacHeaac = false; + std::uint32_t pcmChannels = 0; + std::uint32_t pcmSampleRate = 0; + bool pcmResampled = false; Resampler resampler; ~Instance() { @@ -444,8 +448,25 @@ void ClearResampler(Instance& instance) { instance.resampler.position = 1.0; } +std::int32_t InitializePcm(Instance& instance, const std::uint8_t* parameters, std::uint64_t size) { + if (size != 12) NotImplemented_nid_no_patch("sceAjmBatchJobInitialize (codec 23 parameters other than 12 bytes)"); + std::uint32_t words[3] = {}; + std::memcpy(words, parameters, sizeof(words)); + if (words[0] != 0) NotImplemented_nid_no_patch("sceAjmBatchJobInitialize (codec 23 input format other than 0, interleaved s16)"); + if (words[1] == 0 || words[1] > 8 || words[2] == 0) return AJM_RESULT_INVALID_PARAMETER; + instance.pcmChannels = words[1]; + instance.pcmSampleRate = words[2]; + instance.pcmResampled = false; + instance.initialized = true; + instance.totalDecodedSamples = 0; + instance.segmentSamples = 0; + instance.gapless = {}; + return 0; +} + std::int32_t InitializeInstance(Instance& instance, const std::uint8_t* parameters, std::uint64_t size) { ClearResampler(instance); + if (instance.codec == CODEC_PCM) return InitializePcm(instance, parameters, size); if (instance.codec == CODEC_OPUS) { if (size < 8) return AJM_RESULT_INVALID_PARAMETER; std::uint32_t channels = 0; @@ -616,6 +637,10 @@ void ResamplerPush(Instance& instance, const double* samples, std::size_t frames stored.insert(stored.end(), samples, samples + frames * channels); } +double CatmullRom(double p0, double p1, double p2, double p3, double t) { + return p1 + 0.5 * t * (p2 - p0 + t * (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3 + t * (3.0 * (p1 - p2) + p3 - p0))); +} + void ResamplerProduce(Instance& instance, PcmOutputs& outputs, std::size_t channels, std::uint32_t encoding, std::size_t sampleBytes) { auto& resampler = instance.resampler; if (resampler.frames.empty() || channels == 0) return; @@ -631,8 +656,7 @@ void ResamplerProduce(Instance& instance, PcmOutputs& outputs, std::size_t chann const double p1 = resampler.frames[index * channels + channel]; const double p2 = resampler.frames[(index + 1) * channels + channel]; const double p3 = resampler.frames[(index + 2) * channels + channel]; - const double value = p1 + 0.5 * t * (p2 - p0 + t * (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3 + t * (3.0 * (p1 - p2) + p3 - p0))); - StorePcm(value, frame.data() + channel * sampleBytes, encoding); + StorePcm(CatmullRom(p0, p1, p2, p3, t), frame.data() + channel * sampleBytes, encoding); } outputs.Emit(frame.data(), frameBytes); resampler.position += resampler.ratio; @@ -679,6 +703,7 @@ SidebandFormat CurrentFormat(const Instance& instance) { case CODEC_MP3: return {instance.mp3Channels, ChannelMask(instance.mp3Channels), instance.mp3SampleRate, PcmEncoding(instance), instance.mp3Bitrate, 0}; case CODEC_OPUS: return {instance.opusChannels, ChannelMask(instance.opusChannels), instance.opusSampleRate, PcmEncoding(instance), 0, 0}; case CODEC_M4AAC: return {instance.aacChannels, ChannelMask(instance.aacChannels), instance.aacSampleRate, PcmEncoding(instance), instance.aacBitrate, 0}; + case CODEC_PCM: return {instance.pcmChannels, ChannelMask(instance.pcmChannels), instance.pcmSampleRate, PcmEncoding(instance), 0, 0}; default: throw std::runtime_error("AJM: the format of codec " + std::to_string(instance.codec) + " is not implemented"); } } @@ -1099,6 +1124,44 @@ std::size_t OpusPacketSamples(const std::uint8_t* packet, std::size_t size) { return frames * frameSamples; } +void RunPcm(Instance& instance, const JobHeader& job, const AjmBuffer* inputs, const AjmBuffer* outputs) { + if (job.flags & ~SIDEBAND_STREAM) NotImplemented_nid_no_patch("AJM codec 23 run with flags other than SIDEBAND_STREAM"); + const auto encoding = PcmEncoding(instance); + if (encoding > 2) throw std::runtime_error("AJM codec 23: PCM encoding " + std::to_string(encoding) + " is not implemented"); + if (instance.resampler.change != 0.0f) NotImplemented_nid_no_patch("AJM codec 23 run with a resample ratio change per sample"); + if (instance.gapless.totalSamples != 0 || instance.gapless.skipSamples != 0) NotImplemented_nid_no_patch("AJM codec 23 run with gapless decode counts"); + if (instance.pcmResampled) NotImplemented_nid_no_patch("AJM codec 23 run after a resampled run without an initialize or context clear in between"); + const JobInput input(job, inputs); + PcmOutputs pcmOutputs(outputs, job.outputCount); + const std::size_t channels = instance.pcmChannels; + const std::size_t inputFrameBytes = channels * sizeof(std::int16_t); + const std::size_t sampleBytes = encoding == 0 ? sizeof(std::int16_t) : sizeof(std::int32_t); + if (input.empty()) NotImplemented_nid_no_patch("AJM codec 23 run with an empty input"); + if (input.size() % inputFrameBytes != 0) NotImplemented_nid_no_patch("AJM codec 23 run with an input that is not a whole number of frames"); + const std::size_t frames = input.size() / inputFrameBytes; + const double ratio = instance.resampler.ratio; + const std::size_t room = pcmOutputs.Room() / (channels * sampleBytes); + std::size_t outputFrames = 0; + while (outputFrames < room && static_cast(outputFrames) * ratio < static_cast(frames)) ++outputFrames; + if (static_cast(outputFrames) * ratio < static_cast(frames)) NotImplemented_nid_no_patch("AJM codec 23 run whose output cannot hold the whole resampled input"); + const auto sample = [&](std::size_t index, std::size_t channel) { return PcmValue(input.data() + (std::min(index, frames - 1) * channels + channel) * sizeof(std::int16_t), 0); }; + std::vector frame(channels * sampleBytes); + for (std::size_t output = 0; output < outputFrames; ++output) { + const double position = static_cast(output) * ratio; + const auto index = static_cast(position); + const double t = position - static_cast(index); + for (std::size_t channel = 0; channel < channels; ++channel) { + const double value = CatmullRom(sample(index == 0 ? 0 : index - 1, channel), sample(index, channel), sample(index + 1, channel), sample(index + 2, channel), t); + StorePcm(value, frame.data() + channel * sampleBytes, encoding); + } + pcmOutputs.Emit(frame.data(), frame.size()); + } + if (ratio != 1.0) instance.pcmResampled = true; + instance.totalDecodedSamples += frames; + AJM_TRACE("[ajm] instance %u pcm run flags 0x%llx: %zu input bytes consumed, %zu output bytes, ratio %f -> produced %zu, %zu channels, total samples %llu\n", job.instance, static_cast(job.flags), input.size(), pcmOutputs.capacity, ratio, pcmOutputs.produced, channels, static_cast(instance.totalDecodedSamples)); + WriteRunSideband(job, instance, 0, input.size(), pcmOutputs.produced, 1, CurrentFormat(instance)); +} + void RunOpus(Instance& instance, const JobHeader& job, const AjmBuffer* inputs, const AjmBuffer* outputs) { const JobInput input(job, inputs); PcmOutputs pcmOutputs(outputs, job.outputCount); @@ -1424,6 +1487,7 @@ void ClearContext(Instance& instance) { instance.aacDelayFrames = AacDelayFrames(instance); } instance.opusPending.clear(); + instance.pcmResampled = false; ClearResampler(instance); } @@ -1461,7 +1525,7 @@ void Control(Instance& instance, const JobHeader& job, const AjmBuffer* inputs) } std::size_t HeldSamples(const Instance& instance) { - const std::size_t channels = instance.codec == CODEC_OPUS ? instance.opusChannels : instance.codec == CODEC_MP3 ? instance.mp3Channels : instance.codec == CODEC_M4AAC ? instance.aacChannels : static_cast(std::max(0, instance.info.channels)); + const std::size_t channels = instance.codec == CODEC_PCM ? instance.pcmChannels : instance.codec == CODEC_OPUS ? instance.opusChannels : instance.codec == CODEC_MP3 ? instance.mp3Channels : instance.codec == CODEC_M4AAC ? instance.aacChannels : static_cast(std::max(0, instance.info.channels)); if (Resampling(instance)) return ResamplerHeld(instance, channels); const std::size_t sampleBytes = PcmEncoding(instance) == 0 ? sizeof(std::int16_t) : sizeof(std::int32_t); return channels == 0 ? 0 : instance.opusPending.size() / (channels * sampleBytes); @@ -1527,7 +1591,7 @@ void Execute(const JobHeader& job, const AjmBuffer* inputs, const AjmBuffer* out if (!instance->initialized) { AJM_TRACE("[ajm] instance %u run before initialize\n", job.instance); WriteResult(job.sideband, job.sidebandSize, AJM_RESULT_NOT_INITIALIZED); - } else if (instance->codec != CODEC_AT9 && instance->codec != CODEC_MP3 && instance->codec != CODEC_M4AAC && instance->codec != CODEC_OPUS) { + } else if (instance->codec != CODEC_AT9 && instance->codec != CODEC_MP3 && instance->codec != CODEC_M4AAC && instance->codec != CODEC_PCM && instance->codec != CODEC_OPUS) { throw std::runtime_error("AJM: decoding codec " + std::to_string(instance->codec) + " is not implemented"); } else if ((job.flags & RUN_GET_CODEC_INFO) && (job.flags & RUN_MULTIPLE_FRAMES)) { NotImplemented_nid_no_patch("AJM run job with both RUN_GET_CODEC_INFO and RUN_MULTIPLE_FRAMES (sideband order)"); @@ -1542,6 +1606,8 @@ void Execute(const JobHeader& job, const AjmBuffer* inputs, const AjmBuffer* out RunMp3(*instance, job, inputs, outputs); } else if (instance->codec == CODEC_M4AAC) { RunAac(*instance, job, inputs, outputs); + } else if (instance->codec == CODEC_PCM) { + RunPcm(*instance, job, inputs, outputs); } else { RunOpus(*instance, job, inputs, outputs); } diff --git a/core/libs/prx/libSceAppChecker/Export.cpp b/core/libs/prx/libSceAppChecker/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAppChecker/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAppDbShellCoreClient/Export.cpp b/core/libs/prx/libSceAppDbShellCoreClient/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAppDbShellCoreClient/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAppDbShellCoreClientForSystemService/Export.cpp b/core/libs/prx/libSceAppDbShellCoreClientForSystemService/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAppDbShellCoreClientForSystemService/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAppInstUtil/Export.cpp b/core/libs/prx/libSceAppInstUtil/Export.cpp new file mode 100644 index 000000000000..845fd759b0aa --- /dev/null +++ b/core/libs/prx/libSceAppInstUtil/Export.cpp @@ -0,0 +1,85 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceAppInstUtilInitialize(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilTerminate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppInstallAll(void* reserved) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppInstallTitleDir(const char* titleId, const char* titleDir, void* reserved) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppIsInInstalling(int32_t* installing) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppIsInUpdating() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppExists(const char* titleId, int32_t* exists) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppGetSize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppInstallPkg() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppPrepareOverwritePkg() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppRecoverApp() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppUnInstall(const char* titleId) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppUnInstallAddcont() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppUnInstallPat() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppUnInstallTheme() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilAppUnInstallTypes() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilGetInstallProgressInfo() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilGetInstallProgress() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAppInstUtilGetPrimaryAppSlot() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libSceAsyncStorageInternal/Export.cpp b/core/libs/prx/libSceAsyncStorageInternal/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAsyncStorageInternal/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAudioIn/Export.cpp b/core/libs/prx/libSceAudioIn/Export.cpp index b07ad2150688..fb53fc101010 100644 --- a/core/libs/prx/libSceAudioIn/Export.cpp +++ b/core/libs/prx/libSceAudioIn/Export.cpp @@ -204,4 +204,17 @@ int32_t APS5_VABI sceAudioInIsPadEmbeddedMic(int32_t handle) { return 0; } +int32_t APS5_VABI sceAudioInGetGain(int32_t handle, float* gain) { + (void)handle; + (void)gain; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int32_t APS5_VABI sceAudioInGetRerouteCount(int32_t handle) { + (void)handle; + NotImplemented_nid_no_patch(__func__); + return 0; +} + } diff --git a/core/libs/prx/libSceAudioLatencyEstimation/Export.cpp b/core/libs/prx/libSceAudioLatencyEstimation/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAudioLatencyEstimation/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAudioOut/CMakeLists.txt b/core/libs/prx/libSceAudioOut/CMakeLists.txt index 38bdb8cf0ede..c8198659b417 100644 --- a/core/libs/prx/libSceAudioOut/CMakeLists.txt +++ b/core/libs/prx/libSceAudioOut/CMakeLists.txt @@ -19,6 +19,46 @@ target_link_libraries(libSceAudioOut PRIVATE libc libkernel libScePad SDL2-stati configure_windows_unwind(libSceAudioOut) if(BUILD_TESTING) + add_test_executable(audio_out_vibration_timeline_tests tests/AudioOutVibrationTimeline.cpp) + target_include_directories(audio_out_vibration_timeline_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(audio_out_vibration_timeline_tests PRIVATE libc) + configure_windows_unwind(audio_out_vibration_timeline_tests) + add_test(NAME audio_out_vibration_timeline COMMAND audio_out_vibration_timeline_tests) + set_tests_properties(audio_out_vibration_timeline PROPERTIES TIMEOUT 5) + if(WIN32) + set_tests_properties(audio_out_vibration_timeline PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + + add_test_executable(audio_out_vibration_device_tests tests/AudioOutVibrationDevice.cpp src/AudioOut.cpp src/AudioOut2PadMix.cpp src/AudioOutShutdown.cpp) + target_compile_definitions(audio_out_vibration_device_tests PRIVATE SDL_MAIN_HANDLED) + target_include_directories(audio_out_vibration_device_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR}) + target_link_libraries(audio_out_vibration_device_tests PRIVATE libc libkernel libScePad) + configure_windows_unwind(audio_out_vibration_device_tests) + foreach(vibrationDeviceMode IN ITEMS quad pulse open_failure timing cadence discovery_failure) + set(vibrationDeviceTest "audio_out_vibration_device_${vibrationDeviceMode}") + add_test(NAME ${vibrationDeviceTest} COMMAND audio_out_vibration_device_tests ${vibrationDeviceMode}) + set_tests_properties(${vibrationDeviceTest} PROPERTIES TIMEOUT 5) + if(WIN32) + set_tests_properties(${vibrationDeviceTest} 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) + configure_windows_unwind(audio_out_vibration_tests) + foreach(vibrationMode IN ITEMS s16 f32 batch_s16 batch_f32 mono_s16 mono_f32 gain_s16 gain_f32 muted silence drain ex pad_control) + set(vibrationTest "audio_out_vibration_${vibrationMode}") + add_test(NAME ${vibrationTest} COMMAND audio_out_vibration_tests --${vibrationMode}) + set_tests_properties(${vibrationTest} PROPERTIES TIMEOUT 5 ENVIRONMENT "SDL_AUDIODRIVER=dummy") + if(WIN32) + set_tests_properties(${vibrationTest} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") add_test_executable(audio_out_port_locks_tests tests/AudioOutPortLocks.cpp) target_include_directories(audio_out_port_locks_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR}) diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp index 16d6f40196c6..034d07bdf939 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp @@ -23,6 +23,8 @@ #include "prx/libkernel/Time/include/Time.hpp" #include "prx/libSceAudioOut/include/AudioOutSdl.hpp" #include "AudioOutShutdown.hpp" +#include "AudioOut2PadMix.hpp" +#include "prx/libScePad/include/PadState.hpp" static constexpr int PORT_TYPE_MAIN = 0; static constexpr int PORT_TYPE_BGM = 1; @@ -98,6 +100,14 @@ static std::uint32_t bytesPerSample(Format f) { return formatIsFloat(f) ? sizeof(float) : sizeof(std::int16_t); } +static std::uint32_t checkedBufferSize(std::uint32_t frames, std::uint32_t channels, std::uint32_t bps) { + const std::uint64_t size = static_cast(frames) * channels * bps; + if (size > static_cast(std::numeric_limits::max())) { + throw std::runtime_error("checkedBufferSize: audio buffer size overflows"); + } + return static_cast(size); +} + struct Port { bool used = false; int type = 0; @@ -109,6 +119,7 @@ struct Port { int mixLevel = DEFAULT_VOLUME; std::uint64_t lastDataOutputTime = 0; std::uint64_t virtualQueueEnd = 0; + AudioOut2PadLayout padLayout; SDL_AudioDeviceID device = 0; SDL_AudioSpec spec = {}; }; @@ -160,6 +171,30 @@ static bool openDevice(Port& port) { return true; } +static bool openVibrationDevice(Port& port) { + if (!ensureSdlAudio()) throw std::runtime_error(std::string("AudioOut: could not initialize controller audio: ") + SDL_GetError()); + const int count = SDL_GetNumAudioDevices(0); + if (count < 0) throw std::runtime_error(std::string("AudioOut: could not enumerate controller sound cards: ") + SDL_GetError()); + for (int index = 0; index < count; ++index) { + const char* name = SDL_GetAudioDeviceName(index, 0); + if (!AudioOut2IsPadAudioDevice(name)) continue; + const std::string controllerName(name); + SDL_AudioSpec native{}; + if (SDL_GetAudioDeviceSpec(index, 0, &native) == 0 && native.channels != 0 && native.channels != AUDIO_OUT2_PAD_CHANNELS) continue; + port.padLayout = AudioOut2PadLayoutForDriver(SDL_GetCurrentAudioDriver()); + SDL_AudioSpec desired{}; + desired.freq = static_cast(port.freq); + desired.format = AUDIO_F32SYS; + desired.channels = static_cast(port.padLayout.channels); + desired.samples = static_cast(port.samplesNum); + port.device = SDL_OpenAudioDevice(controllerName.c_str(), 0, &desired, &port.spec, 0); + if (port.device == 0) throw std::runtime_error(std::string("AudioOut: could not open the controller sound card: ") + SDL_GetError()); + SDL_PauseAudioDevice(port.device, 0); + return true; + } + return false; +} + static void closeDevice(Port& port) { if (port.device != 0 && SDL_WasInit(SDL_INIT_AUDIO) != 0) { SDL_ClearQueuedAudio(port.device); @@ -221,6 +256,23 @@ static const void* prepareBuffer(const Port& port, const void* data, std::vector return buf.data(); } +static std::vector prepareVibration(const Port& port, const void* data) { + std::vector prepared; + const auto* samples = prepareBuffer(port, data, prepared); + std::vector pad(static_cast(port.samplesNum) * AUDIO_OUT2_PAD_CHANNELS, 0.0f); + constexpr float unity[2] = {1.0f, 1.0f}; + for (std::uint32_t frame = 0; frame < port.samplesNum; ++frame) { + float input[2]{}; + for (int channel = 0; channel < port.channels; ++channel) { + const auto index = static_cast(frame) * port.channels + channel; + input[channel] = formatIsFloat(port.format) ? static_cast(samples)[index] : static_cast(samples)[index] / 32768.0f; + } + AudioOut2AccumulatePadFrame(AudioOut2Route::PadVibration, input, port.channels, unity, pad.data() + static_cast(frame) * AUDIO_OUT2_PAD_CHANNELS); + } + AudioOut2FinishPadMix(pad.data(), port.samplesNum); + return pad; +} + static void sleepUs(std::unique_lock& lock, std::uint64_t us) { lock.unlock(); PreciseSleepUs(us); @@ -241,12 +293,19 @@ static void queueAudio(Port& port, const void* data, std::unique_lock prepareBuf; - const void* prepared = prepareBuffer(port, data, prepareBuf); - const std::uint32_t preparedSize = port.samplesNum * - static_cast(port.channels) * bytesPerSample(port.format); - - const void* queueData = prepared; - const std::uint32_t queueSize = preparedSize; + std::vector padFrames; + const void* queueData = nullptr; + std::uint32_t queueSize = 0; + if (port.type == PORT_TYPE_VIBRATION) { + queueSize = checkedBufferSize(port.samplesNum, port.padLayout.channels, sizeof(float)); + const auto pad = prepareVibration(port, data); + padFrames.resize(static_cast(port.samplesNum) * port.padLayout.channels); + AudioOut2WritePadFrames(pad.data(), port.padLayout, padFrames.data(), port.samplesNum); + queueData = padFrames.data(); + } else { + queueData = prepareBuffer(port, data, prepareBuf); + queueSize = checkedBufferSize(port.samplesNum, static_cast(port.channels), bytesPerSample(port.format)); + } const std::uint64_t bufferUs = port.freq != 0 ? (1000000ULL * port.samplesNum) / port.freq @@ -283,6 +342,25 @@ static void paceVirtualPort(Port& port, bool hasData, std::unique_lock& lock) { + const auto pad = port.type == PORT_TYPE_VIBRATION && data != nullptr ? prepareVibration(port, data) : std::vector{}; + paceVirtualPort(port, data != nullptr, lock); + const auto source = static_cast(&port - g_ports) + 1; + if (port.type == PORT_TYPE_VIBRATION && data == nullptr) PadClearLegacyAudioHaptics_nid_postfix(source); + if (!pad.empty()) { + constexpr std::uint32_t grainFrames = 256; + const auto begin = port.virtualQueueEnd - (1000000ULL * port.samplesNum) / port.freq; + for (std::uint32_t frame = 0; frame < port.samplesNum;) { + const auto count = std::min(grainFrames, port.samplesNum - frame); + const auto start = begin + (1000000ULL * frame) / port.freq; + const auto end = begin + (1000000ULL * (frame + count)) / port.freq; + const auto rumble = AudioOut2RumbleFromPadFrames(pad.data() + static_cast(frame) * AUDIO_OUT2_PAD_CHANNELS, count); + PadQueueLegacyAudioHaptics_nid_postfix(source, start, end, rumble[0], rumble[1]); + frame += count; + } + } +} + static bool portTypeValid(int type) { return (type >= PORT_TYPE_MAIN && type <= PORT_TYPE_PADSPK) || type == PORT_TYPE_VIBRATION || @@ -312,6 +390,14 @@ static int allocatePort(int type, std::uint32_t len, std::uint32_t freq, Format port.lastDataOutputTime = 0; port.virtualQueueEnd = sceKernelGetProcessTime(); port.mixLevel = type == PORT_TYPE_PADSPK ? DEFAULT_PADSPK_MIX_LEVEL : DEFAULT_VOLUME; + if (type == PORT_TYPE_VIBRATION) { + try { + openVibrationDevice(port); + } catch (...) { + port = Port{}; + throw; + } + } for (int c = 0; c < port.channels; c++) { port.volume[c] = DEFAULT_VOLUME; } @@ -362,6 +448,7 @@ int APS5_VABI sceAudioOutOpen(int userId, int type, int index, std::uint32_t len throw std::runtime_error("sceAudioOutOpen: unknown format param"); } + if (type == PORT_TYPE_VIBRATION && channelsForFormat(format) > 2) throw std::runtime_error("sceAudioOutOpen: vibration layouts above stereo are not implemented"); return allocatePort(type, len, freq, format); } @@ -374,6 +461,7 @@ int APS5_VABI sceAudioOutClose(int handle) { return -2144993277; } closeDevice(*port); + if (port->type == PORT_TYPE_VIBRATION) PadClearLegacyAudioHaptics_nid_postfix(handle); *port = Port{}; return 0; } @@ -390,7 +478,7 @@ int APS5_VABI sceAudioOutOutput(int handle, const void* ptr) try { } if (port->device == 0) { - paceVirtualPort(*port, ptr != nullptr, lock); + outputVirtualPort(*port, ptr, lock); } else { queueAudio(*port, ptr, lock); } @@ -428,7 +516,7 @@ int APS5_VABI sceAudioOutOutputs(AudioOutOutputParam* param, std::uint32_t num) for (std::uint32_t i = 0; i < num; i++) { Port& port = *getPort(param[i].handle); if (port.device == 0) { - paceVirtualPort(port, param[i].ptr != nullptr, lock); + outputVirtualPort(port, param[i].ptr, lock); } else { queueAudio(port, param[i].ptr, lock); } diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutVibration.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutVibration.cpp new file mode 100644 index 000000000000..4f27e0bf7db7 --- /dev/null +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutVibration.cpp @@ -0,0 +1,99 @@ +#include "prx/libScePad/include/PadState.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 sceAudioOutExOpen(int, std::uint32_t); +int APS5_VABI sceAudioOutClose(int); +int APS5_VABI sceAudioOutOutput(int, const void*); +int APS5_VABI sceAudioOutOutputs(AudioOutOutputParam*, std::uint32_t); +int APS5_VABI sceAudioOutSetVolume(int, std::uint32_t, int*); +} + +static void Require(bool value, const char* message) { + if (!value) throw std::runtime_error(message); +} + +struct Case { + std::string_view name; + std::uint32_t format = 1; + bool batch = false; + std::array volume{32768, 32768}; + std::array expected{128, 255}; + bool silence = false; + bool drain = false; + bool ex = false; +}; + +int main(int argc, char** argv) { + try { + const std::string_view mode = argc == 2 ? argv[1] : "--s16"; + const Case cases[] = { + {"--s16"}, {"--f32", 4}, {"--batch_s16", 1, true}, {"--batch_f32", 4, true}, + {"--mono_s16", 0, false, {32768, 32768}, {128, 128}}, + {"--mono_f32", 3, false, {32768, 32768}, {128, 128}}, + {"--gain_s16", 1, false, {16384, 8192}, {64, 64}}, + {"--gain_f32", 4, true, {16384, 8192}, {64, 64}}, + {"--muted", 4, false, {0, 0}, {0, 0}}, + {"--silence", 1, true, {32768, 32768}, {0, 0}, true}, + {"--drain", 4, false, {32768, 32768}, {0, 0}, false, true}, + {"--ex", 1, true, {32768, 32768}, {0, 0}, false, false, true}, + {"--pad_control"} + }; + const Case* selected = nullptr; + for (const auto& entry : cases) if (entry.name == mode) selected = &entry; + Require(argc <= 2 && selected != nullptr, "invalid test arguments"); + const auto& test = *selected; + Pad::Initialize(); + Pad::SetVibrationMode(1); + std::uint32_t seen = 0; + PadOutputState output{}; + static_cast(PadFetchOutput_nid_postfix(&seen, &output)); + struct Port { + int handle = -1; + ~Port() { if (handle > 0) sceAudioOutClose(handle); } + } port; + if (mode == "--pad_control") { + PadSubmitAudioHaptics_nid_postfix(128, 255); + } else { + const std::uint32_t frames = test.ex ? 256u : 48000u; + const bool floating = test.format == 3 || test.format == 4; + const std::uint32_t channels = test.format == 0 || test.format == 3 ? 1u : 2u; + std::vector integers(frames * 2); + std::vector floats(frames * 2); + for (std::uint32_t frame = 0; frame < frames && !test.silence; ++frame) { + integers[frame * channels] = 8192; + floats[frame * channels] = 0.25f; + if (channels == 2) { + integers[frame * channels + 1] = -16384; + floats[frame * channels + 1] = -0.5f; + } + } + port.handle = test.ex ? sceAudioOutExOpen(0x10000000, 0) : sceAudioOutOpen(0x10000000, 10, 0, frames, 48000, test.format); + Require(port.handle > 0, "legacy vibration port did not open"); + auto volume = test.volume; + Require(sceAudioOutSetVolume(port.handle, 3, volume.data()) == 0, "legacy vibration volume failed"); + const void* samples = test.drain ? nullptr : floating ? static_cast(floats.data()) : static_cast(integers.data()); + AudioOutOutputParam param{}; + param.handle = port.handle; + param.ptr = samples; + const int result = test.batch ? sceAudioOutOutputs(¶m, 1) : sceAudioOutOutput(port.handle, samples); + Require(result == static_cast(frames), "legacy vibration output returned the wrong frame count"); + } + const bool changed = PadFetchOutput_nid_postfix(&seen, &output); + Require(changed == (test.expected[0] != 0 || test.expected[1] != 0), "legacy vibration output published the wrong controller state"); + Require(output.audioVibrationLarge == test.expected[0] && output.audioVibrationSmall == test.expected[1], "legacy vibration PCM did not reach the existing motor bridge"); + Require(output.vibrationLarge == 0 && output.vibrationSmall == 0, "audio vibration replaced regular motor state"); + std::puts("legacy vibration bridge test passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutVibrationDevice.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutVibrationDevice.cpp new file mode 100644 index 000000000000..6d0a7ca4f174 --- /dev/null +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutVibrationDevice.cpp @@ -0,0 +1,290 @@ +#include "SDL.h" +#include "prx/libSceAudioOut/include/AudioOutSdl.hpp" +#include "prx/libScePad/include/PadState.hpp" +#include "prx/libScePad/include/PadAudioHapticQueue.hpp" +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#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*); +int APS5_VABI sceAudioOutOutputs(AudioOutOutputParam*, std::uint32_t); +int APS5_VABI sceAudioOutSetVolume(int, std::uint32_t, int*); +int APS5_VABI sceAudioOutGetLastOutputTime(int, std::uint64_t*); +} + +namespace { +constexpr std::uint32_t frames = 256; +bool available = true; +bool failOpen = false; +bool failInitialize = false; +bool failEnumeration = false; +bool pulse = false; +std::uint32_t frequency = 48000; +int opened = 0; +int closed = 0; +int queued = 0; +std::vector played; +struct Block { + int source; + std::uint64_t begin; + std::uint64_t end; + std::array rumble; +}; +std::vector blocks; +std::vector clears; +PadAudioHapticQueue timeline; + +void Require(bool value, const char* message) { + if (!value) throw std::runtime_error(message); +} + +float Expected(std::int16_t sample, int volume, bool floating) { + const float value = sample / 32768.0f; + if (floating) return std::clamp(value * (volume / 32768.0f), -1.0f, 1.0f); + return static_cast(std::clamp(static_cast(sample) * volume / 32768, -32768, 32767)) / 32768.0f; +} + +void Run(std::uint32_t format, bool batch, int volume) { + const bool floating = format >= 3; + const std::uint32_t channels = format == 0 || format == 3 ? 1u : 2u; + const int handle = sceAudioOutOpen(0x10000000, 10, 0, frames, frequency, format); + Require(handle == 1, "a closed or failed vibration port remained allocated"); + std::array levels{volume, volume / 2}; + Require(sceAudioOutSetVolume(handle, 3, levels.data()) == 0, "vibration gains failed"); + std::array integers{}; + std::array floats{}; + for (std::uint32_t frame = 0; frame < frames; ++frame) { + integers[frame * channels] = 8192; + floats[frame * channels] = 0.25f; + if (channels == 2) { + integers[frame * channels + 1] = -16384; + floats[frame * channels + 1] = -0.5f; + } + } + const void* data = floating ? static_cast(floats.data()) : static_cast(integers.data()); + AudioOutOutputParam outputs[2]{}; + outputs[0].handle = handle; + outputs[0].ptr = data; + outputs[1].handle = 9999; + outputs[1].ptr = data; + const int before = queued; + Require(sceAudioOutOutputs(outputs, 2) < 0 && queued == before, "an invalid batch queued a valid prefix"); + Require((batch ? sceAudioOutOutputs(outputs, 1) : sceAudioOutOutput(handle, data)) == static_cast(frames), "vibration frame count changed"); + Require(queued == before + 1, "native vibration did not reach the named controller device"); + const std::uint32_t deviceChannels = pulse ? 6u : 4u; + Require(played.size() == frames * deviceChannels, "native vibration frame layout changed"); + const auto left = Expected(8192, levels[0], floating); + const auto right = channels == 1 ? left : Expected(-16384, levels[1], floating); + for (std::uint32_t frame = 0; frame < frames; ++frame) { + for (std::uint32_t channel = 0; channel < deviceChannels; ++channel) { + const auto expected = channel == deviceChannels - 2 ? left : channel == deviceChannels - 1 ? right : 0.0f; + Require(played[frame * deviceChannels + channel] == expected, "vibration PCM or gain leaked into a speaker channel"); + } + } + std::uint64_t time = 0; + Require(sceAudioOutGetLastOutputTime(handle, &time) == 0 && time != 0, "vibration acceptance time was not recorded"); + Require(sceAudioOutOutput(handle, nullptr) == static_cast(frames) && queued == before + 1, "draining vibration queued samples"); + std::uint64_t after = 0; + Require(sceAudioOutGetLastOutputTime(handle, &after) == 0 && after == time, "a drain changed the last data time"); + Require(sceAudioOutClose(handle) == 0, "native vibration close failed"); +} + +int Submit(std::uint32_t count, std::uint32_t rate, bool split = false) { + const int handle = sceAudioOutOpen(0x10000000, 10, 0, count, rate, 1); + Require(handle > 0, "timed vibration port did not open"); + std::vector samples(static_cast(count) * 2); + for (std::uint32_t frame = 0; frame < count; ++frame) { + samples[frame * 2] = split && frame >= 256 ? 16384 : 8192; + samples[frame * 2 + 1] = split && frame >= 256 ? -8192 : -16384; + } + Require(sceAudioOutOutput(handle, samples.data()) == static_cast(count), "timed vibration output failed"); + return handle; +} + +void RunTiming() { + available = false; + const int shortPort = Submit(256, 48000); + Require(blocks.size() == 1 && blocks[0].end - blocks[0].begin == 5333 && blocks[0].rumble == std::array{128, 255}, "short block timing changed"); + const int longPort = Submit(4800, 24000); + Require(blocks.size() == 20 && blocks.back().end - blocks[1].begin == 200000, "long block duration did not follow frame count/rate"); + for (std::size_t index = 2; index < blocks.size(); ++index) Require(blocks[index].begin == blocks[index - 1].end, "successive grains overlap or leave a gap"); + auto model = timeline; + Require(model.Read(blocks[1].begin + 81000) == std::array{128, 255}, "a long block inherited the AudioOut2 timeout"); + Require(sceAudioOutOutput(shortPort, nullptr) == 256 && clears == std::vector{shortPort}, "a drain cleared another port"); + Require(sceKernelGetProcessTime() >= blocks[0].end, "a drain returned before the submitted block ended"); + Require(sceAudioOutClose(shortPort) == 0 && sceAudioOutClose(longPort) == 0, "timed port close failed"); + Require(clears == std::vector{shortPort, shortPort, longPort}, "close did not clear its own source"); + Require(timeline.Read(sceKernelGetProcessTime()) == std::array{}, "close retained a queued source"); + + const auto firstLong = blocks.size(); + const int drained = Submit(4800, 24000); + const auto end = blocks.back().end; + Require(blocks.size() == firstLong + 19, "long PCM was not divided into grains"); + Require(sceAudioOutOutput(drained, nullptr) == 4800 && sceKernelGetProcessTime() >= end, "long drain did not wait for its frame duration"); + Require(sceAudioOutClose(drained) == 0, "drained port close failed"); + + const auto firstSplit = blocks.size(); + const int split = Submit(512, 48000, true); + Require(blocks.size() == firstSplit + 2, "two-grain PCM was reduced to one value"); + Require(blocks[firstSplit].rumble == std::array{128, 255} && blocks.back().rumble == std::array{255, 128}, "grain-local energy was replaced by whole-block RMS"); + Require(sceAudioOutClose(split) == 0, "split port close failed"); + Require(opened == 0 && queued == 0, "virtual vibration opened a speaker device"); +} + +void RunCadence() { + available = false; + const std::array ports{ + sceAudioOutOpen(0x10000000, 10, 0, frames, frequency, 1), + sceAudioOutOpen(0x10000000, 10, 0, frames, frequency, 1) + }; + Require(ports[0] > 0 && ports[1] > 0 && ports[0] != ports[1], "repeated vibration ports did not open independently"); + constexpr std::array, 4> patterns{{{8192, -16384}, {0, 0}, {-16384, 8192}, {16384, -8192}}}; + constexpr std::array, 4> expected{{{128, 255}, {0, 0}, {255, 128}, {255, 128}}}; + std::array ends{}; + std::array samples{}; + std::vector boundaries; + for (std::size_t call = 0; call < 32; ++call) { + for (std::size_t port = 0; port < ports.size(); ++port) { + const auto pattern = (call + port) % patterns.size(); + for (std::size_t frame = 0; frame < frames; ++frame) { + samples[frame * 2] = patterns[pattern][0]; + samples[frame * 2 + 1] = patterns[pattern][1]; + } + const auto count = blocks.size(); + Require(sceAudioOutOutput(ports[port], samples.data()) == static_cast(frames), "repeated vibration output rejected 256 frames"); + Require(blocks.size() == count + 1, "repeated vibration output did not queue one grain per call"); + const auto& block = blocks.back(); + Require(block.source == ports[port] && block.rumble == expected[pattern], "repeated vibration output used the wrong source or PCM energy"); + Require(block.end - block.begin == 5333 && block.begin >= ends[port], "repeated vibration interval has the wrong duration or overlaps its preceding call"); + ends[port] = block.end; + boundaries.insert(boundaries.end(), {block.begin, block.end - 1, block.end}); + } + } + Require(blocks.size() == 64 && clears.empty() && opened == 0 && queued == 0, "repeated virtual output changed routes or cleared a live port"); + std::sort(boundaries.begin(), boundaries.end()); + boundaries.erase(std::unique(boundaries.begin(), boundaries.end()), boundaries.end()); + const auto check = [&](PadAudioHapticQueue model, int removed) { + for (const auto time : boundaries) { + std::array value{}; + for (const auto& block : blocks) { + if (block.source == removed || time < block.begin || time >= block.end) continue; + for (std::size_t channel = 0; channel < value.size(); ++channel) value[channel] = std::max(value[channel], block.rumble[channel]); + } + Require(model.Read(time) == value, "repeated vibration timeline lost a grain, silence, boundary or independent source"); + } + }; + check(timeline, 0); + Require(sceAudioOutOutput(ports[0], nullptr) == static_cast(frames) && clears == std::vector{ports[0]}, "repeated vibration drain cleared the wrong source"); + check(timeline, ports[0]); + Require(sceAudioOutClose(ports[0]) == 0 && sceAudioOutClose(ports[1]) == 0, "repeated vibration port close failed"); + Require(clears == std::vector{ports[0], ports[0], ports[1]}, "repeated vibration close did not clear only its source"); + Require(timeline.Read(ends[0]) == std::array{}, "repeated vibration close retained queued output"); + std::puts("64 repeated legacy stereo S16/48 kHz outputs: 256 frames per call, 32 calls per port; PCM, interval boundaries, source mixing and drain isolation passed"); +} +} + +int InitializeSdlAudio_nid_no_patch() { return failInitialize ? -1 : 0; } +extern "C" void PadQueueLegacyAudioHaptics_nid_postfix(int source, std::uint64_t begin, std::uint64_t end, std::uint8_t large, std::uint8_t small) { + blocks.push_back({source, begin, end, {large, small}}); + timeline.Queue(source, begin, end, large, small); +} +extern "C" void PadClearLegacyAudioHaptics_nid_postfix(int source) { + clears.push_back(source); + timeline.Clear(source); +} +extern "C" Uint32 SDLCALL SDL_WasInit(Uint32) { return SDL_INIT_AUDIO; } +extern "C" int SDLCALL SDL_GetNumAudioDevices(int) { return failEnumeration ? -1 : available ? 2 : 1; } +extern "C" const char* SDLCALL SDL_GetAudioDeviceName(int index, int) { return index == 0 ? "Main speakers" : "DualSense Wireless Controller"; } +extern "C" int SDLCALL SDL_GetAudioDeviceSpec(int index, int, SDL_AudioSpec* spec) { *spec = {}; spec->channels = index == 0 ? 2 : 4; return 0; } +extern "C" const char* SDLCALL SDL_GetCurrentAudioDriver() { return pulse ? "pulseaudio" : "dummy"; } +extern "C" const char* SDLCALL SDL_GetError() { return "test controller open failure"; } +extern "C" SDL_AudioDeviceID SDLCALL SDL_OpenAudioDevice(const char* name, int capture, const SDL_AudioSpec* desired, SDL_AudioSpec* obtained, int flags) { + Require(name != nullptr && std::string_view(name) == "DualSense Wireless Controller", "vibration opened the default speakers"); + Require(capture == 0 && flags == 0 && desired->callback == nullptr && desired->freq == static_cast(frequency) && desired->format == AUDIO_F32SYS && desired->channels == (pulse ? 6 : 4) && desired->samples == frames, "native controller request changed"); + if (failOpen) return 0; + ++opened; + *obtained = *desired; + return 42; +} +extern "C" void SDLCALL SDL_PauseAudioDevice(SDL_AudioDeviceID device, int) { Require(device == 42, "wrong native device paused"); } +extern "C" Uint32 SDLCALL SDL_GetQueuedAudioSize(SDL_AudioDeviceID device) { Require(device == 42, "wrong native queue queried"); return 0; } +extern "C" int SDLCALL SDL_QueueAudio(SDL_AudioDeviceID device, const void* data, Uint32 bytes) { + Require(device == 42 && bytes % sizeof(float) == 0, "wrong vibration queue"); + const auto* samples = static_cast(data); + const std::size_t sampleCount = bytes / sizeof(float); + played.assign(samples, samples + sampleCount); + ++queued; + return 0; +} +extern "C" void SDLCALL SDL_ClearQueuedAudio(SDL_AudioDeviceID device) { Require(device == 42, "wrong native queue cleared"); } +extern "C" void SDLCALL SDL_CloseAudioDevice(SDL_AudioDeviceID device) { Require(device == 42, "wrong native device closed"); ++closed; } + +int main(int argc, char** argv) { + try { + Require(argc == 2, "invalid test arguments"); + const std::string_view mode = argv[1]; + Require(mode == "quad" || mode == "pulse" || mode == "open_failure" || mode == "timing" || mode == "cadence" || mode == "discovery_failure", "invalid device mode"); + pulse = mode == "pulse"; + Pad::Initialize(); + Pad::SetVibrationMode(1); + std::uint32_t seen = 0; + PadOutputState state{}; + static_cast(PadFetchOutput_nid_postfix(&seen, &state)); + if (mode == "cadence") { + RunCadence(); + return 0; + } + if (mode == "timing") { + RunTiming(); + std::puts("legacy vibration device tests passed"); + return 0; + } + if (mode == "open_failure") { + failOpen = true; + bool threw = false; + try { static_cast(sceAudioOutOpen(0x10000000, 10, 0, frames, frequency, 1)); } + catch (const std::runtime_error&) { threw = true; } + Require(threw && opened == 0 && queued == 0, "a failed native open silently changed routes"); + failOpen = false; + } + if (mode == "discovery_failure") { + for (bool* failure : {&failInitialize, &failEnumeration}) { + *failure = true; + bool threw = false; + try { static_cast(sceAudioOutOpen(0x10000000, 10, 0, frames, frequency, 1)); } + catch (const std::runtime_error&) { threw = true; } + Require(threw && opened == 0 && queued == 0, "a discovery failure silently changed routes"); + *failure = false; + } + } + for (auto rate : {24000u, 48000u}) { + frequency = rate; + for (auto format : {0u, 1u, 3u, 4u}) + for (bool batch : {false, true}) + for (int volume : {0, 16384, 32768, 131072}) Run(format, batch, volume); + } + for (auto format : {2u, 5u, 6u, 7u}) { + bool threw = false; + try { static_cast(sceAudioOutOpen(0x10000000, 10, 0, frames, frequency, format)); } + catch (const std::runtime_error&) { threw = true; } + Require(threw && opened == 64, "an unknown multichannel vibration layout was accepted"); + } + Require(opened == closed && opened == 64, "native vibration device ownership changed"); + Require(!PadFetchOutput_nid_postfix(&seen, &state), "native waveform output also sent motor approximation"); + std::puts("legacy vibration device tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutVibrationTimeline.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutVibrationTimeline.cpp new file mode 100644 index 000000000000..75f961ebfcda --- /dev/null +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutVibrationTimeline.cpp @@ -0,0 +1,70 @@ +#include "prx/libScePad/include/PadAudioHapticQueue.hpp" +#include + +static void Require(bool value, const char* message) { + if (!value) throw std::runtime_error(message); +} + +template +static bool Throws(TAction action) { + try { action(); } + catch (const std::invalid_argument&) { return true; } + return false; +} + +int main() { + try { + { + PadAudioHapticQueue queue; + queue.Queue(1, 1000, 6333, 128, 255); + Require(queue.Read(999) == std::array{}, "a queued block began early"); + Require(queue.Read(1000) == std::array{128, 255}, "a block did not begin at its queued start"); + Require(queue.Read(6332) == std::array{128, 255}, "a block ended early"); + Require(queue.Read(6333) == std::array{}, "a short block inherited an 80 ms hold"); + } + { + PadAudioHapticQueue queue; + queue.Queue(1, 1000, 201000, 200, 100); + Require(queue.Read(81000) == std::array{200, 100}, "a long block expired after 80 ms"); + Require(queue.Read(200999) == std::array{200, 100}, "a long block ended before its frame duration"); + Require(queue.Read(201000) == std::array{}, "a long block outlived its frame duration"); + } + { + PadAudioHapticQueue queue; + queue.Queue(1, 100, 200, 200, 10); + queue.Queue(1, 200, 300, 0, 0); + queue.Queue(1, 400, 500, 20, 30); + Require(queue.Read(199) == std::array{200, 10}, "the first grain changed"); + Require(queue.Read(200) == std::array{}, "silent PCM retained an earlier peak"); + Require(queue.Read(350) == std::array{}, "a gap between blocks vibrated"); + Require(queue.Read(400) == std::array{20, 30}, "a later grain did not start"); + } + { + PadAudioHapticQueue queue; + queue.Queue(1, 0, 1000, 200, 10); + queue.Queue(2, 0, 1000, 20, 220); + Require(queue.Read(10) == std::array{200, 220}, "independent sources did not combine"); + queue.Clear(1); + Require(queue.Read(10) == std::array{20, 220}, "draining one source cleared another"); + queue.Clear(1); + queue.Clear(99); + Require(queue.Read(10) == std::array{20, 220}, "clearing an absent source changed output"); + queue.Clear(2); + Require(queue.Read(10) == std::array{}, "closing the final source retained output"); + } + { + PadAudioHapticQueue queue; + Require(Throws([&] { queue.Queue(0, 0, 1, 1, 1); }), "an invalid source was accepted"); + Require(Throws([&] { queue.Queue(1, 1, 1, 1, 1); }), "a zero-length interval was accepted"); + Require(Throws([&] { queue.Queue(1, 2, 1, 1, 1); }), "a reversed interval was accepted"); + queue.Queue(1, 0, 100, 10, 20); + Require(Throws([&] { queue.Queue(1, 99, 200, 1, 1); }), "overlapping source blocks were accepted"); + Require(queue.Read(50) == std::array{10, 20}, "a refused interval changed the active block"); + } + std::puts("legacy vibration timeline tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/libs/prx/libSceAudioSystem/Export.cpp b/core/libs/prx/libSceAudioSystem/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceAudioSystem/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAudiodReport/Export.cpp b/core/libs/prx/libSceAudiodReport/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAudiodReport/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAudiodecCpuAlac/Export.cpp b/core/libs/prx/libSceAudiodecCpuAlac/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuAlac/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAudiodecCpuDdp/Export.cpp b/core/libs/prx/libSceAudiodecCpuDdp/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuDdp/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAudiodecCpuDts/Export.cpp b/core/libs/prx/libSceAudiodecCpuDts/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuDts/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAudiodecCpuDtsHdLbr/Export.cpp b/core/libs/prx/libSceAudiodecCpuDtsHdLbr/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuDtsHdLbr/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAudiodecCpuDtsHdMa/Export.cpp b/core/libs/prx/libSceAudiodecCpuDtsHdMa/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuDtsHdMa/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAudiodecCpuFlac/Export.cpp b/core/libs/prx/libSceAudiodecCpuFlac/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuFlac/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAudiodecCpuLpcm/Export.cpp b/core/libs/prx/libSceAudiodecCpuLpcm/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuLpcm/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAudiodecCpuM4aac/Export.cpp b/core/libs/prx/libSceAudiodecCpuM4aac/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuM4aac/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAudiodecCpuTrhd/Export.cpp b/core/libs/prx/libSceAudiodecCpuTrhd/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAudiodecCpuTrhd/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAutoMounterClient/Export.cpp b/core/libs/prx/libSceAutoMounterClient/Export.cpp new file mode 100644 index 000000000000..e55d28bc6af5 --- /dev/null +++ b/core/libs/prx/libSceAutoMounterClient/Export.cpp @@ -0,0 +1,21 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceAutoMounterClientGetUsbDeviceInfo() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAutoMounterClientGetUsbDeviceList() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceAutoMounterClientInit() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libSceAvPlayerStreaming/Export.cpp b/core/libs/prx/libSceAvPlayerStreaming/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAvPlayerStreaming/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAvSetting/Export.cpp b/core/libs/prx/libSceAvSetting/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceAvSetting/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAvcap2/Export.cpp b/core/libs/prx/libSceAvcap2/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAvcap2/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceAvcap2mvr/Export.cpp b/core/libs/prx/libSceAvcap2mvr/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceAvcap2mvr/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceBackupRestoreUtil/Export.cpp b/core/libs/prx/libSceBackupRestoreUtil/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceBackupRestoreUtil/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceBeisobmf/Export.cpp b/core/libs/prx/libSceBeisobmf/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceBeisobmf/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceBemp2sys/Export.cpp b/core/libs/prx/libSceBemp2sys/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceBemp2sys/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceBgft/Export.cpp b/core/libs/prx/libSceBgft/Export.cpp new file mode 100644 index 000000000000..1a1a66de1bf9 --- /dev/null +++ b/core/libs/prx/libSceBgft/Export.cpp @@ -0,0 +1,121 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceBgftServiceInit(void* params) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceTerm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceDownloadPauseTaskAll() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceDownloadResumeTaskAll() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceDownloadStartTaskAll() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceDownloadStopTaskAll() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceDownloadFindActivePatchTask() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceDownloadFindActivePupTask() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceDownloadFindTaskByContentId(const char* contentId, int32_t subType, int32_t* taskId) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceDownloadGetProgress(int32_t taskId, void* progress) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceDownloadPauseTask(int32_t taskId) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceDownloadResumeTask(int32_t taskId) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceDownloadStartTask(int32_t taskId) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceDownloadStopTask(int32_t taskId) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadFindActiveGameAndGameAcTask() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadFindActiveTask() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadGetGameAndGameAcProgress() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadGetPatchProgress() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadGetPlayGoProgress() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadGetProgress() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadGetTaskInfo() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadPauseTask() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadRegisterTaskByStorageEx() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadResumeTask() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadStopTask() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadStartTask() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntDownloadUnregisterTask() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceBgftServiceIntInit() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libSceBgsStorage/Export.cpp b/core/libs/prx/libSceBgsStorage/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceBgsStorage/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceCdlgUtilServer/Export.cpp b/core/libs/prx/libSceCdlgUtilServer/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceCdlgUtilServer/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceCesCs/Export.cpp b/core/libs/prx/libSceCesCs/Export.cpp index d3c7cb79f72f..fcca18a28dc1 100644 --- a/core/libs/prx/libSceCesCs/Export.cpp +++ b/core/libs/prx/libSceCesCs/Export.cpp @@ -481,4 +481,48 @@ int APS5_VABI sceCesUtf8ToSbc(const uint8_t* utf8, uint32_t utf8max, uint32_t* u return 0; } +int APS5_VABI sceCesEucJpToUtf8(void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucLength, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length) { + (void)profile; + (void)eucBuffer; + (void)eucMax; + (void)eucLength; + (void)utf8Buffer; + (void)utf8Max; + (void)utf8Length; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCesEucKrToUtf8(void* profile, uint8_t* eucBuffer, uint32_t eucMax, uint32_t* eucLength, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length) { + (void)profile; + (void)eucBuffer; + (void)eucMax; + (void)eucLength; + (void)utf8Buffer; + (void)utf8Max; + (void)utf8Length; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCesBig5ToUtf8(void* profile, uint8_t* big5Buffer, uint32_t big5Max, uint32_t* big5Length, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length) { + (void)profile; + (void)big5Buffer; + (void)big5Max; + (void)big5Length; + (void)utf8Buffer; + (void)utf8Max; + (void)utf8Length; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCesUhcToUtf8(void* profile, uint8_t* uhcBuffer, uint32_t uhcMax, uint32_t* uhcLength, uint8_t* utf8Buffer, uint32_t utf8Max, uint32_t* utf8Length) { + (void)profile; + (void)uhcBuffer; + (void)uhcMax; + (void)uhcLength; + (void)utf8Buffer; + (void)utf8Max; + (void)utf8Length; + NotImplemented_nid_no_patch(__func__); +} + } diff --git a/core/libs/prx/libSceCompanionHttpd/Export.cpp b/core/libs/prx/libSceCompanionHttpd/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceCompanionHttpd/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceCompanionUtil/Export.cpp b/core/libs/prx/libSceCompanionUtil/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceCompanionUtil/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceComposite/Export.cpp b/core/libs/prx/libSceComposite/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceComposite/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceCompositeExt/Export.cpp b/core/libs/prx/libSceCompositeExt/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceCompositeExt/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceConnectAccountDialog/Export.cpp b/core/libs/prx/libSceConnectAccountDialog/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceConnectAccountDialog/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceContentDelete/Export.cpp b/core/libs/prx/libSceContentDelete/Export.cpp index 4c80330e3065..5216e53a4813 100644 --- a/core/libs/prx/libSceContentDelete/Export.cpp +++ b/core/libs/prx/libSceContentDelete/Export.cpp @@ -29,6 +29,26 @@ int APS5_VABI sceContentDeleteById(void) { return 0; } +int APS5_VABI sceContentDeleteByIdListForSf(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceContentDeleteGlobalMstat(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceContentDeleteMemoryInitialize(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceContentDeleteMemoryTerminate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + int APS5_VABI sceContentDeleteTerminate(void) { bool expected = true; if (!g_initialized.compare_exchange_strong(expected, false)) throw std::logic_error(std::string(__func__) + ": not initialized"); diff --git a/core/libs/prx/libSceContentListController/Export.cpp b/core/libs/prx/libSceContentListController/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceContentListController/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceCtrlp/Export.cpp b/core/libs/prx/libSceCtrlp/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceCtrlp/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceCustomMusicAudioOut/Export.cpp b/core/libs/prx/libSceCustomMusicAudioOut/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceCustomMusicAudioOut/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceCustomMusicCore/Export.cpp b/core/libs/prx/libSceCustomMusicCore/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceCustomMusicCore/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceCustomMusicSysCallWrapper/Export.cpp b/core/libs/prx/libSceCustomMusicSysCallWrapper/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceCustomMusicSysCallWrapper/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDataTransfer/Export.cpp b/core/libs/prx/libSceDataTransfer/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceDataTransfer/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDbg/Export.cpp b/core/libs/prx/libSceDbg/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDbg/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDbgAssist/Export.cpp b/core/libs/prx/libSceDbgAssist/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDbgAssist/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDeci5/Export.cpp b/core/libs/prx/libSceDeci5/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDeci5/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDeci5Appmp/Export.cpp b/core/libs/prx/libSceDeci5Appmp/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDeci5Appmp/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDeci5CpComm/Export.cpp b/core/libs/prx/libSceDeci5CpComm/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDeci5CpComm/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDeci5Ctrlp/Export.cpp b/core/libs/prx/libSceDeci5Ctrlp/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDeci5Ctrlp/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDeci5Dbgp/Export.cpp b/core/libs/prx/libSceDeci5Dbgp/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDeci5Dbgp/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDeci5Drfp/Export.cpp b/core/libs/prx/libSceDeci5Drfp/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDeci5Drfp/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDeci5Eventp/Export.cpp b/core/libs/prx/libSceDeci5Eventp/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDeci5Eventp/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDeci5Fsmp/Export.cpp b/core/libs/prx/libSceDeci5Fsmp/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDeci5Fsmp/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDeci5Pgcmp/Export.cpp b/core/libs/prx/libSceDeci5Pgcmp/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDeci5Pgcmp/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDeci5Rdrfp/Export.cpp b/core/libs/prx/libSceDeci5Rdrfp/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDeci5Rdrfp/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDeci5Tsmp/Export.cpp b/core/libs/prx/libSceDeci5Tsmp/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDeci5Tsmp/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDeci5Ttyp/Export.cpp b/core/libs/prx/libSceDeci5Ttyp/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDeci5Ttyp/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDeci5UlpCommon/Export.cpp b/core/libs/prx/libSceDeci5UlpCommon/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDeci5UlpCommon/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDepth/Export.cpp b/core/libs/prx/libSceDepth/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceDepth/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDipsw/Export.cpp b/core/libs/prx/libSceDipsw/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceDipsw/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDiscId/Export.cpp b/core/libs/prx/libSceDiscId/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceDiscId/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDolbyVision/Export.cpp b/core/libs/prx/libSceDolbyVision/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDolbyVision/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDseehx/Export.cpp b/core/libs/prx/libSceDseehx/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceDseehx/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDtcpIp/Export.cpp b/core/libs/prx/libSceDtcpIp/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceDtcpIp/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceDtsEnc/Export.cpp b/core/libs/prx/libSceDtsEnc/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceDtsEnc/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceEditMp4/Export.cpp b/core/libs/prx/libSceEditMp4/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceEditMp4/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceEmbeddedTts/Export.cpp b/core/libs/prx/libSceEmbeddedTts/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceEmbeddedTts/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceEmbeddedTtsCoreG3/Export.cpp b/core/libs/prx/libSceEmbeddedTtsCoreG3/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceEmbeddedTtsCoreG3/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceEsvm/Export.cpp b/core/libs/prx/libSceEsvm/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceEsvm/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceEsvmEngine/Export.cpp b/core/libs/prx/libSceEsvmEngine/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceEsvmEngine/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceFios2/Export.cpp b/core/libs/prx/libSceFios2/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceFios2/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceFontFt/Export.cpp b/core/libs/prx/libSceFontFt/Export.cpp index d34a3ca5597d..0dcd8bcf9039 100644 --- a/core/libs/prx/libSceFontFt/Export.cpp +++ b/core/libs/prx/libSceFontFt/Export.cpp @@ -1,11 +1,41 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include + +#include +#include FT_FREETYPE_H +#include FT_MODULE_H + #include "prx/libSceFontFt/include/FontFtDriver.hpp" #include "prx/libc/include/General.hpp" #pragma GCC visibility push(default) +namespace { + +FT_Library ProbeLibrary() { + static const FT_Library library = [] { + FT_Library created = nullptr; + if (FT_Init_FreeType(&created) != 0) return static_cast(nullptr); + return created; + }(); + return library; +} + +int SupportFormat(const char* function, std::initializer_list modules) { + const FT_Library library = ProbeLibrary(); + if (library == nullptr) throw std::runtime_error(std::string(function) + ": FreeType failed to initialize"); + for (const char* module : modules) { + if (FT_Get_Module(library, module) == nullptr) throw std::runtime_error(std::string(function) + ": FreeType module " + module + " is not built in"); + } + return 0; +} + +} + extern "C" { const Font::SysDriver* APS5_VABI sceFontSelectLibraryFt(int value) { @@ -32,13 +62,11 @@ int APS5_VABI sceFontFtSetAliasPath() { } int APS5_VABI sceFontFtSupportBdf() { - NotImplemented_nid_no_patch(__func__); - return 0; + return SupportFormat(__func__, {"bdf"}); } int APS5_VABI sceFontFtSupportCid() { - NotImplemented_nid_no_patch(__func__); - return 0; + return SupportFormat(__func__, {"t1cid"}); } int APS5_VABI sceFontFtSupportFontFormats() { @@ -47,28 +75,23 @@ int APS5_VABI sceFontFtSupportFontFormats() { } int APS5_VABI sceFontFtSupportOpenType() { - NotImplemented_nid_no_patch(__func__); - return 0; + return SupportFormat(__func__, {"cff", "truetype"}); } int APS5_VABI sceFontFtSupportOpenTypeOtf() { - NotImplemented_nid_no_patch(__func__); - return 0; + return SupportFormat(__func__, {"cff"}); } int APS5_VABI sceFontFtSupportOpenTypeTtf() { - NotImplemented_nid_no_patch(__func__); - return 0; + return SupportFormat(__func__, {"truetype"}); } int APS5_VABI sceFontFtSupportPcf() { - NotImplemented_nid_no_patch(__func__); - return 0; + return SupportFormat(__func__, {"pcf"}); } int APS5_VABI sceFontFtSupportPfr() { - NotImplemented_nid_no_patch(__func__); - return 0; + return SupportFormat(__func__, {"pfr"}); } int APS5_VABI sceFontFtSupportSystemFonts() { @@ -77,8 +100,7 @@ int APS5_VABI sceFontFtSupportSystemFonts() { } int APS5_VABI sceFontFtSupportTrueType() { - NotImplemented_nid_no_patch(__func__); - return 0; + return SupportFormat(__func__, {"truetype"}); } int APS5_VABI sceFontFtSupportTrueTypeGx() { @@ -87,18 +109,15 @@ int APS5_VABI sceFontFtSupportTrueTypeGx() { } int APS5_VABI sceFontFtSupportType1() { - NotImplemented_nid_no_patch(__func__); - return 0; + return SupportFormat(__func__, {"type1"}); } int APS5_VABI sceFontFtSupportType42() { - NotImplemented_nid_no_patch(__func__); - return 0; + return SupportFormat(__func__, {"type42"}); } int APS5_VABI sceFontFtSupportWinFonts() { - NotImplemented_nid_no_patch(__func__); - return 0; + return SupportFormat(__func__, {"winfonts"}); } int APS5_VABI sceFontFtTermAliases() { diff --git a/core/libs/prx/libSceFontGs/Export.cpp b/core/libs/prx/libSceFontGs/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceFontGs/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceFreeTypeOl/Export.cpp b/core/libs/prx/libSceFreeTypeOl/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceFreeTypeOl/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceFreeTypeOptOl/Export.cpp b/core/libs/prx/libSceFreeTypeOptOl/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceFreeTypeOptOl/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceFreeTypeOt/Export.cpp b/core/libs/prx/libSceFreeTypeOt/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceFreeTypeOt/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceFsInternalForVsh/Export.cpp b/core/libs/prx/libSceFsInternalForVsh/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceFsInternalForVsh/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceGLSlimVSH/Export.cpp b/core/libs/prx/libSceGLSlimVSH/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceGLSlimVSH/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceGic/Export.cpp b/core/libs/prx/libSceGic/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceGic/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceGifParser/Export.cpp b/core/libs/prx/libSceGifParser/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceGifParser/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceGnmDriverCompat1/Export.cpp b/core/libs/prx/libSceGnmDriverCompat1/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceGnmDriverCompat1/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceGpuCapture/Export.cpp b/core/libs/prx/libSceGpuCapture/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceGpuCapture/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceGpuTrace/Export.cpp b/core/libs/prx/libSceGpuTrace/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceGpuTrace/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceGvMp4Parser/Export.cpp b/core/libs/prx/libSceGvMp4Parser/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceGvMp4Parser/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceHand/Export.cpp b/core/libs/prx/libSceHand/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceHand/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceHandTracker/Export.cpp b/core/libs/prx/libSceHandTracker/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceHandTracker/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceHdrScopes/Export.cpp b/core/libs/prx/libSceHdrScopes/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceHdrScopes/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceHeadTracker/Export.cpp b/core/libs/prx/libSceHeadTracker/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceHeadTracker/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceHidControl/Export.cpp b/core/libs/prx/libSceHidControl/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceHidControl/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceHmd2/Export.cpp b/core/libs/prx/libSceHmd2/Export.cpp index 9f1c4cb8f9cb..e95cae3110f2 100644 --- a/core/libs/prx/libSceHmd2/Export.cpp +++ b/core/libs/prx/libSceHmd2/Export.cpp @@ -161,4 +161,164 @@ int APS5_VABI sceHmd2SetVibration() { return 0; } +int APS5_VABI sceHmd2GazeGetCalibrationData() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeGetCalibrationDataMaxSize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2ReprojectionClearUserEventEnd() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2ReprojectionClearUserEventStart() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2ReprojectionGetCurrentOutput() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2ReprojectionSetUserEventEnd() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2ReprojectionSetUserEventStart() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeActivateGazeTracking() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeGetDeviceStatus() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeGetUserCalibrationStatus() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeInsertRawDataWithHandle() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeSetEnabledEye() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeSetUserCalibrationData() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeSetUserCalibrationPoint() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeStartUserCalibration() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeStopUserCalibration() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2ReprojectionSeeThroughDraw() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2ReprojectionSeeThroughIC() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2ReprojectionVideoOutEq() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2ReprojectionWaitShader() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2SeeThroughCallbackGetPoseDataFunc() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeObjectSelectionGetSelectedObject() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeObjectSelectionInitialize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeObjectSelectionQueryMemory() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2GazeObjectSelectionTerminate() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2ReprojectionClear() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2ReprojectionSet() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2ReprojectionSetEventGpuEnd() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2ReprojectionSetEventGpuStart() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2RpeorjectionSetParam() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2SetFieldOfView() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmd2SetIpdOffset() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + } diff --git a/core/libs/prx/libSceHmdSetupDialog/Export.cpp b/core/libs/prx/libSceHmdSetupDialog/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceHmdSetupDialog/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceHttp/Export.cpp b/core/libs/prx/libSceHttp/Export.cpp index 86f20a8700d8..a1de4ee7467a 100644 --- a/core/libs/prx/libSceHttp/Export.cpp +++ b/core/libs/prx/libSceHttp/Export.cpp @@ -131,6 +131,20 @@ int APS5_VABI sceHttpAddRequestHeader(int id, const char* name, const char* valu return 0; } +int APS5_VABI sceHttpAuthCacheFlush(int httpCtxId) { + std::lock_guard lock(g_stateMutex); + if (g_contexts.empty()) return ERROR_BEFORE_INIT; + if (!g_contexts.contains(httpCtxId)) return ERROR_INVALID_ID; + return 0; +} + +int APS5_VABI sceHttpCookieFlush(int httpCtxId) { + std::lock_guard lock(g_stateMutex); + if (g_contexts.empty()) return ERROR_BEFORE_INIT; + if (!g_contexts.contains(httpCtxId)) return ERROR_INVALID_ID; + return 0; +} + int APS5_VABI sceHttpCreateConnection(int tmpl_id, const char* server_name, const char* scheme, uint16_t port, int enable_keep_alive) { (void)tmpl_id; (void)server_name; diff --git a/core/libs/prx/libSceHttpCache/Export.cpp b/core/libs/prx/libSceHttpCache/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceHttpCache/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceHubAppUtil/Export.cpp b/core/libs/prx/libSceHubAppUtil/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceHubAppUtil/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceIcu/Export.cpp b/core/libs/prx/libSceIcu/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceIcu/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceIdu/Export.cpp b/core/libs/prx/libSceIdu/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceIdu/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceImageUtil/Export.cpp b/core/libs/prx/libSceImageUtil/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceImageUtil/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceIpmi/Export.cpp b/core/libs/prx/libSceIpmi/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceIpmi/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceJemspace/Export.cpp b/core/libs/prx/libSceJemspace/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceJemspace/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceJitBridge/Export.cpp b/core/libs/prx/libSceJitBridge/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceJitBridge/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceJpegParser/Export.cpp b/core/libs/prx/libSceJpegParser/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceJpegParser/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceJsc/Export.cpp b/core/libs/prx/libSceJsc/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceJsc/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceJscCompiler/Export.cpp b/core/libs/prx/libSceJscCompiler/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceJscCompiler/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceJson/Export.cpp b/core/libs/prx/libSceJson/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceJson/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceJson2/Export.cpp b/core/libs/prx/libSceJson2/Export.cpp index 3b8838bd7523..72547068834c 100644 --- a/core/libs/prx/libSceJson2/Export.cpp +++ b/core/libs/prx/libSceJson2/Export.cpp @@ -47,6 +47,19 @@ static_assert(sizeof(Value) == 8 && sizeof(String) == 8 && sizeof(Array) == 8 && static_assert(sizeof(ArrayIterator) == 8 && sizeof(ObjectIterator) == 8); static_assert(offsetof(Pair, value) == 0x10); +using TypeMismatchHandler = const Value& (APS5_VABI*)(ValueType, const Value*, const Value*, void*); +using SpecialFloatHandler = Value* (APS5_VABI*)(Value*, std::int32_t, double, const Value*, void*, bool*); +using ElementAccessFailureHandler = void (APS5_VABI*)(int, const String*, const std::size_t*, const Value*, const Value*, void*); + +struct Handlers { + TypeMismatchHandler typeMismatch = nullptr; + void* typeMismatchContext = nullptr; + SpecialFloatHandler specialFloat = nullptr; + void* specialFloatContext = nullptr; + ElementAccessFailureHandler elementAccessFailure = nullptr; + void* elementAccessFailureContext = nullptr; +}; + struct Node { ValueType type = TypeNull; bool boolean = false; @@ -56,6 +69,7 @@ struct Node { String string{}; Array array{}; Object object{}; + Handlers handlers{}; }; using NullAccessCallback = const Value& (APS5_VABI*)(ValueType, const Value*, void*); @@ -98,7 +112,9 @@ void Clear(Node& n) { if (n.type == TypeString) Destroy(n.string); if (n.type == TypeArray) Destroy(n.array); if (n.type == TypeObject) Destroy(n.object); + const Handlers handlers = n.handlers; n = Node{}; + n.handlers = handlers; } void Destroy(Value& v) { @@ -174,6 +190,7 @@ void AssignNode(Node& destination, const Node& source) { throw; } Clear(destination); + copy.handlers = destination.handlers; destination = copy; } @@ -218,12 +235,35 @@ const Value& NullAccess(ValueType requested, const Value* parent) { return Defaults(requested); } +const Value& TypeMismatch(ValueType requested, const Value* self) { + const Handlers& handlers = NodeOf(*self).handlers; + const Value& result = handlers.typeMismatch(requested, self, nullptr, handlers.typeMismatchContext); + if (result.node && result.node->type == requested) return result; + return Defaults(requested); +} + const Node& Typed(const Value* self, ValueType requested) { const Node& n = NodeOf(*self); if (n.type == requested) return n; + if (n.type != TypeNull && n.handlers.typeMismatch) return NodeOf(TypeMismatch(requested, self)); return NodeOf(NullAccess(requested, self)); } +const Value& MissingElement(const Value* self) { + if (NodeOf(*self).handlers.elementAccessFailure) + throw std::runtime_error("sce::Json::Value: element access failure handler: the error code the console passes is unverified"); + return NullAccess(TypeNull, self); +} + +template +void SetHandlers(Node& n, const TSetter& setter) { + setter(n.handlers); + if (n.type == TypeArray) + for (auto& item : *n.array.items) SetHandlers(NodeOf(item), setter); + if (n.type == TypeObject) + for (auto& pair : *n.object.items) SetHandlers(NodeOf(pair.value), setter); +} + void AppendEscaped(std::string& out, const std::string& text) { out += '"'; for (const unsigned char c : text) { @@ -260,7 +300,11 @@ void Serialize(std::string& out, const Node& n) { case TypeBoolean: out += n.boolean ? "true" : "false"; return; case TypeInteger: out += std::to_string(n.integer); return; case TypeUInteger: out += std::to_string(n.uinteger); return; - case TypeReal: out += RealText(n.real); return; + case TypeReal: + if (!std::isfinite(n.real) && n.handlers.specialFloat) + throw std::runtime_error("sce::Json::Value: special float handler: the FunctionType the console passes is unverified"); + out += RealText(n.real); + return; case TypeString: AppendEscaped(out, *n.string.text); return; case TypeArray: { out += '['; @@ -292,7 +336,7 @@ void Serialize(std::string& out, const Node& n) { class Parser { public: - Parser(const char* text, std::size_t size) : _text(text), _end(text + size) {} + Parser(const char* text, std::size_t size, const Handlers& handlers) : _text(text), _end(text + size), _handlers(handlers) {} bool Parse(Node& out) { skip(); @@ -306,6 +350,7 @@ class Parser { const char* _text; const char* _end; + Handlers _handlers; std::size_t _depth = 0; void skip() { while (_text < _end && (*_text == ' ' || *_text == '\t' || *_text == '\n' || *_text == '\r')) ++_text; } @@ -441,6 +486,7 @@ class Parser { for (;;) { Value item{}; Construct(item); + item.node->handlers = _handlers; out.array.items->push_back(item); skip(); if (!value(*item.node)) return false; @@ -468,6 +514,7 @@ class Parser { Pair pair{}; Construct(pair.key, key); Construct(pair.value); + pair.value.node->handlers = _handlers; out.object.items->push_back(pair); if (!value(*pair.value.node)) return false; skip(); @@ -778,7 +825,7 @@ const Value* APS5_VABI _ZNK3sce4Json5ValueixEPKc(const Value* self, const char* if (key != nullptr && n.type == TypeObject) for (const auto& pair : *n.object.items) if (*pair.key.text == key) return &pair.value; - return &NullAccess(TypeNull, self); + return &MissingElement(self); } const Value* APS5_VABI _ZNK3sce4Json5ValueixEm(const Value* self, std::size_t index) { const Node& n = NodeOf(*self); @@ -787,7 +834,7 @@ const Value* APS5_VABI _ZNK3sce4Json5ValueixEm(const Value* self, std::size_t in std::advance(it, static_cast(index)); return &*it; } - return &NullAccess(TypeNull, self); + return &MissingElement(self); } void APS5_VABI _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(Value* self, ValueType type) { Construct(*self, [type](Node& node) { SetType(node, type); }); @@ -798,7 +845,7 @@ const Value* APS5_VABI _ZNK3sce4Json5Value8getValueERKNS0_6StringE(const Value* if (key != nullptr && n.type == TypeObject) for (const auto& pair : *n.object.items) if (*pair.key.text == *key->text) return &pair.value; - return &NullAccess(TypeNull, self); + return &MissingElement(self); } Value* APS5_VABI _ZN3sce4Json5Value10referValueEm(Value* self, std::size_t index) { Node& n = NodeOf(*self); @@ -859,7 +906,8 @@ int APS5_VABI _ZN3sce4Json6Parser5parseERNS0_5ValueEPFiRcPvES5_(Value* out, Pars int APS5_VABI _ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(Value* out, const char* text, std::size_t size) { if (text == nullptr || size == 0) return JsonErrorParse; Node parsed{}; - Parser parser(text, size); + parsed.handlers = NodeOf(*out).handlers; + Parser parser(text, size, parsed.handlers); bool parsedOk = false; try { parsedOk = parser.Parse(parsed); @@ -949,18 +997,27 @@ void APS5_VABI _ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEP state.context = context; } -int APS5_VABI _ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(Value* self, ElementAccessFailureHandler handler, void* context) { + SetHandlers(NodeOf(*self), [&](Handlers& handlers) { + handlers.elementAccessFailure = handler; + handlers.elementAccessFailureContext = context; + }); return 0; } -int APS5_VABI _ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(Value* self, SpecialFloatHandler handler, void* context) { + SetHandlers(NodeOf(*self), [&](Handlers& handlers) { + handlers.specialFloat = handler; + handlers.specialFloatContext = context; + }); return 0; } -int APS5_VABI _ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(Value* self, TypeMismatchHandler handler, void* context) { + SetHandlers(NodeOf(*self), [&](Handlers& handlers) { + handlers.typeMismatch = handler; + handlers.typeMismatchContext = context; + }); return 0; } diff --git a/core/libs/prx/libSceJxr/Export.cpp b/core/libs/prx/libSceJxr/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceJxr/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceJxrParser/Export.cpp b/core/libs/prx/libSceJxrParser/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceJxrParser/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceKbEmulate/Export.cpp b/core/libs/prx/libSceKbEmulate/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceKbEmulate/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceLibreSsl3/Export.cpp b/core/libs/prx/libSceLibreSsl3/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl3/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceLoginMgrServer/Export.cpp b/core/libs/prx/libSceLoginMgrServer/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceLoginMgrServer/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceMarlin/Export.cpp b/core/libs/prx/libSceMarlin/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceMarlin/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceMat/Export.cpp b/core/libs/prx/libSceMat/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceMat/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceMbus/Export.cpp b/core/libs/prx/libSceMbus/Export.cpp index 9bfe48812efb..2107420e01fe 100644 --- a/core/libs/prx/libSceMbus/Export.cpp +++ b/core/libs/prx/libSceMbus/Export.cpp @@ -34,4 +34,8 @@ std::int32_t APS5_VABI sceDeviceServiceQueryDeviceInfo_(std::int32_t deviceClass return 0; } +std::int32_t APS5_VABI sceDeviceServiceGetGeneration(void) { + NotImplemented_nid_no_patch(__func__); +} + } diff --git a/core/libs/prx/libSceMediaAppSharedStorage/Export.cpp b/core/libs/prx/libSceMediaAppSharedStorage/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceMediaAppSharedStorage/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceMediaFrameworkInterface/Export.cpp b/core/libs/prx/libSceMediaFrameworkInterface/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceMediaFrameworkInterface/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceMediaFrameworkUtil/Export.cpp b/core/libs/prx/libSceMediaFrameworkUtil/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceMediaFrameworkUtil/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceMetadataReaderWriter/Export.cpp b/core/libs/prx/libSceMetadataReaderWriter/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceMetadataReaderWriter/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceMove/Export.cpp b/core/libs/prx/libSceMove/Export.cpp new file mode 100644 index 000000000000..6c2199ff94f2 --- /dev/null +++ b/core/libs/prx/libSceMove/Export.cpp @@ -0,0 +1,33 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceMoveClose(int32_t handle) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceMoveGetExtensionPortInfo(int32_t handle, void* data) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceMoveReadStateLatest(int32_t handle, void* data) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceMoveReadStateRecent(int32_t handle, int64_t timestamp, void* data, int32_t* out_count) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceMoveResetLightSphere(int32_t handle) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceMoveSetLightSphere(int32_t handle, uint8_t red, uint8_t green, uint8_t blue) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libSceMoveTracker/Export.cpp b/core/libs/prx/libSceMoveTracker/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceMoveTracker/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceMusicCoreInterface/Export.cpp b/core/libs/prx/libSceMusicCoreInterface/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceMusicCoreInterface/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceMusicFrameworkInterface/Export.cpp b/core/libs/prx/libSceMusicFrameworkInterface/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceMusicFrameworkInterface/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNKWeb/Export.cpp b/core/libs/prx/libSceNKWeb/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceNKWeb/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNKWebCdlgInjectedBundle/Export.cpp b/core/libs/prx/libSceNKWebCdlgInjectedBundle/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceNKWebCdlgInjectedBundle/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNKWebKit/Export.cpp b/core/libs/prx/libSceNKWebKit/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNKWebKitRequirements/Export.cpp b/core/libs/prx/libSceNKWebKitRequirements/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceNKWebKitRequirements/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index bcacdeceafea..26d1a01e5335 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -37,6 +37,7 @@ #include "prx/libc/include/GuestArena.hpp" #include "prx/libkernel/Socket/include/SocketPoll.hpp" #include "prx/libkernel/Socket/include/SocketRuntime.hpp" +#include "prx/libkernel/Time/include/Time.hpp" // Guest socket descriptors retain PS5 semantics while their transport is backed by host sockets. @@ -890,6 +891,14 @@ int APS5_VABI sceNetAccept(int s, void* addr, uint32_t* addrlen) { std::lock_guard lk(g_mutex); g_socks.emplace(descriptor, connection); } + if (listener.nonblock) { + const int enabled = 1; + if (sceNetSetsockopt(descriptor, NET_SOL_SOCKET, NET_SO_NBIO, &enabled, sizeof(enabled)) != 0) { + const int error = *errno_slot(); + sceNetSocketClose(descriptor); + return fail(error); + } + } if (addr && !native_to_guest_address(peer, addr, addrlen)) { sceNetSocketClose(descriptor); return fail(NET_EAFNOSUPPORT); @@ -1745,4 +1754,854 @@ int APS5_VABI sceNetShowIfconfig() { extern const std::uint8_t in6addr_any_nid_postfix[16] = {}; extern const std::uint8_t in6addr_loopback_nid_postfix[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; +int APS5_VABI sceNetClearDnsCache(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetStatisticsInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetInterfaceStats(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetSetDnsInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetShowIfconfigWithMemory(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetShowPolicy(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetShowPolicyWithMemory(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetShowRoute(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetShowRouteWithMemory(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetResolverStartAton6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetResolverConnectAbort(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetResolverConnectCreate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetResolverConnect(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetResolverConnectDestroy(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetSockInfo6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetSetDns6Info(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetShowRoute6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetShowRoute6WithMemory(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetAllocateAllRouteInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetAllocateAllRouteInfo6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetControl(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDbgInit(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetEventCallbackCreate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetEventCallbackDestroy(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetEventCallbackGetError(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetEventCallbackWaitCB(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetFreeAllRouteInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetArpInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetAppMemInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetDnsInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetIfList(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetIfListEx(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetIfListOnce(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetIfName(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetIfnameNumList(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetNameToIndex(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetRandom(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetRouteInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetsockoptEx(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetSystemTime(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetSystemMemInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetInitParam(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetInetPtonEx(int af, const char* src, void* dst, int flags) { + if (flags != 0) throw std::runtime_error("sceNetInetPtonEx: flags " + std::to_string(flags) + " are not supported"); + return sceNetInetPton(af, src, dst); +} + +int APS5_VABI sceNetIoctl(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetMemoryAllocate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetMemoryFree(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetSetDnsInfoToKernel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetSetsockoptEx(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetSocketInternal(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetSyncCreate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetSyncDestroy(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetSyncGet(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetSyncSignal(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetSyncWait(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetSysctl(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetThreadCreate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetThreadExit(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetThreadJoin(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetUsleep(int microseconds) { + if (microseconds < 0) return fail(NET_EINVAL); + return sceKernelUsleep_nid_postfix(static_cast(microseconds)); +} + +int APS5_VABI sceNetShowIfconfigForBuffer(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetShowRouteForBuffer(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetShowNetstatForBuffer(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetShowNetstatEx(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetShowNetstatExForBuffer(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetInetNtopWithScopeId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetInetPtonWithScopeId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetDns6Info(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetSetDns6InfoToKernel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetShowRoute6ForBuffer(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigGetIfFlags(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigSetIfFlags(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigUnsetIfFlags(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigDelIfaddr(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigDownInterface(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigSetDefaultRoute(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigSetDefaultRouteDev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigSetIfaddr(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigSetIfLinkLocalAddr6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigUpInterface(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigUpInterfaceWithFlags(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigCleanUpAllInterfaces(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigFlushRoute(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigFlushRoute6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigAddIfaddr(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigGetIfaddr(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigSetIfmtu(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigAddArp(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigAddArpWithInterface(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigAddMRoute(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigAddRoute(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigAddRouteWithInterface(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigDelArp(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigDelArpWithInterface(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigDelDefaultRoute(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigDelDefaultRouteDev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigDelMRoute(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigDelRoute(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigGetDefaultRoute(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDhcpGetAutoipInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDhcpGetInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDhcpGetInfoEx(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDhcpStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDhcpStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigAddIfaddr6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigDelIfaddr6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigDelDefaultRoute6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigSetIfaddr6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigSetIfaddr6WithFlags(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigSetDefaultRoute6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigGetIfaddr6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigGetDefaultRoute6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigDelRoute6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigAddRoute6(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetAddrConfig6Start(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetAddrConfig6Stop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetAddrConfig6GetInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetInfoDumpStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetInfoDumpStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDhcpdStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDhcpdStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDuplicateIpStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDuplicateIpStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanAdhocClearWakeOnWlan(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanAdhocCreate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanAdhocGetWakeOnWlanInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanAdhocJoin(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanAdhocLeave(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanAdhocPspEmuClearWakeOnWlan(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanAdhocPspEmuGetWakeOnWlanInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanAdhocPspEmuSetWakeOnWlan(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanAdhocScanJoin(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanAdhocSetExtInfoElement(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanAdhocSetWakeOnWlan(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanApStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanApStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanBackgroundScanQuery(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanBackgroundScanStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanBackgroundScanStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanDiagGetDeviceInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanDiagSetAntenna(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanDiagSetTxFixedRate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanGetDeviceConfig(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanInfraGetRssiInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanInfraLeave(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanInfraScanJoin(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanScan(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigWlanSetDeviceConfig(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigEtherGetLinkMode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigEtherPostPlugInOutEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigEtherSetLinkMode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetPppoeStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetPppoeStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetBandwidthControlSetDefaultParam(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetBandwidthControlGetDefaultParam(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetBandwidthControlGetDataTraffic(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigSetDefaultScope(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigRoutingStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigRoutingStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigRoutingShowRoutingConfig(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetConfigRoutingShowtCtlVar(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetGetStatisticsInfoInternal(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDumpAbort(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDumpCreate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDumpSetFilterParam(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDumpDestroy(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetDumpRead(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetEmulationDebugSettingsSet(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetEmulationDebugSettingsGet(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetEmulationSet(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetEmulationGet(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + } diff --git a/core/libs/prx/libSceNetCtl/Export.cpp b/core/libs/prx/libSceNetCtl/Export.cpp index a7a5998b4279..8779ff212727 100644 --- a/core/libs/prx/libSceNetCtl/Export.cpp +++ b/core/libs/prx/libSceNetCtl/Export.cpp @@ -15,6 +15,9 @@ #ifdef __linux__ #include #endif +#ifdef __APPLE__ +#include +#endif #endif #include #include @@ -170,6 +173,56 @@ struct HostDefaultRoute { unsigned long metric = 0; }; +#ifdef __APPLE__ +struct NetCtlDefaultRoute { + char name[IF_NAMESIZE]; + in_addr gateway; + std::uint32_t metric; +}; + +extern "C" std::size_t NetCtlDefaultRoutes_nid_no_patch(const char* table, std::size_t size, NetCtlDefaultRoute* routes, std::size_t capacity) { + std::size_t count = 0; + for (std::size_t offset = 0; offset + sizeof(rt_msghdr) <= size;) { + const auto* header = reinterpret_cast(table + offset); + if (header->rtm_msglen < sizeof(rt_msghdr) || header->rtm_msglen > size - offset) break; + const sockaddr* addresses[RTAX_MAX] = {}; + const std::size_t end = offset + header->rtm_msglen; + std::size_t field = offset + sizeof(rt_msghdr); + for (int index = 0; index < RTAX_MAX && field + sizeof(sockaddr) <= end; ++index) { + if (!(header->rtm_addrs & (1 << index))) continue; + const auto* address = reinterpret_cast(table + field); + addresses[index] = address; + field += address->sa_len ? (address->sa_len + sizeof(std::uint32_t) - 1) & ~(sizeof(std::uint32_t) - 1) : sizeof(std::uint32_t); + } + offset = end; + const auto* destination = addresses[RTAX_DST]; + const auto* gateway = addresses[RTAX_GATEWAY]; + if (!destination || destination->sa_family != AF_INET || !gateway || gateway->sa_family != AF_INET) continue; + if (reinterpret_cast(destination)->sin_addr.s_addr != 0) continue; + if (!(header->rtm_flags & RTF_UP) || (header->rtm_flags & (RTF_REJECT | RTF_HOST))) continue; + NetCtlDefaultRoute route{}; + if (!if_indextoname(header->rtm_index, route.name)) continue; + route.gateway = reinterpret_cast(gateway)->sin_addr; + route.metric = (header->rtm_flags & RTF_IFSCOPE) ? 1 : 0; + if (count < capacity) routes[count] = route; + ++count; + } + return count; +} + +static std::vector HostDefaultRoutes() { + int name[] = {CTL_NET, PF_ROUTE, 0, AF_INET, NET_RT_FLAGS, RTF_GATEWAY}; + std::size_t size = 0; + if (sysctl(name, 6, nullptr, &size, nullptr, 0) != 0) throw std::runtime_error("sceNetCtlGetInfo: the host routing table is unavailable"); + std::vector table(size); + if (sysctl(name, 6, table.data(), &size, nullptr, 0) != 0) throw std::runtime_error("sceNetCtlGetInfo: the host routing table is unavailable"); + std::vector found(NetCtlDefaultRoutes_nid_no_patch(table.data(), size, nullptr, 0)); + found.resize(NetCtlDefaultRoutes_nid_no_patch(table.data(), size, found.data(), found.size())); + std::vector routes; + for (const auto& entry : found) routes.push_back({entry.name, entry.gateway, entry.metric}); + return routes; +} +#else static std::vector HostDefaultRoutes() { std::ifstream table("/proc/net/route"); if (!table) throw std::runtime_error("sceNetCtlGetInfo: the host routing table is unavailable"); @@ -192,6 +245,7 @@ static std::vector HostDefaultRoutes() { } return routes; } +#endif static std::uint32_t HostInterfaceMtu(const char* name) { const int descriptor = socket(AF_INET, SOCK_DGRAM, 0); diff --git a/core/libs/prx/libSceNetCtlAp/Export.cpp b/core/libs/prx/libSceNetCtlAp/Export.cpp new file mode 100644 index 000000000000..8791105967b4 --- /dev/null +++ b/core/libs/prx/libSceNetCtlAp/Export.cpp @@ -0,0 +1,72 @@ +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceNetCtlApInit() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +void APS5_VABI sceNetCtlApTerm() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceNetCtlApCheckCallback() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetCtlApClearEvent(int eventType) { + (void)eventType; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetCtlApRegisterCallback(void (*func)(int, void*), void* arg, int* cid) { + (void)func; + (void)arg; + (void)cid; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetCtlApUnregisterCallback(int cid) { + (void)cid; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetCtlApGetResult(int eventType, int* errorCode) { + (void)eventType; + (void)errorCode; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetCtlApGetInfo(void* info) { + (void)info; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetCtlApGetState(int* state) { + (void)state; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetCtlApStop() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNetCtlApGetConnectInfo(std::uint8_t* optStr, void* connectInfo) { + (void)optStr; + (void)connectInfo; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libSceNgs2.native/CMakeLists.txt b/core/libs/prx/libSceNgs2.native/CMakeLists.txt index ca10bd5243b9..4737448b07bd 100644 --- a/core/libs/prx/libSceNgs2.native/CMakeLists.txt +++ b/core/libs/prx/libSceNgs2.native/CMakeLists.txt @@ -2,7 +2,7 @@ include(${CMAKE_CURRENT_SOURCE_DIR}/Ngs2Library.cmake) add_sce_ngs2_library(libSceNgs2.native) if(BUILD_TESTING) - foreach(ngs2Test IN ITEMS Render Atrac9 Filter Custom Parse Stream UserFx Report Geom) + foreach(ngs2Test IN ITEMS Render Atrac9 Filter Custom Parse Stream UserFx Report Geom VoiceHandles) string(TOLOWER ${ngs2Test} testName) add_test_executable(ngs2_${testName}_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Ngs2${ngs2Test}.cpp) target_include_directories(ngs2_${testName}_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/3rdparty/LibAtrac9/C/src) diff --git a/core/libs/prx/libSceNgs2.native/include/Ngs2Types.hpp b/core/libs/prx/libSceNgs2.native/include/Ngs2Types.hpp index 19e7cd543de3..a76406244b2b 100644 --- a/core/libs/prx/libSceNgs2.native/include/Ngs2Types.hpp +++ b/core/libs/prx/libSceNgs2.native/include/Ngs2Types.hpp @@ -14,6 +14,8 @@ static constexpr int SCE_NGS2_ERROR_INVALID_RACK_HANDLE = static_cast(0x804 static constexpr int SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA = static_cast(0x804A8430); static constexpr int SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT = static_cast(0x804A8431); static constexpr int SCE_NGS2_ERROR_UNKNOWN_WAVEFORM_FORMAT = static_cast(0x804A8432); +static constexpr int SCE_NGS2_ERROR_INVALID_VOICE_HANDLE = static_cast(0x804A8203); +static constexpr int SCE_NGS2_ERROR_NULL_PARAM_LIST = static_cast(0x804A8360); static constexpr std::uint32_t SCE_NGS2_RACK_ID_SAMPLER = 0x1000; static constexpr std::uint32_t SCE_NGS2_RACK_ID_SUBMIXER = 0x2000; diff --git a/core/libs/prx/libSceNgs2.native/src/Voice.cpp b/core/libs/prx/libSceNgs2.native/src/Voice.cpp index 4eab58bb6e22..7aa4ea0ef6a1 100644 --- a/core/libs/prx/libSceNgs2.native/src/Voice.cpp +++ b/core/libs/prx/libSceNgs2.native/src/Voice.cpp @@ -390,20 +390,16 @@ static void ApplyParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param) { throw std::runtime_error("NGS2: voice param " + Ngs2Hex(param.id) + " is not implemented"); } -static Ngs2Voice& CheckedVoice(Ngs2Handle handle) { - auto* voice = Ngs2FindVoice(handle); - if (voice == nullptr) throw std::invalid_argument("NGS2: invalid voice handle"); - return *voice; -} - #pragma GCC visibility push(default) extern "C" { int APS5_VABI sceNgs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamHeader* param_list) { + if (param_list == nullptr) return SCE_NGS2_ERROR_NULL_PARAM_LIST; std::lock_guard lock(Ngs2Mutex()); - auto& voice = CheckedVoice(voice_handle); - if (param_list == nullptr) APS5_INVALID_ARG_EX; + auto* found = Ngs2FindVoice(voice_handle); + if (found == nullptr) return SCE_NGS2_ERROR_INVALID_VOICE_HANDLE; + auto& voice = *found; for (const auto* param = param_list;; param = reinterpret_cast(reinterpret_cast(param) + param->next)) { ApplyParam(voice, *param); if (param->next == 0) break; @@ -413,7 +409,9 @@ int APS5_VABI sceNgs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamHe int APS5_VABI sceNgs2VoiceRunCommands(uintptr_t voice_handle, const Ngs2VoiceCommand* commands, size_t num_commands) { std::lock_guard lock(Ngs2Mutex()); - auto& voice = CheckedVoice(voice_handle); + auto* found = Ngs2FindVoice(voice_handle); + if (found == nullptr) return SCE_NGS2_ERROR_INVALID_VOICE_HANDLE; + auto& voice = *found; if (commands == nullptr && num_commands != 0) APS5_INVALID_ARG_EX; for (std::size_t i = 0; i < num_commands; i++) { const auto& command = commands[i]; @@ -434,9 +432,12 @@ int APS5_VABI sceNgs2VoiceRunCommands(uintptr_t voice_handle, const Ngs2VoiceCom } int APS5_VABI sceNgs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* state, size_t state_size) { - if (state == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; std::lock_guard lock(Ngs2Mutex()); - const auto& voice = CheckedVoice(voice_handle); + auto* found = Ngs2FindVoice(voice_handle); + if (found == nullptr) return SCE_NGS2_ERROR_INVALID_VOICE_HANDLE; + if (state_size < sizeof(Ngs2VoiceState)) return SCE_NGS2_ERROR_INVALID_OUT_SIZE; + if (state == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; + const auto& voice = *found; switch (voice.rack->rackId) { case SCE_NGS2_RACK_ID_SAMPLER: { if (state_size != sizeof(Ngs2SamplerVoiceState)) return SCE_NGS2_ERROR_INVALID_OUT_SIZE; @@ -479,7 +480,9 @@ int APS5_VABI sceNgs2VoiceGetPortInfo(uintptr_t voice_handle, uint32_t port, Ngs if (info == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; if (info_size != sizeof(Ngs2VoicePortInfo)) return SCE_NGS2_ERROR_INVALID_OUT_SIZE; std::lock_guard lock(Ngs2Mutex()); - auto& voice = CheckedVoice(voice_handle); + auto* found = Ngs2FindVoice(voice_handle); + if (found == nullptr) return SCE_NGS2_ERROR_INVALID_VOICE_HANDLE; + auto& voice = *found; const auto& source = PortAt(voice, port); *info = {}; info->matrix_id = source.matrix; @@ -491,7 +494,9 @@ int APS5_VABI sceNgs2VoiceGetPortInfo(uintptr_t voice_handle, uint32_t port, Ngs int APS5_VABI sceNgs2VoiceQueryInfo(uintptr_t voice_handle, uint32_t info_id, void* info, size_t info_size) { if (info == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; std::lock_guard lock(Ngs2Mutex()); - const auto& voice = CheckedVoice(voice_handle); + auto* found = Ngs2FindVoice(voice_handle); + if (found == nullptr) return SCE_NGS2_ERROR_INVALID_VOICE_HANDLE; + const auto& voice = *found; switch (info_id) { case SCE_NGS2_VOICE_INFO_CHANNELS: { if (info_size != sizeof(Ngs2VoiceChannelsInfo)) return SCE_NGS2_ERROR_INVALID_OUT_SIZE; @@ -505,7 +510,9 @@ int APS5_VABI sceNgs2VoiceQueryInfo(uintptr_t voice_handle, uint32_t info_id, vo int APS5_VABI sceNgs2VoiceGetStateFlags(uintptr_t voice_handle, uint32_t* state_flags) { if (state_flags == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; std::lock_guard lock(Ngs2Mutex()); - *state_flags = CheckedVoice(voice_handle).stateFlags; + const auto* found = Ngs2FindVoice(voice_handle); + if (found == nullptr) return SCE_NGS2_ERROR_INVALID_VOICE_HANDLE; + *state_flags = found->stateFlags; return SCE_NGS2_OK; } diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2VoiceHandles.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2VoiceHandles.cpp new file mode 100644 index 000000000000..5937b3f76362 --- /dev/null +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2VoiceHandles.cpp @@ -0,0 +1,68 @@ +#include "Ngs2Test.hpp" + +#include +#include +#include +#include +#include +#include +#include + +static constexpr std::array Names{"control", "commands", "state", "port", "query", "flags"}; +static constexpr int InvalidVoice = static_cast(0x804A8203u); +static constexpr int NullList = static_cast(0x804A8360u); + +static void Status(int actual, int expected, const std::string& message) { + if (actual != expected) throw std::runtime_error(message); +} + +static void Invalid(std::string_view api, uintptr_t handle) { + alignas(16) std::array output; + output.fill(0xa5); + const Ngs2VoiceEventParam event{{sizeof(Ngs2VoiceEventParam), 0, SCE_NGS2_VOICE_PARAM_EVENT}, SCE_NGS2_VOICE_EVENT_STOP_IMM}; + const Ngs2VoiceCommand command{2, 0, 4, 0, {.u = SCE_NGS2_VOICE_EVENT_STOP_IMM}}; + int result = 0; + if (api == "control") result = sceNgs2VoiceControl(handle, &event.header); + else if (api == "commands") result = sceNgs2VoiceRunCommands(handle, &command, 1); + else if (api == "state") result = sceNgs2VoiceGetState(handle, reinterpret_cast(output.data()), sizeof(Ngs2SamplerVoiceState)); + else if (api == "port") result = sceNgs2VoiceGetPortInfo(handle, 0, reinterpret_cast(output.data()), sizeof(Ngs2VoicePortInfo)); + else if (api == "query") result = sceNgs2VoiceQueryInfo(handle, SCE_NGS2_VOICE_INFO_CHANNELS, output.data(), sizeof(Ngs2VoiceChannelsInfo)); + else if (api == "flags") result = sceNgs2VoiceGetStateFlags(handle, reinterpret_cast(output.data())); + else throw std::runtime_error("unknown voice API test selection"); + Status(result, InvalidVoice, std::string(api) + " returned the wrong invalid-voice error"); + Require(std::all_of(output.begin(), output.end(), [](auto byte) { return byte == 0xa5; })); +} + +int main(int argc, char** argv) { + try { + if (argc > 2 || (argc == 2 && std::find(Names.begin(), Names.end(), std::string_view(argv[1])) == Names.end())) throw std::runtime_error("unknown voice API test selection"); + const auto selected = [&](auto name) { return argc == 1 || std::string_view(argv[1]) == name; }; + for (auto name : Names) if (selected(name)) { Invalid(name, 0); Invalid(name, 0x1234); } + Status(sceNgs2VoiceControl(0x1234, nullptr), NullList, "Control parameter-list precedence differs from PS5"); + Status(sceNgs2VoiceGetState(0x1234, nullptr, 0), InvalidVoice, "GetState checked output before the voice"); + const auto system = CreateSystem(); + const auto rack = CreateRack(system, SCE_NGS2_RACK_ID_SAMPLER); + const auto voice = Voice(rack); + for (auto name : Names) if (selected(name)) { Invalid(name, system); Invalid(name, rack); } + Status(sceNgs2VoiceControl(voice, nullptr), NullList, "live voice lost the null parameter-list error"); + Status(sceNgs2VoiceGetState(voice, nullptr, 0), SCE_NGS2_ERROR_INVALID_OUT_SIZE, "GetState checked null output before its minimum size"); + Status(sceNgs2VoiceGetState(voice, nullptr, sizeof(Ngs2VoiceState) - 1), SCE_NGS2_ERROR_INVALID_OUT_SIZE, "GetState lost its minimum-size boundary"); + Status(sceNgs2VoiceGetState(voice, nullptr, sizeof(Ngs2VoiceState)), SCE_NGS2_ERROR_INVALID_OUT_ADDRESS, "GetState rejected its minimum-size boundary before null output"); + Status(sceNgs2VoiceGetState(voice, nullptr, sizeof(Ngs2SamplerVoiceState)), SCE_NGS2_ERROR_INVALID_OUT_ADDRESS, "GetState lost null-output validation"); + Status(sceNgs2VoiceGetStateFlags(voice, nullptr), SCE_NGS2_ERROR_INVALID_OUT_ADDRESS, "GetStateFlags lost null-output validation"); + std::uint32_t flags = 0; + Status(sceNgs2VoiceGetStateFlags(voice, &flags), SCE_NGS2_OK, "valid state-flags lookup failed"); + Ngs2SamplerVoiceState state{}; + Status(sceNgs2VoiceGetState(voice, &state.voice_state, sizeof(state)), SCE_NGS2_OK, "valid sampler state lookup failed"); + const Ngs2VoiceCommand stop{2, 0, 4, 0, {.u = SCE_NGS2_VOICE_EVENT_STOP_IMM}}; + Status(sceNgs2VoiceRunCommands(voice, &stop, 1), SCE_NGS2_OK, "valid voice command failed"); + Require(sceNgs2RackDestroy(rack, nullptr) == SCE_NGS2_OK); + for (auto name : Names) if (selected(name)) Invalid(name, voice); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); + std::cout << "NGS2 invalid voice errors, outputs and precedence checked\n"; + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceNotification/Export.cpp b/core/libs/prx/libSceNotification/Export.cpp new file mode 100644 index 000000000000..90362f2991aa --- /dev/null +++ b/core/libs/prx/libSceNotification/Export.cpp @@ -0,0 +1,34 @@ +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceNotificationSend(int type, std::uint8_t flag, std::uint8_t* message) { + (void)type; + (void)flag; + (void)message; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNotificationSendById(int type, std::uint8_t flag, std::uint8_t* id, std::uint8_t* jsonData) { + (void)type; + (void)flag; + (void)id; + (void)jsonData; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNotificationShowPsButtonPersistentBanner(std::uint8_t* jsonParam) { + (void)jsonParam; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +void APS5_VABI sceNotificationHidePsButtonPersistentBanner() { + NotImplemented_nid_no_patch(__func__); +} + +} diff --git a/core/libs/prx/libSceNotificationClient/Export.cpp b/core/libs/prx/libSceNotificationClient/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceNotificationClient/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt b/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt new file mode 100644 index 000000000000..2519e8cc8101 --- /dev/null +++ b/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt @@ -0,0 +1,21 @@ +add_library(libSceNpCppWebApi SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceNpCppWebApi PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceNpCppWebApi) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_np_cpp_web_api_string_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpCppWebApiString.cpp) + target_include_directories(guest_np_cpp_web_api_string_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_cpp_web_api_string_tests PRIVATE libSceNpCppWebApi libc) + configure_windows_unwind(guest_np_cpp_web_api_string_tests) + add_test(NAME guest_np_cpp_web_api_string COMMAND guest_np_cpp_web_api_string_tests) + if(WIN32) + set_tests_properties(guest_np_cpp_web_api_string PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNpCppWebApi/Export.cpp b/core/libs/prx/libSceNpCppWebApi/Export.cpp new file mode 100644 index 000000000000..8bf9b62653e4 --- /dev/null +++ b/core/libs/prx/libSceNpCppWebApi/Export.cpp @@ -0,0 +1,1867 @@ +#include +#include +#include +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +static constexpr int SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN = static_cast(0x80552907); + +// Offline PSN: request entry points (API calls, transaction start/readData, factories) fail with +// SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN, the constant shadPS4/fpPS4 document for this code; parameter +// bookkeeping succeeds; response accessors stay unimplemented because a failed request never +// produces a response. +extern "C" { + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V111UserFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_4UserEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V121GetRankingRequestBody18setStartSerialRankERKi() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V121GetRankingRequestBody8setGroupERKNS3_5GroupE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V121GetRankingRequestBody8setLimitERKi() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V121GetRankingRequestBody8setUsersERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_4UserEEEEE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V121GetRankingRequestBody9setOffsetERKi() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V122GetRankingResponseBody10getEntriesEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V122RecordScoreRequestBody10setCommentEPKc() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V122RecordScoreRequestBody12setSmallDataEPKvm() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V122RecordScoreRequestBody15setNeedsTmpRankERKb() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V122RecordScoreRequestBody19setComparedDateTimeERK10SceRtcTick() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V122RecordScoreRequestBody7setPcIdERKi() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V128GetRankingRequestBodyFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_21GetRankingRequestBodyEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V129RecordScoreRequestBodyFactory6createEPNS1_6Common10LibContextElPNS5_12IntrusivePtrINS3_22RecordScoreRequestBodyEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V14User12setAccountIdERKm() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V14User7setPcIdERKi() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi10getRankingEiRKNS4_21ParameterToGetRankingERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_22GetRankingResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi21ParameterToGetRanking10initializeEPNS1_6Common10LibContextEi() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi21ParameterToGetRanking24setgetRankingRequestBodyENS1_6Common12IntrusivePtrINS3_21GetRankingRequestBodyEEE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi21ParameterToGetRanking9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi21ParameterToGetRankingC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi21ParameterToGetRankingD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi22getLargeDataByObjectIdEiRKNS4_33ParameterToGetLargeDataByObjectIdERNS1_6Common21DownStreamTransactionINS8_12IntrusivePtrINS4_37GetLargeDataByObjectIdResponseHeadersEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi33ParameterToGetLargeDataByObjectId10initializeEPNS1_6Common10LibContextEPKc() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi33ParameterToGetLargeDataByObjectId9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi33ParameterToGetLargeDataByObjectIdC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V17ViewApi33ParameterToGetLargeDataByObjectIdD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19BoardsApi18getBoardDefinitionEiRKNS4_29ParameterToGetBoardDefinitionERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_30GetBoardDefinitionResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19BoardsApi29ParameterToGetBoardDefinition10initializeEPNS1_6Common10LibContextEi() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19BoardsApi29ParameterToGetBoardDefinition9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19BoardsApi29ParameterToGetBoardDefinitionC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19BoardsApi29ParameterToGetBoardDefinitionD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi11recordScoreEiRKNS4_22ParameterToRecordScoreERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_23RecordScoreResponseBodyEEENSA_INS4_26RecordScoreResponseHeadersEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi15recordLargeDataEiRKNS4_26ParameterToRecordLargeDataERNS1_6Common19UpStreamTransactionINS8_12IntrusivePtrINS3_27RecordLargeDataResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi22ParameterToRecordScore10initializeEPNS1_6Common10LibContextEiNS6_12IntrusivePtrINS3_22RecordScoreRequestBodyEEE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi22ParameterToRecordScore22setxPsnAtomicOperationENS5_19XPsnAtomicOperationE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi22ParameterToRecordScore9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi22ParameterToRecordScoreC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi22ParameterToRecordScoreD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi26ParameterToRecordLargeData10initializeEPNS1_6Common10LibContextEiNS5_19XPsnAtomicOperationEPKc() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi26ParameterToRecordLargeData9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi26ParameterToRecordLargeDataC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V19RecordApi26ParameterToRecordLargeDataD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi12downloadDataEiRKNS4_23ParameterToDownloadDataERNS1_6Common21DownStreamTransactionINS8_12IntrusivePtrINS4_27DownloadDataResponseHeadersEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi23ParameterToDownloadData10initializeEPNS1_6Common10LibContextEPKci() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi23ParameterToDownloadData9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi23ParameterToDownloadDataC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi23ParameterToDownloadDataD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common10InitParamsC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common10InitParamsD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common10LibContextC1Ev(uint64_t* self) { + *self = 0; + return 0; +} + +// Initialization succeeds as on an offline console (see libSceJson2); individual requests fail. +int APS5_VABI _ZN3sce2Np9CppWebApi6Common10initializeERKNS2_10InitParamsERNS2_10LibContextE(const void* params, uint64_t* context) { + (void)params; + if (context) *context = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEENS4_INS6_9RecordApi26RecordScoreResponseHeadersEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEENS4_INS6_9RecordApi26RecordScoreResponseHeadersEEEED1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +struct IntrusivePtr { + void* object; + void (APS5_VABI* deleter)(void*); + void* libContext; +}; +static_assert(sizeof(IntrusivePtr) == 0x18); + +static constexpr std::size_t ReferenceCountOffset = 0; +static constexpr std::size_t VectorReferenceCountOffset = 0x30; + +static std::atomic_ref ReferenceCount(void* object, const std::size_t offset) { + return std::atomic_ref(*reinterpret_cast(static_cast(object) + offset)); +} + +static void CopyIntrusivePtr(IntrusivePtr* self, const IntrusivePtr* source, const std::size_t referenceCountOffset) { + *self = *source; + if (self->object) ReferenceCount(self->object, referenceCountOffset).fetch_add(1); +} + +static void ReleaseIntrusivePtr(IntrusivePtr* self, const std::size_t referenceCountOffset, const char* function) { + if (!self->object) return; + if (ReferenceCount(self->object, referenceCountOffset).fetch_sub(1) != 1) return; + if (!self->deleter) NotImplemented_nid_no_patch(function); + self->deleter(self->object); + self->object = nullptr; + self->deleter = nullptr; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, ReferenceCountOffset); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, ReferenceCountOffset); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, VectorReferenceCountOffset, __func__); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_12Leaderboards2V15EntryEEEED2Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEENS4_INS6_9RecordApi26RecordScoreResponseHeadersEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEENS4_INS6_9RecordApi26RecordScoreResponseHeadersEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V127RecordLargeDataResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common19UpStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V127RecordLargeDataResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextEm() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common19UpStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V127RecordLargeDataResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE8sendDataEPKvm() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common19UpStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V127RecordLargeDataResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common19UpStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V127RecordLargeDataResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V17ViewApi37GetLargeDataByObjectIdResponseHeadersEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V17ViewApi37GetLargeDataByObjectIdResponseHeadersEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V17ViewApi37GetLargeDataByObjectIdResponseHeadersEEEE8readDataEPcm() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V17ViewApi37GetLargeDataByObjectIdResponseHeadersEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_12Leaderboards2V17ViewApi37GetLargeDataByObjectIdResponseHeadersEEEED1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V17DataApi27DownloadDataResponseHeadersEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V17DataApi27DownloadDataResponseHeadersEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V17DataApi27DownloadDataResponseHeadersEEEE8readDataEPcm() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V17DataApi27DownloadDataResponseHeadersEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V17DataApi27DownloadDataResponseHeadersEEEED1Ev(void* self) { + (void)self; + return 0; +} + +struct CommonString { + std::int32_t referenceCount; + char* buffer; + std::uint64_t bufferSize; + void* libContext; +}; +static_assert(sizeof(CommonString) == 0x20 && offsetof(CommonString, buffer) == 0x08); +static_assert(offsetof(CommonString, bufferSize) == 0x10 && offsetof(CommonString, libContext) == 0x18); + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(CommonString* self, void* libContext) { + self->referenceCount = 0; + self->buffer = nullptr; + self->bufferSize = 0; + self->libContext = libContext; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringD1Ev(CommonString* self) { + std::free(self->buffer); + self->buffer = nullptr; + self->bufferSize = 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_12Leaderboards2V14UserEEEE8pushBackERKS8_() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_12Leaderboards2V14UserEEEEC1EPNS2_10LibContextE(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_12Leaderboards2V14UserEEEED1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_12Leaderboards2V15EntryEEEE3endEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_12Leaderboards2V15EntryEEEE5beginEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_12Leaderboards2V15EntryEEEEppEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common9terminateERNS2_10LibContextE(uint64_t* context) { + *context = 0; + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V122GetRankingResponseBody22getLastUpdatedDateTimeEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V123RecordScoreResponseBody10getTmpRankEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V123RecordScoreResponseBody16getTmpSerialRankEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody11getSortModeEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody13getEntryLimitEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody13getUpdateModeEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody13sortModeIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody15entryLimitIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody15updateModeIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody16getMaxScoreLimitEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody16getMinScoreLimitEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody18maxScoreLimitIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody18minScoreLimitIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody20getLargeDataNumLimitEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody21getLargeDataSizeLimitEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody22largeDataNumLimitIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V130GetBoardDefinitionResponseBody23largeDataSizeLimitIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry10getCommentEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry11getObjectIdEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry11getOnlineIdEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry12commentIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry12getAccountIdEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry12getSmallDataEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry13getSerialRankEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry13objectIdIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry14getHighestRankEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry14smallDataIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry20getHighestSerialRankEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry7getPcIdEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry7getRankEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V15Entry8getScoreEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi12Leaderboards2V19RecordApi26RecordScoreResponseHeaders24getXPsnAtomicOperationIdEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEENS4_INS6_9RecordApi26RecordScoreResponseHeadersEEEE11getResponseERS8_() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V15EntryEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6BinaryEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEEdeEv(const IntrusivePtr* self) { + return self->object; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEENS4_INS6_9RecordApi26RecordScoreResponseHeadersEEEE18getResponseHeadersERSB_() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6Binary4sizeEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +const char* APS5_VABI _ZNK3sce2Np9CppWebApi6Common6String5c_strEv(const CommonString* self) { + return self->buffer != nullptr ? self->buffer : ""; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_12Leaderboards2V15EntryEEEEdeEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_12Leaderboards2V15EntryEEEEneERKS9_() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +// Shared vector bodies. A Vector handle is an IntrusivePtr whose object is a body; +// bodies adopted from a title through the pointer/deleter constructor keep their +// 32-bit reference count at 0x30, so bodies created here do the same. +static constexpr std::uint64_t VectorBodyMagic = 0x434e507056656342ULL; +struct VectorBody { + std::uint64_t magic; + std::uint8_t reserved[0x28]; + std::int32_t referenceCount; + std::int32_t elementKind; // 1 = IntrusivePtr elements, 2 = String elements + void* elements; + std::uint64_t size; + std::uint64_t capacity; +}; +static_assert(sizeof(VectorBody) == 0x50 && offsetof(VectorBody, referenceCount) == 0x30); + +static void APS5_VABI DestroyVectorBody(void* object) { + auto* body = static_cast(object); + if (body->elementKind == 1) { + auto* elements = static_cast(body->elements); + for (std::uint64_t index = 0; index < body->size; ++index) { + ReleaseIntrusivePtr(&elements[index], 0x00, __func__); + } + } else if (body->elementKind == 2) { + auto* elements = static_cast(body->elements); + for (std::uint64_t index = 0; index < body->size; ++index) { + std::free(elements[index].buffer); + } + } + std::free(body->elements); + std::free(body); +} + +static VectorBody* VectorBodyFor(IntrusivePtr* self, const std::int32_t elementKind, const char* function) { + auto* body = static_cast(self->object); + if (body == nullptr) { + body = static_cast(std::calloc(1, sizeof(VectorBody))); + if (body == nullptr) return nullptr; + body->magic = VectorBodyMagic; + body->referenceCount = 1; + body->elementKind = elementKind; + self->object = body; + self->deleter = &DestroyVectorBody; + return body; + } + if (body->magic != VectorBodyMagic || body->elementKind != elementKind) { + // appending to a title-provided body needs its element layout, which is unknown + NotImplemented_nid_no_patch(function); + return nullptr; + } + return body; +} + +static bool VectorGrow(VectorBody* body, const std::size_t elementSize) { + if (body->size < body->capacity) return true; + const std::uint64_t capacity = body->capacity != 0 ? body->capacity * 2 : 4; + void* elements = std::realloc(body->elements, static_cast(capacity) * elementSize); + if (elements == nullptr) return false; + body->elements = elements; + body->capacity = capacity; + return true; +} + +static int VectorPushBackIntrusivePtr(IntrusivePtr* self, const IntrusivePtr* element) { + VectorBody* body = VectorBodyFor(self, 1, __func__); + if (body == nullptr) return -1; + if (!VectorGrow(body, sizeof(IntrusivePtr))) return -1; + auto* elements = static_cast(body->elements); + elements[body->size] = *element; + if (element->object != nullptr) ReferenceCount(element->object, ReferenceCountOffset).fetch_add(1); + body->size += 1; + return 0; +} + +static int VectorPushBackString(IntrusivePtr* self, const CommonString* element) { + VectorBody* body = VectorBodyFor(self, 2, __func__); + if (body == nullptr) return -1; + if (!VectorGrow(body, sizeof(CommonString))) return -1; + CommonString copy = *element; + copy.referenceCount = 0; + copy.buffer = nullptr; + copy.bufferSize = 0; + if (element->buffer != nullptr) { + const std::size_t length = std::strlen(element->buffer) + 1; + copy.buffer = static_cast(std::malloc(length)); + if (copy.buffer == nullptr) return -1; + std::memcpy(copy.buffer, element->buffer, length); + copy.bufferSize = length; + } + static_cast(body->elements)[body->size] = copy; + body->size += 1; + return 0; +} +// -------------------------------------------------------------------------- +// SessionManager2V1 and extra Common symbols, from a title dump whose modules lack +// libSceNpCppWebApi.prx. +// -------------------------------------------------------------------------- + + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V115LocalizedStringEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, 0x00); +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V120RequestPlayerSessionEEptEv(const IntrusivePtr* self) { + return self->object; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120RequestPlayerSession16setMaxSpectatorsERKi() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V139RequestPlayerSessionMemberPlayerFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_32RequestCreatePlayerSessionPlayerEEEEEPNS9_INS3_32RequestPlayerSessionMemberPlayerEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEEE3endEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayerD1Ev() { + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V110FromMember11getOnlineIdEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitations26setinvitationInvalidFilterEPKc() { + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V120RequestPlayerSessionEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V151PostPlayerSessionsSessionIdMemberPlayersRequestBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSessionD1Ev() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V122LocalizedStringFactory6createEPNS1_6Common10LibContextEPKcNS_4Json6ObjectEPNS5_12IntrusivePtrINS3_15LocalizedStringEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V151PostPlayerSessionsSessionIdMemberPlayersRequestBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V132RequestCreatePlayerSessionPlayerEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEEED2Ev(void* self) { + (void)self; + return 0; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V113PlayerSessionEEptEv(const IntrusivePtr* self) { + return self->object; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120RequestPlayerSession20setInvitableUserTypeERKNS3_17InvitableUserTypeE() { + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V132RequestCreatePlayerSessionPlayerEEEEC1EPNS2_10LibContextE(IntrusivePtr* self, void* libContext) { + self->object = nullptr; + self->deleter = nullptr; + self->libContext = libContext; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V132RequestCreatePlayerSessionPlayerEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEEC1EPNS2_10LibContextE(IntrusivePtr* self, void* libContext) { + self->object = nullptr; + self->deleter = nullptr; + self->libContext = libContext; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V120RequestPlayerSessionEEEE8pushBackERKS8_(IntrusivePtr* self, const void* element) { + return VectorPushBackIntrusivePtr(self, static_cast(element)); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEEE5beginEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V129PostPlayerSessionsRequestBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEEEEEdeEv(const IntrusivePtr* self) { + return self->object; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V115LocalizedStringEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V151PostPlayerSessionsSessionIdMemberPlayersRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, 0x00); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi25joinPlayerSessionAsPlayerEiRKNS4_36ParameterToJoinPlayerSessionAsPlayerERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_52PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6String6appendEPKc(CommonString* self, const char* text) { + if (text == nullptr) return 0; + const std::size_t textLength = std::strlen(text); + const std::size_t oldLength = self->buffer != nullptr ? std::strlen(self->buffer) : 0; + const std::size_t needed = oldLength + textLength + 1; + if (needed > self->bufferSize) { + std::size_t capacity = self->bufferSize != 0 ? static_cast(self->bufferSize) : 16; + while (capacity < needed) capacity *= 2; + char* buffer = static_cast(std::malloc(capacity)); + if (buffer == nullptr) return -1; + if (oldLength != 0) std::memcpy(buffer, self->buffer, oldLength); + std::free(self->buffer); + self->buffer = buffer; + self->bufferSize = capacity; + } + std::memcpy(self->buffer + oldLength, text, textLength + 1); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V132RequestCreatePlayerSessionPlayerEEEE8pushBackERKS8_(IntrusivePtr* self, const void* element) { + return VectorPushBackIntrusivePtr(self, static_cast(element)); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15DefaultResponseC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayer9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessions10initializeEPNS1_6Common10LibContextENS6_12IntrusivePtrINS3_29PostPlayerSessionsRequestBodyEEE() { + return 0; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V110FromMemberEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V132RequestPlayerSessionMemberPlayerEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSessionC1Ev() { + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V136UsersPlayerSessionsInvitationForRead12getSessionIdEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V136UsersPlayerSessionsInvitationForRead7getFromEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V129PostPlayerSessionsRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, 0x00); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V130PostPlayerSessionsResponseBody17getPlayerSessionsEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi18leavePlayerSessionEiRKNS4_29ParameterToLeavePlayerSessionERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V124PlayerSessionPushContextEEEE8pushBackERKS8_(IntrusivePtr* self, const void* element) { + return VectorPushBackIntrusivePtr(self, static_cast(element)); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessionsD1Ev() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi27getPlayerSessionInvitationsEiRKNS4_38ParameterToGetPlayerSessionInvitationsERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_54GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V132RequestPlayerSessionMemberPlayerEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V120RequestPlayerSessionEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V113PlayerSessionEEEEEEdeEv(const IntrusivePtr* self) { + return self->object; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V124PlayerSessionPushContextEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V110FromMemberEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEptEv(const IntrusivePtr* self) { + return self->object; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitationsC1Ev() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEE8pushBackERKS8_(IntrusivePtr* self, const void* element) { + return VectorPushBackIntrusivePtr(self, static_cast(element)); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEEEppEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120RequestPlayerSession19setJoinableUserTypeERKNS3_16JoinableUserTypeE() { + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitationsD1Ev() { + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V113PlayerSessionEEEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V132RequestPlayerSessionMemberPlayerEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, 0x00); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V136UsersPlayerSessionsInvitationForRead14sessionIdIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V136UsersPlayerSessionsInvitationForRead17invitationIdIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessionsC1Ev() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayer10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_51PostPlayerSessionsSessionIdMemberPlayersRequestBodyEEE() { + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBody16invitationsIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V113PlayerSessionEEEEixEm() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSession10initializeEPNS1_6Common10LibContextEPKcSA_() { + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V115LocalizedStringEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, 0x30); +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEEEdeEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitations9setfieldsENS1_6Common12IntrusivePtrINS6_6VectorINS6_6StringEEEEE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15DefaultResponseD1Ev(void* self) { + (void)self; + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEEC1EPS6_PFvS8_EPNS2_10LibContextE(IntrusivePtr* self, void* body, void (APS5_VABI* deleter)(void*), void* libContext) { + self->object = body; + self->deleter = deleter; + self->libContext = libContext; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V120RequestPlayerSessionEEEEC1EPNS2_10LibContextE(IntrusivePtr* self, void* libContext) { + self->object = nullptr; + self->deleter = nullptr; + self->libContext = libContext; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V133RequestPlayerSessionPlayerFactory6createEPNS1_6Common10LibContextEPKcS9_PNS5_12IntrusivePtrINS3_26RequestPlayerSessionPlayerEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi20createPlayerSessionsEiRKNS4_31ParameterToCreatePlayerSessionsERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_30PostPlayerSessionsResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToJoinPlayerSessionAsPlayerC1Ev() { + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V136UsersPlayerSessionsInvitationForRead9fromIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V126RequestPlayerSessionPlayer15setPushContextsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_24PlayerSessionPushContextEEEEE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V127RequestPlayerSessionFactory6createEPNS1_6Common10LibContextEiNS5_12IntrusivePtrINS3_32RequestPlayerSessionMemberPlayerEEERKNS5_6VectorINS5_6StringEEENS8_INS3_15LocalizedStringEEEPNS8_INS3_20RequestPlayerSessionEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEEC1EPNS2_10LibContextE(IntrusivePtr* self, void* libContext) { + self->object = nullptr; + self->deleter = nullptr; + self->libContext = libContext; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V124PlayerSessionPushContextEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V131PlayerSessionPushContextFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_24PlayerSessionPushContextEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V129PostPlayerSessionsRequestBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE8pushBackERKS4_(IntrusivePtr* self, const void* element) { + return VectorPushBackString(self, static_cast(element)); +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEEEneERKS9_() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38ParameterToGetPlayerSessionInvitations10initializeEPNS1_6Common10LibContextEPKc() { + return 0; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBody14getInvitationsEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V132RequestCreatePlayerSessionPlayerEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V136UsersPlayerSessionsInvitationForRead22receivedTimestampIsSetEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V120RequestPlayerSessionEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv() { + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEE11getResponseERS4_() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V139RequestCreatePlayerSessionPlayerFactory6createEPNS1_6Common10LibContextEPKcS9_RKNS5_6VectorINS5_12IntrusivePtrINS3_24PlayerSessionPushContextEEEEEPNSB_INS3_32RequestCreatePlayerSessionPlayerEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V124PlayerSessionPushContextEEEEC1EPNS2_10LibContextE(IntrusivePtr* self, void* libContext) { + self->object = nullptr; + self->deleter = nullptr; + self->libContext = libContext; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V124PlayerSessionPushContextEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V113PlayerSession12getSessionIdEv() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V158PostPlayerSessionsSessionIdMemberPlayersRequestBodyFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_26RequestPlayerSessionPlayerEEEEEPNS9_INS3_51PostPlayerSessionsSessionIdMemberPlayersRequestBodyEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V136PostPlayerSessionsRequestBodyFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_20RequestPlayerSessionEEEEEPNS9_INS3_29PostPlayerSessionsRequestBodyEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120RequestPlayerSession28setExclusiveLeaderPrivilegesERKNS1_6Common6VectorINS5_6StringEEE() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +APS5_EXPORT("+gSMGXHgrPU", sceNpCppWebApiUnknown00); +int APS5_VABI sceNpCppWebApiUnknown00(void) { + NotImplemented_nid_no_patch("+gSMGXHgrPU"); + return 0; +} + +APS5_EXPORT("-XNfwZTFkWw", sceNpCppWebApiUnknown01); +int APS5_VABI sceNpCppWebApiUnknown01(void) { + NotImplemented_nid_no_patch("-XNfwZTFkWw"); + return 0; +} + +APS5_EXPORT("0f5+eBcgB14", sceNpCppWebApiUnknown02); +int APS5_VABI sceNpCppWebApiUnknown02(void) { + NotImplemented_nid_no_patch("0f5+eBcgB14"); + return 0; +} + +APS5_EXPORT("1iq5Jtw4jVs", sceNpCppWebApiUnknown03); +int APS5_VABI sceNpCppWebApiUnknown03(void) { + NotImplemented_nid_no_patch("1iq5Jtw4jVs"); + return 0; +} + +APS5_EXPORT("1wpCXuFzH10", sceNpCppWebApiUnknown04); +int APS5_VABI sceNpCppWebApiUnknown04(void) { + NotImplemented_nid_no_patch("1wpCXuFzH10"); + return 0; +} + +APS5_EXPORT("4AFThLW28xQ", sceNpCppWebApiUnknown05); +int APS5_VABI sceNpCppWebApiUnknown05(void) { + NotImplemented_nid_no_patch("4AFThLW28xQ"); + return 0; +} + +APS5_EXPORT("8-9Y4oS+OuE", sceNpCppWebApiUnknown06); +int APS5_VABI sceNpCppWebApiUnknown06(void) { + NotImplemented_nid_no_patch("8-9Y4oS+OuE"); + return 0; +} + +APS5_EXPORT("8dWkWjo5EeE", sceNpCppWebApiUnknown07); +int APS5_VABI sceNpCppWebApiUnknown07(void) { + NotImplemented_nid_no_patch("8dWkWjo5EeE"); + return 0; +} + +APS5_EXPORT("BHvfIGdarBQ", sceNpCppWebApiUnknown08); +int APS5_VABI sceNpCppWebApiUnknown08(void) { + NotImplemented_nid_no_patch("BHvfIGdarBQ"); + return 0; +} + +APS5_EXPORT("Fxxr5lYBfl4", sceNpCppWebApiUnknown09); +int APS5_VABI sceNpCppWebApiUnknown09(void) { + NotImplemented_nid_no_patch("Fxxr5lYBfl4"); + return 0; +} + +APS5_EXPORT("M1LnjRscx-w", sceNpCppWebApiUnknown10); +int APS5_VABI sceNpCppWebApiUnknown10(void) { + NotImplemented_nid_no_patch("M1LnjRscx-w"); + return 0; +} + +APS5_EXPORT("PsX657reZeo", sceNpCppWebApiUnknown11); +int APS5_VABI sceNpCppWebApiUnknown11(void) { + NotImplemented_nid_no_patch("PsX657reZeo"); + return 0; +} + +APS5_EXPORT("RvH9AZ5rCkQ", sceNpCppWebApiUnknown12); +int APS5_VABI sceNpCppWebApiUnknown12(void) { + NotImplemented_nid_no_patch("RvH9AZ5rCkQ"); + return 0; +} + +APS5_EXPORT("SR3BKonD8yk", sceNpCppWebApiUnknown13); +int APS5_VABI sceNpCppWebApiUnknown13(void) { + NotImplemented_nid_no_patch("SR3BKonD8yk"); + return 0; +} + +APS5_EXPORT("TSoQhYKyq5g", sceNpCppWebApiUnknown14); +int APS5_VABI sceNpCppWebApiUnknown14(void) { + NotImplemented_nid_no_patch("TSoQhYKyq5g"); + return 0; +} + +APS5_EXPORT("TgsJjQqp+2k", sceNpCppWebApiUnknown15); +int APS5_VABI sceNpCppWebApiUnknown15(void) { + NotImplemented_nid_no_patch("TgsJjQqp+2k"); + return 0; +} + +APS5_EXPORT("UHQxE3HhTXA", sceNpCppWebApiUnknown16); +int APS5_VABI sceNpCppWebApiUnknown16(void) { + NotImplemented_nid_no_patch("UHQxE3HhTXA"); + return 0; +} + +APS5_EXPORT("ZN-glpk0Rug", sceNpCppWebApiUnknown17); +int APS5_VABI sceNpCppWebApiUnknown17(void) { + NotImplemented_nid_no_patch("ZN-glpk0Rug"); + return 0; +} + +APS5_EXPORT("ZQzXwokCnUE", sceNpCppWebApiUnknown18); +int APS5_VABI sceNpCppWebApiUnknown18(void) { + NotImplemented_nid_no_patch("ZQzXwokCnUE"); + return 0; +} + +APS5_EXPORT("ZwjmbSr4Cxc", sceNpCppWebApiUnknown19); +int APS5_VABI sceNpCppWebApiUnknown19(void) { + NotImplemented_nid_no_patch("ZwjmbSr4Cxc"); + return 0; +} + +APS5_EXPORT("b7leY-LNVnI", sceNpCppWebApiUnknown20); +int APS5_VABI sceNpCppWebApiUnknown20(void) { + NotImplemented_nid_no_patch("b7leY-LNVnI"); + return 0; +} + +APS5_EXPORT("c3J9K9XbQqE", sceNpCppWebApiUnknown21); +int APS5_VABI sceNpCppWebApiUnknown21(void) { + NotImplemented_nid_no_patch("c3J9K9XbQqE"); + return 0; +} + +APS5_EXPORT("cSLbQUQO2Ns", sceNpCppWebApiUnknown22); +int APS5_VABI sceNpCppWebApiUnknown22(void) { + NotImplemented_nid_no_patch("cSLbQUQO2Ns"); + return 0; +} + +APS5_EXPORT("cqOxjL0ZfFA", sceNpCppWebApiUnknown23); +int APS5_VABI sceNpCppWebApiUnknown23(void) { + NotImplemented_nid_no_patch("cqOxjL0ZfFA"); + return 0; +} + +APS5_EXPORT("d1eEjWR60wk", sceNpCppWebApiUnknown24); +int APS5_VABI sceNpCppWebApiUnknown24(void) { + NotImplemented_nid_no_patch("d1eEjWR60wk"); + return 0; +} + +APS5_EXPORT("epwr+cBCIFs", sceNpCppWebApiUnknown25); +int APS5_VABI sceNpCppWebApiUnknown25(void) { + NotImplemented_nid_no_patch("epwr+cBCIFs"); + return 0; +} + +APS5_EXPORT("eruduJ0KrT0", sceNpCppWebApiUnknown26); +int APS5_VABI sceNpCppWebApiUnknown26(void) { + NotImplemented_nid_no_patch("eruduJ0KrT0"); + return 0; +} + +APS5_EXPORT("fG06a38iao8", sceNpCppWebApiUnknown27); +int APS5_VABI sceNpCppWebApiUnknown27(void) { + NotImplemented_nid_no_patch("fG06a38iao8"); + return 0; +} + +APS5_EXPORT("jfpSx14AeLc", sceNpCppWebApiUnknown28); +int APS5_VABI sceNpCppWebApiUnknown28(void) { + NotImplemented_nid_no_patch("jfpSx14AeLc"); + return 0; +} + +APS5_EXPORT("kBxwE4YfbIM", sceNpCppWebApiUnknown29); +int APS5_VABI sceNpCppWebApiUnknown29(void) { + NotImplemented_nid_no_patch("kBxwE4YfbIM"); + return 0; +} + +APS5_EXPORT("kz2Z38yTLq0", sceNpCppWebApiUnknown30); +int APS5_VABI sceNpCppWebApiUnknown30(void) { + NotImplemented_nid_no_patch("kz2Z38yTLq0"); + return 0; +} + +APS5_EXPORT("lB8driFKaoU", sceNpCppWebApiUnknown31); +int APS5_VABI sceNpCppWebApiUnknown31(void) { + NotImplemented_nid_no_patch("lB8driFKaoU"); + return 0; +} + +APS5_EXPORT("mCsV7izOkjY", sceNpCppWebApiUnknown32); +int APS5_VABI sceNpCppWebApiUnknown32(void) { + NotImplemented_nid_no_patch("mCsV7izOkjY"); + return 0; +} + +APS5_EXPORT("n1yCLMxpseQ", sceNpCppWebApiUnknown33); +int APS5_VABI sceNpCppWebApiUnknown33(void) { + NotImplemented_nid_no_patch("n1yCLMxpseQ"); + return 0; +} + +APS5_EXPORT("nL9vSVq-29s", sceNpCppWebApiUnknown34); +int APS5_VABI sceNpCppWebApiUnknown34(void) { + NotImplemented_nid_no_patch("nL9vSVq-29s"); + return 0; +} + +APS5_EXPORT("nmz5JYKcMfY", sceNpCppWebApiUnknown35); +int APS5_VABI sceNpCppWebApiUnknown35(void) { + NotImplemented_nid_no_patch("nmz5JYKcMfY"); + return 0; +} + +APS5_EXPORT("oHRl7a+zdMU", sceNpCppWebApiUnknown36); +int APS5_VABI sceNpCppWebApiUnknown36(void) { + NotImplemented_nid_no_patch("oHRl7a+zdMU"); + return 0; +} + +APS5_EXPORT("pSpO3hPNv64", sceNpCppWebApiUnknown37); +int APS5_VABI sceNpCppWebApiUnknown37(void) { + NotImplemented_nid_no_patch("pSpO3hPNv64"); + return 0; +} + +APS5_EXPORT("q4UFICay6Hk", sceNpCppWebApiUnknown38); +int APS5_VABI sceNpCppWebApiUnknown38(void) { + NotImplemented_nid_no_patch("q4UFICay6Hk"); + return 0; +} + +APS5_EXPORT("qiD2PU2Jstc", sceNpCppWebApiUnknown39); +int APS5_VABI sceNpCppWebApiUnknown39(void) { + NotImplemented_nid_no_patch("qiD2PU2Jstc"); + return 0; +} + +APS5_EXPORT("tFe964qzGEM", sceNpCppWebApiUnknown40); +int APS5_VABI sceNpCppWebApiUnknown40(void) { + NotImplemented_nid_no_patch("tFe964qzGEM"); + return 0; +} + +APS5_EXPORT("tHaO36kincQ", sceNpCppWebApiUnknown41); +int APS5_VABI sceNpCppWebApiUnknown41(void) { + NotImplemented_nid_no_patch("tHaO36kincQ"); + return 0; +} + +APS5_EXPORT("umm5m+mXiZs", sceNpCppWebApiUnknown42); +int APS5_VABI sceNpCppWebApiUnknown42(void) { + NotImplemented_nid_no_patch("umm5m+mXiZs"); + return 0; +} + +APS5_EXPORT("uwhztB49KOQ", sceNpCppWebApiUnknown43); +int APS5_VABI sceNpCppWebApiUnknown43(void) { + NotImplemented_nid_no_patch("uwhztB49KOQ"); + return 0; +} + +APS5_EXPORT("w4K4nTYwhVE", sceNpCppWebApiUnknown44); +int APS5_VABI sceNpCppWebApiUnknown44(void) { + NotImplemented_nid_no_patch("w4K4nTYwhVE"); + return 0; +} + +APS5_EXPORT("wFcm6bgWB9Q", sceNpCppWebApiUnknown45); +int APS5_VABI sceNpCppWebApiUnknown45(void) { + NotImplemented_nid_no_patch("wFcm6bgWB9Q"); + return 0; +} + +APS5_EXPORT("wp8+c84G5Xw", sceNpCppWebApiUnknown46); +int APS5_VABI sceNpCppWebApiUnknown46(void) { + NotImplemented_nid_no_patch("wp8+c84G5Xw"); + return 0; +} + +APS5_EXPORT("zjqTmP0ST9A", sceNpCppWebApiUnknown47); +int APS5_VABI sceNpCppWebApiUnknown47(void) { + NotImplemented_nid_no_patch("zjqTmP0ST9A"); + return 0; +} + +APS5_EXPORT("zzO8ZGJ74ng", sceNpCppWebApiUnknown48); +int APS5_VABI sceNpCppWebApiUnknown48(void) { + NotImplemented_nid_no_patch("zzO8ZGJ74ng"); + return 0; +} + +APS5_EXPORT("12wY179+CE8", sceNpCppWebApiUnknown49); +int APS5_VABI sceNpCppWebApiUnknown49(void) { + NotImplemented_nid_no_patch("12wY179+CE8"); + return 0; +} + +APS5_EXPORT("3bjEFf8OhTs", sceNpCppWebApiUnknown50); +int APS5_VABI sceNpCppWebApiUnknown50(void) { + NotImplemented_nid_no_patch("3bjEFf8OhTs"); + return 0; +} + +APS5_EXPORT("6FK4IOnANkc", sceNpCppWebApiUnknown51); +int APS5_VABI sceNpCppWebApiUnknown51(void) { + NotImplemented_nid_no_patch("6FK4IOnANkc"); + return 0; +} + +APS5_EXPORT("EJAOHYWUki4", sceNpCppWebApiUnknown52); +int APS5_VABI sceNpCppWebApiUnknown52(void) { + NotImplemented_nid_no_patch("EJAOHYWUki4"); + return 0; +} + +APS5_EXPORT("FlagLhjAEmc", sceNpCppWebApiUnknown53); +int APS5_VABI sceNpCppWebApiUnknown53(void) { + NotImplemented_nid_no_patch("FlagLhjAEmc"); + return 0; +} + +APS5_EXPORT("G3h-NDHnyW4", sceNpCppWebApiUnknown54); +int APS5_VABI sceNpCppWebApiUnknown54(void) { + NotImplemented_nid_no_patch("G3h-NDHnyW4"); + return 0; +} + +APS5_EXPORT("H0XjNhflED4", sceNpCppWebApiUnknown55); +int APS5_VABI sceNpCppWebApiUnknown55(void) { + NotImplemented_nid_no_patch("H0XjNhflED4"); + return 0; +} + +APS5_EXPORT("IbWx007Acn4", sceNpCppWebApiUnknown56); +int APS5_VABI sceNpCppWebApiUnknown56(void) { + NotImplemented_nid_no_patch("IbWx007Acn4"); + return 0; +} + +APS5_EXPORT("Is+nI7Hq9jU", sceNpCppWebApiUnknown57); +int APS5_VABI sceNpCppWebApiUnknown57(void) { + NotImplemented_nid_no_patch("Is+nI7Hq9jU"); + return 0; +} + +APS5_EXPORT("Kc4x2uy0FFk", sceNpCppWebApiUnknown58); +int APS5_VABI sceNpCppWebApiUnknown58(void) { + NotImplemented_nid_no_patch("Kc4x2uy0FFk"); + return 0; +} + +APS5_EXPORT("OK+Ggpve7B0", sceNpCppWebApiUnknown59); +int APS5_VABI sceNpCppWebApiUnknown59(void) { + NotImplemented_nid_no_patch("OK+Ggpve7B0"); + return 0; +} + +APS5_EXPORT("P3daBLFFREw", sceNpCppWebApiUnknown60); +int APS5_VABI sceNpCppWebApiUnknown60(void) { + NotImplemented_nid_no_patch("P3daBLFFREw"); + return 0; +} + +APS5_EXPORT("RZcseJ3THfY", sceNpCppWebApiUnknown61); +int APS5_VABI sceNpCppWebApiUnknown61(void) { + NotImplemented_nid_no_patch("RZcseJ3THfY"); + return 0; +} + +APS5_EXPORT("RbvteD35X-8", sceNpCppWebApiUnknown62); +int APS5_VABI sceNpCppWebApiUnknown62(void) { + NotImplemented_nid_no_patch("RbvteD35X-8"); + return 0; +} + +APS5_EXPORT("XCtf+23QT0k", sceNpCppWebApiUnknown63); +int APS5_VABI sceNpCppWebApiUnknown63(void) { + NotImplemented_nid_no_patch("XCtf+23QT0k"); + return 0; +} + +APS5_EXPORT("a4q15LI1a4E", sceNpCppWebApiUnknown64); +int APS5_VABI sceNpCppWebApiUnknown64(void) { + NotImplemented_nid_no_patch("a4q15LI1a4E"); + return 0; +} + +APS5_EXPORT("eT4TQB7OsLA", sceNpCppWebApiUnknown65); +int APS5_VABI sceNpCppWebApiUnknown65(void) { + NotImplemented_nid_no_patch("eT4TQB7OsLA"); + return 0; +} + +APS5_EXPORT("g2dEqFnhFuw", sceNpCppWebApiUnknown66); +int APS5_VABI sceNpCppWebApiUnknown66(void) { + NotImplemented_nid_no_patch("g2dEqFnhFuw"); + return 0; +} + +APS5_EXPORT("lPAMjFZEpt8", sceNpCppWebApiUnknown67); +int APS5_VABI sceNpCppWebApiUnknown67(void) { + NotImplemented_nid_no_patch("lPAMjFZEpt8"); + return 0; +} + +APS5_EXPORT("mBagn+lW-iM", sceNpCppWebApiUnknown68); +int APS5_VABI sceNpCppWebApiUnknown68(void) { + NotImplemented_nid_no_patch("mBagn+lW-iM"); + return 0; +} + +APS5_EXPORT("mkWsKEh0h0o", sceNpCppWebApiUnknown69); +int APS5_VABI sceNpCppWebApiUnknown69(void) { + NotImplemented_nid_no_patch("mkWsKEh0h0o"); + return 0; +} + +APS5_EXPORT("nE0ooeCMRm8", sceNpCppWebApiUnknown70); +int APS5_VABI sceNpCppWebApiUnknown70(void) { + NotImplemented_nid_no_patch("nE0ooeCMRm8"); + return 0; +} + +APS5_EXPORT("qf2I9BlKXis", sceNpCppWebApiUnknown71); +int APS5_VABI sceNpCppWebApiUnknown71(void) { + NotImplemented_nid_no_patch("qf2I9BlKXis"); + return 0; +} + +APS5_EXPORT("xwy1I52dE8o", sceNpCppWebApiUnknown72); +int APS5_VABI sceNpCppWebApiUnknown72(void) { + NotImplemented_nid_no_patch("xwy1I52dE8o"); + return 0; +} +} diff --git a/core/libs/prx/libSceNpEulaDialog/Export.cpp b/core/libs/prx/libSceNpEulaDialog/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceNpEulaDialog/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNpFriendListDialog/Export.cpp b/core/libs/prx/libSceNpFriendListDialog/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceNpFriendListDialog/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNpGameIntent/Export.cpp b/core/libs/prx/libSceNpGameIntent/Export.cpp index 4df4d87fd4a8..22fd576faef0 100644 --- a/core/libs/prx/libSceNpGameIntent/Export.cpp +++ b/core/libs/prx/libSceNpGameIntent/Export.cpp @@ -46,4 +46,31 @@ int APS5_VABI sceNpGameIntentTerminate(void) { return 0; } +int APS5_VABI sceNpGameIntentInitParamInit(NpGameIntentInitParam* param) { + if (param == nullptr) { + APS5_INVALID_ARG_EX; + } + std::memset(param, 0, sizeof(NpGameIntentInitParam)); + param->size = sizeof(NpGameIntentInitParam); + return 0; +} + +int APS5_VABI sceNpGameIntentInfoInit(NpGameIntentInfo* info) { + if (info == nullptr) { + APS5_INVALID_ARG_EX; + } + std::memset(info, 0, sizeof(NpGameIntentInfo)); + info->size = sizeof(NpGameIntentInfo); + info->user_id = NpGameIntentUserIdInvalid; + return 0; +} + +int APS5_VABI sceNpGameIntentVshInitialize(void) { + return 0; +} + +int APS5_VABI sceNpGameIntentVshTerminate(void) { + return 0; +} + } diff --git a/core/libs/prx/libSceNpParty/Export.cpp b/core/libs/prx/libSceNpParty/Export.cpp new file mode 100644 index 000000000000..8440dbddcf86 --- /dev/null +++ b/core/libs/prx/libSceNpParty/Export.cpp @@ -0,0 +1,13 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceNpPartyGetState(void* state) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libSceNpProfileDialog/Export.cpp b/core/libs/prx/libSceNpProfileDialog/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceNpProfileDialog/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNpRemotePlaySessionSignaling/Export.cpp b/core/libs/prx/libSceNpRemotePlaySessionSignaling/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceNpRemotePlaySessionSignaling/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNpSns/Export.cpp b/core/libs/prx/libSceNpSns/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceNpSns/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNpSnsYouTubeDialog/Export.cpp b/core/libs/prx/libSceNpSnsYouTubeDialog/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceNpSnsYouTubeDialog/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNpSpeechToText/Export.cpp b/core/libs/prx/libSceNpSpeechToText/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceNpSpeechToText/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNpTcs/Export.cpp b/core/libs/prx/libSceNpTcs/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceNpTcs/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNpTextToSpeech/Export.cpp b/core/libs/prx/libSceNpTextToSpeech/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceNpTextToSpeech/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceNpTus/CMakeLists.txt b/core/libs/prx/libSceNpTus/CMakeLists.txt new file mode 100644 index 000000000000..1cb8f4a6760c --- /dev/null +++ b/core/libs/prx/libSceNpTus/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(libSceNpTus SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceNpTus PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceNpTus) + +if(BUILD_TESTING) + add_test_executable(guest_np_tus_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpTus.cpp) + target_include_directories(guest_np_tus_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_tus_tests PRIVATE libSceNpTus) + configure_windows_unwind(guest_np_tus_tests) + add_test(NAME guest_np_tus COMMAND guest_np_tus_tests) + set_tests_properties(guest_np_tus PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_np_tus PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNpTus/Export.cpp b/core/libs/prx/libSceNpTus/Export.cpp new file mode 100644 index 000000000000..11b8a99218ba --- /dev/null +++ b/core/libs/prx/libSceNpTus/Export.cpp @@ -0,0 +1,129 @@ +#include +#include +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +static constexpr int SCE_NP_ERROR_SIGNED_OUT = static_cast(0x80550006); +static constexpr int SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT = static_cast(0x80550704); +static constexpr int SCE_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS = static_cast(0x80550706); +static constexpr int SCE_NP_COMMUNITY_ERROR_ABORTED = static_cast(0x80550707); +static constexpr int SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT = static_cast(0x8055070C); +static constexpr int SCE_NP_COMMUNITY_ERROR_INVALID_ID = static_cast(0x8055070E); +static constexpr int SCE_NP_COMMUNITY_ERROR_TOO_MANY_SLOTID = static_cast(0x80550718); +static constexpr int POLL_FINISHED = 0; +static constexpr int POLL_RUNNING = 1; +static constexpr std::size_t MAX_TITLE_CONTEXTS = 32; +static constexpr std::size_t MAX_REQUESTS = 256; +static constexpr int MAX_SLOTS = 64; + +namespace { +struct Request { + bool started = false; + bool aborted = false; + int result = 0; +}; + +std::mutex tusMutex; +std::unordered_set titleContexts; +std::unordered_map requests; +int nextTitleContext = 1; +int nextRequest = 1; + +int ValidateSlots(const std::int32_t* slotIds, int arrayLen, const void* option) { + if (!slotIds) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (arrayLen < 1 || option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayLen > MAX_SLOTS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_SLOTID; + for (int index = 0; index < arrayLen; ++index) { + if (slotIds[index] < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + } + return 0; +} + +int StartOffline(int reqId) { + std::lock_guard lock(tusMutex); + const auto request = requests.find(reqId); + if (request == requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID; + request->second.started = true; + request->second.result = request->second.aborted ? SCE_NP_COMMUNITY_ERROR_ABORTED : SCE_NP_ERROR_SIGNED_OUT; + return 0; +} +} + +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; +} + +int APS5_VABI sceNpTusCreateRequest(int titleCtxId) { + std::lock_guard lock(tusMutex); + if (!titleContexts.contains(titleCtxId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ID; + if (requests.size() >= MAX_REQUESTS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS; + const int reqId = nextRequest++; + requests.emplace(reqId, Request{}); + return reqId; +} + +int APS5_VABI sceNpTusDeleteRequest(int reqId) { + std::lock_guard lock(tusMutex); + return requests.erase(reqId) ? 0 : SCE_NP_COMMUNITY_ERROR_INVALID_ID; +} + +int APS5_VABI sceNpTusAbortRequest(int reqId) { + std::lock_guard lock(tusMutex); + const auto request = requests.find(reqId); + if (request == requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID; + request->second.aborted = true; + return 0; +} + +int APS5_VABI sceNpTusPollAsync(int reqId, int* result) { + std::lock_guard lock(tusMutex); + const auto request = requests.find(reqId); + if (request == requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID; + if (!request->second.started) return POLL_RUNNING; + if (result) *result = request->second.result; + return POLL_FINISHED; +} + +int APS5_VABI sceNpTusGetMultiSlotVariableAAsync(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option) { + (void)targetAccountId; + (void)variableArraySize; + if (!variableArray) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (const int error = ValidateSlots(slotIds, arrayLen, option)) return error; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusSetMultiSlotVariableAAsync(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, const std::int64_t* variableArray, int arrayLen, void* option) { + (void)targetAccountId; + if (!variableArray) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (const int error = ValidateSlots(slotIds, arrayLen, option)) return error; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusDeleteMultiSlotVariableAAsync(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, int arrayLen, void* option) { + (void)targetAccountId; + if (const int error = ValidateSlots(slotIds, arrayLen, option)) return error; + return StartOffline(reqId); +} + +int APS5_VABI sceNpTusAddAndGetVariableAAsync(int reqId, std::uint64_t targetAccountId, std::int32_t slotId, std::int64_t inVariable, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option) { + (void)targetAccountId; + (void)inVariable; + (void)isLastChangedAuthor; + (void)isLastChangedDate; + (void)outVariableSize; + if (!outVariable) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (slotId < 0 || option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return StartOffline(reqId); +} + +} diff --git a/core/libs/prx/libSceOpusCeltEnc/Export.cpp b/core/libs/prx/libSceOpusCeltEnc/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceOpusCeltEnc/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceOpusSilkEnc/Export.cpp b/core/libs/prx/libSceOpusSilkEnc/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceOpusSilkEnc/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceOrbisCompat/Export.cpp b/core/libs/prx/libSceOrbisCompat/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceOrbisCompat/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libScePad/include/PadAudioHapticQueue.hpp b/core/libs/prx/libScePad/include/PadAudioHapticQueue.hpp new file mode 100644 index 000000000000..2ad526fa00f1 --- /dev/null +++ b/core/libs/prx/libScePad/include/PadAudioHapticQueue.hpp @@ -0,0 +1,48 @@ +#ifndef CORE_LIBS_PRX_LIBSCEPAD_PADAUDIOHAPTICQUEUE_HPP +#define CORE_LIBS_PRX_LIBSCEPAD_PADAUDIOHAPTICQUEUE_HPP + +#include +#include +#include +#include +#include +#include +#include + +class PadAudioHapticQueue { + struct Block { + std::uint64_t begin; + std::uint64_t end; + std::array rumble; + }; + std::map> sources; + +public: + void Queue(int source, std::uint64_t begin, std::uint64_t end, std::uint8_t large, std::uint8_t small) { + if (source <= 0 || begin >= end) throw std::invalid_argument("Pad: invalid legacy haptic interval"); + auto& blocks = sources[source]; + if (!blocks.empty() && begin < blocks.back().end) throw std::invalid_argument("Pad: overlapping legacy haptic intervals"); + blocks.push_back({begin, end, {large, small}}); + } + + void Clear(int source) { sources.erase(source); } + + std::array Read(std::uint64_t now) { + std::array result{}; + for (auto source = sources.begin(); source != sources.end();) { + auto& blocks = source->second; + while (!blocks.empty() && blocks.front().end <= now) blocks.pop_front(); + if (blocks.empty()) { + source = sources.erase(source); + continue; + } + if (blocks.front().begin <= now) { + for (std::size_t channel = 0; channel < result.size(); ++channel) result[channel] = std::max(result[channel], blocks.front().rumble[channel]); + } + ++source; + } + return result; + } +}; + +#endif diff --git a/core/libs/prx/libScePad/include/PadState.hpp b/core/libs/prx/libScePad/include/PadState.hpp index c8c7d44e46aa..8186ee492a3a 100644 --- a/core/libs/prx/libScePad/include/PadState.hpp +++ b/core/libs/prx/libScePad/include/PadState.hpp @@ -69,5 +69,7 @@ extern "C" void PadPublishInput_nid_postfix(const PadInputState& input); extern "C" void PadReportInputFailure_nid_postfix(std::exception_ptr error); extern "C" bool PadFetchOutput_nid_postfix(std::uint32_t* seenSequence, PadOutputState* out); extern "C" void PadSubmitAudioHaptics_nid_postfix(std::uint8_t large, std::uint8_t small); +extern "C" void PadQueueLegacyAudioHaptics_nid_postfix(int source, std::uint64_t begin, std::uint64_t end, std::uint8_t large, std::uint8_t small); +extern "C" void PadClearLegacyAudioHaptics_nid_postfix(int source); #endif diff --git a/core/libs/prx/libScePad/src/PadState.cpp b/core/libs/prx/libScePad/src/PadState.cpp index f615a0ff5fc9..d95766a59f03 100644 --- a/core/libs/prx/libScePad/src/PadState.cpp +++ b/core/libs/prx/libScePad/src/PadState.cpp @@ -1,4 +1,5 @@ #include "prx/libScePad/include/PadState.hpp" +#include "prx/libScePad/include/PadAudioHapticQueue.hpp" #include "prx/libScePad/include/Pad.hpp" #include "prx/libScePad/include/InputCommandFile.hpp" #include "prx/libScePad/include/InputScript.hpp" @@ -23,6 +24,8 @@ namespace { constexpr std::uint64_t kLatchExpiry = 100000; PadOutputState output; std::chrono::steady_clock::time_point audioHapticEnd; + std::array audioHapticRumble{}; + PadAudioHapticQueue legacyHaptics; std::uint64_t timestamp = 0; struct QueuedInput { @@ -45,6 +48,17 @@ namespace { bool prevTouchActive[2] = {false, false}; std::uint8_t touchIds[2] = {0, 0}; + bool RefreshAudioHaptics() { + if (std::chrono::steady_clock::now() >= audioHapticEnd) audioHapticRumble = {}; + const auto legacy = legacyHaptics.Read(sceKernelGetProcessTime()); + const auto large = std::max(audioHapticRumble[0], legacy[0]); + const auto small = std::max(audioHapticRumble[1], legacy[1]); + if (output.audioVibrationLarge == large && output.audioVibrationSmall == small) return false; + output.audioVibrationLarge = large; + output.audioVibrationSmall = small; + return true; + } + void normalize(Quat& q) { const float n = std::sqrt(q.w * q.w + q.x * q.x + q.y * q.y + q.z * q.z); if (n < 1e-6f) { q = Quat{}; return; } @@ -417,10 +431,7 @@ extern "C" void PadReportInputFailure_nid_postfix(std::exception_ptr error) { extern "C" bool PadFetchOutput_nid_postfix(std::uint32_t* seenSequence, PadOutputState* out) { std::lock_guard lock(stateMutex); - if ((output.audioVibrationLarge || output.audioVibrationSmall) && std::chrono::steady_clock::now() >= audioHapticEnd) { - output.audioVibrationLarge = output.audioVibrationSmall = 0; - ++output.sequence; - } + if (RefreshAudioHaptics()) ++output.sequence; if (seenSequence == nullptr || out == nullptr || *seenSequence == output.sequence) return false; *seenSequence = output.sequence; *out = output; @@ -431,9 +442,22 @@ extern "C" void PadSubmitAudioHaptics_nid_postfix(std::uint8_t large, std::uint8 std::lock_guard lock(stateMutex); if (large == 0 && small == 0) return; const auto now = std::chrono::steady_clock::now(); - if (now >= audioHapticEnd) output.audioVibrationLarge = output.audioVibrationSmall = 0; - output.audioVibrationLarge = std::max(output.audioVibrationLarge, large); - output.audioVibrationSmall = std::max(output.audioVibrationSmall, small); + if (now >= audioHapticEnd) audioHapticRumble = {}; + audioHapticRumble[0] = std::max(audioHapticRumble[0], large); + audioHapticRumble[1] = std::max(audioHapticRumble[1], small); audioHapticEnd = now + std::chrono::milliseconds(80); + static_cast(RefreshAudioHaptics()); ++output.sequence; } + +extern "C" void PadQueueLegacyAudioHaptics_nid_postfix(int source, std::uint64_t begin, std::uint64_t end, std::uint8_t large, std::uint8_t small) { + std::lock_guard lock(stateMutex); + legacyHaptics.Queue(source, begin, end, large, small); + if (RefreshAudioHaptics()) ++output.sequence; +} + +extern "C" void PadClearLegacyAudioHaptics_nid_postfix(int source) { + std::lock_guard lock(stateMutex); + legacyHaptics.Clear(source); + if (RefreshAudioHaptics()) ++output.sequence; +} diff --git a/core/libs/prx/libScePadTracker/Export.cpp b/core/libs/prx/libScePadTracker/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libScePadTracker/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libScePatchCheckerClient/Export.cpp b/core/libs/prx/libScePatchCheckerClient/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libScePatchCheckerClient/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libScePerf/Export.cpp b/core/libs/prx/libScePerf/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libScePerf/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libScePlayReady/Export.cpp b/core/libs/prx/libScePlayReady/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libScePlayReady/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libScePlayReady2/Export.cpp b/core/libs/prx/libScePlayReady2/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libScePlayReady2/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libScePlayReady4/Export.cpp b/core/libs/prx/libScePlayReady4/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libScePlayReady4/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libScePngParser/Export.cpp b/core/libs/prx/libScePngParser/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libScePngParser/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libScePs2EmuMenuDialog/Export.cpp b/core/libs/prx/libScePs2EmuMenuDialog/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libScePs2EmuMenuDialog/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libScePsm/Export.cpp b/core/libs/prx/libScePsm/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libScePsm/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libScePsmUtil/Export.cpp b/core/libs/prx/libScePsmUtil/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libScePsmUtil/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceRazorCpu/Export.cpp b/core/libs/prx/libSceRazorCpu/Export.cpp index 116c784a6f26..c47fa0c77c97 100644 --- a/core/libs/prx/libSceRazorCpu/Export.cpp +++ b/core/libs/prx/libSceRazorCpu/Export.cpp @@ -68,4 +68,65 @@ int APS5_VABI sceRazorCpuSync(void) { return 0; } +void APS5_VABI sceRazorCpuDisableFiberUserMarkers(void) { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRazorCpuInitDataTags(void* buffer, size_t size) { + (void)buffer; + (void)size; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRazorCpuShutdownDataTags(void) { + NotImplemented_nid_no_patch(__func__); +} + +size_t APS5_VABI sceRazorCpuGetDataTagStorageSize(uint32_t tagCount) { + (void)tagCount; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRazorCpuTagArray(const char* label, uint32_t category, void* array, size_t bufferSize, size_t elementSize) { + (void)label; + (void)category; + (void)array; + (void)bufferSize; + (void)elementSize; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRazorCpuTagBuffer(const char* label, uint32_t category, void* buffer, size_t bufferSize) { + (void)label; + (void)category; + (void)buffer; + (void)bufferSize; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRazorCpuResizeTaggedBuffer(uint32_t category, void* buffer, size_t newBufferSize) { + (void)category; + (void)buffer; + (void)newBufferSize; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRazorCpuUnTagBuffer(uint32_t category, void* buffer) { + (void)category; + (void)buffer; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRazorCpuBeginLogicalFileAccess(const char* filename, uint64_t tag, uint64_t size, uint8_t operation) { + (void)filename; + (void)tag; + (void)size; + (void)operation; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRazorCpuEndLogicalFileAccess(void) { + NotImplemented_nid_no_patch(__func__); +} + } diff --git a/core/libs/prx/libSceRazorCpu_debug/Export.cpp b/core/libs/prx/libSceRazorCpu_debug/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceRazorCpu_debug/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceRegMgr/Export.cpp b/core/libs/prx/libSceRegMgr/Export.cpp index d71daed4b071..4626f92e6b03 100644 --- a/core/libs/prx/libSceRegMgr/Export.cpp +++ b/core/libs/prx/libSceRegMgr/Export.cpp @@ -1,8 +1,34 @@ #include +#include +#include "SceTypes.hpp" #include "prx/libc/include/General.hpp" extern "C" { +int APS5_VABI sceRegMgrGetBin() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceRegMgrGetInt(int32_t optionId, int32_t* outInt) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceRegMgrGetStr(int32_t optionId, char* outString, std::size_t stringLength) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceRegMgrSetBin() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceRegMgrSetInt(int32_t optionId, int32_t inInt) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceRegMgrSetStr(int32_t optionId, const char* inStirng, std::size_t stringLength) { + NotImplemented_nid_no_patch(__func__); + return 0; +} std::int32_t APS5_VABI sceRegMgrNonSysGetInt(std::int64_t key, std::int32_t unk, std::int32_t* data) { (void)key; (void)unk; diff --git a/core/libs/prx/libSceRemotePlayClientIpc/Export.cpp b/core/libs/prx/libSceRemotePlayClientIpc/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceRemotePlayClientIpc/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceResourceArbitrator/Export.cpp b/core/libs/prx/libSceResourceArbitrator/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceResourceArbitrator/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceRnpsAppMgr/Export.cpp b/core/libs/prx/libSceRnpsAppMgr/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceRnpsAppMgr/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceRnpsNKInjectedBundle/Export.cpp b/core/libs/prx/libSceRnpsNKInjectedBundle/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceRnpsNKInjectedBundle/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceRtc/Export.cpp b/core/libs/prx/libSceRtc/Export.cpp index dcc861103dfa..629ee77fa560 100644 --- a/core/libs/prx/libSceRtc/Export.cpp +++ b/core/libs/prx/libSceRtc/Export.cpp @@ -630,4 +630,106 @@ int APS5_VABI sceRtcTickAddYears(RtcTick* dst, const RtcTick* src, int16_t years return addMonths(dst, src, static_cast(years) * 12); } +int APS5_VABI sceRtcInit(void) { + return 0; +} + +int APS5_VABI sceRtcEnd(void) { + return 0; +} + +int APS5_VABI sceRtcCompareTick(const RtcTick* first, const RtcTick* second) { + if (!first || !second) return SCE_RTC_ERROR_INVALID_POINTER; + if (first->tick < second->tick) return -1; + return first->tick > second->tick ? 1 : 0; +} + +int APS5_VABI sceRtcGetCurrentRawNetworkTick(RtcTick* tick) { + return sceRtcGetCurrentTick(tick); +} + +int APS5_VABI sceRtcGetCurrentAdNetworkTick(RtcTick* tick) { + return sceRtcGetCurrentTick(tick); +} + +int APS5_VABI sceRtcGetCurrentDebugNetworkTick(RtcTick* tick) { + return sceRtcGetCurrentTick(tick); +} + +static int checkSettableTick(const RtcTick* tick) { + if (!tick) return SCE_RTC_ERROR_INVALID_POINTER; + return tick->tick < UNIX_EPOCH_TICK ? SCE_RTC_ERROR_INVALID_VALUE : 0; +} + +int APS5_VABI sceRtcSetCurrentTick(const RtcTick* tick) { + if (const int result = checkSettableTick(tick); result != 0) return result; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRtcSetCurrentNetworkTick(const RtcTick* tick) { + if (const int result = checkSettableTick(tick); result != 0) return result; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRtcSetCurrentAdNetworkTick(const RtcTick* tick) { + if (const int result = checkSettableTick(tick); result != 0) return result; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRtcSetCurrentDebugNetworkTick(const RtcTick* tick) { + if (const int result = checkSettableTick(tick); result != 0) return result; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRtcSetConf(int first, int second, int third, int fourth) { + (void)first; + (void)second; + (void)third; + (void)fourth; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRtcFormatRFC3339Precise(char* date_time, const RtcTick* utc, int time_zone_minutes, int precision) { + if (!date_time) return SCE_RTC_ERROR_INVALID_POINTER; + RtcTick now{}; + if (!utc) { + now.tick = currentTick(); + utc = &now; + } + if (time_zone_minutes < -1439 || time_zone_minutes > 1439 || precision < 0 || precision > 6) return SCE_RTC_ERROR_INVALID_VALUE; + RtcTick local{}; + if (const int result = addTicks(&local, utc, time_zone_minutes, TICKS_PER_MINUTE); result != 0) return result; + const RtcDateTime time = fromTick(local.tick); + int written = std::snprintf(date_time, 20, "%04u-%02u-%02uT%02u:%02u:%02u", time.year, time.month, time.day, time.hour, time.minute, time.second); + if (precision != 0) { + std::uint32_t divisor = 1; + for (int digits = precision; digits < 6; ++digits) divisor *= 10; + written += std::snprintf(date_time + written, 8, ".%0*u", precision, time.microsecond / divisor); + } + if (time_zone_minutes == 0) { + std::snprintf(date_time + written, 2, "Z"); + } else { + const int offset = time_zone_minutes < 0 ? -time_zone_minutes : time_zone_minutes; + std::snprintf(date_time + written, 7, "%c%02d:%02d", time_zone_minutes < 0 ? '-' : '+', offset / 60, offset % 60); + } + return 0; +} + +int APS5_VABI sceRtcFormatRFC3339PreciseLocalTime(char* date_time, const RtcTick* utc, int precision) { + if (!date_time) return SCE_RTC_ERROR_INVALID_POINTER; + RtcTick now{}; + if (!utc) { + now.tick = currentTick(); + utc = &now; + } + if (precision < 0 || precision > 6) return SCE_RTC_ERROR_INVALID_VALUE; + if (utc->tick > MAX_TICK) return SCE_RTC_ERROR_INVALID_VALUE; + return sceRtcFormatRFC3339Precise(date_time, utc, localOffsetMinutes(*utc), precision); +} + } diff --git a/core/libs/prx/libSceS3DConversion/Export.cpp b/core/libs/prx/libSceS3DConversion/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceS3DConversion/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceS3da/Export.cpp b/core/libs/prx/libSceS3da/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceS3da/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceScm/Export.cpp b/core/libs/prx/libSceScm/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceScm/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceScreenShot/Export.cpp b/core/libs/prx/libSceScreenShot/Export.cpp new file mode 100644 index 000000000000..41e538d9cce3 --- /dev/null +++ b/core/libs/prx/libSceScreenShot/Export.cpp @@ -0,0 +1,17 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceScreenShotSetOverlayImage() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceScreenShotSetOverlayImageWithOrigin() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libSceSdma/Export.cpp b/core/libs/prx/libSceSdma/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceSdma/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceShareFactoryUtil/Export.cpp b/core/libs/prx/libSceShareFactoryUtil/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceShareFactoryUtil/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceShareInternal.native/Export.cpp b/core/libs/prx/libSceShareInternal.native/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceShareInternal.native/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceShareUtility/Export.cpp b/core/libs/prx/libSceShareUtility/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceShareUtility/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceShellUIUtil/Export.cpp b/core/libs/prx/libSceShellUIUtil/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceShellUIUtil/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSmart/Export.cpp b/core/libs/prx/libSceSmart/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceSmart/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSocialScreen/Export.cpp b/core/libs/prx/libSceSocialScreen/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceSocialScreen/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSocialScreenDialog/Export.cpp b/core/libs/prx/libSceSocialScreenDialog/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceSocialScreenDialog/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSpSysCallWrapper/Export.cpp b/core/libs/prx/libSceSpSysCallWrapper/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceSpSysCallWrapper/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSrcUtl/Export.cpp b/core/libs/prx/libSceSrcUtl/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceSrcUtl/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSulphaDrv/Export.cpp b/core/libs/prx/libSceSulphaDrv/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceSulphaDrv/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSysBridge/Export.cpp b/core/libs/prx/libSceSysBridge/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceSysBridge/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSysCore/Export.cpp b/core/libs/prx/libSceSysCore/Export.cpp new file mode 100644 index 000000000000..7c5cd4bbd6b8 --- /dev/null +++ b/core/libs/prx/libSceSysCore/Export.cpp @@ -0,0 +1,13 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceApplicationGetAppInfoByAppId() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libSceSysUtil/Export.cpp b/core/libs/prx/libSceSysUtil/Export.cpp new file mode 100644 index 000000000000..8cdc46a18251 --- /dev/null +++ b/core/libs/prx/libSceSysUtil/Export.cpp @@ -0,0 +1,13 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceSysUtilSendSystemNotificationWithText(int32_t type, const char* message) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libSceSysmodule/Export.cpp b/core/libs/prx/libSceSysmodule/Export.cpp index e9c5f23e1bfa..d03961037602 100644 --- a/core/libs/prx/libSceSysmodule/Export.cpp +++ b/core/libs/prx/libSceSysmodule/Export.cpp @@ -274,4 +274,19 @@ int APS5_VABI sceSysmoduleUnloadModuleInternal(std::uint32_t id) { return 0; } +int APS5_VABI sceSysmoduleIsLoadedInternal(std::uint32_t id) { + if ((id & 0x7fffffffu) == 0) { + return SCE_SYSMODULE_ERROR_INVALID_VALUE; + } + if (!findModuleName(id)) { + throw std::runtime_error(std::string("sceSysmoduleIsLoadedInternal: unknown id ") + std::to_string(id)); + } + std::lock_guard lock(gMutex); + const auto it = gLoadCount.find(id); + if (it == gLoadCount.end() || it->second < 1) { + return SCE_SYSMODULE_ERROR_UNLOADED; + } + return 0; +} + } diff --git a/core/libs/prx/libSceSysmodule/ModuleTable.hpp b/core/libs/prx/libSceSysmodule/ModuleTable.hpp index 890a993b2a7a..eba12e75ac66 100644 --- a/core/libs/prx/libSceSysmodule/ModuleTable.hpp +++ b/core/libs/prx/libSceSysmodule/ModuleTable.hpp @@ -7,6 +7,7 @@ inline const std::unordered_map kModuleTable = { {0x00000001, "libSceAudioOut"}, {0x00000002, "libSceAudioIn"}, + {0x00000005, "libScePerf"}, {0x00000006, "libSceFiber"}, {0x00000007, "libSceUlt"}, {0x0000000b, "libSceNgs2"}, @@ -147,9 +148,11 @@ inline const std::unordered_map kModuleTable = { {0x00000107, "libSceGic"}, {0x00000108, "libScePlayReady2"}, {0x00000109, "libSceHdrScopes"}, + {0x0000010a, "libSceDeci5"}, {0x0000010b, "libSceAcm"}, {0x0000010c, "libSceCesCs"}, {0x0000010d, "libScePlayerInvitationDialog"}, + {0x0000010e, "libScePlayerSelectionDialog"}, {0x0000010f, "libSceShare"}, {0x00000110, "libSceNpTrophy2"}, {0x00000112, "libSceNpSessionSignaling"}, @@ -158,8 +161,16 @@ inline const std::unordered_map kModuleTable = { {0x00000115, "libSceNpCppWebApi"}, {0x00000116, "libSceHubAppUtil"}, {0x0000011a, "libSceNpPartner001"}, + {0x0000011b, "libSceFrontPanelDisplay"}, {0x00000120, "libSceAudioPropagation"}, + {0x00000125, "libSceTextToSpeech2"}, + {0x00000126, "libSceCdlgG2P"}, + {0x00000127, "libSceCdlgPlayerReview"}, + {0x00000128, "libSceDepth2"}, + {0x0000012a, "libSceHmd2"}, + {0x0000012b, "libSceVrTracker2"}, {0x0000012f, "libSceFontGs"}, + {0x00000130, "libSceVrSetupDialog"}, {0x00000132, "libSceProprietaryVoiceChatHelperPrx"}, {0x00000135, "libSceFontGsm"}, {0x00000137, "libScePsml"}, @@ -274,23 +285,74 @@ inline const std::unordered_map kModuleTable = { {0x80000078, "libSceHttpCache"}, {0x80000079, "libSceNKWeb"}, {0x8000007a, "libSceNKWebKit"}, + {0x8000007b, "libSceGpuTrace"}, {0x8000007c, "libSceVnaInternal"}, {0x8000007d, "libSceVnaWebsocket"}, + {0x8000007e, "libSceAjmi"}, + {0x8000007f, "libSceGpuCapture"}, + {0x80000080, "libSceAgcDriver"}, + {0x80000082, "libSceAudiodecCpuTrhd"}, + {0x80000083, "libSceAudioSystem"}, + {0x80000084, "libSceVenc"}, + {0x80000085, "libSceAvcap2"}, + {0x80000086, "libSceAgcVsh"}, + {0x80000087, "libSceAgcVshDebug"}, + {0x80000088, "libSceSystemLogger2NativeQueueClient"}, + {0x80000089, "libSceSystemLogger2Delivery"}, {0x8000008a, "libSceComposite"}, {0x8000008b, "libSceCompositeExt"}, {0x8000008c, "libSceHttp2"}, {0x8000008d, "libSceNpGameIntent"}, + {0x8000008e, "libSceShareInternal"}, {0x8000008f, "libSceNpWebApi2"}, + {0x80000090, "libSceNgs2"}, + {0x80000091, "libSceVcodec"}, + {0x80000092, "libSceResourceArbitrator"}, + {0x80000093, "libSceAgcResourceRegistration"}, {0x80000094, "libSceAgc"}, + {0x80000095, "libSceMediaFrameworkInterface"}, + {0x80000097, "libSceSystemTts"}, {0x80000098, "libScePosixForWebKit"}, + {0x80000099, "libSceVoiceCommand"}, {0x8000009a, "libSceNpRemotePlaySessionSignaling"}, + {0x8000009b, "libSceEmbeddedTtsCoreG3"}, + {0x8000009c, "libSceEmbeddedTts"}, + {0x8000009e, "libSceJemspace"}, + {0x8000009f, "libSceSystemLogger2Game"}, + {0x800000a0, "libSceNpTcs"}, + {0x800000a1, "libSceAbstractTcs"}, + {0x800000a2, "libSceTEEClient"}, + {0x800000a3, "libSceBgsStorage"}, + {0x800000a4, "libSceWebmParserMdrw"}, + {0x800000a6, "libSceIdu"}, + {0x800000a7, "libSceAppDbShellCoreClient"}, + {0x800000a8, "libSceIcu"}, + {0x800000a9, "libSceGLSlimVSH"}, {0x800000aa, "libicu"}, {0x800000ab, "libpng16"}, {0x800000ac, "libcairo"}, + {0x800000ad, "libSceContentListController"}, + {0x800000ae, "libSceSocialScreen"}, + {0x800000af, "libSceVdecSvp9"}, + {0x800000b0, "libSceJsc"}, {0x800000b1, "libcurl"}, + {0x800000b2, "libSceVideoStreamingEngine_sys"}, + {0x800000b3, "libSceSystemLogger2"}, + {0x800000b4, "libSceJxr"}, + {0x800000b5, "libSceJxrParser"}, + {0x800000b6, "libSceMediaFrameworkUtil"}, + {0x800000b7, "libSceAmpr"}, {0x800000b8, "libSceLibreSsl3"}, + {0x800000b9, "libSceRemotePlayClientIpc"}, + {0x800000ba, "libSceEsvmEngine"}, + {0x800000bb, "libSceWorkspace"}, + {0x800000bc, "libSceAvcap2mvr"}, + {0x800000bd, "libSceHdrScopes"}, + {0x800000be, "libSceAppDbShellCoreClientForSystemService"}, + {0x800000bf, "libwvoec"}, {0x800000c0, "libSceGLSlimClientVSH"}, {0x800000c1, "libSceGLSlimServerVSH"}, + {0x800000c3, "libSceRnpsBundle"}, }; #endif diff --git a/core/libs/prx/libSceSystemLogger/Export.cpp b/core/libs/prx/libSceSystemLogger/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceSystemLogger/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSystemLogger2/Export.cpp b/core/libs/prx/libSceSystemLogger2/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceSystemLogger2/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSystemLogger2Delivery/Export.cpp b/core/libs/prx/libSceSystemLogger2Delivery/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceSystemLogger2Delivery/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSystemLogger2Game/Export.cpp b/core/libs/prx/libSceSystemLogger2Game/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceSystemLogger2Game/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSystemLogger2NativeQueueClient/Export.cpp b/core/libs/prx/libSceSystemLogger2NativeQueueClient/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceSystemLogger2NativeQueueClient/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceSystemTts/Export.cpp b/core/libs/prx/libSceSystemTts/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceSystemTts/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceTEEClient/Export.cpp b/core/libs/prx/libSceTEEClient/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceTEEClient/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceTextToSpeech/Export.cpp b/core/libs/prx/libSceTextToSpeech/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceTextToSpeech/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceTtsCoreG3Bc/Export.cpp b/core/libs/prx/libSceTtsCoreG3Bc/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceTtsCoreG3Bc/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceUpdateService/Export.cpp b/core/libs/prx/libSceUpdateService/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceUpdateService/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceUrlConfigResolver/Export.cpp b/core/libs/prx/libSceUrlConfigResolver/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceUrlConfigResolver/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceUsbStorage/Export.cpp b/core/libs/prx/libSceUsbStorage/Export.cpp index fb34206c211f..befdd3e768db 100644 --- a/core/libs/prx/libSceUsbStorage/Export.cpp +++ b/core/libs/prx/libSceUsbStorage/Export.cpp @@ -5,6 +5,75 @@ extern "C" { -APS5_DUMMY_FUN +int APS5_VABI sceUsbStorageInit(void* attr) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageTerm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageRegisterCallback(int32_t eventType, void* callback, void* arg) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageUnregisterCallback(int32_t eventType, void* callback) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageRegisterCallbackForMapAvailable(uint32_t device, const char* directory, void* callback, void* arg) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageUnregisterCallbackForMapAvailable(uint32_t device, const char* directory, void* callback) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageRegisterCallbackForMapUnavailable(uint32_t device, const char* directory, void* callback, void* arg) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageUnregisterCallbackForMapUnavailable(uint32_t device, const char* directory, void* callback) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageGetDeviceInfo(uint32_t device, void* info) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageGetDeviceList(uint32_t* devices, int32_t* count) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageGetMountPointOfShellCore(uint32_t device, char* mountPoint) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageIsExist(uint32_t device) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageRequestMap(uint32_t device, const char* directory, int32_t flags, + uint64_t volumeSize, char* mountPoint, uint64_t* mountId, + const void* reserved, size_t reservedSize) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageRequestUnmap(uint32_t device, const char* directory) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageGetdentsOpen(uint32_t device, int32_t* fd, uint64_t* bufferSize) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageGetdentsRead(int32_t fd, void* buffer, uint64_t bufferSize, uint64_t* bytesRead) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceUsbStorageGetdentsClose(int32_t fd) { + NotImplemented_nid_no_patch(__func__); + return 0; +} } diff --git a/core/libs/prx/libSceVcodec/Export.cpp b/core/libs/prx/libSceVcodec/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceVcodec/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVdecCore.native/Export.cpp b/core/libs/prx/libSceVdecCore.native/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceVdecCore.native/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVdecCore/Export.cpp b/core/libs/prx/libSceVdecCore/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceVdecCore/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVdecSavc/Export.cpp b/core/libs/prx/libSceVdecSavc/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceVdecSavc/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVdecSavc2.native/Export.cpp b/core/libs/prx/libSceVdecSavc2.native/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceVdecSavc2.native/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVdecSavc2/Export.cpp b/core/libs/prx/libSceVdecSavc2/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceVdecSavc2/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVdecShevc.native/Export.cpp b/core/libs/prx/libSceVdecShevc.native/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceVdecShevc.native/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVdecShevc/Export.cpp b/core/libs/prx/libSceVdecShevc/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceVdecShevc/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVdecSvp9.native/Export.cpp b/core/libs/prx/libSceVdecSvp9.native/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceVdecSvp9.native/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVdecsw/Export.cpp b/core/libs/prx/libSceVdecsw/Export.cpp index 8d394b9fb34c..cab091bb320b 100644 --- a/core/libs/prx/libSceVdecsw/Export.cpp +++ b/core/libs/prx/libSceVdecsw/Export.cpp @@ -373,6 +373,14 @@ int APS5_VABI sceVdecswSyncDecodeOutput(std::uint64_t handle, OutputInfo* output } } +int APS5_VABI sceVdecswGetPictureInfo(const OutputInfo* output, void* first, void* second) { + (void)output; + (void)first; + (void)second; + NotImplemented_nid_no_patch(__func__); + return 0; +} + 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"); @@ -401,6 +409,13 @@ int APS5_VABI sceVdecswGetHevcPictureInfo(const OutputInfo* output, void* pictur return 0; } +int APS5_VABI sceVdecswGetVp9PictureInfo(const OutputInfo* output, void* picture) { + (void)output; + (void)picture; + NotImplemented_nid_no_patch(__func__); + return 0; +} + int APS5_VABI sceVdecswFinalizeDecodeSequence(std::uint64_t handle) { const auto decoder = findDecoder(handle); std::lock_guard lock(decoder->mutex); diff --git a/core/libs/prx/libSceVdecwrap/Export.cpp b/core/libs/prx/libSceVdecwrap/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceVdecwrap/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVenc/Export.cpp b/core/libs/prx/libSceVenc/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceVenc/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVideoArbitration/Export.cpp b/core/libs/prx/libSceVideoArbitration/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceVideoArbitration/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVideoCoreInterface/Export.cpp b/core/libs/prx/libSceVideoCoreInterface/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceVideoCoreInterface/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVideoCoreServerInterface/Export.cpp b/core/libs/prx/libSceVideoCoreServerInterface/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceVideoCoreServerInterface/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVideoDecoderArbitration/Export.cpp b/core/libs/prx/libSceVideoDecoderArbitration/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceVideoDecoderArbitration/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVideoNativeExtEssential/Export.cpp b/core/libs/prx/libSceVideoNativeExtEssential/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceVideoNativeExtEssential/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVideoOutSecondary/Export.cpp b/core/libs/prx/libSceVideoOutSecondary/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceVideoOutSecondary/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVideoRecording.native/Export.cpp b/core/libs/prx/libSceVideoRecording.native/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceVideoRecording.native/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVideoRecording/Export.cpp b/core/libs/prx/libSceVideoRecording/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceVideoRecording/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVideoRecordingP/Export.cpp b/core/libs/prx/libSceVideoRecordingP/Export.cpp index 7d1cb61a2998..915a17728e79 100644 --- a/core/libs/prx/libSceVideoRecordingP/Export.cpp +++ b/core/libs/prx/libSceVideoRecordingP/Export.cpp @@ -16,12 +16,33 @@ std::map> g_info; extern "C" { +void APS5_VABI _sceVideoRecordingQueryParam_nid_postfix(void* param) { + (void)param; + NotImplemented_nid_no_patch("_sceVideoRecordingQueryParam"); +} + int APS5_VABI sceVideoRecordingClose(int discard) { (void)discard; NotImplemented_nid_no_patch(__func__); return 0; } +int APS5_VABI sceVideoRecordingCloseByHandle(int handle, int discard) { + (void)handle; + (void)discard; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVideoRecordingCopyBGRAtoNV12(void* dst, const void* src, uint32_t width, uint32_t height) { + (void)dst; + (void)src; + (void)width; + (void)height; + NotImplemented_nid_no_patch(__func__); + return 0; +} + int APS5_VABI sceVideoRecordingGetInfo(int32_t info, void* data, size_t size) { if (data == nullptr) APS5_INVALID_ARG_EX; std::lock_guard lock(g_infoMutex); @@ -32,11 +53,43 @@ int APS5_VABI sceVideoRecordingGetInfo(int32_t info, void* data, size_t size) { return 0; } +int APS5_VABI sceVideoRecordingGetInfoByHandle(int handle, int32_t info, void* data, size_t size) { + (void)handle; + (void)info; + (void)data; + (void)size; + NotImplemented_nid_no_patch(__func__); + return 0; +} + int APS5_VABI sceVideoRecordingGetStatus(void) { NotImplemented_nid_no_patch(__func__); return 0; } +int APS5_VABI sceVideoRecordingGetSupportInfo(void* info) { + (void)info; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVideoRecordingGetVideoOutMode(void* mode) { + (void)mode; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVideoRecordingInit(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVideoRecordingInitialize(void* param) { + (void)param; + NotImplemented_nid_no_patch(__func__); + return 0; +} + int APS5_VABI sceVideoRecordingOpen(const char* path, const void* param, void* heap, int heapSize) { (void)path; (void)param; @@ -46,12 +99,52 @@ int APS5_VABI sceVideoRecordingOpen(const char* path, const void* param, void* h return 0; } +int APS5_VABI sceVideoRecordingOpen2(const char* path, const void* param, void* heap, int heapSize) { + (void)path; + (void)param; + (void)heap; + (void)heapSize; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVideoRecordingQueryHddSize2(const void* param) { + (void)param; + NotImplemented_nid_no_patch(__func__); + return 0; +} + int APS5_VABI sceVideoRecordingQueryMemSize(const void* param) { (void)param; NotImplemented_nid_no_patch(__func__); return 0; } +int APS5_VABI sceVideoRecordingQueryMemSize2(const void* param) { + (void)param; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVideoRecordingRestModeProcessGetStatus(void* status) { + (void)status; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVideoRecordingRestModeProcessStart(void* param) { + (void)param; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVideoRecordingSendCommand(int cmd, const void* param) { + (void)cmd; + (void)param; + NotImplemented_nid_no_patch(__func__); + return 0; +} + int APS5_VABI sceVideoRecordingSetInfo(int32_t info, const void* data, size_t size) { if (data == nullptr && size != 0) APS5_INVALID_ARG_EX; const auto* bytes = static_cast(data); @@ -70,6 +163,11 @@ int APS5_VABI sceVideoRecordingStop(void) { return 0; } +int APS5_VABI sceVideoRecordingTerm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + APS5_EXPORT("iQS6DUtLybE", videoRecordingUnknown_iQS6DUtLybE); int APS5_VABI videoRecordingUnknown_iQS6DUtLybE(void) { NotImplemented_nid_no_patch("iQS6DUtLybE"); diff --git a/core/libs/prx/libSceVideoStreamingEngine/Export.cpp b/core/libs/prx/libSceVideoStreamingEngine/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceVideoStreamingEngine/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVideoStreamingEngine_sys/Export.cpp b/core/libs/prx/libSceVideoStreamingEngine_sys/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceVideoStreamingEngine_sys/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVideodec/Export.cpp b/core/libs/prx/libSceVideodec/Export.cpp new file mode 100644 index 000000000000..8f20f3fc6989 --- /dev/null +++ b/core/libs/prx/libSceVideodec/Export.cpp @@ -0,0 +1,29 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceVideodecCreateDecoder(const void* pCfgInfoIn, const void* pRsrcInfoIn, void* pCtrlOut) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceVideodecDecode(void* pCtrlIn, const void* pInputDataIn, void* pFrameBufferInOut, void* pPictureInfoOut) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceVideodecDeleteDecoder(void* pCtrlIn) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceVideodecFlush(void* pCtrlIn, void* pFrameBufferInOut, void* pPictureInfoOut) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceVideodecReset(void* pCtrlIn) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libSceVisionManager/Export.cpp b/core/libs/prx/libSceVisionManager/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceVisionManager/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVnaInternal/Export.cpp b/core/libs/prx/libSceVnaInternal/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceVnaInternal/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVnaWebsocket/Export.cpp b/core/libs/prx/libSceVnaWebsocket/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceVnaWebsocket/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVoiceChat/Export.cpp b/core/libs/prx/libSceVoiceChat/Export.cpp index aa3728e42e56..49e582df5fc6 100644 --- a/core/libs/prx/libSceVoiceChat/Export.cpp +++ b/core/libs/prx/libSceVoiceChat/Export.cpp @@ -66,209 +66,100 @@ VOICECHAT_OFFLINE_REQUEST(sceVoiceChatRequestJoinVoiceChatGroup) VOICECHAT_OFFLINE_REQUEST(sceVoiceChatRequestLeaveVoiceChatGroup) VOICECHAT_OFFLINE_REQUEST(sceVoiceChatRequestDeleteVoiceChatGroup) -int APS5_VABI sceVoiceChatAbortRequest(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatGetActiveChannel(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatGetChannelMemberActiveChannelMatchingState(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatGetChannelMemberVoiceConnectionState(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatGetChatChannelMemberInfoList(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatGetChatGroupMemberInfoList(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatGetMicState(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatGetUserVoiceVolumeGain(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatRegisterChannelEventHandler(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatRegisterDaemonEventHandler(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatRegisterMemberEventHandler(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatRegisterPartyUserStateChangedHandler(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatRegisterVoiceEventHandler(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatRequest0(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatRequest1(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatRequestAcsTts(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatRequestAction(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatRequestSetMemberVolume(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatRequestUpdateGameSessionVoiceChatChannelName(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatRequestUpdatePlayerSessionVoiceChatChannelName(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatSetUserVoiceVolumeGain(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatStartAccessibilityEvent(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatStartChannelEvent(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatStartDaemonEvent(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatStartMemberEvent(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatStartRangedMemberEvent(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatStartVoiceEvent(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} +VOICECHAT_OFFLINE_REQUEST(sceVoiceChatRequest0) +VOICECHAT_OFFLINE_REQUEST(sceVoiceChatRequest1) +VOICECHAT_OFFLINE_REQUEST(sceVoiceChatRequestAcsTts) +VOICECHAT_OFFLINE_REQUEST(sceVoiceChatRequestAction) +VOICECHAT_OFFLINE_REQUEST(sceVoiceChatRequestSetMemberVolume) +VOICECHAT_OFFLINE_REQUEST(sceVoiceChatRequestUpdateGameSessionVoiceChatChannelName) +VOICECHAT_OFFLINE_REQUEST(sceVoiceChatRequestUpdatePlayerSessionVoiceChatChannelName) -int APS5_VABI sceVoiceChatStopAccessibilityEvent(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVoiceChatAbortRequest(int requestId) { + (void)requestId; return 0; } -int APS5_VABI sceVoiceChatStopChannelEvent(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatStopDaemonEvent(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatStopMemberEvent(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatStopRangedMemberEvent(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceVoiceChatStopVoiceEvent(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVoiceChatGetActiveChannel(void* channel) { + (void)channel; + logSoft(__func__, "SIGNED_OUT (offline)"); + return VOICECHAT_ERROR_SIGNED_OUT; } -int APS5_VABI sceVoiceChatUnregisterAccessibilityEventHandler(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVoiceChatGetChannelMemberActiveChannelMatchingState(void* a, void* b) { + (void)a; + (void)b; + logSoft(__func__, "SIGNED_OUT (offline)"); + return VOICECHAT_ERROR_SIGNED_OUT; } -int APS5_VABI sceVoiceChatUnregisterChannelEventHandler(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVoiceChatGetChannelMemberVoiceConnectionState(void* a, void* b) { + (void)a; + (void)b; + logSoft(__func__, "SIGNED_OUT (offline)"); + return VOICECHAT_ERROR_SIGNED_OUT; } -int APS5_VABI sceVoiceChatUnregisterDaemonEventHandler(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVoiceChatGetChatChannelMemberInfoList(void* a, void* b) { + (void)a; + (void)b; + logSoft(__func__, "SIGNED_OUT (offline)"); + return VOICECHAT_ERROR_SIGNED_OUT; } -int APS5_VABI sceVoiceChatUnregisterMemberEventHandler(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVoiceChatGetChatGroupMemberInfoList(void* a, void* b) { + (void)a; + (void)b; + logSoft(__func__, "SIGNED_OUT (offline)"); + return VOICECHAT_ERROR_SIGNED_OUT; } -int APS5_VABI sceVoiceChatUnregisterMicEventHandler(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVoiceChatGetMicState(void* state) { + (void)state; + logSoft(__func__, "SIGNED_OUT (offline)"); + return VOICECHAT_ERROR_SIGNED_OUT; } -int APS5_VABI sceVoiceChatUnregisterPartyUserStateChangedHandler(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVoiceChatGetUserVoiceVolumeGain(void* a, void* gain) { + (void)a; + (void)gain; + logSoft(__func__, "SIGNED_OUT (offline)"); + return VOICECHAT_ERROR_SIGNED_OUT; } -int APS5_VABI sceVoiceChatUnregisterRangedMemberEventHandler(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVoiceChatSetUserVoiceVolumeGain(void* a, int32_t gain) { + (void)a; + (void)gain; + logSoft(__func__, "SIGNED_OUT (offline)"); + return VOICECHAT_ERROR_SIGNED_OUT; } -int APS5_VABI sceVoiceChatUnregisterVoiceEventHandler(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} +int APS5_VABI sceVoiceChatRegisterChannelEventHandler(void* a, void* b) { (void)a; (void)b; return 0; } +int APS5_VABI sceVoiceChatRegisterDaemonEventHandler(void* a, void* b) { (void)a; (void)b; return 0; } +int APS5_VABI sceVoiceChatRegisterMemberEventHandler(void* a, void* b) { (void)a; (void)b; return 0; } +int APS5_VABI sceVoiceChatRegisterPartyUserStateChangedHandler(void* a, void* b) { (void)a; (void)b; return 0; } +int APS5_VABI sceVoiceChatRegisterVoiceEventHandler(void* a, void* b) { (void)a; (void)b; return 0; } + +int APS5_VABI sceVoiceChatStartAccessibilityEvent(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatStartChannelEvent(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatStartDaemonEvent(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatStartMemberEvent(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatStartRangedMemberEvent(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatStartVoiceEvent(void* a) { (void)a; return 0; } + +int APS5_VABI sceVoiceChatStopAccessibilityEvent(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatStopChannelEvent(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatStopDaemonEvent(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatStopMemberEvent(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatStopRangedMemberEvent(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatStopVoiceEvent(void* a) { (void)a; return 0; } + +int APS5_VABI sceVoiceChatUnregisterAccessibilityEventHandler(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatUnregisterChannelEventHandler(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatUnregisterDaemonEventHandler(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatUnregisterMemberEventHandler(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatUnregisterMicEventHandler(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatUnregisterPartyUserStateChangedHandler(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatUnregisterRangedMemberEventHandler(void* a) { (void)a; return 0; } +int APS5_VABI sceVoiceChatUnregisterVoiceEventHandler(void* a) { (void)a; return 0; } } diff --git a/core/libs/prx/libSceVoiceCommand/Export.cpp b/core/libs/prx/libSceVoiceCommand/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceVoiceCommand/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVrServiceDialog/Export.cpp b/core/libs/prx/libSceVrServiceDialog/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceVrServiceDialog/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVrTracker/Export.cpp b/core/libs/prx/libSceVrTracker/Export.cpp new file mode 100644 index 000000000000..c2e4e7c8f34f --- /dev/null +++ b/core/libs/prx/libSceVrTracker/Export.cpp @@ -0,0 +1,33 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceVrTrackerCpuProcess() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceVrTrackerGetResult() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceVrTrackerGetTime() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceVrTrackerGpuSubmit() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceVrTrackerGpuWait() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceVrTrackerInit() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libSceWeb/Export.cpp b/core/libs/prx/libSceWeb/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceWeb/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceWebKit2/Export.cpp b/core/libs/prx/libSceWebKit2/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceWebKit2/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceWebKit2Secure/Export.cpp b/core/libs/prx/libSceWebKit2Secure/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceWebKit2Secure/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceWebmParserMdrw/Export.cpp b/core/libs/prx/libSceWebmParserMdrw/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceWebmParserMdrw/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceWkFontConfig/Export.cpp b/core/libs/prx/libSceWkFontConfig/Export.cpp new file mode 100644 index 000000000000..fb34206c211f --- /dev/null +++ b/core/libs/prx/libSceWkFontConfig/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceWorkspace/Export.cpp b/core/libs/prx/libSceWorkspace/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libSceWorkspace/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 2c0fe28f97ec..a70e62e048ec 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -38,10 +38,13 @@ target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Multibyte.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RuneLocale.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/StringExtensions.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/VectorAlgorithms.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Math.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/MathExact.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Random.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/FloatBuiltins.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/HostEntropy.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/DinkumwareAtomics.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Time.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/AtomicBuiltins.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/FileAndHeap.cpp @@ -95,17 +98,6 @@ if(WIN32) endif() if(BUILD_TESTING) - add_test_executable(guest_binary_word_tests ${LIBS_INCLUDE_DIR}/tests/GuestBinaryWord.cpp) - target_include_directories(guest_binary_word_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_binary_word_tests PRIVATE libc) - configure_windows_unwind(guest_binary_word_tests) - add_test(NAME guest_binary_word COMMAND guest_binary_word_tests guest_binary_word.tmp) - set_tests_properties(guest_binary_word PROPERTIES TIMEOUT 10) - if(WIN32) - set_tests_properties(guest_binary_word PROPERTIES ENVIRONMENT_MODIFICATION - "PATH=path_list_prepend:$") - endif() - if(UNIX) add_test_executable(guest_fopen_errors_tests ${LIBS_INCLUDE_DIR}/tests/GuestFopenErrors.cpp) target_include_directories(guest_fopen_errors_tests PRIVATE ${LIBS_INCLUDE_DIR}) @@ -320,6 +312,15 @@ if(BUILD_TESTING) set_tests_properties(guest_std_execute_once PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_math_exact_tests ${LIBS_INCLUDE_DIR}/tests/GuestMathExact.cpp) + target_include_directories(guest_math_exact_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_math_exact_tests PRIVATE libc) + configure_windows_unwind(guest_math_exact_tests) + add_test(NAME guest_math_exact COMMAND guest_math_exact_tests) + if(WIN32) + set_tests_properties(guest_math_exact PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_libc_threads_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibcThreads.cpp) target_include_directories(guest_libc_threads_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_libc_threads_tests PRIVATE libc libkernel) @@ -406,6 +407,15 @@ if(BUILD_TESTING) set_tests_properties(guest_environment PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_dinkumware_atomics_tests ${LIBS_INCLUDE_DIR}/tests/GuestDinkumwareAtomics.cpp) + target_include_directories(guest_dinkumware_atomics_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_dinkumware_atomics_tests PRIVATE libc) + configure_windows_unwind(guest_dinkumware_atomics_tests) + add_test(NAME guest_dinkumware_atomics COMMAND guest_dinkumware_atomics_tests) + if(WIN32) + set_tests_properties(guest_dinkumware_atomics PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_hostname_tests ${LIBS_INCLUDE_DIR}/tests/GuestHostname.cpp) target_include_directories(guest_hostname_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_hostname_tests PRIVATE libc) @@ -416,6 +426,15 @@ if(BUILD_TESTING) set_tests_properties(guest_hostname PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_vector_algorithms_tests ${LIBS_INCLUDE_DIR}/tests/GuestVectorAlgorithms.cpp) + target_include_directories(guest_vector_algorithms_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_vector_algorithms_tests PRIVATE libc) + configure_windows_unwind(guest_vector_algorithms_tests) + add_test(NAME guest_vector_algorithms COMMAND guest_vector_algorithms_tests) + if(WIN32) + set_tests_properties(guest_vector_algorithms PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_memrchr_tests ${LIBS_INCLUDE_DIR}/tests/GuestMemrchr.cpp) target_include_directories(guest_memrchr_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_memrchr_tests PRIVATE libc) @@ -749,12 +768,13 @@ if(BUILD_TESTING) add_test(NAME application_heap_invalid_metadata COMMAND application_heap_tests invalid) add_test(NAME application_heap_default COMMAND application_heap_tests default) add_test(NAME application_heap_exit_order COMMAND application_heap_tests exit-order) + add_test(NAME application_heap_scripting COMMAND application_heap_tests scripting) # The lazy mode needs process parameters that ApplicationHeap.cpp installs only on Windows and Linux. if(NOT APPLE) add_test(NAME application_heap_lazy COMMAND application_heap_tests lazy) endif() if(WIN32) - set_tests_properties(application_heap_replacement application_heap_invalid_metadata application_heap_default application_heap_exit_order application_heap_lazy + set_tests_properties(application_heap_replacement application_heap_invalid_metadata application_heap_default application_heap_exit_order application_heap_scripting application_heap_lazy PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() add_test_executable(guest_allocator_failures_tests ${LIBS_INCLUDE_DIR}/tests/GuestAllocatorFailures.cpp) diff --git a/core/libs/prx/libc/include/FileStream.hpp b/core/libs/prx/libc/include/FileStream.hpp index 6e203a265d63..b2eb126d9f1d 100644 --- a/core/libs/prx/libc/include/FileStream.hpp +++ b/core/libs/prx/libc/include/FileStream.hpp @@ -54,6 +54,7 @@ class FileStream { std::FILE* _handle; bool _dynamic; bool guestError = false; + bool randomDevice = false; public: explicit FileStream(std::FILE* handle, bool dynamic = false) : _handle(handle), _dynamic(dynamic) { @@ -109,10 +110,20 @@ class FileStream { errno = error; } + void MarkRandomDevice() { + randomDevice = true; + _guest.descriptor = -1; + } + + bool IsRandomDevice() const { + return randomDevice; + } + bool Reopen(const std::filesystem::path& filename, const char* mode) { auto* previous = GetHandle(); _guest = {}; guestError = false; + randomDevice = false; #ifdef _WIN32 std::wstring wideMode; for (const char character : std::string_view(mode)) wideMode.push_back(static_cast(character)); @@ -189,11 +200,17 @@ class FileStream { }; static_assert(std::is_standard_layout_v); -inline std::FILE* GetNativeStream(FileStream* stream) { +inline std::FILE* GetStreamHandle(FileStream* stream) { if (!stream) throw std::runtime_error("FileStream: null stream"); return stream->GetHandle(); } +inline std::FILE* GetNativeStream(FileStream* stream) { + auto* handle = GetStreamHandle(stream); + if (stream->IsRandomDevice()) throw std::runtime_error("FileStream: this function is not implemented on a /dev/random or /dev/urandom stream"); + return handle; +} + extern "C" { extern FileStream _Stdout_nid_postfix; extern FileStream _Stderr_nid_postfix; diff --git a/core/libs/prx/libc/include/General.hpp b/core/libs/prx/libc/include/General.hpp index 9e8f1124a8e7..b6b5e3854c6a 100644 --- a/core/libs/prx/libc/include/General.hpp +++ b/core/libs/prx/libc/include/General.hpp @@ -2,6 +2,7 @@ #define CORE_LIBS_PRX_LIBC_INCLUDE_GENERAL_HPP #include +#include #include #include @@ -17,6 +18,8 @@ extern "C" int KeepHostStandardError_nid_no_patch(); extern "C" int HostStandardError_nid_no_patch(); #endif +extern "C" bool CopyGuestEnvironment_nid_no_patch(const char* name, std::string& value); + extern "C" std::filesystem::path ResolvePath_nid_no_patch(const char* path); extern "C" std::filesystem::path ResolvePathOrRelinkedModule_nid_no_patch(const char* path); extern "C" std::filesystem::path RelinkedModulePath_nid_no_patch(const std::filesystem::path& path); diff --git a/core/libs/prx/libc/include/SonyCrt.hpp b/core/libs/prx/libc/include/SonyCrt.hpp index 0eadd73731ee..dee6a42018b9 100644 --- a/core/libs/prx/libc/include/SonyCrt.hpp +++ b/core/libs/prx/libc/include/SonyCrt.hpp @@ -20,6 +20,8 @@ extern "C" GuestTm* APS5_VABI localtime_s_nid_postfix(const std::int64_t* timer, GuestTm* result); +using SonyConstraintHandler = void (APS5_VABI *)(const char* message, void* pointer, int error); + namespace { template @@ -36,9 +38,9 @@ inline TInteger ParseInteger(const char* text, char** end, int base, TInteger (* #endif } -using SonyConstraintHandler = void (*)(const char* message, void* pointer, int error); +} -inline void SonyDefaultConstraintHandler(const char* message, void* pointer, int error) { +inline void APS5_VABI SonyDefaultConstraintHandler(const char* message, void* pointer, int error) { (void)message; (void)pointer; (void)error; @@ -55,6 +57,8 @@ inline SonyConstraintHandler& SonyConstraintHandlerSlot() { return handler; } +namespace { + inline void SonyInvokeConstraintHandler(const char* message, void* pointer, int error) { SonyConstraintHandler handler = nullptr; { diff --git a/core/libs/prx/libc/src/ApplicationHeap.cpp b/core/libs/prx/libc/src/ApplicationHeap.cpp index 76d72e26ed24..26224bd98d73 100644 --- a/core/libs/prx/libc/src/ApplicationHeap.cpp +++ b/core/libs/prx/libc/src/ApplicationHeap.cpp @@ -209,14 +209,16 @@ extern "C" void* APS5_VABI scriptingGetMem_nid_postfix(std::size_t alignment, st alignment = std::max(alignment, 16); if ((alignment & (alignment - 1)) != 0) return nullptr; try { - return ApplicationHeapAlign_nid_no_patch(alignment, bytes); + return GuestHeap::GuestHeapAlign_nid_postfix(alignment, bytes); } catch (const std::bad_alloc&) { return nullptr; + } catch (const std::length_error&) { + return nullptr; } } extern "C" void APS5_VABI scriptingFreeMem_nid_postfix(void* pointer) { - ApplicationHeapFree_nid_no_patch(pointer); + GuestHeap::GuestHeapFree_nid_postfix(pointer); } void* ApplicationHeapRealign_nid_no_patch(void* pointer, std::size_t bytes, std::size_t alignment) { diff --git a/core/libs/prx/libc/src/DinkumwareAtomics.cpp b/core/libs/prx/libc/src/DinkumwareAtomics.cpp new file mode 100644 index 000000000000..f8aecb774ec5 --- /dev/null +++ b/core/libs/prx/libc/src/DinkumwareAtomics.cpp @@ -0,0 +1,175 @@ +#include +#include +#include + +#include "prx/libc/include/General.hpp" + +namespace { + +constexpr int MemoryOrderSeqCst = 5; + +template +void Store(volatile TValue* target, TValue value) { + __atomic_store_n(target, value, __ATOMIC_SEQ_CST); +} + +template +TValue Load(volatile TValue* target) { + return __atomic_load_n(target, __ATOMIC_SEQ_CST); +} + +bool IsValidOrder(int order) { + return order >= 0 && order <= MemoryOrderSeqCst; +} + +template +TValue Exchange(volatile TValue* target, TValue value) { + return __atomic_exchange_n(target, value, __ATOMIC_SEQ_CST); +} + +template +int CompareExchange(volatile TValue* target, TValue* expected, TValue desired) { + return __atomic_compare_exchange_n(target, expected, desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) ? 1 : 0; +} + +void LockFlag(volatile unsigned char* flag) { + while (__atomic_exchange_n(flag, static_cast(1), __ATOMIC_ACQUIRE) != 0) { + } +} + +void UnlockFlag(volatile unsigned char* flag) { + __atomic_store_n(flag, static_cast(0), __ATOMIC_SEQ_CST); +} + +void CopyBytes(volatile void* destination, const volatile void* source, std::size_t size) { + std::memcpy(const_cast(destination), const_cast(source), size); +} + +int LockedCompareExchange(volatile unsigned char* flag, std::size_t size, volatile void* target, void* expected, const void* desired) { + LockFlag(flag); + void* object = const_cast(target); + const bool equal = std::memcmp(object, expected, size) == 0; + if (equal) { + std::memcpy(object, desired, size); + } else { + std::memcpy(expected, object, size); + } + UnlockFlag(flag); + return equal ? 1 : 0; +} + +} + +extern "C" { + +void APS5_VABI _Atomic_store_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { Store(target, value); } +void APS5_VABI _Atomic_store_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { Store(target, value); } +void APS5_VABI _Atomic_store_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int) { Store(target, value); } +void APS5_VABI _Atomic_store_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int) { Store(target, value); } + +std::uint8_t APS5_VABI _Atomic_load_1_nid_postfix(volatile std::uint8_t* target, int) { return Load(target); } +std::uint16_t APS5_VABI _Atomic_load_2_nid_postfix(volatile std::uint16_t* target, int) { return Load(target); } +std::uint64_t APS5_VABI _Atomic_load_8_nid_postfix(volatile std::uint64_t* target, int order) { + if (order == 3 || order == 4 || !IsValidOrder(order)) return 0; + return Load(target); +} + +std::uint8_t APS5_VABI _Atomic_exchange_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { return Exchange(target, value); } +std::uint16_t APS5_VABI _Atomic_exchange_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { return Exchange(target, value); } +std::uint32_t APS5_VABI _Atomic_exchange_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int) { return Exchange(target, value); } +std::uint64_t APS5_VABI _Atomic_exchange_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int order) { return IsValidOrder(order) ? Exchange(target, value) : 0; } + +int APS5_VABI _Atomic_compare_exchange_strong_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t* expected, std::uint8_t desired, int, int) { + return CompareExchange(target, expected, desired); +} + +int APS5_VABI _Atomic_compare_exchange_strong_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t* expected, std::uint16_t desired, int, int) { + return CompareExchange(target, expected, desired); +} + +int APS5_VABI _Atomic_compare_exchange_strong_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t* expected, std::uint32_t desired, int, int) { + return CompareExchange(target, expected, desired); +} + +int APS5_VABI _Atomic_compare_exchange_strong_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t* expected, std::uint64_t desired, int, int) { + return CompareExchange(target, expected, desired); +} + +int APS5_VABI _Atomic_compare_exchange_weak_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t* expected, std::uint8_t desired, int, int) { + return CompareExchange(target, expected, desired); +} + +int APS5_VABI _Atomic_compare_exchange_weak_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t* expected, std::uint16_t desired, int, int) { + return CompareExchange(target, expected, desired); +} + +int APS5_VABI _Atomic_compare_exchange_weak_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t* expected, std::uint64_t desired, int, int) { + return CompareExchange(target, expected, desired); +} + +std::uint8_t APS5_VABI _Atomic_fetch_add_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { return __atomic_fetch_add(target, value, __ATOMIC_SEQ_CST); } +std::uint16_t APS5_VABI _Atomic_fetch_add_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { return __atomic_fetch_add(target, value, __ATOMIC_SEQ_CST); } +std::uint64_t APS5_VABI _Atomic_fetch_add_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int order) { return IsValidOrder(order) ? __atomic_fetch_add(target, value, __ATOMIC_SEQ_CST) : 0; } +std::uint8_t APS5_VABI _Atomic_fetch_sub_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { return __atomic_fetch_sub(target, value, __ATOMIC_SEQ_CST); } +std::uint16_t APS5_VABI _Atomic_fetch_sub_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { return __atomic_fetch_sub(target, value, __ATOMIC_SEQ_CST); } +std::uint64_t APS5_VABI _Atomic_fetch_sub_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int order) { return IsValidOrder(order) ? __atomic_fetch_sub(target, value, __ATOMIC_SEQ_CST) : 0; } +std::uint8_t APS5_VABI _Atomic_fetch_and_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { return __atomic_fetch_and(target, value, __ATOMIC_SEQ_CST); } +std::uint16_t APS5_VABI _Atomic_fetch_and_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { return __atomic_fetch_and(target, value, __ATOMIC_SEQ_CST); } +std::uint32_t APS5_VABI _Atomic_fetch_and_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int) { return __atomic_fetch_and(target, value, __ATOMIC_SEQ_CST); } +std::uint64_t APS5_VABI _Atomic_fetch_and_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int order) { return IsValidOrder(order) ? __atomic_fetch_and(target, value, __ATOMIC_SEQ_CST) : 0; } +std::uint8_t APS5_VABI _Atomic_fetch_or_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { return __atomic_fetch_or(target, value, __ATOMIC_SEQ_CST); } +std::uint16_t APS5_VABI _Atomic_fetch_or_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { return __atomic_fetch_or(target, value, __ATOMIC_SEQ_CST); } +std::uint32_t APS5_VABI _Atomic_fetch_or_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int) { return __atomic_fetch_or(target, value, __ATOMIC_SEQ_CST); } +std::uint64_t APS5_VABI _Atomic_fetch_or_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int order) { return IsValidOrder(order) ? __atomic_fetch_or(target, value, __ATOMIC_SEQ_CST) : 0; } +std::uint8_t APS5_VABI _Atomic_fetch_xor_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { return __atomic_fetch_xor(target, value, __ATOMIC_SEQ_CST); } +std::uint16_t APS5_VABI _Atomic_fetch_xor_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { return __atomic_fetch_xor(target, value, __ATOMIC_SEQ_CST); } +std::uint32_t APS5_VABI _Atomic_fetch_xor_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int) { return __atomic_fetch_xor(target, value, __ATOMIC_SEQ_CST); } +std::uint64_t APS5_VABI _Atomic_fetch_xor_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int order) { return IsValidOrder(order) ? __atomic_fetch_xor(target, value, __ATOMIC_SEQ_CST) : 0; } + +int APS5_VABI _Atomic_is_lock_free_1_nid_postfix() { return 1; } +int APS5_VABI _Atomic_is_lock_free_2_nid_postfix() { return 1; } +int APS5_VABI _Atomic_is_lock_free_4_nid_postfix() { return 1; } +int APS5_VABI _Atomic_is_lock_free_8_nid_postfix() { return 1; } + +int APS5_VABI _Atomic_flag_test_and_set_nid_postfix(volatile unsigned char* flag, int) { + return __atomic_exchange_n(flag, static_cast(1), __ATOMIC_SEQ_CST); +} + +void APS5_VABI _Atomic_flag_clear_nid_postfix(volatile unsigned char* flag, int) { + __atomic_store_n(flag, static_cast(0), __ATOMIC_SEQ_CST); +} + +void APS5_VABI _Atomic_thread_fence_nid_postfix(int order) { + if (order == MemoryOrderSeqCst) __atomic_thread_fence(__ATOMIC_SEQ_CST); +} + +void APS5_VABI _Atomic_signal_fence_nid_postfix(int) { +} + +void APS5_VABI _Atomic_copy_nid_postfix(volatile unsigned char* flag, std::size_t size, volatile void* target, const volatile void* source, int) { + LockFlag(flag); + CopyBytes(target, source, size); + UnlockFlag(flag); +} + +void APS5_VABI _Atomic_exchange_nid_postfix(volatile unsigned char* flag, std::size_t size, volatile void* target, volatile void* value, int) { + LockFlag(flag); + auto* object = static_cast(const_cast(target)); + auto* other = static_cast(const_cast(value)); + for (std::size_t i = 0; i < size; ++i) { + const unsigned char byte = object[i]; + object[i] = other[i]; + other[i] = byte; + } + UnlockFlag(flag); +} + +int APS5_VABI _Atomic_compare_exchange_strong_nid_postfix(volatile unsigned char* flag, std::size_t size, volatile void* target, void* expected, const void* desired, int, int) { + return LockedCompareExchange(flag, size, target, expected, desired); +} + +int APS5_VABI _Atomic_compare_exchange_weak_nid_postfix(volatile unsigned char* flag, std::size_t size, volatile void* target, void* expected, const void* desired, int, int) { + return LockedCompareExchange(flag, size, target, expected, desired); +} + +} diff --git a/core/libs/prx/libc/src/Environment.cpp b/core/libs/prx/libc/src/Environment.cpp index 03996644bf0e..31abdc857bad 100644 --- a/core/libs/prx/libc/src/Environment.cpp +++ b/core/libs/prx/libc/src/Environment.cpp @@ -7,6 +7,7 @@ #include #include #include +#include "prx/libc/include/General.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #ifndef _WIN32 @@ -49,6 +50,17 @@ int EnvironmentError(int error) { errno = error; return -1; } } extern "C" { +bool CopyGuestEnvironment_nid_no_patch(const char* name, std::string& value) { + if (!ValidEnvironmentName(name)) return false; + std::lock_guard lock(environmentMutex); + for (auto& entry : Environment()) + if (entry.Matches(name)) { + value = entry.Data() + std::strlen(name) + 1; + return true; + } + return false; +} + char* APS5_VABI getenv_nid_postfix(const char* name) { if (!ValidEnvironmentName(name)) { errno = 22; return nullptr; } try { diff --git a/core/libs/prx/libc/src/FileAndHeap.cpp b/core/libs/prx/libc/src/FileAndHeap.cpp index c235383189d9..8f61659574c2 100644 --- a/core/libs/prx/libc/src/FileAndHeap.cpp +++ b/core/libs/prx/libc/src/FileAndHeap.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -21,12 +22,14 @@ #include #endif +#include "prx/libc/include/SonyCrt.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/FilesystemError.hpp" #include "prx/libc/include/HostErrno.hpp" #include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/HostEntropy.hpp" #ifdef __linux__ #include @@ -127,6 +130,7 @@ static bool IsPipeStream(std::FILE* native) { #endif using GuestCompare = int (APS5_VABI *)(const void*, const void*); +using GuestContextCompare = int (APS5_VABI *)(const void*, const void*, void*); static void SwapElements(unsigned char* left, unsigned char* right, size_t size) { for (size_t index = 0; index < size; ++index) std::swap(left[index], right[index]); @@ -136,19 +140,22 @@ static void SwapRanges(unsigned char* left, unsigned char* right, size_t bytes) if (bytes > 0) SwapElements(left, right, bytes); } -static unsigned char* MedianOfThree(unsigned char* a, unsigned char* b, unsigned char* c, GuestCompare compare) { +template +static unsigned char* MedianOfThree(unsigned char* a, unsigned char* b, unsigned char* c, Compare compare) { return compare(a, b) < 0 ? (compare(b, c) < 0 ? b : (compare(a, c) < 0 ? c : a)) : (compare(b, c) > 0 ? b : (compare(a, c) < 0 ? a : c)); } -static void InsertionSort(unsigned char* base, size_t count, size_t size, GuestCompare compare) { +template +static void InsertionSort(unsigned char* base, size_t count, size_t size, Compare compare) { for (unsigned char* next = base + size; next < base + count * size; next += size) for (unsigned char* element = next; element > base && compare(element - size, element) > 0; element -= size) SwapElements(element, element - size, size); } -static void SortElements(unsigned char* base, size_t count, size_t size, GuestCompare compare) { +template +static void SortElements(unsigned char* base, size_t count, size_t size, Compare compare) { for (;;) { if (count < 7) { InsertionSort(base, count, size, compare); @@ -302,6 +309,19 @@ FileStream* APS5_VABI fopen_nid_postfix(const char* filename, const char* mode) errno = EINVAL; return nullptr; } + if (const std::string_view path(filename); path == "/dev/random" || path == "/dev/urandom") { + if (parsed->writes) throw std::runtime_error(std::string(__func__) + ": writing to " + filename + " is not implemented"); +#ifdef _WIN32 + std::unique_ptr device(std::fopen("NUL", "rb"), std::fclose); +#else + std::unique_ptr device(std::fopen("/dev/null", "rb"), std::fclose); +#endif + if (!device) return nullptr; + auto stream = std::make_unique(device.get(), true); + device.release(); + stream->MarkRandomDevice(); + return stream.release(); + } std::filesystem::path fpath; if (const int error = ResolvePathError_nid_no_patch(filename, fpath)) { errno = error; return nullptr; } std::unique_ptr handle(OpenNative(fpath, parsed->native), std::fclose); @@ -332,7 +352,7 @@ int APS5_VABI fclose_nid_postfix(FileStream* stream) { errno = 9; return -1; } - GetNativeStream(stream); + GetStreamHandle(stream); std::unique_ptr owner(stream->IsDynamic() ? stream : nullptr); return stream->Close(); } @@ -364,7 +384,7 @@ FileStream* APS5_VABI _ZSt7_FiopenPKcNSt5_IosbIiE9_OpenmodeEi_nid_postfix(const size_t APS5_VABI fread_nid_postfix(void* buffer, size_t size, size_t count, FileStream* stream) { if (stream && !stream->IsOpen()) { errno = 9; return 0; } - auto* handle = GetNativeStream(stream); + auto* handle = GetStreamHandle(stream); if (size == 0 || count == 0) return 0; if (!buffer) throw std::runtime_error("fread: null buffer"); if (count > std::numeric_limits::max() / size) throw std::overflow_error("fread: buffer size overflow"); @@ -372,6 +392,11 @@ size_t APS5_VABI fread_nid_postfix(void* buffer, size_t size, size_t count, File if (bytes > std::numeric_limits::max() - reinterpret_cast(buffer)) throw std::overflow_error("fread: buffer address overflow"); const GuestArena::HostWrite destination(buffer, bytes); if (!destination.Open()) throw std::runtime_error("fread: buffer is not writable"); + if (stream->IsRandomDevice()) { + HostEntropy_nid_no_patch(buffer, bytes); + stream->SyncStatus(); + return count; + } const auto result = std::fread(buffer, size, count, handle); stream->SyncStatus(); return result; @@ -453,7 +478,8 @@ int APS5_VABI fputs_nid_postfix(const char* str, FileStream* stream) { int APS5_VABI fflush_nid_postfix(FileStream* stream) { if (stream && !stream->IsOpen()) { errno = 9; return -1; } - std::FILE* native = stream ? GetNativeStream(stream) : nullptr; + std::FILE* native = stream ? GetStreamHandle(stream) : nullptr; + if (stream && stream->IsRandomDevice()) return 0; #ifdef __linux__ if (native && !__fwriting(native)) return 0; #endif @@ -590,4 +616,20 @@ void APS5_VABI qsort_nid_postfix(void* base, size_t count, size_t size, int (APS SortElements(static_cast(base), count, size, compare); } +int APS5_VABI qsort_s_nid_postfix(void* base, size_t count, size_t size, GuestContextCompare compare, void* context) { + constexpr size_t RsizeMax = std::numeric_limits::max() >> 1; + const char* const violation = count > RsizeMax ? "qsort_s : n > RSIZE_MAX" + : size > RsizeMax ? "qsort_s : es > RSIZE_MAX" + : count != 0 && !base ? "qsort_s : a == NULL" + : count != 0 && !compare ? "qsort_s : cmp == NULL" + : nullptr; + if (violation) { + SonyInvokeConstraintHandler(violation, nullptr, 22); + return 22; + } + if (count > 1 && size != 0) + SortElements(static_cast(base), count, size, [compare, context](const void* left, const void* right) { return compare(left, right, context); }); + return 0; +} + } diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index ab7442b936ec..e855820768f9 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -625,10 +625,6 @@ int APS5_VABI isnanf_nid_postfix(float x) { return __isnanf_nid_postfix(x); } int APS5_VABI isnan_nid_postfix(double x) { return __isnan_nid_postfix(x); } int APS5_VABI isinf_nid_postfix(double x) { return __isinf_nid_postfix(x); } int APS5_VABI __fpclassifyf_nid_postfix(float x) { return SonyFpclassify(x); } -int APS5_VABI __fpclassifyl_nid_postfix(long double x) { - static_assert(sizeof(long double) == 16, "guest long double requires x87 extended precision storage"); - return SonyFpclassify(x); -} int APS5_VABI __signbitf_nid_postfix(float x) { return std::signbit(x) ? 1 : 0; } int APS5_VABI __signbitl_nid_postfix(long double x) { static_assert(sizeof(long double) == 16, "guest long double requires x87 extended precision storage"); diff --git a/core/libs/prx/libc/src/MathExact.cpp b/core/libs/prx/libc/src/MathExact.cpp new file mode 100644 index 000000000000..1217ca4a4c74 --- /dev/null +++ b/core/libs/prx/libc/src/MathExact.cpp @@ -0,0 +1,261 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "prx/libc/include/General.hpp" + +namespace { + +class ErrnoGuard { +public: + ErrnoGuard() : saved(errno) {} + ~ErrnoGuard() { errno = saved; } + ErrnoGuard(const ErrnoGuard&) = delete; + ErrnoGuard& operator=(const ErrnoGuard&) = delete; + +private: + int saved; +}; + +struct ExtendedBits { + std::uint64_t mantissa; + std::uint16_t signExponent; +}; + +ExtendedBits Bits(const long double& value) { + ExtendedBits bits; + unsigned char bytes[sizeof(long double)]; + std::memcpy(bytes, &value, sizeof(bytes)); + std::memcpy(&bits.mantissa, bytes, sizeof(bits.mantissa)); + std::memcpy(&bits.signExponent, bytes + 8, sizeof(bits.signExponent)); + return bits; +} + +constexpr short Denormal = -2, Finite = -1, Zero = 0, Infinite = 1, NotANumber = 2; + +short TestExtended(const ExtendedBits& bits) { + const int exponent = bits.signExponent & 0x7fff; + const std::uint64_t fraction = bits.mantissa & 0x7fffffffffffffffull; + if (exponent == 0x7fff) return fraction != 0 ? NotANumber : Infinite; + if (exponent != 0 || (bits.mantissa >> 63) != 0) return Finite; + return bits.mantissa != 0 ? Denormal : Zero; +} + +template +short TestFloat(TFloat value) { + switch (std::fpclassify(value)) { + case FP_NAN: + return NotANumber; + case FP_INFINITE: + return Infinite; + case FP_ZERO: + return Zero; + case FP_SUBNORMAL: + return Denormal; + default: + return Finite; + } +} + +template +T Remainder(T x, T y) { + const bool domain = !std::isnan(x) && !std::isnan(y) && (std::isinf(x) || y == T(0)); + T result; + { + ErrnoGuard guard; + result = std::remainder(x, y); + } + if (domain) errno = EDOM; + return result; +} + +int Exponent(std::int64_t exponent) { + return static_cast(std::clamp(exponent, INT_MIN, INT_MAX)); +} + +} + +extern "C" { + +long double APS5_VABI copysignl_nid_postfix(long double x, long double y) { return std::copysign(x, y); } +long double APS5_VABI fdiml_nid_postfix(long double x, long double y) { + if (std::isnan(x)) return x; + if (std::isnan(y)) return y; + return x > y ? x - y : 0.0L; +} + +long double APS5_VABI fmodl_nid_postfix(long double x, long double y) { + if (!std::isfinite(x) || y == 0.0L || std::isnan(y)) { + errno = EDOM; + return (x * y) / (x * y); + } + ErrnoGuard guard; + return std::fmod(x, y); +} + +long double APS5_VABI remainderl_nid_postfix(long double x, long double y) { + return Remainder(x, y); +} + +long double APS5_VABI remquol_nid_postfix(long double x, long double y, int* quotient) { + const bool domain = !std::isnan(x) && !std::isnan(y) && (std::isinf(x) || y == 0.0L); + int bits = 0; + long double result; + { + ErrnoGuard guard; + result = std::remquo(x, y, &bits); + } + if (domain) errno = EDOM; + if (!std::isnan(result)) *quotient = bits < 0 ? -(-bits & 7) : (bits & 7); + return result; +} + +long double APS5_VABI logbl_nid_postfix(long double x) { + ErrnoGuard guard; + return std::logb(x); +} + +int APS5_VABI ilogbl_nid_postfix(long double x) { + if ((Bits(x).signExponent & 0x7fff) == 0x7fff) return INT_MAX; + ErrnoGuard guard; + return std::ilogb(x); +} + +long double APS5_VABI rintl_nid_postfix(long double x) { return std::rint(x); } +long double APS5_VABI roundl_nid_postfix(long double x) { return std::isfinite(x) ? std::round(x) : x; } + +std::int64_t APS5_VABI lroundl_nid_postfix(long double x) { + ErrnoGuard guard; + return std::llround(x); +} + +std::int64_t APS5_VABI lrintl_nid_postfix(long double x) { + ErrnoGuard guard; + return std::llrint(x); +} + +long long APS5_VABI llrintl_nid_postfix(long double x) { + ErrnoGuard guard; + return std::llrint(x); +} + +long double APS5_VABI sqrtl_nid_postfix(long double x) { + return std::sqrt(x); +} + +long double APS5_VABI modfl_nid_postfix(long double x, long double* integral) { + if (std::isnan(x)) { + *integral = x; + return x; + } + return std::modf(x, integral); +} +long double APS5_VABI nanl_nid_postfix(const char*) { return std::numeric_limits::quiet_NaN(); } + +long double APS5_VABI scalblnl_nid_postfix(long double x, std::int64_t exponent) { + ErrnoGuard guard; + return std::scalbn(x, static_cast(exponent)); +} + +long double APS5_VABI fmal_nid_postfix(long double x, long double y, long double z) { + if (x == 0.0L || y == 0.0L) return x * y + z; + if (z == 0.0L) return x * y; + if (!std::isfinite(x) || !std::isfinite(y)) return x * y + z; + if (!std::isfinite(z)) return z; + int xExponent = 0, yExponent = 0, zExponent = 0; + std::frexp(x, &xExponent); + std::frexp(y, &yExponent); + std::frexp(z, &zExponent); + if (xExponent + yExponent - zExponent > 2 * std::numeric_limits::digits) return x * y; + ErrnoGuard guard; + return std::fma(x, y, z); +} + +double APS5_VABI rint_nid_postfix(double x) { return std::isnan(x) ? x + x : std::rint(x); } +float APS5_VABI rintf_nid_postfix(float x) { return std::isnan(x) ? x + x : std::rint(x); } + +std::int64_t APS5_VABI lrint_nid_postfix(double x) { + ErrnoGuard guard; + return std::llrint(x); +} + +std::int64_t APS5_VABI lrintf_nid_postfix(float x) { + ErrnoGuard guard; + return std::llrint(x); +} + +long long APS5_VABI llrint_nid_postfix(double x) { + ErrnoGuard guard; + return std::llrint(x); +} + +long long APS5_VABI llrintf_nid_postfix(float x) { + ErrnoGuard guard; + return std::llrint(x); +} + +double APS5_VABI drem_nid_postfix(double x, double y) { + return Remainder(x, y); +} + +float APS5_VABI dremf_nid_postfix(float x, float y) { + return Remainder(x, y); +} + +double APS5_VABI scalbln_nid_postfix(double x, std::int64_t exponent) { + ErrnoGuard guard; + return std::scalbn(x, Exponent(exponent)); +} + +float APS5_VABI scalblnf_nid_postfix(float x, std::int64_t exponent) { + ErrnoGuard guard; + return std::scalbn(x, Exponent(exponent)); +} + +double APS5_VABI scalb_nid_postfix(double x, double exponent) { + ErrnoGuard guard; + if (std::isnan(x) || std::isnan(exponent)) return x * exponent; + if (std::isinf(exponent)) return exponent > 0 ? x * exponent : x / -exponent; + if (std::rint(exponent) != exponent) return (exponent - exponent) / (exponent - exponent); + if (exponent > 65000.0) return std::scalbn(x, 65000); + if (exponent < -65000.0) return std::scalbn(x, -65000); + return std::scalbn(x, static_cast(exponent)); +} + +float APS5_VABI scalbf_nid_postfix(float x, float exponent) { + ErrnoGuard guard; + if (std::isnan(x) || std::isnan(exponent)) return x * exponent; + if (std::isinf(exponent)) return exponent > 0 ? x * exponent : x / -exponent; + if (std::rint(exponent) != exponent) return (exponent - exponent) / (exponent - exponent); + if (exponent > 65000.0f) return std::scalbn(x, 65000); + if (exponent < -65000.0f) return std::scalbn(x, -65000); + return std::scalbn(x, static_cast(exponent)); +} + +int APS5_VABI finite_nid_postfix(double x) { return std::isfinite(x) ? 1 : 0; } +int APS5_VABI finitef_nid_postfix(float x) { return std::isfinite(x) ? 1 : 0; } + +int APS5_VABI __fpclassifyl_nid_postfix(long double x) { + const ExtendedBits bits = Bits(x); + if ((bits.signExponent & 0x7fff) != 0 || bits.mantissa == 0) return TestExtended(bits); + return Denormal; +} +int APS5_VABI __isinfl_nid_postfix(long double x) { return TestExtended(Bits(x)) == Infinite ? 1 : 0; } +int APS5_VABI __isnanl_nid_postfix(long double x) { return TestExtended(Bits(x)) == NotANumber ? 1 : 0; } + +int APS5_VABI __isnormall_nid_postfix(long double x) { + const int exponent = Bits(x).signExponent & 0x7fff; + return exponent != 0 && exponent != 0x7fff ? 1 : 0; +} + +short APS5_VABI _LDtest_nid_postfix(const long double* value) { return TestExtended(Bits(*value)); } +int APS5_VABI _Dclass_nid_postfix(double x) { return TestFloat(x); } +int APS5_VABI _FDclass_nid_postfix(float x) { return TestFloat(x); } +int APS5_VABI _LDclass_nid_postfix(long double x) { return TestExtended(Bits(x)); } +short APS5_VABI _LDsign_nid_postfix(long double x) { return static_cast(Bits(x).signExponent & 0x8000); } + +} diff --git a/core/libs/prx/libc/src/RuntimeSupport.cpp b/core/libs/prx/libc/src/RuntimeSupport.cpp index f3509f8ff4cd..b59e9deae7ea 100644 --- a/core/libs/prx/libc/src/RuntimeSupport.cpp +++ b/core/libs/prx/libc/src/RuntimeSupport.cpp @@ -135,18 +135,18 @@ unsigned int APS5_VABI _ZSt14_Random_devicev_nid_postfix() { void APS5_VABI _Lockfilelock_nid_postfix(FileStream* stream) { if (stream == nullptr) throw std::invalid_argument("_Lockfilelock: null stream"); #ifdef _WIN32 - _lock_file(GetNativeStream(stream)); + _lock_file(GetStreamHandle(stream)); #else - flockfile(GetNativeStream(stream)); + flockfile(GetStreamHandle(stream)); #endif } void APS5_VABI _Unlockfilelock_nid_postfix(FileStream* stream) { if (stream == nullptr) throw std::invalid_argument("_Unlockfilelock: null stream"); #ifdef _WIN32 - _unlock_file(GetNativeStream(stream)); + _unlock_file(GetStreamHandle(stream)); #else - funlockfile(GetNativeStream(stream)); + funlockfile(GetStreamHandle(stream)); #endif } @@ -161,9 +161,9 @@ void APS5_VABI funlockfile_nid_postfix(FileStream* stream) { int APS5_VABI ftrylockfile_nid_postfix(FileStream* stream) { if (stream == nullptr) throw std::invalid_argument("ftrylockfile: null stream"); #ifdef _WIN32 - return TryEnterCriticalSection(&reinterpret_cast(GetNativeStream(stream))->lock) ? 0 : -1; + return TryEnterCriticalSection(&reinterpret_cast(GetStreamHandle(stream))->lock) ? 0 : -1; #else - return ftrylockfile(GetNativeStream(stream)) == 0 ? 0 : -1; + return ftrylockfile(GetStreamHandle(stream)) == 0 ? 0 : -1; #endif } diff --git a/core/libs/prx/libc/src/StandardStreams.cpp b/core/libs/prx/libc/src/StandardStreams.cpp index ec6c26c02c42..1f48e1c791df 100644 --- a/core/libs/prx/libc/src/StandardStreams.cpp +++ b/core/libs/prx/libc/src/StandardStreams.cpp @@ -171,7 +171,7 @@ int APS5_VABI setvbuf_nid_postfix(FileStream* stream, char* buffer, int mode, st buffer = nullptr; size = BUFSIZ; } - return std::setvbuf(GetNativeStream(stream), buffer, native, size); + return std::setvbuf(GetStreamHandle(stream), buffer, native, size); } void APS5_VABI setbuf_nid_postfix(FileStream* stream, char* buffer) { setvbuf_nid_postfix(stream, buffer, buffer ? 0 : 2, buffer ? 1024 : 0); diff --git a/core/libs/prx/libc/src/Strings.cpp b/core/libs/prx/libc/src/Strings.cpp index cc57f2fa359d..99b366f09aab 100644 --- a/core/libs/prx/libc/src/Strings.cpp +++ b/core/libs/prx/libc/src/Strings.cpp @@ -94,6 +94,10 @@ void APS5_VABI bzero_nid_postfix(void* destination, size_t count) { std::memset(destination, 0, count); } +void APS5_VABI bcopy_nid_postfix(const void* source, void* destination, size_t count) { + if (count != 0) std::memmove(destination, source, count); +} + void* APS5_VABI memcpy_nid_postfix(void* dest, const void* src, size_t n) { return std::memcpy(dest, src, n); } @@ -308,7 +312,9 @@ int APS5_VABI strcoll_nid_postfix(const char* s1, const char* s2) { int APS5_VABI strncpy_s_nid_postfix(char* dest, size_t destsz, const char* src, size_t count) { constexpr int GuestEinval = 22; constexpr int GuestErange = 34; + constexpr size_t RsizeMax = SIZE_MAX >> 1; if (!dest || destsz == 0) return GuestEinval; + if (destsz > RsizeMax) return GuestErange; if (!src) { dest[0] = '\0'; return GuestEinval; @@ -331,7 +337,9 @@ int APS5_VABI strcpy_s_nid_postfix(char* dest, size_t destsz, const char* src) { int APS5_VABI strncat_s_nid_postfix(char* dest, size_t destsz, const char* src, size_t count) { constexpr int GuestEinval = 22; constexpr int GuestErange = 34; + constexpr size_t RsizeMax = SIZE_MAX >> 1; if (!dest || destsz == 0) return GuestEinval; + if (destsz > RsizeMax) return GuestErange; size_t used = 0; while (used < destsz && dest[used] != '\0') ++used; if (used == destsz || !src) { diff --git a/core/libs/prx/libc/src/Time.cpp b/core/libs/prx/libc/src/Time.cpp index 76a6d1ba79a3..aa6a1678c2b0 100644 --- a/core/libs/prx/libc/src/Time.cpp +++ b/core/libs/prx/libc/src/Time.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -18,6 +19,8 @@ #include "prx/libc/include/GuestTime.hpp" #include "prx/libc/include/SonyCrt.hpp" +extern "C" char* tzname_nid_postfix[2]; + namespace { constexpr const char* WEEKDAY_NAMES[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; @@ -115,6 +118,80 @@ extern "C" int64_t APS5_VABI libc_mktime_nid_postfix(GuestTm* timeptr); namespace { +bool setHostZone(const char* zone) { +#ifdef _WIN32 + return _putenv_s("TZ", zone == nullptr ? "" : zone) == 0; +#else + return zone == nullptr ? ::unsetenv("TZ") == 0 : ::setenv("TZ", zone, 1) == 0; +#endif +} + +const char* hostZoneFor(const char* guest) { +#ifdef _WIN32 + return guest != nullptr && guest[0] == 0 ? "UTC0" : guest; +#else + return guest == nullptr ? ":/etc/localtime" : guest; +#endif +} + +std::recursive_mutex& zoneMutex() { + static std::recursive_mutex mutex; + return mutex; +} + +char** HostZoneNames(); + +void applyGuestZone() { + static bool initialized = false; + static bool present = false; + static std::string current; + if (!initialized) { + const char* host = std::getenv("TZ"); + present = host != nullptr; + if (present) current = host; + initialized = true; + } + std::string guest; + const bool guestPresent = CopyGuestEnvironment_nid_no_patch("TZ", guest); + if (guestPresent == present && (!guestPresent || current == guest)) return; + if (!setHostZone(hostZoneFor(guestPresent ? guest.c_str() : nullptr))) throw std::runtime_error("tzset: the host rejected the guest TZ"); + present = guestPresent; + current = guest; +#ifdef _WIN32 + _tzset(); +#else + ::tzset(); +#endif + char** names = HostZoneNames(); + tzname_nid_postfix[0] = names[0]; + tzname_nid_postfix[1] = names[1]; +} + +int& zoneDepth() { + static thread_local int depth = 0; + return depth; +} + +class ZoneScope { +public: + ZoneScope() : lock(zoneMutex()) { + if (zoneDepth() == 0) applyGuestZone(); + ++zoneDepth(); + } + + ~ZoneScope() { --zoneDepth(); } + + ZoneScope(const ZoneScope&) = delete; + ZoneScope& operator=(const ZoneScope&) = delete; + +private: + std::lock_guard lock; +}; + +} // namespace + +namespace { + constexpr const char* WEEKDAY_FULL_NAMES[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; constexpr const char* MONTH_FULL_NAMES[] = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; @@ -408,6 +485,7 @@ std::size_t APS5_VABI mbsrtowcs_nid_postfix(std::uint16_t* destination, const ch int APS5_VABI ___mb_cur_max_nid_postfix(); void APS5_VABI tzset_nid_postfix(void) { + const ZoneScope zone; char** names = HostZoneNames(); tzname_nid_postfix[0] = names[0]; tzname_nid_postfix[1] = names[1]; @@ -475,6 +553,7 @@ GuestTm* APS5_VABI localtime_nid_postfix(const int64_t* timer) { GuestTm* APS5_VABI localtime_s_nid_postfix(const int64_t* timer, GuestTm* result) { if (timer == nullptr || result == nullptr) return nullptr; + const ZoneScope zone; const std::time_t t = static_cast(*timer); std::tm host{}; const int callerError = errno; @@ -516,6 +595,7 @@ GuestTm* APS5_VABI gmtime_nid_postfix(const int64_t* timer) { } int64_t APS5_VABI libc_mktime_nid_postfix(GuestTm* timeptr) { + const ZoneScope zone; std::tm host = toHostTm(*timeptr); host.tm_wday = -1; const std::time_t t = std::mktime(&host); @@ -530,6 +610,7 @@ int64_t APS5_VABI mktime_nid_postfix(GuestTm* timeptr) { } size_t APS5_VABI libc_strftime_nid_postfix(char* str, size_t count, const char* format, const GuestTm* timeptr) { + const ZoneScope zone; tzset_nid_postfix(); return formatGuestTime(str, count, format, *timeptr); } diff --git a/core/libs/prx/libc/src/VectorAlgorithms.cpp b/core/libs/prx/libc/src/VectorAlgorithms.cpp new file mode 100644 index 000000000000..169c0a23d8b9 --- /dev/null +++ b/core/libs/prx/libc/src/VectorAlgorithms.cpp @@ -0,0 +1,161 @@ +#include +#include +#include +#include + +#include "prx/libc/include/General.hpp" + +namespace { + +struct MinMaxPointers { + const void* minimum; + const void* maximum; +}; + +template +TValue Read(const unsigned char* address) { + TValue value; + std::memcpy(&value, address, sizeof(value)); + return value; +} + +template +const void* Find(const void* first, const void* last, TValue value) { + auto* current = static_cast(first); + auto* end = static_cast(last); + for (; current != end; current += sizeof(TValue)) { + if (Read(current) == value) return current; + } + return end; +} + +constexpr std::uintptr_t UnsizedBlock = 32; + +template +const void* FindUnsized(const void* first, TValue value) { + auto* start = static_cast(first); + auto* block = start - (reinterpret_cast(start) & (UnsizedBlock - 1)); + for (auto* current = start; current != block + UnsizedBlock; current += sizeof(TValue)) { + if (Read(current) == value) return current; + } + auto* current = block; + while (Read(current) != value) current += sizeof(TValue); + return current; +} + +template +std::size_t Count(const void* first, const void* last, TValue value) { + auto* current = static_cast(first); + auto* end = static_cast(last); + std::size_t count = 0; + for (; current != end; current += sizeof(TValue)) { + if (Read(current) == value) ++count; + } + return count; +} + +template +MinMaxPointers MinMax(const void* first, const void* last, bool wantMinimum, bool wantMaximum) { + auto* current = static_cast(first); + auto* end = static_cast(last); + MinMaxPointers result{first, first}; + if (current == end) return result; + TValue minimum = Read(current); + TValue maximum = minimum; + for (current += sizeof(TValue); current != end; current += sizeof(TValue)) { + const TValue value = Read(current); + if (wantMinimum && value < minimum) { + minimum = value; + result.minimum = current; + } + if (wantMaximum && (wantMinimum ? !(value < maximum) : maximum < value)) { + maximum = value; + result.maximum = current; + } + } + return result; +} + +template +MinMaxPointers MinMaxBySign(const void* first, const void* last, bool isSigned, bool wantMinimum, bool wantMaximum) { + using TSigned = std::make_signed_t; + return isSigned ? MinMax(first, last, wantMinimum, wantMaximum) : MinMax(first, last, wantMinimum, wantMaximum); +} + +template +void Reverse(void* first, void* last) { + auto* low = static_cast(first); + auto* high = static_cast(last); + unsigned char buffer[Size]; + while (high - low >= static_cast(2 * Size)) { + high -= Size; + std::memcpy(buffer, low, Size); + std::memcpy(low, high, Size); + std::memcpy(high, buffer, Size); + low += Size; + } +} + +template +void ReverseCopy(const void* first, const void* last, void* destination) { + auto* begin = static_cast(first); + auto* end = static_cast(last); + auto* out = static_cast(destination); + while (end != begin) { + end -= Size; + std::memcpy(out, end, Size); + out += Size; + } +} + +} + +extern "C" { + +const void* APS5_VABI __std_find_trivial_1_nid_postfix(const void* first, const void* last, std::uint8_t value) { return Find(first, last, value); } +const void* APS5_VABI __std_find_trivial_2_nid_postfix(const void* first, const void* last, std::uint16_t value) { return Find(first, last, value); } +const void* APS5_VABI __std_find_trivial_4_nid_postfix(const void* first, const void* last, std::uint32_t value) { return Find(first, last, value); } +const void* APS5_VABI __std_find_trivial_8_nid_postfix(const void* first, const void* last, std::uint64_t value) { return Find(first, last, value); } +const void* APS5_VABI __std_find_trivial_unsized_1_nid_postfix(const void* first, std::uint8_t value) { return FindUnsized(first, value); } +const void* APS5_VABI __std_find_trivial_unsized_2_nid_postfix(const void* first, std::uint16_t value) { return FindUnsized(first, value); } +const void* APS5_VABI __std_find_trivial_unsized_4_nid_postfix(const void* first, std::uint32_t value) { return FindUnsized(first, value); } +const void* APS5_VABI __std_find_trivial_unsized_8_nid_postfix(const void* first, std::uint64_t value) { return FindUnsized(first, value); } +std::size_t APS5_VABI __std_count_trivial_1_nid_postfix(const void* first, const void* last, std::uint8_t value) { return Count(first, last, value); } +std::size_t APS5_VABI __std_count_trivial_2_nid_postfix(const void* first, const void* last, std::uint16_t value) { return Count(first, last, value); } +std::size_t APS5_VABI __std_count_trivial_4_nid_postfix(const void* first, const void* last, std::uint32_t value) { return Count(first, last, value); } +std::size_t APS5_VABI __std_count_trivial_8_nid_postfix(const void* first, const void* last, std::uint64_t value) { return Count(first, last, value); } + +const void* APS5_VABI __std_min_element_1_nid_postfix(const void* first, const void* last, bool isSigned) { return MinMaxBySign(first, last, isSigned, true, false).minimum; } +const void* APS5_VABI __std_min_element_2_nid_postfix(const void* first, const void* last, bool isSigned) { return MinMaxBySign(first, last, isSigned, true, false).minimum; } +const void* APS5_VABI __std_min_element_4_nid_postfix(const void* first, const void* last, bool isSigned) { return MinMaxBySign(first, last, isSigned, true, false).minimum; } +const void* APS5_VABI __std_min_element_8_nid_postfix(const void* first, const void* last, bool isSigned) { return MinMaxBySign(first, last, isSigned, true, false).minimum; } +const void* APS5_VABI __std_max_element_1_nid_postfix(const void* first, const void* last, bool isSigned) { return MinMaxBySign(first, last, isSigned, false, true).maximum; } +const void* APS5_VABI __std_max_element_2_nid_postfix(const void* first, const void* last, bool isSigned) { return MinMaxBySign(first, last, isSigned, false, true).maximum; } +const void* APS5_VABI __std_max_element_4_nid_postfix(const void* first, const void* last, bool isSigned) { return MinMaxBySign(first, last, isSigned, false, true).maximum; } +const void* APS5_VABI __std_max_element_8_nid_postfix(const void* first, const void* last, bool isSigned) { return MinMaxBySign(first, last, isSigned, false, true).maximum; } +MinMaxPointers APS5_VABI __std_minmax_element_1_nid_postfix(const void* first, const void* last, bool isSigned) { return MinMaxBySign(first, last, isSigned, true, true); } +MinMaxPointers APS5_VABI __std_minmax_element_2_nid_postfix(const void* first, const void* last, bool isSigned) { return MinMaxBySign(first, last, isSigned, true, true); } +MinMaxPointers APS5_VABI __std_minmax_element_4_nid_postfix(const void* first, const void* last, bool isSigned) { return MinMaxBySign(first, last, isSigned, true, true); } +MinMaxPointers APS5_VABI __std_minmax_element_8_nid_postfix(const void* first, const void* last, bool isSigned) { return MinMaxBySign(first, last, isSigned, true, true); } + +void APS5_VABI __std_reverse_trivially_swappable_1_nid_postfix(void* first, void* last) { Reverse<1>(first, last); } +void APS5_VABI __std_reverse_trivially_swappable_2_nid_postfix(void* first, void* last) { Reverse<2>(first, last); } +void APS5_VABI __std_reverse_trivially_swappable_4_nid_postfix(void* first, void* last) { Reverse<4>(first, last); } +void APS5_VABI __std_reverse_trivially_swappable_8_nid_postfix(void* first, void* last) { Reverse<8>(first, last); } +void APS5_VABI __std_reverse_copy_trivially_copyable_1_nid_postfix(const void* first, const void* last, void* destination) { ReverseCopy<1>(first, last, destination); } +void APS5_VABI __std_reverse_copy_trivially_copyable_2_nid_postfix(const void* first, const void* last, void* destination) { ReverseCopy<2>(first, last, destination); } +void APS5_VABI __std_reverse_copy_trivially_copyable_4_nid_postfix(const void* first, const void* last, void* destination) { ReverseCopy<4>(first, last, destination); } +void APS5_VABI __std_reverse_copy_trivially_copyable_8_nid_postfix(const void* first, const void* last, void* destination) { ReverseCopy<8>(first, last, destination); } + +void APS5_VABI __std_swap_ranges_trivially_swappable_noalias_nid_postfix(void* first1, void* last1, void* first2) { + auto* left = static_cast(first1); + auto* right = static_cast(first2); + const std::size_t length = static_cast(static_cast(last1) - left); + for (std::size_t i = 0; i < length; ++i) { + const unsigned char byte = left[i]; + left[i] = right[i]; + right[i] = byte; + } +} + +} diff --git a/core/libs/prx/libc/src/exception/StandardExceptions.cpp b/core/libs/prx/libc/src/exception/StandardExceptions.cpp index e6fd4e1f2368..2a4ad4aff429 100644 --- a/core/libs/prx/libc/src/exception/StandardExceptions.cpp +++ b/core/libs/prx/libc/src/exception/StandardExceptions.cpp @@ -93,6 +93,7 @@ const char* PlainWhat(const ExceptionObject* object) { if (std::strcmp(name, "St17bad_function_call") == 0) return "std::bad_function_call"; if (std::strcmp(name, "St12bad_weak_ptr") == 0) return "bad_weak_ptr"; if (std::strcmp(name, "St19bad_optional_access") == 0) return "bad optional access"; + if (std::strcmp(name, "St18bad_variant_access") == 0) return "bad_variant_access"; return name; } const char* MessageWhat(const ExceptionObject* object) { return object->message ? object->message : ""; } @@ -342,6 +343,8 @@ void APS5_VABI _ZNSt19bad_optional_accessD0Ev_nid_postfix(LibcException::Excepti const char* APS5_VABI _ZNKSt19bad_optional_access4whatEv_nid_postfix(const LibcException::ExceptionObject* self) { return LibcException::PlainWhat(self); } void APS5_VABI _ZNSt19bad_optional_accessC1Ev_nid_postfix(LibcException::ExceptionObject* self) { self->vtable = &_ZTVSt19bad_optional_access_nid_postfix.destroy; } void APS5_VABI _ZNSt19bad_optional_accessC2Ev_nid_postfix(LibcException::ExceptionObject* self) { self->vtable = &_ZTVSt19bad_optional_access_nid_postfix.destroy; } +LibcException::TypeRecord _ZTISt18bad_variant_access_nid_postfix {LibcException::SingleTypeVtable + 2, "St18bad_variant_access", &_ZTISt9exception_nid_postfix}; +LibcException::ExceptionVtable _ZTVSt18bad_variant_access_nid_postfix {0, &_ZTISt18bad_variant_access_nid_postfix, LibcException::GuestDestroyPlain_nid_no_patch, LibcException::GuestDeletePlain_nid_no_patch, LibcException::GuestPlainWhat_nid_no_patch}; LibcException::TypeRecord _ZTISt11logic_error_nid_postfix {LibcException::SingleTypeVtable + 2, "St11logic_error", &_ZTISt9exception_nid_postfix}; LibcException::ExceptionVtable _ZTVSt11logic_error_nid_postfix {0, &_ZTISt11logic_error_nid_postfix, LibcException::GuestDestroyMessage_nid_no_patch, LibcException::GuestDeleteMessage_nid_no_patch, LibcException::GuestMessageWhat_nid_no_patch}; @@ -479,6 +482,7 @@ const char* APS5_VABI _ZNKSt8ios_base7failure4whatEv_nid_postfix(const LibcExcep [[noreturn]] void APS5_VABI _ZNSt8__sce_v219_Xbad_function_callEv_nid_postfix() { LibcException::ThrowPlain(_ZTVSt17bad_function_call_nid_postfix); } [[noreturn]] void APS5_VABI _ZSt19_Throw_bad_weak_ptrv_nid_postfix() { LibcException::ThrowPlain(_ZTVSt12bad_weak_ptr_nid_postfix); } [[noreturn]] void APS5_VABI _ZSt26_Throw_bad_optional_accessv_nid_postfix() { LibcException::ThrowPlain(_ZTVSt19bad_optional_access_nid_postfix); } +[[noreturn]] void APS5_VABI _ZSt26__throw_bad_variant_accessv_nid_postfix() { LibcException::ThrowPlain(_ZTVSt18bad_variant_access_nid_postfix); } [[noreturn]] void APS5_VABI _ZSt13_Xregex_errorNSt15regex_constants10error_typeE_nid_postfix(std::regex_constants::error_type code) { struct RegexObject { LibcException::ExceptionObject base; std::regex_constants::error_type code; }; auto* object = static_cast(__cxa_allocate_exception_nid_postfix(sizeof(RegexObject))); diff --git a/core/libs/prx/libc/src/exception/Unwind.cpp b/core/libs/prx/libc/src/exception/Unwind.cpp index 509e41bdf19c..3c08a5069b2d 100644 --- a/core/libs/prx/libc/src/exception/Unwind.cpp +++ b/core/libs/prx/libc/src/exception/Unwind.cpp @@ -105,15 +105,22 @@ bool FindFrameFast(Lookup& query) { if (header[0] != 1) return false; static std::mutex mutex; static std::map, std::pair> bases; + const std::tuple key {header, static_cast(object.dlfo_map_start), static_cast(object.dlfo_map_end)}; { std::lock_guard lock(mutex); - const std::tuple key {header, static_cast(object.dlfo_map_start), static_cast(object.dlfo_map_end)}; auto found = bases.find(key); - if (found == bases.end()) { - Lookup probe {query.pc}; - dl_iterate_phdr(FindFrame, &probe); - found = bases.emplace(key, std::pair {probe.text, probe.data}).first; + if (found != bases.end()) { + query.text = found->second.first; + query.data = found->second.second; + SearchHeader(header, query); + return true; } + } + Lookup probe {query.pc}; + dl_iterate_phdr(FindFrame, &probe); + { + std::lock_guard lock(mutex); + const auto found = bases.emplace(key, std::pair {probe.text, probe.data}).first; query.text = found->second.first; query.data = found->second.second; } diff --git a/core/libs/prx/libkernel/Aio/src/Aio.cpp b/core/libs/prx/libkernel/Aio/src/Aio.cpp index b30777014d4a..d09937b56a58 100644 --- a/core/libs/prx/libkernel/Aio/src/Aio.cpp +++ b/core/libs/prx/libkernel/Aio/src/Aio.cpp @@ -128,9 +128,9 @@ int SubmitCommands(KernelAioRwRequest* req, std::int32_t size, bool write, std:: } int SubmitCommandsMultiple(KernelAioRwRequest* req, std::int32_t size, bool write, std::int32_t* id, const char* name) { + if (req != nullptr && id != nullptr && size > AioRequestNumMax) throw std::runtime_error(std::string(name) + ": more than 128 requests per batch is not modelled"); const int error = ValidateRequests(req, size, id); if (error != 0) return error; - if (size > AioRequestNumMax) throw std::runtime_error(std::string(name) + ": more than 128 requests per batch is not modelled"); for (std::int32_t i = 0; i < size; ++i) { const std::int32_t queue = AllocateId(); SetState(queue, RunRequest(req[i], write) ? AioCompleted : AioAborted); diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 2eff9cfb6a8f..8229477efab0 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -1449,6 +1449,16 @@ if(BUILD_TESTING) set_tests_properties(guest_memory guest_mmap_fixed_anonymous PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_protection_report_tests ${LIBS_INCLUDE_DIR}/tests/GuestProtectionReport.cpp) + target_include_directories(guest_protection_report_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_protection_report_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_protection_report_tests) + add_test(NAME guest_protection_report COMMAND guest_protection_report_tests) + set_tests_properties(guest_protection_report PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_protection_report PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_flexible_memory_budget_tests ${LIBS_INCLUDE_DIR}/tests/GuestFlexibleMemoryBudget.cpp) target_include_directories(guest_flexible_memory_budget_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_flexible_memory_budget_tests PRIVATE libkernel libc) diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp index 533e4a623199..317e6e04a622 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp @@ -909,11 +909,18 @@ void RecordReservation(const void* addr, size_t len) { g_reservations.emplace(start, start + len); } +int ReportedProtection(int prot) { + if ((prot & 0x2) != 0) prot |= 0x1; + if ((prot & 0x80) != 0) prot |= 0x40; + if ((prot & 0x200) != 0) prot |= 0x100; + return prot; +} + void RecordProtection(const void* addr, size_t len, int prot) { const auto start = reinterpret_cast(addr); std::lock_guard lock(g_protectionLock); EraseProtections(start, start + len); - if (prot >= 0) g_protections.emplace(start, ProtectedRange{start + len, prot}); + if (prot >= 0) g_protections.emplace(start, ProtectedRange{start + len, ReportedProtection(prot)}); } void* MapAligned(void* addr, size_t len, int prot, int flags, size_t alignment) { diff --git a/core/libs/prx/libkernel/DirectMemory/Export.cpp b/core/libs/prx/libkernel/DirectMemory/Export.cpp index 8f1306a48744..8991e4dd516c 100644 --- a/core/libs/prx/libkernel/DirectMemory/Export.cpp +++ b/core/libs/prx/libkernel/DirectMemory/Export.cpp @@ -127,7 +127,20 @@ int _mapFlexible(void** addr, size_t len, int prot, int flags) { const size_t configured = _configuredFlexibleSize(); const size_t images = GuestImageSegmentBytes_nid_no_patch(); std::lock_guard lock(g_flexibleLock); - const size_t used = images + _flexibleUsedLocked(); + size_t replaced = 0; + constexpr int MapFixed = 0x10; + constexpr int MapNoOverwrite = 0x80; + if (addr != nullptr && *addr != nullptr && (flags & (MapFixed | MapNoOverwrite)) == MapFixed) { + const auto start = reinterpret_cast(*addr); + if (len <= std::numeric_limits::max() - start) { + const auto end = start + len; + for (const auto& [rangeStart, rangeLength] : g_flexibleRanges) { + const auto rangeEnd = rangeStart + rangeLength; + if (rangeStart < end && rangeEnd > start) replaced += std::min(end, rangeEnd) - std::max(start, rangeStart); + } + } + } + const size_t used = images + _flexibleUsedLocked() - replaced; if (len > configured - std::min(configured, used)) { std::fprintf(stderr, "[memory] flexible memory exhausted: request 0x%zx with 0x%zx of 0x%zx in use\n", len, used, configured); return SCE_KERNEL_ERROR_ENOMEM; diff --git a/core/libs/prx/libkernel/Equeue/src/Kqueue.cpp b/core/libs/prx/libkernel/Equeue/src/Kqueue.cpp index 20859f0b23d6..d03afadb9e0b 100644 --- a/core/libs/prx/libkernel/Equeue/src/Kqueue.cpp +++ b/core/libs/prx/libkernel/Equeue/src/Kqueue.cpp @@ -1,6 +1,7 @@ #include "prx/libkernel/Equeue/Equeue.hpp" #include "prx/libc/include/General.hpp" #include "prx/libkernel/File/include/File.hpp" +#include "prx/libkernel/File/include/HostStreams.hpp" #include "prx/libkernel/Socket/include/SocketRuntime.hpp" #include "prx/libkernel/Time/include/TimedWait.hpp" @@ -140,6 +141,17 @@ std::int64_t DescriptorLowWater(const KernelEvent& change) { return change.data; } +int AddStandardInputKnote(const KernelEqueueRef& queue, KernelEvent& change, std::uint16_t flags) { + KernelEqueueEvent event{}; + event.event = change; + event.event.flags = flags; + event.event.fflags = 0; + event.event.data = 0; + change.fflags = 0; + change.data = 0; + return GuestErrno(queue->AddEvent(event)); +} + int AddDescriptorKnote(const KernelEqueueRef& queue, KernelEvent& change, std::uint16_t flags) { const int descriptor = static_cast(change.ident); if (IsKqueueDescriptor(descriptor)) { @@ -148,6 +160,9 @@ int AddDescriptorKnote(const KernelEqueueRef& queue, KernelEvent& change, std::u } NotImplemented_nid_no_patch("kevent: EVFILT_READ on a kqueue descriptor"); } + if (descriptor == 0 && change.filter == EVFILT_READ && File::IsHostStream(descriptor)) { + return AddStandardInputKnote(queue, change, flags); + } const bool socket = GuestSockets::IsOpen(descriptor); std::int64_t data = 0; bool eof = false; diff --git a/core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp b/core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp index 4381004897e9..604d0d64c7f2 100644 --- a/core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp +++ b/core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp @@ -1,12 +1,18 @@ #include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libkernel/Time/include/Time.hpp" #include "prx/libkernel/Time/include/TimedWait.hpp" #include +#include #include +#include +#include #include +#include +#include #include #include @@ -27,6 +33,15 @@ static constexpr uint32_t EVF_WAITMODE_OR = 0x02; static constexpr uint32_t EVF_WAITMODE_CLEAR_ALL = 0x10; static constexpr uint32_t EVF_WAITMODE_CLEAR_PAT = 0x20; +struct WaiterNode { + uint64_t bitPattern = 0; + uint32_t waitMode = 0; + uint64_t resultPattern = 0; + bool satisfied = false; + WaiterNode* prev = nullptr; + WaiterNode* next = nullptr; +}; + struct KernelEventFlagPrivate { std::mutex _mutex; TimedWait::Condition _changed; @@ -36,8 +51,56 @@ struct KernelEventFlagPrivate { bool _deleted = false; int _waiters = 0; uint64_t _cancelGeneration = 0; + WaiterNode* _waiterHead = nullptr; + WaiterNode* _waiterTail = nullptr; + + void pushWaiter(WaiterNode* node) { + node->prev = _waiterTail; + node->next = nullptr; + if (_waiterTail) { + _waiterTail->next = node; + } else { + _waiterHead = node; + } + _waiterTail = node; + } + + void removeWaiter(WaiterNode* node) { + if (node->prev) { + node->prev->next = node->next; + } else if (_waiterHead == node) { + _waiterHead = node->next; + } + if (node->next) { + node->next->prev = node->prev; + } else if (_waiterTail == node) { + _waiterTail = node->prev; + } + node->prev = nullptr; + node->next = nullptr; + } }; +namespace { + +std::shared_mutex eventFlagRegistryMutex; +std::unordered_map> eventFlagRegistry; + +std::shared_ptr ResolveEventFlag(KernelEventFlag handle) { + if (!handle) return {}; + std::shared_lock lock(eventFlagRegistryMutex); + const auto it = eventFlagRegistry.find(handle); + return it == eventFlagRegistry.end() ? std::shared_ptr{} : it->second; +} + +void RetireEventFlag(KernelEventFlag handle, const std::shared_ptr& owner) { + std::unique_lock lock(eventFlagRegistryMutex); + const auto it = eventFlagRegistry.find(handle); + if (it != eventFlagRegistry.end() && it->second == owner) eventFlagRegistry.erase(it); +} + +} + static bool TraceSync() { static const bool enabled = std::getenv("APS5_TRACE_SYNC") != nullptr; return enabled; @@ -67,43 +130,63 @@ int APS5_VABI sceKernelCreateEventFlag(KernelEventFlag* ef, const char* name, ui if (!ef || !name) return SCE_KERNEL_ERROR_EINVAL; if ((attr & EVF_ATTR_SINGLE) && (attr & EVF_ATTR_MULTI)) return SCE_KERNEL_ERROR_EINVAL; if ((attr & ~0x33u) != 0) return SCE_KERNEL_ERROR_EINVAL; - auto* flag = new (std::nothrow) KernelEventFlagPrivate(); - if (!flag) return SCE_KERNEL_ERROR_ENOMEM; - flag->_name = name; - flag->_pattern = init_pattern; - flag->_multipleWaiters = (attr & EVF_ATTR_MULTI) != 0; - *ef = flag; - if (TraceSync()) std::fprintf(stderr, "[evf] create %p '%s' attr=0x%x init=0x%llx\n", static_cast(flag), name, attr, static_cast(init_pattern)); + std::shared_ptr flag; + try { + flag = std::make_shared(); + flag->_name = name; + flag->_pattern = init_pattern; + flag->_multipleWaiters = (attr & EVF_ATTR_MULTI) != 0; + std::unique_lock lock(eventFlagRegistryMutex); + eventFlagRegistry.emplace(flag.get(), flag); + } catch (const std::bad_alloc&) { + return SCE_KERNEL_ERROR_ENOMEM; + } + *ef = flag.get(); + if (TraceSync()) std::fprintf(stderr, "[evf] create %p '%s' attr=0x%x init=0x%llx\n", static_cast(flag.get()), name, attr, static_cast(init_pattern)); return SCE_OK; } int APS5_VABI sceKernelDeleteEventFlag(KernelEventFlag ef) { if (!ef) return SCE_KERNEL_ERROR_ESRCH; - { - std::lock_guard lock(ef->_mutex); - if (ef->_deleted) return SCE_KERNEL_ERROR_ESRCH; - ef->_deleted = true; - if (ef->_waiters != 0) { - ef->_changed.NotifyAll(); - return SCE_OK; - } - } - delete ef; + const auto keepAlive = ResolveEventFlag(ef); + if (!keepAlive) return SCE_KERNEL_ERROR_ESRCH; + std::unique_lock lock(ef->_mutex); + if (ef->_deleted) return SCE_KERNEL_ERROR_ESRCH; + ef->_deleted = true; + ef->_changed.NotifyAll(); + ef->_changed.Wait(lock, [&] { return ef->_waiters == 0; }); + lock.unlock(); + RetireEventFlag(ef, keepAlive); return SCE_OK; } int APS5_VABI sceKernelSetEventFlag(KernelEventFlag ef, uint64_t bit_pattern) { if (!ef) return SCE_KERNEL_ERROR_ESRCH; + const auto keepAlive = ResolveEventFlag(ef); + if (!keepAlive) return SCE_KERNEL_ERROR_ESRCH; std::lock_guard lock(ef->_mutex); if (ef->_deleted) return SCE_KERNEL_ERROR_ESRCH; ef->_pattern |= bit_pattern; if (TraceSync()) std::fprintf(stderr, "[evf] set %p '%s' |=0x%llx -> 0x%llx caller=%p\n", static_cast(ef), ef->_name.c_str(), static_cast(bit_pattern), static_cast(ef->_pattern), __builtin_return_address(0)); + for (auto* waiter = ef->_waiterHead; waiter != nullptr; waiter = waiter->next) { + if (!waiter->satisfied && IsSatisfied(ef->_pattern, waiter->bitPattern, waiter->waitMode)) { + waiter->satisfied = true; + waiter->resultPattern = ef->_pattern; + if (waiter->waitMode & EVF_WAITMODE_CLEAR_ALL) { + ef->_pattern = 0; + } else if (waiter->waitMode & EVF_WAITMODE_CLEAR_PAT) { + ef->_pattern &= ~waiter->bitPattern; + } + } + } ef->_changed.NotifyAll(); return SCE_OK; } int APS5_VABI sceKernelClearEventFlag(KernelEventFlag ef, uint64_t bit_pattern) { if (!ef) return SCE_KERNEL_ERROR_ESRCH; + const auto keepAlive = ResolveEventFlag(ef); + if (!keepAlive) return SCE_KERNEL_ERROR_ESRCH; std::lock_guard lock(ef->_mutex); if (ef->_deleted) return SCE_KERNEL_ERROR_ESRCH; ef->_pattern &= bit_pattern; @@ -112,6 +195,8 @@ int APS5_VABI sceKernelClearEventFlag(KernelEventFlag ef, uint64_t bit_pattern) int APS5_VABI sceKernelCancelEventFlag(KernelEventFlag ef, uint64_t set_pattern, int* num_wait_threads) { if (!ef) return SCE_KERNEL_ERROR_ESRCH; + const auto keepAlive = ResolveEventFlag(ef); + if (!keepAlive) return SCE_KERNEL_ERROR_ESRCH; std::lock_guard lock(ef->_mutex); if (ef->_deleted) return SCE_KERNEL_ERROR_ESRCH; if (num_wait_threads) *num_wait_threads = ef->_waiters; @@ -123,6 +208,8 @@ int APS5_VABI sceKernelCancelEventFlag(KernelEventFlag ef, uint64_t set_pattern, int APS5_VABI sceKernelPollEventFlag(KernelEventFlag ef, uint64_t bit_pattern, uint32_t wait_mode, uint64_t* result_pat) { if (!ef) return SCE_KERNEL_ERROR_ESRCH; + const auto keepAlive = ResolveEventFlag(ef); + if (!keepAlive) return SCE_KERNEL_ERROR_ESRCH; if (bit_pattern == 0 || !IsValidWaitMode(wait_mode)) return SCE_KERNEL_ERROR_EINVAL; std::lock_guard lock(ef->_mutex); if (ef->_deleted) return SCE_KERNEL_ERROR_ESRCH; @@ -137,14 +224,33 @@ int APS5_VABI sceKernelPollEventFlag(KernelEventFlag ef, uint64_t bit_pattern, u int APS5_VABI sceKernelWaitEventFlag(KernelEventFlag ef, uint64_t bit_pattern, uint32_t wait_mode, uint64_t* result_pat, KernelUseconds* timeout) { if (!ef) return SCE_KERNEL_ERROR_ESRCH; + const auto keepAlive = ResolveEventFlag(ef); + if (!keepAlive) return SCE_KERNEL_ERROR_ESRCH; if (bit_pattern == 0 || !IsValidWaitMode(wait_mode)) return SCE_KERNEL_ERROR_EINVAL; std::unique_lock lock(ef->_mutex); if (ef->_deleted) return SCE_KERNEL_ERROR_ESRCH; if (!ef->_multipleWaiters && ef->_waiters != 0) return SCE_KERNEL_ERROR_EPERM; + if (IsSatisfied(ef->_pattern, bit_pattern, wait_mode)) { + Consume(ef, bit_pattern, wait_mode, result_pat); + return SCE_OK; + } const uint64_t generation = ef->_cancelGeneration; if (TraceSync()) std::fprintf(stderr, "[evf] wait %p '%s' want=0x%llx mode=0x%x have=0x%llx timeout=%d caller=%p outer=%p\n", static_cast(ef), ef->_name.c_str(), static_cast(bit_pattern), wait_mode, static_cast(ef->_pattern), timeout ? static_cast(*timeout) : -1, __builtin_return_address(0), static_cast(static_cast(__builtin_frame_address(0))[0])[1]); - const auto released = [&] { return ef->_deleted || ef->_cancelGeneration != generation || IsSatisfied(ef->_pattern, bit_pattern, wait_mode); }; ++ef->_waiters; + WaiterNode waiter; + waiter.bitPattern = bit_pattern; + waiter.waitMode = wait_mode; + ef->pushWaiter(&waiter); + struct WaiterGuard { + KernelEventFlagPrivate* flag; + WaiterNode* node; + ~WaiterGuard() { + flag->removeWaiter(node); + --flag->_waiters; + flag->_changed.NotifyAll(); + } + } waiterGuard{ef, &waiter}; + const auto released = [&] { return ef->_deleted || ef->_cancelGeneration != generation || waiter.satisfied; }; bool timedOut = false; const auto waitStart = std::chrono::steady_clock::now(); if (timeout) { @@ -155,23 +261,26 @@ int APS5_VABI sceKernelWaitEventFlag(KernelEventFlag ef, uint64_t bit_pattern, u ef->_changed.Wait(lock, released); } KernelTraceWait_nid_postfix("evf", __builtin_return_address(0), static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - waitStart).count()), timedOut); - --ef->_waiters; if (ef->_deleted) { - const bool last = ef->_waiters == 0; - lock.unlock(); - if (last) delete ef; return SCE_KERNEL_ERROR_EACCES; } if (ef->_cancelGeneration != generation) { if (result_pat) *result_pat = ef->_pattern; return SCE_KERNEL_ERROR_ECANCELED; } - if (timedOut) { + if (timedOut && !waiter.satisfied) { if (result_pat) *result_pat = ef->_pattern; return SCE_KERNEL_ERROR_ETIMEDOUT; } - Consume(ef, bit_pattern, wait_mode, result_pat); + if (result_pat) *result_pat = waiter.resultPattern; return SCE_OK; } } + +extern "C" int APS5_Test_GetEventFlagWaiterCount(KernelEventFlag handle) { + const auto keepAlive = ResolveEventFlag(handle); + if (!keepAlive) return -1; + std::lock_guard lock(keepAlive->_mutex); + return keepAlive->_waiters; +} diff --git a/core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp b/core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp index 4979c63c067f..b1a9ec0b2a4c 100644 --- a/core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp +++ b/core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp @@ -114,7 +114,7 @@ std::optional SeekDirectoryDescriptor(int fd, std::int64_t offset, errno = EINVAL; return std::int64_t{-1}; } - if (whence == SeekEnd) Load(state); + if (whence == SeekEnd) Load(state, fd); const auto cursor = static_cast(whence == SeekEnd ? state.entries.size() : state.cursor); if (whence == SeekCurrent && offset == 0) return cursor; if ((whence == SeekCurrent || whence == SeekEnd) && offset > std::numeric_limits::max() - cursor) { diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index b1c486aa1449..01aaf6f5d74e 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -494,7 +494,7 @@ extern "C" int APS5_VABI pipe_nid_postfix(int* descriptors) { if (!destination.Open()) return PosixFailure(GUEST_EFAULT); int native[2]; #ifdef _WIN32 - const int result = ::_pipe(native, 4096, _O_BINARY); + const int result = ::_pipe(native, 65536, _O_BINARY); #else const int result = ::pipe(native); #endif @@ -979,6 +979,9 @@ int APS5_VABI sceKernelGetdirentries(int fd, char* buf, int nbytes, int64_t* bas if (WithoutParameterHandler([fd] { return ::_get_osfhandle(fd); }) == -1) return SceErrorFromErrno(GUEST_EBADF); return SceErrorFromErrno(GUEST_EINVAL); } + const GuestArena::HostWrite destination(buf, static_cast(nbytes)); + const GuestArena::HostWrite baseDestination(basep, basep == nullptr ? 0 : sizeof(*basep)); + if (!destination.Open() || !baseDestination.Open()) return SceErrorFromErrno(GUEST_EFAULT); if (basep != nullptr) *basep = 0; return File::ReadDirectoryDescriptor(fd, buf, nbytes); } @@ -1017,6 +1020,12 @@ int APS5_VABI sceKernelGetdirentries(int fd, char* buf, int nbytes, int64_t* bas constexpr std::size_t GuestMaxName = 255; if (buf == nullptr) return SceErrorFromErrno(GUEST_EFAULT); if (nbytes <= 0) return SceErrorFromErrno(GUEST_EINVAL); + struct stat status{}; + if (::fstat(fd, &status) != 0) return SceErrorFromErrno(errno); + if (!S_ISDIR(status.st_mode)) return SceErrorFromErrno(GUEST_EINVAL); + const GuestArena::HostWrite destination(buf, static_cast(nbytes)); + const GuestArena::HostWrite baseDestination(basep, basep == nullptr ? 0 : sizeof(*basep)); + if (!destination.Open() || !baseDestination.Open()) return SceErrorFromErrno(GUEST_EFAULT); const off_t base = ::lseek(fd, 0, SEEK_CUR); if (base < 0) return SceErrorFromErrno(errno); if (basep != nullptr) *basep = static_cast(base); @@ -1293,24 +1302,8 @@ int64_t APS5_VABI pwritev_nid_postfix(int d, const KernelIovec* iov, int iovcnt, int APS5_VABI sceKernelRename(const char* from, const char* to) { if (!from || !to) return SceErrorFromErrno(GUEST_EFAULT); - if (*from == '\0' || *to == '\0') return SceErrorFromErrno(GUEST_ENOENT); - std::filesystem::path source; - if (const int error = ResolvePathError_nid_no_patch(from, source)) return SceKernelError(error); - std::error_code error; - const auto sourceStatus = std::filesystem::status(source, error); - if (!std::filesystem::exists(sourceStatus)) return SceErrorFromErrno(GUEST_ENOENT); - std::filesystem::path destination; - if (const int result = ResolvePathError_nid_no_patch(to, destination)) return SceKernelError(result); - bool replacesFile = false; -#ifdef _WIN32 - const auto destinationStatus = std::filesystem::symlink_status(destination, error); - replacesFile = !std::filesystem::is_directory(sourceStatus) && std::filesystem::exists(destinationStatus) && !std::filesystem::is_directory(destinationStatus); -#endif - error = RenameNative_nid_no_patch(source, destination, replacesFile); - if (error) return SceErrorFromErrno(GUEST_EIO); - RecordWrittenPath_nid_no_patch(source); - RecordWrittenPath_nid_no_patch(destination); - return 0; + const int result = RenameGuestPath_nid_no_patch(from, to); + return result == 0 ? 0 : SceKernelError(errno); } int APS5_VABI sceKernelRmdir(const char* path) { diff --git a/core/libs/prx/libkernel/Pthread/src/Thread.cpp b/core/libs/prx/libkernel/Pthread/src/Thread.cpp index 0f6b251a9b38..3cdc574104f7 100644 --- a/core/libs/prx/libkernel/Pthread/src/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Thread.cpp @@ -188,17 +188,23 @@ static bool HostStackLimits(std::uintptr_t* low, std::uintptr_t* high) { *high = reinterpret_cast(pthread_get_stackaddr_np(self)); *low = *high - pthread_get_stacksize_np(self); #else - pthread_attr_t attr; - if (pthread_getattr_np(pthread_self(), &attr) != 0) - return false; - void* address = nullptr; - std::size_t size = 0; - const bool queried = pthread_attr_getstack(&attr, &address, &size) == 0; - pthread_attr_destroy(&attr); - if (!queried) - return false; - *low = reinterpret_cast(address); - *high = *low + size; + static thread_local std::uintptr_t stackLow = 0; + static thread_local std::uintptr_t stackHigh = 0; + if (stackHigh == 0) { + pthread_attr_t attr; + if (pthread_getattr_np(pthread_self(), &attr) != 0) + return false; + void* address = nullptr; + std::size_t size = 0; + const bool queried = pthread_attr_getstack(&attr, &address, &size) == 0; + pthread_attr_destroy(&attr); + if (!queried || size == 0) + return false; + stackLow = reinterpret_cast(address); + stackHigh = stackLow + size; + } + *low = stackLow; + *high = stackHigh; #endif return *high > *low; } diff --git a/core/libs/prx/libkernel/System/src/Process.cpp b/core/libs/prx/libkernel/System/src/Process.cpp index eb917d95f44c..2a1981abbf7d 100644 --- a/core/libs/prx/libkernel/System/src/Process.cpp +++ b/core/libs/prx/libkernel/System/src/Process.cpp @@ -463,11 +463,19 @@ int APS5_VABI sched_get_priority_min_nid_postfix(int policy) { } int APS5_VABI sysarch(int operation, void* parameters) { + constexpr int i386GetFsBase = 7; + constexpr int i386GetGsBase = 9; constexpr int getFsBase = 128; constexpr int getGsBase = 130; constexpr int setGsBase = 131; constexpr std::uint64_t userAddressEnd = 0x0000800000000000ull; - if (operation != getFsBase && operation != getGsBase && operation != setGsBase) + constexpr std::array definedOperations{0, 1, 3, 4, 7, 8, 9, 10, 11, 128, 129, 130, 131, 132}; + if (std::find(definedOperations.begin(), definedOperations.end(), operation) == definedOperations.end()) { + *__error_nid_postfix() = guestInvalid; + return -1; + } + const bool i386 = operation == i386GetFsBase || operation == i386GetGsBase; + if (!i386 && operation != getFsBase && operation != getGsBase && operation != setGsBase) NotImplemented_nid_no_patch(("sysarch: operation " + std::to_string(operation)).c_str()); #if defined(__linux__) && defined(__x86_64__) if (parameters == nullptr) { @@ -484,8 +492,14 @@ int APS5_VABI sysarch(int operation, void* parameters) { if (::syscall(SYS_arch_prctl, ARCH_SET_GS, base) != 0) throw std::system_error(errno, std::generic_category(), "sysarch: setting the GS base"); return 0; } - if (::syscall(SYS_arch_prctl, operation == getFsBase ? ARCH_GET_FS : ARCH_GET_GS, &base) != 0) + const bool fs = operation == getFsBase || operation == i386GetFsBase; + if (::syscall(SYS_arch_prctl, fs ? ARCH_GET_FS : ARCH_GET_GS, &base) != 0) throw std::system_error(errno, std::generic_category(), "sysarch: reading a segment base"); + if (i386) { + const auto low = static_cast(base); + std::memcpy(parameters, &low, sizeof(low)); + return 0; + } std::memcpy(parameters, &base, sizeof(base)); return 0; #else diff --git a/core/libs/prx/libkernel/System/src/Signals.cpp b/core/libs/prx/libkernel/System/src/Signals.cpp index 36678ba19659..def7bb030fad 100644 --- a/core/libs/prx/libkernel/System/src/Signals.cpp +++ b/core/libs/prx/libkernel/System/src/Signals.cpp @@ -271,6 +271,7 @@ extern "C" int APS5_VABI _sigprocmask_nid_postfix(int how, const GuestSignalSet* } namespace { +constexpr std::int64_t MaxItimerSeconds = 0x7fffffff / 2; struct AlarmState { std::mutex lock; std::condition_variable changed; @@ -347,6 +348,10 @@ void NotifyAlarms(AlarmState& alarms) { } extern "C" unsigned int APS5_VABI GuestAlarm_nid_no_patch(unsigned int seconds) { + if (seconds > MaxItimerSeconds) { + *__error_nid_postfix() = 22; + return static_cast(-1); + } auto& alarms = Alarms(); std::lock_guard lock(alarms.lock); const auto now = std::chrono::steady_clock::now(); @@ -399,7 +404,6 @@ extern "C" int APS5_VABI getitimer_nid_postfix(int which, GuestItimerval* output namespace { constexpr int ItimerReal = 0; constexpr int ItimerProf = 2; -constexpr std::int64_t MaxItimerSeconds = 0x7fffffff / 2; constexpr std::int64_t MinExactIntervalMicros = 1000; bool ValidItimer(const KernelTimeval& time) { diff --git a/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp b/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp index 895a78c599e5..78e89552b220 100644 --- a/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp +++ b/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp @@ -22,6 +22,11 @@ extern "C" int* APS5_VABI __error_nid_postfix(); extern "C" int APS5_VABI getpid_nid_postfix(); extern "C" std::uint64_t APS5_VABI sceKernelGetTscFrequency(void); +struct GuestRlimit { + std::int64_t rlim_cur; + std::int64_t rlim_max; +}; +extern "C" int APS5_VABI getrlimit_nid_postfix(int resource, GuestRlimit* limit); struct SceKernelSystemSwVersion { std::uint64_t size; @@ -123,12 +128,25 @@ std::int64_t APS5_VABI sysconf_nid_postfix(int name) { const int saved = *__error_nid_postfix(); std::int64_t result = -1; switch (name) { + case 1: // _SC_ARG_MAX + result = 262144; + break; case 3: // _SC_CLK_TCK result = 128; break; + case 5: { // _SC_OPEN_MAX + GuestRlimit limit{}; + if (getrlimit_nid_postfix(8, &limit) == 0) { + result = limit.rlim_cur; + } + break; + } case 47: // _SC_PAGESIZE result = PS5_PAGE_SIZE; break; + case 56: // _SC_IOV_MAX + result = 1024; + break; case 57: // _SC_NPROCESSORS_CONF case 58: // _SC_NPROCESSORS_ONLN #ifdef _WIN32 diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index 3a4e3f494f29..c740a96ee513 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -42,11 +43,23 @@ extern "C" int* APS5_VABI __error_nid_postfix(); namespace { +#ifdef _WIN32 +extern "C" _invalid_parameter_handler _set_thread_local_invalid_parameter_handler(_invalid_parameter_handler); +static void IgnoreInvalidParameter(const wchar_t*, const wchar_t*, const wchar_t*, unsigned int, std::uintptr_t) {} +static int GuestFstat(int fd) { + const auto previous = _set_thread_local_invalid_parameter_handler(IgnoreInvalidParameter); + struct _stat64 status{}; + const int result = ::_fstat64(fd, &status); + _set_thread_local_invalid_parameter_handler(previous); + return result; +} +#endif + constexpr int GuestEnoent = 2; constexpr int GuestEperm = 1; constexpr int GuestEio = 5; -constexpr int GuestEfault = 14; constexpr int GuestEbadf = 9; +constexpr int GuestEfault = 14; constexpr int GuestEnotdir = 20; constexpr int GuestEisdir = 21; constexpr int GuestEinval = 22; @@ -428,11 +441,12 @@ int APS5_VABI chroot_nid_postfix(const char* path) { int APS5_VABI symlink_nid_postfix(const char* target, const char* created) { if (target == nullptr || created == nullptr) return static_cast(Fail(GuestEfault)); if (*target == '\0' || *created == '\0') return static_cast(Fail(GuestEnoent)); - if (*target == '/') { + const std::filesystem::path contents(target); + if (contents.has_root_name() || contents.has_root_directory()) { NotImplemented_nid_no_patch("symlink: absolute targets"); return -1; } - for (const auto& part : std::filesystem::path(target)) { + for (const auto& part : contents) { if (part == "..") { NotImplemented_nid_no_patch("symlink: parent-directory targets"); return -1; @@ -447,7 +461,6 @@ int APS5_VABI symlink_nid_postfix(const char* target, const char* created) { if (std::filesystem::exists(std::filesystem::symlink_status(native, error))) return static_cast(Fail(GuestEexist)); #endif if (!std::filesystem::is_directory(native.parent_path(), error)) return static_cast(Fail(GuestEnoent)); - const std::filesystem::path contents(target); const bool directory = std::filesystem::is_directory(native.parent_path() / contents, error); #ifdef _WIN32 DWORD flags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; @@ -567,6 +580,19 @@ int APS5_VABI fchmodat_nid_postfix(int fd, const char* path, int mode, int flag) return 0; } +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"); + if (gid != 0 && gid != Unchanged) NotImplemented_nid_no_patch("fchown: a group other than the guest group"); +#ifdef _WIN32 + if (GuestFstat(fd) != 0) return static_cast(Fail(GuestEbadf)); +#else + struct stat status{}; + if (::fstat(fd, &status) != 0) return static_cast(Fail(GuestEbadf)); +#endif + return 0; +} + int APS5_VABI unlink_nid_postfix(const char* path); int APS5_VABI rmdir_nid_postfix(const char* path); diff --git a/core/libs/prx/libmdbg_syscore/Export.cpp b/core/libs/prx/libmdbg_syscore/Export.cpp new file mode 100644 index 000000000000..d81f4a5e32c4 --- /dev/null +++ b/core/libs/prx/libmdbg_syscore/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/tests/ApplicationHeap.cpp b/core/libs/tests/ApplicationHeap.cpp index 7477d42fd027..e30453224341 100644 --- a/core/libs/tests/ApplicationHeap.cpp +++ b/core/libs/tests/ApplicationHeap.cpp @@ -201,6 +201,28 @@ int main(int argc, char** argv) { require(initializes == 1); return 0; } + if (argc > 1 && std::strcmp(argv[1], "scripting") == 0) { + installProcessParameters(); + constexpr std::size_t blockBytes = 0x40000; + auto* first = static_cast(scriptingGetMem_nid_postfix(0x1000, blockBytes)); + require(initializes == 0 && lastSize == 0); + auto* second = static_cast(scriptingGetMem_nid_postfix(0x1000, blockBytes)); + require(initializes == 0 && lastSize == 0); + require(first != nullptr && second != nullptr && reinterpret_cast(first) % 0x1000 == 0 && reinterpret_cast(second) % 0x1000 == 0); + require(first + blockBytes <= second || second + blockBytes <= first); + std::memset(first, 0x5a, blockBytes); + std::memset(second, 0xa5, blockBytes); + require(first[blockBytes - 1] == std::byte{0x5a} && second[0] == std::byte{0xa5}); + scriptingFreeMem_nid_postfix(first); + scriptingFreeMem_nid_postfix(second); + require(initializes == 0 && frees == 0); + _init_env_nid_postfix(); + require(initializes == 1); + require(_Znwm_nid_postfix(40) == storage.data() && lastSize == 40); + _ZdlPv_nid_postfix(storage.data()); + require(frees == 1); + return 0; + } if (argc > 1 && std::strcmp(argv[1], "exit-order") == 0) { require(atexit_nid_postfix(exitCallbackAllocates) == 0); ApplicationHeapInitialize_nid_no_patch(process.data()); @@ -392,14 +414,23 @@ int main(int argc, char** argv) { require(getcwd_nid_postfix(nullptr, 0) == nullptr && *__error_nid_postfix() == 12); fail = false; const auto beforeScripting = frees; + const auto sizeBeforeScripting = lastSize; scriptingFreeMem_nid_postfix(nullptr); - require(frees == beforeScripting); - require(scriptingGetMem_nid_postfix(0, 31) == storage.data() && lastAlignment == 16 && lastSize == 31); - require(scriptingGetMem_nid_postfix(64, 37) == storage.data() && lastAlignment == 64 && lastSize == 37); - scriptingFreeMem_nid_postfix(storage.data()); - require(frees == beforeScripting + 1); - require(scriptingGetMem_nid_postfix(24, 80) == nullptr && lastSize == 37); + auto* small = static_cast(scriptingGetMem_nid_postfix(0, 31)); + auto* aligned = static_cast(scriptingGetMem_nid_postfix(64, 37)); + require(small != nullptr && small != storage.data() && reinterpret_cast(small) % 16 == 0); + require(aligned != nullptr && aligned != storage.data() && reinterpret_cast(aligned) % 64 == 0); + std::memset(small, 0x5a, 31); + std::memset(aligned, 0xa5, 37); + require(small + 31 <= aligned || aligned + 37 <= small); + scriptingFreeMem_nid_postfix(small); + scriptingFreeMem_nid_postfix(aligned); + require(scriptingGetMem_nid_postfix(24, 80) == nullptr); + require(scriptingGetMem_nid_postfix(16, std::numeric_limits::max()) == nullptr); fail = true; - require(scriptingGetMem_nid_postfix(16, 64) == nullptr); + auto* unaffected = scriptingGetMem_nid_postfix(16, 64); + require(unaffected != nullptr); + scriptingFreeMem_nid_postfix(unaffected); fail = false; + require(frees == beforeScripting && lastSize == sizeBeforeScripting); } diff --git a/core/libs/tests/ExceptionRuntime.cpp b/core/libs/tests/ExceptionRuntime.cpp index 42079527dade..b3d327330876 100644 --- a/core/libs/tests/ExceptionRuntime.cpp +++ b/core/libs/tests/ExceptionRuntime.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,7 @@ extern "C" [[noreturn]] void _ZSt13_Xrange_errorPKc_nid_postfix(const char*); extern "C" [[noreturn]] void _ZNSt8__sce_v219_Xbad_function_callEv_nid_postfix(); extern "C" [[noreturn]] void __cxa_bad_cast_nid_postfix(); extern "C" [[noreturn]] void _ZSt19_Throw_bad_weak_ptrv_nid_postfix(); +extern "C" [[noreturn]] void _ZSt26__throw_bad_variant_accessv_nid_postfix(); extern "C" [[noreturn]] void _ZNKSt9exception6_RaiseEv_nid_postfix(const void*); extern "C" void* __cxa_vec_new3_nid_postfix(std::size_t, std::size_t, std::size_t, void(*)(void*), void(*)(void*), void*(*)(std::size_t), void(*)(void*, std::size_t)); extern "C" void __cxa_vec_delete3_nid_postfix(void*, std::size_t, std::size_t, void(*)(void*), void(*)(void*, std::size_t)); @@ -250,6 +252,8 @@ int main() { catch (const std::exception& value) { assert(value.what() != nullptr); } try { _ZSt19_Throw_bad_weak_ptrv_nid_postfix(); std::abort(); } catch (const std::bad_weak_ptr& value) { if (std::strcmp(value.what(), "bad_weak_ptr") != 0) std::abort(); } + try { _ZSt26__throw_bad_variant_accessv_nid_postfix(); std::abort(); } + catch (const std::bad_variant_access& value) { if (std::strcmp(value.what(), "bad_variant_access") != 0) std::abort(); } try { _ZNKSt9exception6_RaiseEv_nid_postfix(nullptr); assert(false); } catch (const std::invalid_argument&) {} try { _ZNKSt9exception6_RaiseEv_nid_postfix(&object); assert(false); } diff --git a/core/libs/tests/GuestAio.cpp b/core/libs/tests/GuestAio.cpp index a0bc2b611fd8..a401b9032155 100644 --- a/core/libs/tests/GuestAio.cpp +++ b/core/libs/tests/GuestAio.cpp @@ -341,6 +341,16 @@ int main(int argc, char** argv) { Check(sceKernelAioSubmitWriteCommandsMultiple(writeBatch, 0, 0, writeIds) == SCE_KERNEL_ERROR_EINVAL); KernelAioRwRequest noResult{0, 1, readBack.data(), nullptr, batchFd}; Check(sceKernelAioSubmitWriteCommandsMultiple(&noResult, 1, 0, writeIds) == SCE_KERNEL_ERROR_EFAULT); + std::array unread{}; + std::array unreadIds{}; + for (const auto submit : {sceKernelAioSubmitReadCommandsMultiple, sceKernelAioSubmitWriteCommandsMultiple}) { + bool limited = false; + try { submit(unread.data(), 129, 0, unreadIds.data()); } catch (const std::runtime_error&) { limited = true; } + Check(limited); + } + std::int32_t unreadId = 0; + Check(sceKernelAioSubmitReadCommands(unread.data(), 129, 0, &unreadId) == SCE_KERNEL_ERROR_EFAULT); + Check(sceKernelAioSubmitWriteCommands(unread.data(), 129, 0, &unreadId) == SCE_KERNEL_ERROR_EFAULT); Check(sceKernelAioWaitRequests(readIds, 2, nullptr, 1, nullptr) == SCE_KERNEL_ERROR_EFAULT); Check(sceKernelAioWaitRequests(nullptr, 2, readStates, 1, nullptr) == SCE_KERNEL_ERROR_EFAULT); Check(sceKernelAioWaitRequests(readIds, -1, readStates, 1, nullptr) == SCE_KERNEL_ERROR_EINVAL); diff --git a/core/libs/tests/GuestAjm.cpp b/core/libs/tests/GuestAjm.cpp index c1970c1f2d45..2ed644c11c0a 100644 --- a/core/libs/tests/GuestAjm.cpp +++ b/core/libs/tests/GuestAjm.cpp @@ -1584,6 +1584,112 @@ void TestOpusGaplessReset(std::uint32_t context, std::size_t outputFrames, float Require(sceAjmInstanceDestroy(context, reference) == 0); } +struct PcmResults { + std::int64_t initialize[2] = {-1, -1}; + std::int64_t resample[2] = {-1, -1}; + DecodeSideband decode{}; +}; + +AjmBatchInfo PcmJobs(std::vector& batch, std::uint32_t instance, const std::uint32_t* parameters, float ratio, const std::vector& input, std::vector& output, PcmResults& results) { + AjmBatchInfo info{}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + if (parameters) Require(sceAjmBatchJobInitialize(&info, instance, parameters, 3 * sizeof(std::uint32_t), results.initialize) == 0); + if (ratio > 0) Require(sceAjmBatchJobSetResampleParameters(&info, instance, ratio, 1, results.resample) == 0); + Require(sceAjmBatchJobDecode(&info, instance, input.data(), input.size() * sizeof(std::int16_t), output.data(), output.size() * sizeof(std::int16_t), &results.decode) == 0); + return info; +} + +void TestPcmResample(std::uint32_t context) { + constexpr std::uint64_t flags = 0x1002; + constexpr std::int16_t untouched = 0x7FFF; + const std::uint32_t parameters[3] = {0, 2, 44100}; + constexpr std::size_t inputFrames = 235; + constexpr float ratio = 0.91875f; + std::vector input(inputFrames * 2); + for (std::size_t frame = 0; frame < inputFrames; ++frame) { + input[frame * 2] = static_cast(frame * 100); + input[frame * 2 + 1] = static_cast((static_cast(frame * 7919 % 2001) - 1000) * 20); + } + const auto at = [&](std::ptrdiff_t frame, std::size_t channel) { + const auto clamped = std::clamp(frame, 0, static_cast(inputFrames) - 1); + return static_cast(input[static_cast(clamped) * 2 + channel]); + }; + std::vector batch(4096); + + std::uint32_t instance = 0; + Require(sceAjmInstanceCreate(context, 23, flags, &instance) == 0); + for (int block = 0; block < 2; ++block) { + std::vector output(300 * 2, untouched); + PcmResults results; + Submit(context, PcmJobs(batch, instance, parameters, ratio, input, output, results)); + Require(results.initialize[0] == 0 && results.resample[0] == 0 && results.decode.result == 0); + Require(results.decode.inputConsumed == 940 && results.decode.outputWritten == 1024 && results.decode.totalDecodedSamples == 235); + for (std::size_t frame = 0; frame < 256; ++frame) { + const double position = static_cast(frame) * static_cast(ratio); + const auto index = static_cast(position); + const double t = position - static_cast(index); + for (std::size_t channel = 0; channel < 2; ++channel) { + const double p0 = at(index - 1, channel), p1 = at(index, channel), p2 = at(index + 1, channel), p3 = at(index + 2, channel); + const double expected = std::clamp(p1 + 0.5 * t * (p2 - p0 + t * (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3 + t * (3.0 * (p1 - p2) + p3 - p0))), -32768.0, 32767.0); + Require(std::fabs(output[frame * 2 + channel] - expected) <= 1.0); + } + } + Require(std::all_of(output.begin() + 256 * 2, output.end(), [&](std::int16_t value) { return value == untouched; })); + } + { + std::vector output(256 * 2); + PcmResults results; + Require(Refused(context, PcmJobs(batch, instance, nullptr, ratio, input, output, results))); + } + { + std::vector output(255 * 2); + PcmResults results; + Require(Refused(context, PcmJobs(batch, instance, parameters, ratio, input, output, results))); + } + { + const GaplessDecode gapless{100, 0, 0}; + std::int32_t gaplessResult[2] = {-1, -1}; + std::vector output(256 * 2); + PcmResults results; + AjmBatchInfo info{}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobInitialize(&info, instance, parameters, sizeof(parameters), results.initialize) == 0); + Require(sceAjmBatchJobSetGaplessDecode(&info, instance, &gapless, 1, gaplessResult) == 0); + Require(sceAjmBatchJobDecode(&info, instance, input.data(), input.size() * sizeof(std::int16_t), output.data(), output.size() * sizeof(std::int16_t), &results.decode) == 0); + Require(Refused(context, info)); + } + Require(sceAjmInstanceDestroy(context, instance) == 0); + + std::uint32_t plain = 0; + Require(sceAjmInstanceCreate(context, 23, flags, &plain) == 0); + for (int run = 0; run < 2; ++run) { + std::vector output(input.size() + 4, untouched); + PcmResults results; + Submit(context, PcmJobs(batch, plain, run == 0 ? parameters : nullptr, 0, input, output, results)); + Require(results.decode.result == 0 && results.decode.inputConsumed == 940 && results.decode.outputWritten == 940); + Require(results.decode.totalDecodedSamples == 235u * static_cast(run + 1)); + Require(std::equal(input.begin(), input.end(), output.begin()) && output[input.size()] == untouched); + } + Require(sceAjmInstanceDestroy(context, plain) == 0); + + std::uint32_t invalid = 0; + Require(sceAjmInstanceCreate(context, 23, flags, &invalid) == 0); + { + const std::uint32_t noChannels[3] = {0, 0, 44100}; + std::vector output(256 * 2); + PcmResults results; + Submit(context, PcmJobs(batch, invalid, noChannels, 0, input, output, results)); + Require(results.initialize[0] == 4 && results.decode.result == 1); + } + { + const std::uint32_t otherFormat[3] = {1, 2, 44100}; + std::vector output(256 * 2); + PcmResults results; + Require(Refused(context, PcmJobs(batch, invalid, otherFormat, 0, input, output, results))); + } + Require(sceAjmInstanceDestroy(context, invalid) == 0); +} + } int main() { @@ -1641,5 +1747,6 @@ int main() { TestResampleAt9(context); TestAt9GaplessSegments(context); TestAt9RunDecodesWholeInput(context); + TestPcmResample(context); Require(sceAjmFinalize(context) == 0); } diff --git a/core/libs/tests/GuestAprRead.cpp b/core/libs/tests/GuestAprRead.cpp new file mode 100644 index 000000000000..d2f12108a7dd --- /dev/null +++ b/core/libs/tests/GuestAprRead.cpp @@ -0,0 +1,267 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libkernel/Apr/include/AprCommandBuffer.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#endif + +extern "C" { +int APS5_VABI sceKernelAprResolveFilepathsToIds(const char**, std::uint32_t, std::uint32_t*, std::uint32_t*); +int APS5_VABI sceKernelAprSubmitCommandBuffer(const Apr::CommandBufferObject*, std::uint32_t); +int APS5_VABI sceKernelAllocateDirectMemory(std::int64_t, std::int64_t, std::size_t, std::size_t, int, std::int64_t*); +int APS5_VABI sceKernelMapDirectMemory(void**, std::size_t, int, int, std::int64_t, std::size_t); +int APS5_VABI sceKernelMunmap(void*, std::size_t); +int APS5_VABI sceKernelMprotect(const void*, std::size_t, int); +int APS5_VABI sceKernelReleaseDirectMemory(std::int64_t, std::size_t); +void* APS5_VABI fopen_nid_postfix(const char*, const char*); +int APS5_VABI fclose_nid_postfix(void*); +} + +namespace { + +constexpr std::size_t FileBytes = (16u << 20u) + 257u; +constexpr std::size_t MappingBytes = 17u << 20u; +constexpr const char* FilePath = "apr_read_generated.bin"; + +void Require(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +std::uint8_t FileByte(std::uint64_t offset) { + return static_cast((offset * 37u) ^ (offset >> 8u) ^ (offset >> 16u)); +} + +class Fixture { +public: + Fixture() { + std::ofstream stream(FilePath, std::ios::binary | std::ios::trunc); + std::array chunk; + for (std::size_t offset = 0; offset < FileBytes; offset += chunk.size()) { + const auto bytes = std::min(chunk.size(), FileBytes - offset); + for (std::size_t index = 0; index < bytes; ++index) chunk[index] = static_cast(FileByte(offset + index)); + stream.write(chunk.data(), static_cast(bytes)); + } + stream.close(); + Require(!stream.fail(), "cannot create the synthetic APR file"); + const char* path = FilePath; + std::uint32_t failed = 0; + Require(sceKernelAprResolveFilepathsToIds(&path, 1, &id, &failed) == 0 && failed == 0, "cannot resolve the synthetic APR file"); + } + + ~Fixture() { + std::error_code ignored; + std::filesystem::remove(FilePath, ignored); + } + + void Read(void* destination, std::uint64_t bytes, std::uint64_t offset) const { + const Apr::ReadFileCommand command{{Apr::Opcode::ReadFile, sizeof(Apr::ReadFileCommand)}, id, 0, + reinterpret_cast(destination), bytes, offset}; + alignas(8) std::array memory{}; + std::memcpy(memory.data(), &command, sizeof(command)); + Apr::CommandBufferObject buffer{Apr::BufferType::Generic, 0, sizeof(memory), 1, sizeof(memory), memory.data()}; + Require(sceKernelAprSubmitCommandBuffer(&buffer, 0) == 0, "APR read submission failed"); + } + +private: + std::uint32_t id = 0; +}; + +class DirectBuffer { +public: + DirectBuffer() { + Require(sceKernelAllocateDirectMemory(0, 13824ll << 20, MappingBytes, 0x4000, 0, &physical) == 0, "cannot allocate direct memory"); + if (sceKernelMapDirectMemory(&pointer, MappingBytes, 0x33, 0, physical, 0x4000) != 0) { + sceKernelReleaseDirectMemory(physical, MappingBytes); + throw std::runtime_error("cannot map direct memory"); + } + } + + ~DirectBuffer() { + sceKernelMunmap(pointer, MappingBytes); + sceKernelReleaseDirectMemory(physical, MappingBytes); + } + + std::uint8_t* Data() const { return static_cast(pointer); } + +private: + void* pointer = nullptr; + std::int64_t physical = 0; +}; + +void CheckRead(const Fixture& fixture, std::uint8_t* data, std::size_t bytes, std::uint64_t offset) { + data[-1] = 0xA5; + data[bytes] = 0x5A; + fixture.Read(data, bytes, offset); + Require(data[-1] == 0xA5 && data[bytes] == 0x5A, "APR wrote outside the requested range"); + for (std::size_t index = 0; index < bytes; ++index) { + const auto expected = offset + index < FileBytes ? FileByte(offset + index) : std::uint8_t{0}; + Require(data[index] == expected, "APR returned incorrect bytes or did not zero-fill EOF"); + } +} + +void TestRead(const Fixture& fixture, std::uint8_t* data) { + CheckRead(fixture, data + 17, (16u << 20u) + 13u, 7); + for (const auto bytes : {std::size_t{0}, std::size_t{1}, std::size_t{65535}, std::size_t{65536}, std::size_t{65537}}) { + CheckRead(fixture, data + 17, bytes, 31); + } +} + +void TestEof(const Fixture& fixture) { + std::vector storage(131200, 0xCC); + auto* data = storage.data() + 17; + CheckRead(fixture, data, 131073, FileBytes - 65537); + CheckRead(fixture, data, 65537, FileBytes); + CheckRead(fixture, data, 65537, FileBytes + 19); +} + +void TestErrors(const Fixture& fixture) { + std::array data; + data.fill(0xA5); + bool rejected = false; + try { + fixture.Read(data.data(), data.size(), std::uint64_t{1} << 63u); + } catch (const std::runtime_error&) { + rejected = true; + } + Require(rejected, "APR accepted an unseekable offset"); + for (const auto byte : data) Require(byte == 0xA5, "failed APR read modified the destination"); +#ifdef _WIN32 + { + const DirectBuffer storage; + storage.Data()[17] = 0xA5; + Require(sceKernelMprotect(storage.Data(), MappingBytes, 0x11) == 0, "cannot protect the direct read buffer"); + rejected = false; + try { + fixture.Read(storage.Data() + 17, 65537, 0); + } catch (const std::runtime_error& error) { + rejected = std::string(error.what()).find("not writable guest memory") != std::string::npos; + } + Require(rejected && storage.Data()[17] == 0xA5, "APR did not reject a read-only guest destination"); + } +#endif + Require(std::filesystem::remove(FilePath), "cannot remove the synthetic APR file"); + rejected = false; + try { + fixture.Read(data.data(), data.size(), 0); + } catch (const std::runtime_error&) { + rejected = true; + } + Require(rejected, "APR accepted a missing file"); + for (const auto byte : data) Require(byte == 0xA5, "missing-file APR read modified the destination"); +} + +void TestStreams(const Fixture& fixture) { + constexpr int StreamCount = 600; + std::vector streams; + for (int index = 0; index < StreamCount; ++index) { + void* stream = fopen_nid_postfix(("apr_stream_" + std::to_string(index) + ".tmp").c_str(), "w"); + if (stream == nullptr) break; + streams.push_back(stream); + } + const bool held = streams.size() == StreamCount; + std::vector storage(131200, 0xCC); + std::string error; + try { + CheckRead(fixture, storage.data() + 17, 65537, 0); + } catch (const std::runtime_error& failure) { + error = failure.what(); + } + for (void* stream : streams) fclose_nid_postfix(stream); + for (int index = 0; index < StreamCount; ++index) std::filesystem::remove("apr_stream_" + std::to_string(index) + ".tmp"); + Require(error.empty(), ("APR read failed while " + std::to_string(streams.size()) + " guest streams were open: " + error).c_str()); + Require(held, "cannot hold 600 guest fopen streams"); +} + +#ifdef _WIN32 +std::uintptr_t faultBegin = 0; +std::size_t faultBytes = 0; +std::size_t faultPage = 0; +std::size_t committedPages = 0; + +LONG CALLBACK CommitOnWrite(EXCEPTION_POINTERS* exception) { + const auto& record = *exception->ExceptionRecord; + if (record.ExceptionCode != EXCEPTION_ACCESS_VIOLATION || record.NumberParameters < 2 || record.ExceptionInformation[0] != 1) return EXCEPTION_CONTINUE_SEARCH; + const auto address = static_cast(record.ExceptionInformation[1]); + if (address < faultBegin || address - faultBegin >= faultBytes) return EXCEPTION_CONTINUE_SEARCH; + const auto page = address & ~(static_cast(faultPage) - 1); + if (VirtualAlloc(reinterpret_cast(page), faultPage, MEM_COMMIT, PAGE_READWRITE) == nullptr) return EXCEPTION_CONTINUE_SEARCH; + ++committedPages; + return EXCEPTION_CONTINUE_EXECUTION; +} + +class FaultBuffer { +public: + FaultBuffer() { + SYSTEM_INFO system{}; + GetSystemInfo(&system); + pointer = VirtualAlloc(nullptr, MappingBytes, MEM_RESERVE, PAGE_NOACCESS); + Require(pointer != nullptr, "cannot reserve the fault-backed read buffer"); + handler = AddVectoredExceptionHandler(1, CommitOnWrite); + if (handler == nullptr) { + VirtualFree(pointer, 0, MEM_RELEASE); + throw std::runtime_error("cannot install the synthetic page fault handler"); + } + faultPage = system.dwPageSize; + faultBytes = MappingBytes; + faultBegin = reinterpret_cast(pointer); + committedPages = 0; + } + + ~FaultBuffer() { + faultBytes = 0; + RemoveVectoredExceptionHandler(handler); + VirtualFree(pointer, 0, MEM_RELEASE); + } + + std::uint8_t* Data() const { return static_cast(pointer); } + +private: + void* pointer = nullptr; + void* handler = nullptr; +}; +#endif + +} + +int main(int argc, char** argv) { + try { + Require(argc == 2, "expected host, direct, eof, errors or fault"); + const Fixture fixture; + const std::string mode = argv[1]; + if (mode == "host") { + std::vector storage(MappingBytes, 0xCC); + TestRead(fixture, storage.data()); + } else if (mode == "direct") { + const DirectBuffer storage; + TestRead(fixture, storage.Data()); + } else if (mode == "eof") { + TestEof(fixture); + } else if (mode == "errors") { + TestErrors(fixture); + } else if (mode == "streams") { + TestStreams(fixture); +#ifdef _WIN32 + } else if (mode == "fault") { + const FaultBuffer storage; + TestRead(fixture, storage.Data()); + Require(committedPages > 2, "APR did not exercise demand-committed destination pages"); +#endif + } else { + throw std::runtime_error("unknown APR read case"); + } + std::puts("APR synthetic read checks passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/libs/tests/GuestAudioIn.cpp b/core/libs/tests/GuestAudioIn.cpp index d9e957911493..0071148f888d 100644 --- a/core/libs/tests/GuestAudioIn.cpp +++ b/core/libs/tests/GuestAudioIn.cpp @@ -74,6 +74,14 @@ void TestValidation() { Require(ReportsNotImplemented("sceAudioInAsyncOpen", [] { sceAudioInAsyncOpen(user, 0, 0, 128, 48000, 0x10); })); } +void InputSound(int handle, std::vector& block) { + for (int attempt = 0; attempt < 200; ++attempt) { + Require(sceAudioInInput(handle, block.data()) == 256); + if (std::any_of(block.begin(), block.end() - 1, [](std::int16_t sample) { return sample != 0; })) return; + } + Require(false); +} + void TestCapture() { const auto path = std::filesystem::temp_directory_path() / ("anyps5_audio_in_capture-" + std::to_string(std::random_device{}()) + ".raw"); std::vector recorded(256 * 2 * 4); @@ -88,7 +96,7 @@ void TestCapture() { std::vector block(256 * 2 + 1, 0x5555); std::vector captured; for (int i = 0; i < 4; ++i) { - Require(sceAudioInInput(handle, block.data()) == 256); + InputSound(handle, block); captured.insert(captured.end(), block.begin(), block.end() - 1); Require(block.back() == 0x5555); } @@ -98,7 +106,7 @@ void TestCapture() { Require(sceAudioInClose(handle) == 0); const int async = sceAudioInAsyncOpen(user, 0, 0, 256, 48000, 2); Require(async == handle && sceAudioInGetSilentState(async) == 0); - Require(sceAudioInInput(async, block.data()) == 256); + InputSound(async, block); Require(std::equal(block.begin(), block.end() - 1, recorded.begin()) && block.back() == 0x5555); Require(sceAudioInClose(async) == 0); std::filesystem::remove(path); diff --git a/core/libs/tests/GuestDinkumwareAtomics.cpp b/core/libs/tests/GuestDinkumwareAtomics.cpp new file mode 100644 index 000000000000..8e45d769024c --- /dev/null +++ b/core/libs/tests/GuestDinkumwareAtomics.cpp @@ -0,0 +1,120 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +extern "C" { +void APS5_VABI _Atomic_store_1_nid_postfix(volatile std::uint8_t*, std::uint8_t, int); +void APS5_VABI _Atomic_store_8_nid_postfix(volatile std::uint64_t*, std::uint64_t, int); +std::uint16_t APS5_VABI _Atomic_load_2_nid_postfix(volatile std::uint16_t*, int); +std::uint64_t APS5_VABI _Atomic_load_8_nid_postfix(volatile std::uint64_t*, int); +std::uint32_t APS5_VABI _Atomic_exchange_4_nid_postfix(volatile std::uint32_t*, std::uint32_t, int); +std::uint64_t APS5_VABI _Atomic_exchange_8_nid_postfix(volatile std::uint64_t*, std::uint64_t, int); +int APS5_VABI _Atomic_compare_exchange_strong_2_nid_postfix(volatile std::uint16_t*, std::uint16_t*, std::uint16_t, int, int); +int APS5_VABI _Atomic_compare_exchange_weak_8_nid_postfix(volatile std::uint64_t*, std::uint64_t*, std::uint64_t, int, int); +std::uint8_t APS5_VABI _Atomic_fetch_add_1_nid_postfix(volatile std::uint8_t*, std::uint8_t, int); +std::uint64_t APS5_VABI _Atomic_fetch_add_8_nid_postfix(volatile std::uint64_t*, std::uint64_t, int); +std::uint16_t APS5_VABI _Atomic_fetch_sub_2_nid_postfix(volatile std::uint16_t*, std::uint16_t, int); +std::uint32_t APS5_VABI _Atomic_fetch_and_4_nid_postfix(volatile std::uint32_t*, std::uint32_t, int); +std::uint8_t APS5_VABI _Atomic_fetch_or_1_nid_postfix(volatile std::uint8_t*, std::uint8_t, int); +std::uint64_t APS5_VABI _Atomic_fetch_xor_8_nid_postfix(volatile std::uint64_t*, std::uint64_t, int); +int APS5_VABI _Atomic_is_lock_free_4_nid_postfix(); +int APS5_VABI _Atomic_flag_test_and_set_nid_postfix(volatile unsigned char*, int); +void APS5_VABI _Atomic_flag_clear_nid_postfix(volatile unsigned char*, int); +void APS5_VABI _Atomic_thread_fence_nid_postfix(int); +void APS5_VABI _Atomic_signal_fence_nid_postfix(int); +void APS5_VABI _Atomic_copy_nid_postfix(volatile unsigned char*, std::size_t, volatile void*, const volatile void*, int); +void APS5_VABI _Atomic_exchange_nid_postfix(volatile unsigned char*, std::size_t, volatile void*, volatile void*, int); +int APS5_VABI _Atomic_compare_exchange_strong_nid_postfix(volatile unsigned char*, std::size_t, volatile void*, void*, const void*, int, int); +int APS5_VABI _Atomic_compare_exchange_weak_nid_postfix(volatile unsigned char*, std::size_t, volatile void*, void*, const void*, int, int); +} + +static void Require(bool value) { if (!value) std::abort(); } + +constexpr int Relaxed = 0, Acquire = 2, Release = 3, AcqRel = 4, SeqCst = 5; + +static void CheckTyped() { + std::uint8_t byte = 0; + _Atomic_store_1_nid_postfix(&byte, 7, SeqCst); + Require(byte == 7 && _Atomic_fetch_add_1_nid_postfix(&byte, 250, Relaxed) == 7 && byte == 1); + Require(_Atomic_fetch_or_1_nid_postfix(&byte, 0x80, AcqRel) == 1 && byte == 0x81); + std::uint16_t half = 5; + Require(_Atomic_load_2_nid_postfix(&half, Release) == 5 && _Atomic_fetch_sub_2_nid_postfix(&half, 6, SeqCst) == 5 && half == 0xffff); + std::uint16_t expectedHalf = 1; + Require(_Atomic_compare_exchange_strong_2_nid_postfix(&half, &expectedHalf, 2, SeqCst, Relaxed) == 0 && expectedHalf == 0xffff); + Require(_Atomic_compare_exchange_strong_2_nid_postfix(&half, &expectedHalf, 2, SeqCst, Relaxed) == 1 && half == 2); + std::uint32_t word = 0xff0f; + Require(_Atomic_fetch_and_4_nid_postfix(&word, 0xf0ff, Acquire) == 0xff0f && word == 0xf00f); + Require(_Atomic_exchange_4_nid_postfix(&word, 9, 6) == 0xf00f && word == 9); + std::uint64_t wide = 0; + _Atomic_store_8_nid_postfix(&wide, 0x123456789ull, Release); + Require(_Atomic_load_8_nid_postfix(&wide, Acquire) == 0x123456789ull); + Require(_Atomic_load_8_nid_postfix(&wide, Release) == 0 && _Atomic_load_8_nid_postfix(&wide, AcqRel) == 0); + Require(_Atomic_fetch_add_8_nid_postfix(&wide, 1, SeqCst) == 0x123456789ull && wide == 0x12345678aull); + Require(_Atomic_fetch_add_8_nid_postfix(&wide, 1, 6) == 0 && wide == 0x12345678aull); + Require(_Atomic_exchange_8_nid_postfix(&wide, 1, 7) == 0 && wide == 0x12345678aull); + Require(_Atomic_fetch_xor_8_nid_postfix(&wide, 0xf, Relaxed) == 0x12345678aull && wide == 0x123456785ull); + std::uint64_t expectedWide = 0x123456785ull; + Require(_Atomic_compare_exchange_weak_8_nid_postfix(&wide, &expectedWide, 3, SeqCst, SeqCst) == 1 && wide == 3); + Require(_Atomic_is_lock_free_4_nid_postfix() == 1); +} + +static void CheckFlagsAndFences() { + unsigned char flag = 0; + Require(_Atomic_flag_test_and_set_nid_postfix(&flag, SeqCst) == 0 && flag == 1); + Require(_Atomic_flag_test_and_set_nid_postfix(&flag, Relaxed) == 1); + _Atomic_flag_clear_nid_postfix(&flag, Release); + Require(flag == 0); + _Atomic_thread_fence_nid_postfix(SeqCst); + _Atomic_signal_fence_nid_postfix(SeqCst); +} + +struct Wide { + std::uint64_t parts[3]; +}; + +static void CheckGeneric() { + unsigned char lock = 0; + Wide target{{1, 2, 3}}, source{{4, 5, 6}}, expected{{1, 2, 3}}; + _Atomic_copy_nid_postfix(&lock, sizeof(Wide), &target, &source, SeqCst); + Require(std::memcmp(&target, &source, sizeof(Wide)) == 0 && lock == 0); + Wide other{{7, 8, 9}}; + _Atomic_exchange_nid_postfix(&lock, sizeof(Wide), &target, &other, SeqCst); + Require(target.parts[0] == 7 && other.parts[0] == 4 && lock == 0); + const Wide desired{{10, 11, 12}}; + Require(_Atomic_compare_exchange_strong_nid_postfix(&lock, sizeof(Wide), &target, &expected, &desired, SeqCst, SeqCst) == 0); + Require(std::memcmp(&expected, &target, sizeof(Wide)) == 0 && lock == 0); + Require(_Atomic_compare_exchange_weak_nid_postfix(&lock, sizeof(Wide), &target, &expected, &desired, SeqCst, SeqCst) == 1); + Require(std::memcmp(&target, &desired, sizeof(Wide)) == 0 && lock == 0); +} + +static void CheckConcurrentGeneric() { + unsigned char lock = 0; + Wide counter{{0, 0, 0}}; + constexpr int Iterations = 20000; + auto worker = [&] { + for (int i = 0; i < Iterations; ++i) { + Wide expected; + _Atomic_copy_nid_postfix(&lock, sizeof(Wide), &expected, &counter, SeqCst); + Wide next; + do { + next = Wide{{expected.parts[0] + 1, expected.parts[1] + 2, expected.parts[2] + 3}}; + } while (!_Atomic_compare_exchange_strong_nid_postfix(&lock, sizeof(Wide), &counter, &expected, &next, SeqCst, SeqCst)); + } + }; + std::thread first(worker); + std::thread second(worker); + first.join(); + second.join(); + Require(counter.parts[0] == 2 * Iterations && counter.parts[1] == 4 * Iterations && counter.parts[2] == 6 * Iterations); +} + +int main() { + CheckTyped(); + CheckFlagsAndFences(); + CheckGeneric(); + CheckConcurrentGeneric(); + return 0; +} diff --git a/core/libs/tests/GuestFilesystem.cpp b/core/libs/tests/GuestFilesystem.cpp index dbfe47bf187b..b569e04a93f9 100644 --- a/core/libs/tests/GuestFilesystem.cpp +++ b/core/libs/tests/GuestFilesystem.cpp @@ -71,6 +71,16 @@ int APS5_VABI chdir_nid_postfix(const char*); void* APS5_VABI opendir_nid_postfix(const char*); FileStream* APS5_VABI fopen_nid_postfix(const char*, const char*); FileStream* APS5_VABI freopen_nid_postfix(const char*, const char*, FileStream*); +std::size_t APS5_VABI fread_nid_postfix(void*, std::size_t, std::size_t, FileStream*); +int APS5_VABI fclose_nid_postfix(FileStream*); +int APS5_VABI fgetc_nid_postfix(FileStream*); +int APS5_VABI feof_nid_postfix(FileStream*); +int APS5_VABI ferror_nid_postfix(FileStream*); +int APS5_VABI setvbuf_nid_postfix(FileStream*, char*, int, std::size_t); +int APS5_VABI fflush_nid_postfix(FileStream*); +void APS5_VABI flockfile_nid_postfix(FileStream*); +void APS5_VABI funlockfile_nid_postfix(FileStream*); +int APS5_VABI ftrylockfile_nid_postfix(FileStream*); int* APS5_VABI __error_nid_postfix(); int APS5_VABI pipe_nid_postfix(int*); int APS5_VABI dup2_nid_postfix(int, int); @@ -580,6 +590,41 @@ int main() { Require(rename_nid_postfix(name(targetFile).c_str(), name(targetFile).c_str()) == 0); Require(rename_nid_postfix(name(sourceDirectory).c_str(), name(emptyDirectory).c_str()) == 0); Require(!std::filesystem::exists(sourceDirectory) && std::filesystem::is_directory(emptyDirectory / "child")); + const auto kernelSource = area / "kernel_source"; + const auto kernelTarget = area / "kernel_target"; + Require(std::filesystem::create_directories(kernelSource / "child")); + Require(std::filesystem::create_directory(kernelTarget)); + { std::ofstream stream(kernelSource / "marker"); stream << "kernel directory contents"; } + const int directoryResult = sceKernelRename(name(kernelSource).c_str(), name(kernelTarget).c_str()); + if (directoryResult != 0) + std::fprintf(stderr, "kernel empty-directory replacement result=0x%08x, expected=0\n", static_cast(directoryResult)); + Require(directoryResult == 0); + Require(!std::filesystem::exists(kernelSource) && std::filesystem::is_directory(kernelTarget / "child")); + { std::ifstream stream(kernelTarget / "marker"); std::string contents; std::getline(stream, contents); + Require(contents == "kernel directory contents"); } + const int parentResult = sceKernelRename(name(targetFile).c_str(), absentParent.c_str()); + Require(parentResult == static_cast(0x80020002u)); + Require(std::filesystem::is_regular_file(targetFile) && !std::filesystem::exists(area / "absent")); + const int prefixResult = sceKernelRename(name(targetFile).c_str(), name(targetFile / "target").c_str()); + Require(prefixResult == static_cast(0x80020014u)); + Require(std::filesystem::is_regular_file(targetFile)); + const int fileDirectoryResult = sceKernelRename(name(targetFile).c_str(), name(fullDirectory).c_str()); + Require(fileDirectoryResult == static_cast(0x80020015u)); + Require(std::filesystem::is_regular_file(targetFile) && std::filesystem::is_regular_file(fullDirectory / "entry")); + const int directoryFileResult = sceKernelRename(name(kernelTarget).c_str(), name(targetFile).c_str()); + Require(directoryFileResult == static_cast(0x80020014u)); + Require(std::filesystem::is_regular_file(kernelTarget / "marker") && std::filesystem::is_regular_file(targetFile)); + const int nonemptyResult = sceKernelRename(name(kernelTarget).c_str(), name(fullDirectory).c_str()); + Require(nonemptyResult == static_cast(0x80020042u)); + Require(std::filesystem::is_regular_file(kernelTarget / "marker") && std::filesystem::is_regular_file(fullDirectory / "entry")); + const auto descendant = kernelTarget / "child" / "moved"; + const int descendantResult = sceKernelRename(name(kernelTarget).c_str(), name(descendant).c_str()); + Require(descendantResult == static_cast(0x80020016u)); + Require(std::filesystem::is_directory(kernelTarget / "child") && !std::filesystem::exists(descendant)); + { std::ifstream stream(targetFile); std::string contents; std::getline(stream, contents); Require(contents == "source"); } + { std::ifstream stream(fullDirectory / "entry"); std::string contents; std::getline(stream, contents); Require(contents == "entry"); } + { std::ifstream stream(kernelTarget / "marker"); std::string contents; std::getline(stream, contents); + Require(contents == "kernel directory contents"); } for (const bool kernel : {false, true}) { const auto incoming = name(area / "incoming.txt"); const auto occupied = name(area / "occupied.txt"); @@ -1053,6 +1098,38 @@ int main() { Require(read_nid_postfix(random, second, 3) == 3); Require(std::memcmp(first, second, sizeof(first)) != 0 && second[3] == 0); Require(close_nid_postfix(random) == 0); + auto* stream = fopen_nid_postfix(device, "rb"); + Require(stream != nullptr); + Require(setvbuf_nid_postfix(stream, nullptr, 2, 0) == 0); + std::memset(first, 0, sizeof(first)); + std::memset(second, 0, sizeof(second)); + Require(fread_nid_postfix(first, 1, sizeof(first), stream) == sizeof(first)); + Require(fread_nid_postfix(second, 8, 8, stream) == 8); + Require(std::memcmp(first, second, sizeof(first)) != 0); + Require(feof_nid_postfix(stream) == 0 && ferror_nid_postfix(stream) == 0); + flockfile_nid_postfix(stream); + Require(fread_nid_postfix(first, 1, 4, stream) == 4); + funlockfile_nid_postfix(stream); + Require(ftrylockfile_nid_postfix(stream) == 0); + funlockfile_nid_postfix(stream); + Require(fflush_nid_postfix(stream) == 0); + bool unsupported = false; + try { + fgetc_nid_postfix(stream); + } catch (const std::runtime_error&) { + unsupported = true; + } + Require(unsupported); + Require(fclose_nid_postfix(stream) == 0); + for (const char* mode : {"wb", "ab", "r+b"}) { + bool rejected = false; + try { + fopen_nid_postfix(device, mode); + } catch (const std::runtime_error&) { + rejected = true; + } + Require(rejected); + } } Require(unlink_nid_postfix(presentName.c_str()) == 0 && !std::filesystem::exists(present)); const auto empty = root / "empty"; diff --git a/core/libs/tests/GuestFlexibleMemoryBudget.cpp b/core/libs/tests/GuestFlexibleMemoryBudget.cpp index ced1a258d5dc..cba381e73422 100644 --- a/core/libs/tests/GuestFlexibleMemoryBudget.cpp +++ b/core/libs/tests/GuestFlexibleMemoryBudget.cpp @@ -22,6 +22,14 @@ std::uint64_t GuestImageSegmentBytes_nid_no_patch(); void* APS5_VABI dlopen_nid_postfix(const char*, int); char* APS5_VABI dlerror_nid_postfix(); int APS5_VABI dlclose_nid_postfix(void*); +std::size_t APS5_VABI sceKernelGetDirectMemorySize(); +int APS5_VABI sceKernelAllocateDirectMemory(std::int64_t, std::int64_t, std::size_t, std::size_t, int, std::int64_t*); +int APS5_VABI sceKernelMapDirectMemory(void**, std::size_t, int, int, std::int64_t, std::size_t); +int APS5_VABI sceKernelMapDirectMemory2(void**, std::size_t, int, int, int, std::int64_t, std::size_t); +int APS5_VABI sceKernelMapNamedDirectMemory(void**, std::size_t, int, int, std::int64_t, std::size_t, const char*); +int APS5_VABI sceKernelBatchMap2(KernelBatchMapEntry*, int, int*, int); +int APS5_VABI sceKernelReleaseDirectMemory(std::int64_t, std::size_t); +int APS5_VABI sceKernelVirtualQuery(const void*, int, VirtualQueryInfo*, std::uint64_t); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -115,13 +123,124 @@ int main(int argc, char** argv) { Require(sceKernelMapFlexibleMemory(&oversized, before + PS5_PAGE_SIZE, 3, 0) == SCE_KERNEL_ERROR_ENOMEM && oversized == nullptr); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before); if (mode == "limited") { + const auto checkBudget = [&](std::size_t expected) { + std::size_t current = 0; + Require(sceKernelAvailableFlexibleMemorySize(¤t) == 0); + std::printf("Fixed flexible mapping: available=%zu expected=%zu\n", current, expected); + std::fflush(stdout); + Require(current == expected); + }; + constexpr auto page = PS5_PAGE_SIZE; + void* original = nullptr; + Require(sceKernelMapFlexibleMemory(&original, page * 4, 3, 0) == 0); + auto* bytes = static_cast(original); + for (std::size_t i = 0; i < 4; ++i) bytes[i * page] = static_cast(17 + i); + auto* middle = static_cast(original) + page; + void* replacement = middle; + Require(sceKernelMapFlexibleMemory(&replacement, page, 3, 0x10) == 0 && replacement == middle); + Require(bytes[0] == 17 && bytes[page] == 0 && bytes[page * 2] == 19 && bytes[page * 3] == 20); + checkBudget(before - page * 4); + bytes[page] = 23; + Require(sceKernelMapFlexibleMemory(&replacement, page, 3, 0x90) == SCE_KERNEL_ERROR_ENOMEM); + Require(bytes[page] == 23); + checkBudget(before - page * 4); + replacement = original; + Require(sceKernelMapFlexibleMemory(&replacement, page, 3, 0x10) == 0 && replacement == original); + Require(bytes[0] == 0 && bytes[page] == 23 && bytes[page * 3] == 20); + checkBudget(before - page * 4); + Require(sceKernelMapFlexibleMemory(&replacement, page * 3, 3, 0x10) == 0 && replacement == original); + Require(bytes[0] == 0 && bytes[page] == 0 && bytes[page * 2] == 0 && bytes[page * 3] == 20); + checkBudget(before - page * 4); + Require(sceKernelReleaseFlexibleMemory(middle, page * 2) == 0); + checkBudget(before - page * 2); + replacement = middle; + Require(sceKernelMapFlexibleMemory(&replacement, page * 3, 3, 0x10) == 0 && replacement == middle); + Require(bytes[page] == 0 && bytes[page * 3] == 0); + checkBudget(before - page * 4); + Require(sceKernelReleaseFlexibleMemory(original, page * 4) == 0); + checkBudget(before); const auto length = before & ~(PS5_PAGE_SIZE - 1); void* full = nullptr; Require(sceKernelMapFlexibleMemory(&full, length, 3, 0) == 0); void* extra = nullptr; Require(sceKernelMapFlexibleMemory(&extra, PS5_PAGE_SIZE, 3, 0) == SCE_KERNEL_ERROR_ENOMEM && extra == nullptr); + static_cast(full)[0] = 31; + replacement = full; + Require(sceKernelMapFlexibleMemory(&replacement, length, 3, 0x90) == SCE_KERNEL_ERROR_ENOMEM); + Require(static_cast(full)[0] == 31); + checkBudget(before - length); + auto* fullBytes = static_cast(full); + fullBytes[page] = 37; + fullBytes[length - page] = 41; + auto* fullMiddle = static_cast(full) + page; + replacement = fullMiddle; + Require(sceKernelMapFlexibleMemory(&replacement, page, 3, 0x10) == 0 && replacement == fullMiddle); + Require(fullBytes[0] == 31 && fullBytes[page] == 0 && fullBytes[length - page] == 41); + checkBudget(before - length); + replacement = static_cast(full) + length - page; + Require(sceKernelMapFlexibleMemory(&replacement, page * 2, 3, 0x10) == SCE_KERNEL_ERROR_ENOMEM); + Require(fullBytes[0] == 31 && fullBytes[length - page] == 41); + checkBudget(before - length); + replacement = full; + Require(sceKernelMapFlexibleMemory(&replacement, length + page, 3, 0x10) == SCE_KERNEL_ERROR_ENOMEM); + Require(static_cast(full)[0] == 31); + checkBudget(before - length); + Require(sceKernelMapFlexibleMemory(&replacement, length, 3, 0x10) == 0 && replacement == full); + Require(static_cast(full)[0] == 0); + checkBudget(before - length); Require(sceKernelReleaseFlexibleMemory(full, length) == 0); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before); + for (int api = 0; api < 4; ++api) { + constexpr auto directPage = PS5_PAGE_SIZE; + const auto checkDirectBudget = [&](std::size_t expected) { + std::size_t current = 0; + Require(sceKernelAvailableFlexibleMemorySize(¤t) == 0); + std::printf("Direct replacement API=%d available=%zu expected=%zu\n", api, current, expected); + std::fflush(stdout); + Require(current == expected); + }; + void* original = nullptr; + Require(sceKernelMapFlexibleMemory(&original, directPage * 4, 3, 0) == 0); + auto* bytes = static_cast(original); + bytes[0] = 17; + bytes[directPage * 4 - 1] = 29; + bytes[directPage] = 37; + bytes[directPage * 3 - 1] = 41; + std::int64_t physical = -1; + Require(sceKernelAllocateDirectMemory(0, sceKernelGetDirectMemorySize(), directPage * 2, directPage, 0, &physical) == 0); + const auto mapDirect = [&](void*& address, int flags) { + if (api == 0) return sceKernelMapDirectMemory(&address, directPage * 2, 3, flags, physical, directPage); + if (api == 1) return sceKernelMapDirectMemory2(&address, directPage * 2, 0, 3, flags, physical, directPage); + if (api == 2) return sceKernelMapNamedDirectMemory(&address, directPage * 2, 3, flags, physical, directPage, "budget direct"); + KernelBatchMapEntry entry{address, static_cast(physical), directPage * 2, 3, 0, 0, 0}; + int processed = -1; + const int result = sceKernelBatchMap2(&entry, 1, &processed, flags); + Require(processed == (result == 0 ? 1 : 0)); + address = entry.start; + return result; + }; + void* middle = static_cast(original) + directPage; + Require(mapDirect(middle, 0x90) == SCE_KERNEL_ERROR_ENOMEM); + Require(middle == static_cast(original) + directPage); + checkDirectBudget(before - directPage * 4); + Require(bytes[0] == 17 && bytes[directPage * 4 - 1] == 29); + Require(bytes[directPage] == 37 && bytes[directPage * 3 - 1] == 41); + Require(mapDirect(middle, 0x10) == 0 && middle == static_cast(original) + directPage); + VirtualQueryInfo info{}; + Require(sceKernelVirtualQuery(middle, 0, &info, sizeof(info)) == 0 && info.is_direct && !info.is_flexible); + Require(bytes[0] == 17 && bytes[directPage * 4 - 1] == 29); + checkDirectBudget(before - directPage * 2); + const auto recovered = before - directPage * 2; + void* reused = nullptr; + Require(sceKernelMapFlexibleMemory(&reused, recovered, 3, 0) == 0); + static_cast(reused)[recovered - 1] = 31; + Require(sceKernelReleaseFlexibleMemory(reused, recovered) == 0); + checkDirectBudget(before - directPage * 2); + Require(sceKernelReleaseDirectMemory(physical, directPage * 2) == 0); + checkDirectBudget(before - directPage * 2); + Require(sceKernelReleaseFlexibleMemory(original, directPage * 4) == 0); + checkDirectBudget(before); + } } if (module) { Require(dlclose_nid_postfix(module) == 0); diff --git a/core/libs/tests/GuestFont.cpp b/core/libs/tests/GuestFont.cpp index f65a313d4070..a43268ca121c 100644 --- a/core/libs/tests/GuestFont.cpp +++ b/core/libs/tests/GuestFont.cpp @@ -59,6 +59,17 @@ const FontWritingStep* APS5_VABI sceFontWritingRefersRenderStep(FontWriting*); FontTextCharacter* APS5_VABI sceFontWritingRefersRenderStepCharacter(FontWriting*, const FontWritingLetterStep**); const void* APS5_VABI sceFontSelectLibraryFt(int); const void* APS5_VABI sceFontSelectRendererFt(int); +int APS5_VABI sceFontFtSupportBdf(); +int APS5_VABI sceFontFtSupportCid(); +int APS5_VABI sceFontFtSupportOpenType(); +int APS5_VABI sceFontFtSupportOpenTypeOtf(); +int APS5_VABI sceFontFtSupportOpenTypeTtf(); +int APS5_VABI sceFontFtSupportPcf(); +int APS5_VABI sceFontFtSupportPfr(); +int APS5_VABI sceFontFtSupportTrueType(); +int APS5_VABI sceFontFtSupportType1(); +int APS5_VABI sceFontFtSupportType42(); +int APS5_VABI sceFontFtSupportWinFonts(); int APS5_VABI sceFontCharacterGetSyllableStringState(const FontTextCharacter*, int*); FontTextCodes* APS5_VABI sceFontCharactersRefersTextCodes(const FontTextCharacter*, const FontTextCharacter*, FontTextCodes*); FontTextCodes* APS5_VABI sceFontTextCodesStepNext(FontTextCodes*); @@ -229,6 +240,7 @@ int main() { Require(sceFontMemoryInit(&memory, nullptr, 0, &iface, nullptr, nullptr, nullptr) == SCE_FONT_OK); Require(sceFontSelectLibraryFt(0) != nullptr && sceFontSelectLibraryFt(1) == nullptr); Require(sceFontSelectRendererFt(0) != nullptr && sceFontSelectRendererFt(1) == nullptr); + Require(sceFontFtSupportBdf() == 0 && sceFontFtSupportCid() == 0 && sceFontFtSupportOpenType() == 0 && sceFontFtSupportOpenTypeOtf() == 0 && sceFontFtSupportOpenTypeTtf() == 0 && sceFontFtSupportPcf() == 0 && sceFontFtSupportPfr() == 0 && sceFontFtSupportTrueType() == 0 && sceFontFtSupportType1() == 0 && sceFontFtSupportType42() == 0 && sceFontFtSupportWinFonts() == 0); FontLibrary library = nullptr; Require(sceFontCreateLibrary(&memory, nullptr, &library) == SCE_FONT_ERROR_INVALID_PARAMETER && library == nullptr); diff --git a/core/libs/tests/GuestGetitimer.cpp b/core/libs/tests/GuestGetitimer.cpp index 04f037f4997a..1fa5f825167e 100644 --- a/core/libs/tests/GuestGetitimer.cpp +++ b/core/libs/tests/GuestGetitimer.cpp @@ -86,9 +86,18 @@ int main() { Require(GuestAlarm_nid_no_patch(0) > 0, "getitimer cancelled the alarm"); Require(ReadRemaining() == 0, "cancelled alarm is not zero"); - GuestAlarm_nid_no_patch(std::numeric_limits::max()); - Require(ReadRemaining() > static_cast(std::numeric_limits::max()) * 1000000, - "long alarm was truncated to signed 32 bits"); + constexpr unsigned int longestAlarm = std::numeric_limits::max() / 2; + Require(GuestAlarm_nid_no_patch(longestAlarm) == 0, "alarm was already active"); + Require(ReadRemaining() > static_cast(longestAlarm - 1) * 1000000, + "long alarm was truncated"); + for (unsigned int invalid : {longestAlarm + 1, static_cast(std::numeric_limits::max()), std::numeric_limits::max()}) { + *__error_nid_postfix() = 0; + Require(GuestAlarm_nid_no_patch(invalid) == std::numeric_limits::max() && *__error_nid_postfix() == 22, + "an alarm beyond the itimer range did not fail with EINVAL"); + Require(ReadRemaining() > static_cast(longestAlarm - 1) * 1000000, + "a rejected alarm replaced the pending one"); + } + Require(GuestAlarm_nid_no_patch(0) == longestAlarm, "a rejected alarm changed the remaining time"); GuestAlarm_nid_no_patch(1); std::this_thread::sleep_for(std::chrono::milliseconds(1100)); Require(ReadRemaining() == 0, "expired alarm is not zero"); diff --git a/core/libs/tests/GuestGetrlimit.cpp b/core/libs/tests/GuestGetrlimit.cpp index 15e183c9e0b0..d9dc18406f83 100644 --- a/core/libs/tests/GuestGetrlimit.cpp +++ b/core/libs/tests/GuestGetrlimit.cpp @@ -11,6 +11,9 @@ #ifdef __APPLE__ #include #endif +#ifdef __APPLE__ +#include +#endif struct ResourceLimit { std::int64_t rlim_cur; diff --git a/core/libs/tests/GuestHmd2.cpp b/core/libs/tests/GuestHmd2.cpp index 85af8b0350a7..4276c24a61a8 100644 --- a/core/libs/tests/GuestHmd2.cpp +++ b/core/libs/tests/GuestHmd2.cpp @@ -10,6 +10,11 @@ std::int32_t APS5_VABI sceHmd2Initialize(const void* param); int APS5_VABI sceHmd2Open(); } +extern "C" { +int APS5_VABI sceHmd2GazeActivateGazeTracking(); +int APS5_VABI sceHmd2ReprojectionGetCurrentOutput(); +int APS5_VABI sceHmd2SetFieldOfView(); +} namespace { void Require(bool condition, const char* message) { @@ -32,6 +37,27 @@ int main() { threw = true; } Require(threw, "open after failed initialization did not throw"); + threw = false; + try { + sceHmd2GazeActivateGazeTracking(); + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw, "gaze activation did not throw"); + threw = false; + try { + sceHmd2ReprojectionGetCurrentOutput(); + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw, "reprojection get current output did not throw"); + threw = false; + try { + sceHmd2SetFieldOfView(); + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw, "set field of view did not throw"); std::puts("HMD2 tests passed"); return 0; } diff --git a/core/libs/tests/GuestHttp.cpp b/core/libs/tests/GuestHttp.cpp index e2e78d23fabd..bd3a9b01c96f 100644 --- a/core/libs/tests/GuestHttp.cpp +++ b/core/libs/tests/GuestHttp.cpp @@ -27,6 +27,8 @@ int APS5_VABI sceHttpWaitRequest(HttpEpollHandle, HttpNBEvent*, int, int); int APS5_VABI sceHttpAbortWaitRequest(HttpEpollHandle); int APS5_VABI sceHttpInit_nid_postfix(int, int, std::uint64_t); int APS5_VABI sceHttpTerm_nid_postfix(int); +int APS5_VABI sceHttpAuthCacheFlush(int); +int APS5_VABI sceHttpCookieFlush(int); int APS5_VABI sceHttpReadData(int, void*, std::size_t); int APS5_VABI sceHttpCreateRequest2(int, const char*, const char*, std::uint64_t); int APS5_VABI sceHttpsEnableOption(int, std::uint32_t); @@ -254,6 +256,13 @@ int main() { constexpr int beforeInit = static_cast(0x80431001); constexpr int aborted = static_cast(0x80431080); constexpr int invalidId = static_cast(0x80431100); + const auto flushCaches = [](int id, int expected) { + Require(sceHttpAuthCacheFlush(id) == expected); + Require(sceHttpCookieFlush(id) == expected); + }; + flushCaches(0, beforeInit); + flushCaches(-1, beforeInit); + flushCaches(request, beforeInit); HttpEpollHandle epoll = nullptr; HttpNBEvent events[2]{}; Require(sceHttpCreateEpoll(1, &epoll) == beforeInit); @@ -261,6 +270,12 @@ int main() { Require(sceHttpTerm_nid_postfix(1) == beforeInit); const int context = sceHttpInit_nid_postfix(0, 0, 0x10000); Require(context > 0); + flushCaches(context, 0); + flushCaches(context, 0); + flushCaches(0, invalidId); + flushCaches(-1, invalidId); + flushCaches(INT32_MAX, invalidId); + flushCaches(request, invalidId); Require(sceHttpCreateEpoll(-1, &epoll) == invalidId); Require(sceHttpCreateEpoll(context, nullptr) == invalidValue); Require(sceHttpCreateEpoll(context, &epoll) == 0 && epoll != nullptr); @@ -304,12 +319,23 @@ int main() { Require(sceHttpCreateEpoll(context, &epoll) == 0); const int otherContext = sceHttpInit_nid_postfix(0, 0, 0x10000); Require(otherContext > 0 && otherContext != context); + flushCaches(otherContext, 0); Require(sceHttpTerm_nid_postfix(otherContext) == 0); + flushCaches(otherContext, invalidId); + flushCaches(context, 0); Require(sceHttpTerm_nid_postfix(otherContext) == invalidId); Require(sceHttpWaitRequest(epoll, events, 2, 0) == 0); Require(waitWokenBy([&] { Require(sceHttpTerm_nid_postfix(context) == 0); }) == 0); Require(sceHttpWaitRequest(epoll, events, 2, 0) == beforeInit); Require(sceHttpTerm_nid_postfix(context) == beforeInit); + flushCaches(context, beforeInit); + const int newContext = sceHttpInit_nid_postfix(0, 0, 0x10000); + Require(newContext > 0 && newContext != context && newContext != otherContext); + flushCaches(context, invalidId); + flushCaches(otherContext, invalidId); + flushCaches(newContext, 0); + Require(sceHttpTerm_nid_postfix(newContext) == 0); + flushCaches(newContext, beforeInit); char data[16]; Require(sceHttpReadData(1, data, sizeof(data)) == network); diff --git a/core/libs/tests/GuestJson.cpp b/core/libs/tests/GuestJson.cpp index 465ba6398da2..5a0b6859bf56 100644 --- a/core/libs/tests/GuestJson.cpp +++ b/core/libs/tests/GuestJson.cpp @@ -24,6 +24,9 @@ struct Pair { }; using NullAccessCallback = const Value& (APS5_VABI*)(std::int32_t, const Value*, void*); using SetNullAccessCallback = int (APS5_VABI*)(void*, NullAccessCallback, void*); +using TypeMismatchHandler = const Value& (APS5_VABI*)(std::int32_t, const Value*, const Value*, void*); +using SpecialFloatHandler = Value* (APS5_VABI*)(Value*, std::int32_t, double, const Value*, void*, bool*); +using ElementAccessFailureHandler = void (APS5_VABI*)(std::int32_t, const String*, const std::size_t*, const Value*, const Value*, void*); extern "C" { int APS5_VABI _ZN3sce4Json11InitializerC1Ev(void*); @@ -33,6 +36,13 @@ int APS5_VABI _ZN3sce4Json11Initializer9terminateEv(void*); int APS5_VABI _ZN3sce4Json11Initializer27setGlobalNullAccessCallBackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_(void*, NullAccessCallback, void*); int APS5_VABI _ZN3sce4Json11Initializer27setGlobalNullAccessCallbackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_(void*, NullAccessCallback, void*); void APS5_VABI _ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(NullAccessCallback, void*); +int APS5_VABI _ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(Value*, TypeMismatchHandler, void*); +int APS5_VABI _ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(Value*, SpecialFloatHandler, void*); +int APS5_VABI _ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(Value*, ElementAccessFailureHandler, void*); +int APS5_VABI _ZN3sce4Json5Value3setEd(Value*, double); +int APS5_VABI _ZN3sce4Json5Value3setERKS1_(Value*, const Value*); +Value* APS5_VABI _ZN3sce4Json5ValueaSERKS1_(Value*, const Value*); +void APS5_VABI _ZN3sce4Json5ValueC1ERKS1_(Value*, const Value*); void APS5_VABI _ZN3sce4Json6StringC1Ev(String*); void APS5_VABI _ZN3sce4Json6StringC1EPKc(String*, const char*); void APS5_VABI _ZN3sce4Json6StringD1Ev(String*); @@ -769,6 +779,161 @@ static void StaticNullAccess() { _ZN3sce4Json5ValueD1Ev(&fallback); } +static Value replacement{}; +static int mismatchCalls = 0; +static std::int32_t mismatchRequested = -1; +static const Value* mismatchValue = nullptr; +static const Value* mismatchSecond = nullptr; +static void* mismatchContext = nullptr; + +static const Value& APS5_VABI OnTypeMismatch(std::int32_t requested, const Value* value, const Value* second, void* context) { + ++mismatchCalls; + mismatchRequested = requested; + mismatchValue = value; + mismatchSecond = second; + mismatchContext = context; + return replacement; +} + +static void RequireMismatch(int calls, std::int32_t requested, const Value* value, void* context) { + Require(mismatchCalls == calls && mismatchRequested == requested && mismatchValue == value); + Require(mismatchSecond == nullptr && mismatchContext == context); +} + +static void ValueTypeMismatchHandler() { + _ZN3sce4Json5ValueC1Ev(&replacement); + Require(_ZN3sce4Json5Value3setEl(&replacement, 42) == 0); + int context = 0; + Value root{}; + _ZN3sce4Json5ValueC1Ev(&root); + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&root, OnTypeMismatch, &context) == 0); + const char text[] = "{\"name\":\"text\",\"none\":null,\"list\":[true]}"; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, text, sizeof(text) - 1) == 0); + + const Value& name = Member(root, "name"); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&name) == 42); + RequireMismatch(1, TypeInteger, &name, &context); + Require(!*_ZNK3sce4Json5Value10getBooleanEv(&name)); + RequireMismatch(2, TypeBoolean, &name, &context); + Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(_ZNK3sce4Json5Value9getStringEv(&name)), "text") == 0); + Require(mismatchCalls == 2); + Require(_ZNK3sce4Json5Array4sizeEv(_ZNK3sce4Json5Value8getArrayEv(&root)) == 0); + RequireMismatch(3, TypeArray, &root, &context); + const Value* element = _ZNK3sce4Json5ValueixEm(&Member(root, "list"), 0); + Require(*_ZNK3sce4Json5Value7getRealEv(element) == 0.0); + RequireMismatch(4, TypeReal, element, &context); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&Member(root, "none")) == 0); + Require(mismatchCalls == 4); + + Value later{}; + _ZN3sce4Json5ValueC1Ev(&later); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&later, text, sizeof(text) - 1) == 0); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&Member(later, "name")) == 0); + Require(mismatchCalls == 4); + int laterContext = 0; + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&later, OnTypeMismatch, &laterContext) == 0); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&Member(later, "name")) == 42); + RequireMismatch(5, TypeInteger, &Member(later, "name"), &laterContext); + + Value other{}; + _ZN3sce4Json5ValueC1Ev(&other); + Require(_ZN3sce4Json5Value3setEPKc(&other, "other") == 0); + Require(_ZN3sce4Json5ValueaSERKS1_(&root, &other) == &root); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&root) == 42); + RequireMismatch(6, TypeInteger, &root, &context); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&other) == 0); + Require(mismatchCalls == 6); + Require(_ZN3sce4Json5Value3setEd(&other, 1.5) == 0); + Require(_ZN3sce4Json5Value3setERKS1_(&root, &other) == 0); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&root) == 42); + RequireMismatch(7, TypeInteger, &root, &context); + _ZN3sce4Json5Value5clearEv(&root); + Require(_ZN3sce4Json5Value3setEPKc(&root, "cleared") == 0); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&root) == 42); + RequireMismatch(8, TypeInteger, &root, &context); + Value copy{}; + _ZN3sce4Json5ValueC1ERKS1_(©, &root); + Require(*_ZNK3sce4Json5Value10getIntegerEv(©) == 0); + Require(mismatchCalls == 8); + + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&root, nullptr, nullptr) == 0); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&root) == 0); + Require(mismatchCalls == 8); + _ZN3sce4Json5ValueD1Ev(©); + _ZN3sce4Json5ValueD1Ev(&other); + _ZN3sce4Json5ValueD1Ev(&later); + _ZN3sce4Json5ValueD1Ev(&root); + _ZN3sce4Json5ValueD1Ev(&replacement); +} + +static int failureCalls = 0; + +static void APS5_VABI OnElementAccessFailure(std::int32_t, const String*, const std::size_t*, const Value*, const Value*, void*) { + ++failureCalls; +} + +template +static bool ThrowsUnverified(const TAccess& access) { + try { + access(); + } catch (const std::runtime_error& error) { + return std::string(error.what()).find("unverified") != std::string::npos; + } + return false; +} + +static void ValueElementAccessFailureHandler() { + Value root{}; + _ZN3sce4Json5ValueC1Ev(&root); + const char text[] = "{\"list\":[1],\"key\":2}"; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, text, sizeof(text) - 1) == 0); + int context = 0; + Require(_ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(&root, OnElementAccessFailure, &context) == 0); + + const Value& list = Member(root, "list"); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&Member(root, "key")) == 2); + Require(*_ZNK3sce4Json5Value10getIntegerEv(_ZNK3sce4Json5Value8getValueEm(&list, 0)) == 1); + String missing{}; + _ZN3sce4Json6StringC1EPKc(&missing, "missing"); + Require(ThrowsUnverified([&] { _ZNK3sce4Json5ValueixEPKc(&root, "missing"); })); + Require(ThrowsUnverified([&] { _ZNK3sce4Json5Value8getValueERKNS0_6StringE(&root, &missing); })); + Require(ThrowsUnverified([&] { _ZNK3sce4Json5ValueixEm(&root, 0); })); + Require(ThrowsUnverified([&] { _ZNK3sce4Json5Value8getValueEm(&list, 1); })); + Require(failureCalls == 0); + + Require(_ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(&root, nullptr, nullptr) == 0); + Require(_ZNK3sce4Json5Value7getTypeEv(&Member(root, "missing")) == TypeNull); + Require(_ZNK3sce4Json5Value7getTypeEv(_ZNK3sce4Json5Value8getValueEm(&list, 1)) == TypeNull); + _ZN3sce4Json6StringD1Ev(&missing); + _ZN3sce4Json5ValueD1Ev(&root); +} + +static int specialFloatCalls = 0; + +static Value* APS5_VABI OnSpecialFloat(Value* result, std::int32_t, double, const Value*, void*, bool*) { + ++specialFloatCalls; + _ZN3sce4Json5ValueC1Ev(result); + return result; +} + +static void ValueSpecialFloatHandler() { + Value real{}; + _ZN3sce4Json5ValueC1Ed(&real, std::numeric_limits::quiet_NaN()); + int context = 0; + Require(_ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(&real, OnSpecialFloat, &context) == 0); + String out{}; + _ZN3sce4Json6StringC1Ev(&out); + Require(ThrowsUnverified([&] { _ZN3sce4Json5Value9serializeERNS0_6StringE(&real, &out); })); + Require(_ZN3sce4Json5Value3setEd(&real, std::numeric_limits::infinity()) == 0); + Require(ThrowsUnverified([&] { _ZN3sce4Json5Value9serializeERNS0_6StringE(&real, &out); })); + _ZN3sce4Json6StringD1Ev(&out); + Require(_ZN3sce4Json5Value3setEd(&real, 2.5) == 0); + Require(Serialize(real) == "2.5"); + Require(specialFloatCalls == 0); + _ZN3sce4Json5ValueD1Ev(&real); +} + + static int NumericLocale() { const std::string previous = std::setlocale(LC_NUMERIC, nullptr); const char* selected = nullptr; @@ -851,6 +1016,9 @@ int main(int argc, char** argv) { NullAccess(_ZN3sce4Json11Initializer27setGlobalNullAccessCallBackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_); NullAccess(_ZN3sce4Json11Initializer27setGlobalNullAccessCallbackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_); StaticNullAccess(); + ValueTypeMismatchHandler(); + ValueElementAccessFailureHandler(); + ValueSpecialFloatHandler(); ValueAccess(); InitParameter2BaseConstructor(); InitParameters(); diff --git a/core/libs/tests/GuestKernelErrors.cpp b/core/libs/tests/GuestKernelErrors.cpp index 44bb942f032a..b1981c7ee029 100644 --- a/core/libs/tests/GuestKernelErrors.cpp +++ b/core/libs/tests/GuestKernelErrors.cpp @@ -1,8 +1,11 @@ #include "SceTypes.hpp" +#include +#include #include #include #include #include +#include extern "C" { int APS5_VABI sceKernelCreateSema(KernelSema*, const char*, std::uint32_t, int, int, void*); @@ -36,6 +39,7 @@ int APS5_VABI sceKernelClearEventFlag(KernelEventFlag ef, std::uint64_t bit_patt int APS5_VABI sceKernelCancelEventFlag(KernelEventFlag ef, std::uint64_t set_pattern, int* num_wait_threads); int APS5_VABI sceKernelPollEventFlag(KernelEventFlag ef, std::uint64_t bit_pattern, std::uint32_t wait_mode, std::uint64_t* result_pat); int APS5_VABI sceKernelWaitEventFlag(KernelEventFlag ef, std::uint64_t bit_pattern, std::uint32_t wait_mode, std::uint64_t* result_pat, KernelUseconds* timeout); +int APS5_Test_GetEventFlagWaiterCount(KernelEventFlag handle); } static constexpr int SCE_OK = 0; @@ -45,6 +49,7 @@ static constexpr int SCE_KERNEL_ERROR_ESRCH = static_cast(0x80020003); static constexpr int SCE_KERNEL_ERROR_EBADF = static_cast(0x80020009); static constexpr int SCE_KERNEL_ERROR_EDEADLK = static_cast(0x8002000B); static constexpr int SCE_KERNEL_ERROR_EFAULT = static_cast(0x8002000E); +static constexpr int SCE_KERNEL_ERROR_EACCES = static_cast(0x8002000D); static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016); static constexpr int SCE_KERNEL_ERROR_EBUSY = static_cast(0x80020010); static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = static_cast(0x8002003C); @@ -64,6 +69,14 @@ static constexpr int POSIX_EDEADLK = 11; static void Require(bool value) { if (!value) std::abort(); } +static void WaitForEventFlagWaiters(KernelEventFlag handle, int expected) { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (APS5_Test_GetEventFlagWaiterCount(handle) != expected) { + if (std::chrono::steady_clock::now() >= deadline) Require(false); + std::this_thread::yield(); + } +} + int main() { constexpr int maximum = std::numeric_limits::max(); KernelSema semaphore = nullptr; @@ -236,4 +249,101 @@ int main() { Require(sceKernelCreateEventFlag(&ef, "single", EVF_ATTR_SINGLE | 0x02u, 0, nullptr) == SCE_OK); Require(sceKernelDeleteEventFlag(ef) == SCE_OK); + + Require(sceKernelCreateEventFlag(&ef, "imm_sat", EVF_ATTR_MULTI, 0x05, nullptr) == SCE_OK); + pattern = 0; + Require(sceKernelWaitEventFlag(ef, 0x01, EVF_WAITMODE_AND | EVF_WAITMODE_CLEAR_PAT, &pattern, nullptr) == SCE_OK); + Require(pattern == 0x05); + Require(sceKernelPollEventFlag(ef, 0x01, EVF_WAITMODE_AND, &pattern) == SCE_KERNEL_ERROR_EBUSY); + Require(sceKernelPollEventFlag(ef, 0x04, EVF_WAITMODE_AND, &pattern) == SCE_OK); + Require(sceKernelDeleteEventFlag(ef) == SCE_OK); + + Require(sceKernelCreateEventFlag(&ef, "multi_order_nonclear_first", EVF_ATTR_MULTI, 0, nullptr) == SCE_OK); + std::atomic a1Res{-1}; + std::atomic a2Res{-1}; + std::uint64_t a1Pat = 0; + std::uint64_t a2Pat = 0; + std::thread a1([&] { + a1Res = sceKernelWaitEventFlag(ef, 0x01, EVF_WAITMODE_AND, &a1Pat, nullptr); + }); + WaitForEventFlagWaiters(ef, 1); + std::thread a2([&] { + a2Res = sceKernelWaitEventFlag(ef, 0x01, EVF_WAITMODE_AND | EVF_WAITMODE_CLEAR_PAT, &a2Pat, nullptr); + }); + WaitForEventFlagWaiters(ef, 2); + Require(sceKernelSetEventFlag(ef, 0x01) == SCE_OK); + a1.join(); + a2.join(); + Require(a1Res == SCE_OK); + Require(a2Res == SCE_OK); + Require(a1Pat == 0x01); + Require(a2Pat == 0x01); + Require(sceKernelPollEventFlag(ef, 0x01, EVF_WAITMODE_AND, &pattern) == SCE_KERNEL_ERROR_EBUSY); + Require(sceKernelDeleteEventFlag(ef) == SCE_OK); + + Require(sceKernelCreateEventFlag(&ef, "multi_order_clear_first", EVF_ATTR_MULTI, 0, nullptr) == SCE_OK); + std::atomic b1Res{-1}; + std::atomic b2Res{-1}; + std::uint64_t b1Pat = 0; + std::uint64_t b2Pat = 0; + std::thread b1([&] { + b1Res = sceKernelWaitEventFlag(ef, 0x01, EVF_WAITMODE_AND | EVF_WAITMODE_CLEAR_PAT, &b1Pat, nullptr); + }); + WaitForEventFlagWaiters(ef, 1); + std::thread b2([&] { + b2Res = sceKernelWaitEventFlag(ef, 0x01, EVF_WAITMODE_AND, &b2Pat, nullptr); + }); + WaitForEventFlagWaiters(ef, 2); + Require(sceKernelSetEventFlag(ef, 0x01) == SCE_OK); + b1.join(); + Require(b1Res == SCE_OK); + Require(b1Pat == 0x01); + Require(APS5_Test_GetEventFlagWaiterCount(ef) == 1); + Require(b2Res == -1); + Require(sceKernelSetEventFlag(ef, 0x01) == SCE_OK); + b2.join(); + Require(b2Res == SCE_OK); + Require(b2Pat == 0x01); + Require(sceKernelDeleteEventFlag(ef) == SCE_OK); + + Require(sceKernelCreateEventFlag(&ef, "del_waiters", EVF_ATTR_MULTI, 0, nullptr) == SCE_OK); + std::atomic delRes1{-1}; + std::atomic delRes2{-1}; + std::thread delT1([&] { + delRes1 = sceKernelWaitEventFlag(ef, 0x01, EVF_WAITMODE_AND, nullptr, nullptr); + }); + std::thread delT2([&] { + delRes2 = sceKernelWaitEventFlag(ef, 0x02, EVF_WAITMODE_AND, nullptr, nullptr); + }); + WaitForEventFlagWaiters(ef, 2); + Require(sceKernelDeleteEventFlag(ef) == SCE_OK); + delT1.join(); + delT2.join(); + Require(delRes1 == SCE_KERNEL_ERROR_EACCES); + Require(delRes2 == SCE_KERNEL_ERROR_EACCES); + + for (int iter = 0; iter < 50; ++iter) { + KernelEventFlag raceEf = nullptr; + Require(sceKernelCreateEventFlag(&raceEf, "set_del_race", EVF_ATTR_MULTI, 0, nullptr) == SCE_OK); + std::atomic setResult{-1}; + std::thread concurrentSet([&] { + setResult = sceKernelSetEventFlag(raceEf, 0x01); + }); + Require(sceKernelDeleteEventFlag(raceEf) == SCE_OK); + concurrentSet.join(); + Require(setResult == SCE_OK || setResult == SCE_KERNEL_ERROR_ESRCH); + } + + for (int iter = 0; iter < 50; ++iter) { + KernelEventFlag pollEf = nullptr; + Require(sceKernelCreateEventFlag(&pollEf, "poll_del_race", EVF_ATTR_MULTI, 0x01, nullptr) == SCE_OK); + std::atomic pollResult{-1}; + std::thread concurrentPoll([&] { + std::uint64_t observed = 0; + pollResult = sceKernelPollEventFlag(pollEf, 0x01, EVF_WAITMODE_AND, &observed); + }); + Require(sceKernelDeleteEventFlag(pollEf) == SCE_OK); + concurrentPoll.join(); + Require(pollResult == SCE_OK || pollResult == SCE_KERNEL_ERROR_ESRCH); + } } diff --git a/core/libs/tests/GuestKernelSys.cpp b/core/libs/tests/GuestKernelSys.cpp index 89c9b9350fef..32cedd479f18 100644 --- a/core/libs/tests/GuestKernelSys.cpp +++ b/core/libs/tests/GuestKernelSys.cpp @@ -45,6 +45,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 fchown_nid_postfix(int, std::uint32_t, std::uint32_t); std::int64_t APS5_VABI sceKernelRead(int, void*, std::size_t); std::uint16_t APS5_VABI umask_nid_postfix(std::uint16_t); int* APS5_VABI __error_nid_postfix(); @@ -79,6 +80,44 @@ bool ChrootThrows(const std::string& path) { return false; } +bool SymlinkTargetThrows(const char* target, const std::string& created) { + try { + symlink_nid_postfix(target, created.c_str()); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +void CheckSymlinkTargetRestrictions(const std::filesystem::path& root) { + const auto created = (root / "rejected").string(); + for (const auto& target : {std::string(".."), std::string("../x"), std::string("a/../b"), std::string("a/../../x"), std::string("/abs")}) + Require(SymlinkTargetThrows(target.c_str(), created)); +#ifdef _WIN32 + for (const auto& target : {std::string("C:/"), std::string("\\\\server\\share")}) + Require(SymlinkTargetThrows(target.c_str(), created)); +#endif + const auto links = [](const std::string& created, const char* target) { + char buffer[16]; + const auto length = std::strlen(target); + return readlink_nid_postfix(created.c_str(), buffer, sizeof(buffer)) == static_cast(length) && + std::memcmp(buffer, target, length) == 0; + }; + const auto nested = (root / "accepted-nested").string(); + const int link = symlink_nid_postfix("sub/dir", nested.c_str()); + if (link == 0) { + Require(links(nested, "sub/dir")); + const auto dots = (root / "accepted-dots").string(); + Require(symlink_nid_postfix("a..b", dots.c_str()) == 0 && links(dots, "a..b")); + const auto ellipsis = (root / "accepted-ellipsis").string(); + Require(symlink_nid_postfix("...", ellipsis.c_str()) == 0 && links(ellipsis, "...")); + Require(std::filesystem::remove(nested) && std::filesystem::remove(dots) && std::filesystem::remove(ellipsis)); + } else { + Require(link == -1 && Error() == 1); + std::fprintf(stderr, "symbolic links unavailable: the symlink target success cases not tested\n"); + } +} + void CheckPathconf(const std::filesystem::path& root, const std::string& file) { const auto directory = root.generic_string(); for (const auto& path : {file, directory}) { @@ -381,6 +420,7 @@ int main() { Require(symlink_nid_postfix("original.bin", (root / "missing" / "symbolic").string().c_str()) == -1 && Error() == 2); Require(symlink_nid_postfix(nullptr, symbolic.c_str()) == -1 && Error() == 14); Require(symlink_nid_postfix("original.bin", "") == -1 && Error() == 2); + CheckSymlinkTargetRestrictions(root); const int base = sceKernelOpen(root.string().c_str(), SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0); Require(base >= 0); const int relative = openat_nid_postfix(base, "file.txt", SCE_KERNEL_O_RDONLY); @@ -535,6 +575,15 @@ int main() { 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 + Require(fchown_nid_postfix(base, 0, 0) == 0); + Require(fchown_nid_postfix(base, 0xFFFFFFFFu, 0xFFFFFFFFu) == 0); + Require(fchown_nid_postfix(-1, 0, 0) == -1 && Error() == 9); + bool fchownThrew = false; + try { fchown_nid_postfix(base, 1, 0); } catch (const std::exception&) { fchownThrew = true; } + Require(fchownThrew); + fchownThrew = false; + try { fchown_nid_postfix(base, 0, 1); } catch (const std::exception&) { fchownThrew = true; } + Require(fchownThrew); Require(sceKernelClose(base) == 0); #ifndef _WIN32 if (created == 0) { diff --git a/core/libs/tests/GuestKqueue.cpp b/core/libs/tests/GuestKqueue.cpp index b3d463f9d86d..ca3404840e50 100644 --- a/core/libs/tests/GuestKqueue.cpp +++ b/core/libs/tests/GuestKqueue.cpp @@ -230,12 +230,12 @@ static void PipeRead() { char buffer[4]{}; Require(read_nid_postfix(pipe[0], buffer, sizeof(buffer)) == 3); Require(Poll(kq, events, 4) == 0); + const KernelTimespec limit{5, 0}; + const auto start = std::chrono::steady_clock::now(); std::thread writer([&pipe] { std::this_thread::sleep_for(std::chrono::milliseconds(50)); Require(write_nid_postfix(pipe[1], "d", 1) == 1); }); - const KernelTimespec limit{5, 0}; - const auto start = std::chrono::steady_clock::now(); Require(kevent_nid_postfix(kq, nullptr, 0, events, 4, &limit) == 1); const auto waited = std::chrono::steady_clock::now() - start; writer.join(); @@ -639,7 +639,29 @@ static void Timeout() { Require(close_nid_postfix(kq) == 0); } +static void StandardInput() { + const int copy = dup_nid_postfix(0); + if (copy < 0) { + Require(*__error_nid_postfix() == GuestEbadf); + return; + } + Require(close_nid_postfix(copy) == 0); + const int kq = kqueue_nid_postfix(); + int marker = 0; + KernelEvent events[4]{}; + Require(Apply(kq, Change(0, EvfiltRead, EvAdd, 0, 0, &marker)) == 0); + Require(Poll(kq, events, 4) == 0); + const KernelTimespec brief{0, 20000000}; + Require(kevent_nid_postfix(kq, nullptr, 0, events, 4, &brief) == 0); + Require(Apply(kq, Change(0, EvfiltRead, EvAdd, 0, 0, &marker)) == 0); + Require(Apply(kq, Change(0, EvfiltRead, EvDelete)) == 0); + RequireFailure(Apply(kq, Change(0, EvfiltRead, EvDelete)), GuestEnoent); + Require(ThrownMessage([kq] { Apply(kq, Change(0, EvfiltWrite, EvAdd)); }).find("EVFILT_WRITE on a non-socket descriptor") != std::string::npos); + Require(close_nid_postfix(kq) == 0); +} + int main() { + StandardInput(); CreateAndClose(); Timers(); PipeRead(); diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index 18032dad386b..7bebc8d6407e 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -83,6 +83,8 @@ float APS5_VABI copysignf_nid_postfix(float, float); double APS5_VABI remquo_nid_postfix(double, double, int*); float APS5_VABI remquof_nid_postfix(float, float, int*); float APS5_VABI fmaf_nid_postfix(float, float, float); +long double APS5_VABI ceill_nid_postfix(long double); +long double APS5_VABI floorl_nid_postfix(long double); float APS5_VABI tanf_nid_postfix(float); int APS5_VABI rand_nid_postfix(); void APS5_VABI srand_nid_postfix(unsigned int); diff --git a/core/libs/tests/GuestMathExact.cpp b/core/libs/tests/GuestMathExact.cpp new file mode 100644 index 000000000000..b3afbca1acf1 --- /dev/null +++ b/core/libs/tests/GuestMathExact.cpp @@ -0,0 +1,117 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +extern "C" { +long double APS5_VABI fdiml_nid_postfix(long double, long double); +long double APS5_VABI fmodl_nid_postfix(long double, long double); +long double APS5_VABI remquol_nid_postfix(long double, long double, int*); +int APS5_VABI ilogbl_nid_postfix(long double); +long double APS5_VABI roundl_nid_postfix(long double); +long double APS5_VABI modfl_nid_postfix(long double, long double*); +long double APS5_VABI nanl_nid_postfix(const char*); +long double APS5_VABI scalblnl_nid_postfix(long double, std::int64_t); +long double APS5_VABI fmal_nid_postfix(long double, long double, long double); +double APS5_VABI scalb_nid_postfix(double, double); +double APS5_VABI scalbln_nid_postfix(double, std::int64_t); +long double APS5_VABI remainderl_nid_postfix(long double, long double); +double APS5_VABI drem_nid_postfix(double, double); +float APS5_VABI dremf_nid_postfix(float, float); +std::int64_t APS5_VABI lrint_nid_postfix(double); +int APS5_VABI finite_nid_postfix(double); +int APS5_VABI __fpclassifyl_nid_postfix(long double); +int APS5_VABI __isnanl_nid_postfix(long double); +int APS5_VABI __isnormall_nid_postfix(long double); +short APS5_VABI _LDtest_nid_postfix(const long double*); +int APS5_VABI _Dclass_nid_postfix(double); +short APS5_VABI _LDsign_nid_postfix(long double); +} + +static void Require(bool value) { if (!value) std::abort(); } + +static long double Extended(std::uint16_t signExponent, std::uint64_t mantissa) { + long double value = 0; + unsigned char bytes[sizeof(long double)] = {}; + std::memcpy(bytes, &mantissa, sizeof(mantissa)); + std::memcpy(bytes + 8, &signExponent, sizeof(signExponent)); + std::memcpy(&value, bytes, sizeof(bytes)); + return value; +} + +static bool SameBits(long double left, long double right) { return std::memcmp(&left, &right, 10) == 0; } + +static void CheckSignalingNanPassThrough() { + const long double signaling = Extended(0x7fff, 0x8000000000001234ull); + Require(SameBits(fdiml_nid_postfix(signaling, 1.0L), signaling)); + Require(SameBits(fdiml_nid_postfix(1.0L, signaling), signaling)); + Require(SameBits(roundl_nid_postfix(signaling), signaling)); + long double integral = 0; + Require(SameBits(modfl_nid_postfix(signaling, &integral), signaling) && SameBits(integral, signaling)); + Require(SameBits(fmal_nid_postfix(1.0L, 1.0L, signaling), signaling)); + Require(SameBits(nanl_nid_postfix("123"), Extended(0x7fff, 0xc000000000000000ull))); +} + +static void CheckErrno() { + errno = 0; + Require(std::isnan(fmodl_nid_postfix(NAN, 1.0L)) && errno == EDOM); + errno = 0; + Require(fmodl_nid_postfix(7.0L, 2.0L) == 1.0L && errno == 0); + int quotient = 0; + errno = 0; + Require(std::isnan(remquol_nid_postfix(1.0L, 0.0L, "ient)) && errno == EDOM); + errno = 0; + remquol_nid_postfix(NAN, 0.0L, "ient); + Require(errno == 0); + Require(remquol_nid_postfix(-7.0L, 2.0L, "ient) == 1.0L && quotient == -4); + errno = 0; + Require(std::isnan(scalb_nid_postfix(1.0, 2.5)) && errno == 0); + Require(scalb_nid_postfix(1.0, 3.0) == 8.0 && scalb_nid_postfix(4.0, -INFINITY) == 0.0); + errno = 0; + Require(std::isnan(drem_nid_postfix(1.0, 0.0)) && errno == EDOM); + errno = 0; + Require(std::isnan(drem_nid_postfix(INFINITY, 1.0)) && errno == EDOM); + errno = 0; + Require(std::isnan(dremf_nid_postfix(1.0f, 0.0f)) && errno == EDOM); + errno = 0; + Require(std::isnan(remainderl_nid_postfix(1.0L, 0.0L)) && errno == EDOM); + errno = 0; + Require(std::isnan(remainderl_nid_postfix(INFINITY, 1.0L)) && errno == EDOM); + errno = 0; + Require(std::isnan(remainderl_nid_postfix(NAN, 0.0L)) && errno == 0); + Require(remainderl_nid_postfix(7.0L, 2.0L) == -1.0L && errno == 0); + Require(drem_nid_postfix(7.0, 2.0) == -1.0 && lrint_nid_postfix(2.5) == 2); +} + +static void CheckConsoleQuirks() { + Require(ilogbl_nid_postfix(NAN) == INT_MAX && ilogbl_nid_postfix(0.0L) == INT_MIN && ilogbl_nid_postfix(8.0L) == 3); + Require(scalblnl_nid_postfix(1.0L, INT64_C(0x100000001)) == 2.0L && scalblnl_nid_postfix(1.0L, INT64_MAX) == 0.5L); + Require(scalbln_nid_postfix(1.0, INT64_C(0x100000001)) == INFINITY && scalbln_nid_postfix(1.0, INT64_MIN) == 0.0); + Require(lrint_nid_postfix(0x1p40) == INT64_C(0x10000000000)); + const long double product = Extended(0x4000, 0xe000000000000000ull) * Extended(0x4001, 0xda76b022816f6a94ull); + Require(SameBits(fmal_nid_postfix(Extended(0x4000, 0xe000000000000000ull), Extended(0x4001, 0xda76b022816f6a94ull), Extended(0x9b45, 0xd3b49eac84283c95ull)), product)); + Require(finite_nid_postfix(1.0) == 1 && finite_nid_postfix(INFINITY) == 0); +} + +static void CheckClassification() { + const long double pseudoDenormal = Extended(0x0000, 0x8000000000000000ull); + Require(_LDtest_nid_postfix(&pseudoDenormal) == -1 && __fpclassifyl_nid_postfix(pseudoDenormal) == -2); + const long double zero = 0.0L, infinity = INFINITY, nan = NAN, denormal = Extended(0, 1); + Require(_LDtest_nid_postfix(&zero) == 0 && _LDtest_nid_postfix(&infinity) == 1 && _LDtest_nid_postfix(&nan) == 2 && _LDtest_nid_postfix(&denormal) == -2); + Require(__fpclassifyl_nid_postfix(1.0L) == -1 && __fpclassifyl_nid_postfix(0.0L) == 0 && __fpclassifyl_nid_postfix(INFINITY) == 1); + Require(__isnanl_nid_postfix(NAN) == 1 && __isnanl_nid_postfix(INFINITY) == 0); + Require(__isnormall_nid_postfix(1.0L) == 1 && __isnormall_nid_postfix(denormal) == 0); + Require(_Dclass_nid_postfix(5e-324) == -2 && _Dclass_nid_postfix(-0.0) == 0); + Require(_LDsign_nid_postfix(-2.0L) == -32768 && _LDsign_nid_postfix(2.0L) == 0); +} + +int main() { + CheckSignalingNanPassThrough(); + CheckErrno(); + CheckConsoleQuirks(); + CheckClassification(); + return 0; +} diff --git a/core/libs/tests/GuestMktime.cpp b/core/libs/tests/GuestMktime.cpp index 0a81573f9eab..06dbfcac6f6f 100644 --- a/core/libs/tests/GuestMktime.cpp +++ b/core/libs/tests/GuestMktime.cpp @@ -13,6 +13,7 @@ std::int64_t APS5_VABI libc_mktime_nid_postfix(GuestTm*); std::int64_t APS5_VABI mktime_nid_postfix(GuestTm*); GuestTm* APS5_VABI localtime_s_nid_postfix(const std::int64_t*, GuestTm*); void APS5_VABI tzset_nid_postfix(); +int APS5_VABI setenv_nid_postfix(const char*, const char*, int); } using Converter = std::int64_t (APS5_VABI *)(GuestTm*); @@ -77,11 +78,7 @@ static void CheckFailure(Converter convert) { } static void CheckZone(const char* zone) { -#ifdef _WIN32 - Require(_putenv_s("TZ", zone) == 0, "Could not set the time zone"); -#else - Require(setenv("TZ", zone, 1) == 0, "Could not set the time zone"); -#endif + Require(setenv_nid_postfix("TZ", zone, 1) == 0, "Could not set the time zone"); tzset_nid_postfix(); constexpr std::array converters{libc_mktime_nid_postfix, mktime_nid_postfix}; constexpr std::array timers{-86401, -2, -1, 0, 1, 59, 60, 86400}; diff --git a/core/libs/tests/GuestNetCtl.cpp b/core/libs/tests/GuestNetCtl.cpp index ad3bfb9ec6b8..9203f1c97239 100644 --- a/core/libs/tests/GuestNetCtl.cpp +++ b/core/libs/tests/GuestNetCtl.cpp @@ -8,6 +8,11 @@ #include #include #endif +#ifdef __APPLE__ +#include +#include +#include +#endif #include "prx/libc/include/general/VabiMacros.hpp" #include "SceTypes.hpp" #include @@ -18,10 +23,21 @@ #include #include +#ifdef __APPLE__ +struct NetCtlDefaultRoute { + char name[IF_NAMESIZE]; + in_addr gateway; + std::uint32_t metric; +}; +#endif + extern "C" { int APS5_VABI sceNetCtlGetInfo(int, NetCtlInfo*); int APS5_VABI sceNetCtlGetNatInfo(NetCtlNatInfo*); int APS5_VABI sceNetCtlGetState(int*); +#ifdef __APPLE__ +std::size_t NetCtlDefaultRoutes_nid_no_patch(const char*, std::size_t, NetCtlDefaultRoute*, std::size_t); +#endif } namespace { @@ -85,10 +101,66 @@ bool Binds(in_addr address) { } +#ifdef __APPLE__ +void AppendRoute(std::vector& table, std::uint32_t destination, std::uint32_t gateway, int flags, unsigned short index) { + rt_msghdr header{}; + header.rtm_msglen = static_cast(sizeof(header) + 2 * sizeof(sockaddr_in) + sizeof(std::uint32_t)); + header.rtm_version = RTM_VERSION; + header.rtm_type = RTM_GET; + header.rtm_index = index; + header.rtm_flags = flags; + header.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK; + sockaddr_in addresses[2]{}; + for (auto& address : addresses) { + address.sin_len = sizeof(address); + address.sin_family = AF_INET; + } + addresses[0].sin_addr.s_addr = htonl(destination); + addresses[1].sin_addr.s_addr = htonl(gateway); + const std::uint32_t emptyMask = 0; + const auto* bytes = reinterpret_cast(&header); + table.insert(table.end(), bytes, bytes + sizeof(header)); + bytes = reinterpret_cast(addresses); + table.insert(table.end(), bytes, bytes + sizeof(addresses)); + bytes = reinterpret_cast(&emptyMask); + table.insert(table.end(), bytes, bytes + sizeof(emptyMask)); +} + +void CheckDefaultRoutes() { + const auto loopback = static_cast(if_nametoindex("lo0")); + Require(loopback != 0); + constexpr int up = RTF_UP | RTF_GATEWAY | RTF_STATIC; + std::vector table; + AppendRoute(table, 0, 0x0A000001, up, loopback); + AppendRoute(table, 0x01020304, 0x0A000009, up | RTF_HOST, loopback); + AppendRoute(table, 0, 0x0A000009, up | RTF_HOST, loopback); + AppendRoute(table, 0, 0x0A000009, up | RTF_REJECT, loopback); + AppendRoute(table, 0, 0x0A000002, up | RTF_IFSCOPE, loopback); + AppendRoute(table, 0, 0x0A000009, RTF_GATEWAY | RTF_STATIC, loopback); + AppendRoute(table, 0, 0x0A000009, up, 0xFFFF); + const std::size_t complete = table.size(); + AppendRoute(table, 0, 0x0A000009, up, loopback); + table.resize(table.size() - sizeof(std::uint32_t)); + + NetCtlDefaultRoute routes[4]{}; + Require(NetCtlDefaultRoutes_nid_no_patch(table.data(), table.size(), routes, 4) == 2); + Require(std::strcmp(routes[0].name, "lo0") == 0 && ntohl(routes[0].gateway.s_addr) == 0x0A000001 && routes[0].metric == 0); + Require(std::strcmp(routes[1].name, "lo0") == 0 && ntohl(routes[1].gateway.s_addr) == 0x0A000002 && routes[1].metric == 1); + + NetCtlDefaultRoute first[1]{}; + Require(NetCtlDefaultRoutes_nid_no_patch(table.data(), complete, first, 1) == 2); + Require(ntohl(first[0].gateway.s_addr) == 0x0A000001); + Require(NetCtlDefaultRoutes_nid_no_patch(table.data(), 0, nullptr, 0) == 0); +} +#endif + int main() { #ifdef _WIN32 WSADATA data{}; Require(WSAStartup(MAKEWORD(2, 2), &data) == 0); +#endif +#ifdef __APPLE__ + CheckDefaultRoutes(); #endif NetCtlInfo info{}; Require(sceNetCtlGetInfo(0, &info) == kErrInvalidCode); diff --git a/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp b/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp new file mode 100644 index 000000000000..99f3f83152fd --- /dev/null +++ b/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp @@ -0,0 +1,321 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +using Deleter = void (APS5_VABI*)(void*); + +struct IntrusivePtr { + void* object; + Deleter deleter; + void* libContext; +}; + +using Construct = void (APS5_VABI*)(IntrusivePtr*); +using Copy = void (APS5_VABI*)(IntrusivePtr*, const IntrusivePtr*); +using Destroy = void (APS5_VABI*)(IntrusivePtr*); +using Access = void* (APS5_VABI*)(const IntrusivePtr*); + +extern "C" { +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEC1Ev(IntrusivePtr*); + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1ERS7_(IntrusivePtr*, const IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1ERS7_(IntrusivePtr*, const IntrusivePtr*); + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEED1Ev(IntrusivePtr*); + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V15EntryEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6BinaryEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEEdeEv(const IntrusivePtr*); + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEEC1EPNS2_10LibContextE(IntrusivePtr*, void*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEED1Ev(IntrusivePtr*); +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*); +} + +static void Require(bool value) { if (!value) std::abort(); } + +static constexpr std::size_t ObjectSize = 0x40; +static constexpr std::size_t VectorReferenceCountOffset = 0x30; + +struct DestroyCase { + Destroy destroy; + std::size_t referenceCountOffset; +}; + +static void* deletedObject = nullptr; +static int deleterCalls = 0; + +static void APS5_VABI RecordDelete(void* object) { + deletedObject = object; + ++deleterCalls; +} + +static std::int32_t ReadCount(const unsigned char* object, const std::size_t offset) { + std::int32_t value; + std::memcpy(&value, object + offset, sizeof(value)); + return value; +} + +static void WriteCount(unsigned char* object, const std::size_t offset, const std::int32_t value) { + std::memcpy(object + offset, &value, sizeof(value)); +} + +static bool OnlyCountChanged(const unsigned char* object, const std::size_t offset) { + for (std::size_t index = 0; index < ObjectSize; ++index) { + if (index >= offset && index < offset + sizeof(std::int32_t)) continue; + if (object[index] != 0x5a) return false; + } + return true; +} + +static void TestConstruct(const Construct construct) { + IntrusivePtr pointer; + std::memset(&pointer, 0xa5, sizeof(pointer)); + construct(&pointer); + Require(pointer.object == nullptr); + Require(pointer.deleter == nullptr); + Require(pointer.libContext == nullptr); +} + +static void TestAccess(const Access access) { + int object = 0; + int context = 0; + const IntrusivePtr empty{nullptr, RecordDelete, &context}; + Require(access(&empty) == nullptr); + const IntrusivePtr filled{&object, nullptr, nullptr}; + Require(access(&filled) == &object); +} + +static void TestCopy(const Copy copy) { + alignas(8) unsigned char object[ObjectSize]; + std::memset(object, 0x5a, sizeof(object)); + WriteCount(object, 0, 1); + int context = 0; + + const IntrusivePtr source{object, RecordDelete, &context}; + IntrusivePtr target; + std::memset(&target, 0xa5, sizeof(target)); + copy(&target, &source); + Require(target.object == object); + Require(target.deleter == RecordDelete); + Require(target.libContext == &context); + Require(source.object == object); + Require(ReadCount(object, 0) == 2); + Require(OnlyCountChanged(object, 0)); + + const IntrusivePtr empty{nullptr, RecordDelete, &context}; + std::memset(&target, 0xa5, sizeof(target)); + copy(&target, &empty); + Require(target.object == nullptr); + Require(target.deleter == RecordDelete); + Require(target.libContext == &context); +} + +static void TestDestroy(const DestroyCase& testCase) { + alignas(8) unsigned char object[ObjectSize]; + std::memset(object, 0x5a, sizeof(object)); + int context = 0; + const std::size_t offset = testCase.referenceCountOffset; + + deleterCalls = 0; + deletedObject = nullptr; + + IntrusivePtr empty{nullptr, RecordDelete, &context}; + testCase.destroy(&empty); + Require(empty.object == nullptr); + Require(empty.deleter == RecordDelete); + Require(empty.libContext == &context); + Require(deleterCalls == 0); + + WriteCount(object, offset, 2); + IntrusivePtr shared{object, RecordDelete, &context}; + testCase.destroy(&shared); + Require(ReadCount(object, offset) == 1); + Require(shared.object == object); + Require(shared.deleter == RecordDelete); + Require(shared.libContext == &context); + Require(deleterCalls == 0); + + IntrusivePtr last{object, RecordDelete, &context}; + testCase.destroy(&last); + Require(ReadCount(object, offset) == 0); + Require(OnlyCountChanged(object, offset)); + Require(deleterCalls == 1); + Require(deletedObject == object); + Require(last.object == nullptr); + Require(last.deleter == nullptr); + Require(last.libContext == &context); + + WriteCount(object, offset, 1); + IntrusivePtr pooled{object, nullptr, &context}; + bool thrown = false; + try { + testCase.destroy(&pooled); + } catch (const std::runtime_error&) { + thrown = true; + } + Require(thrown); + Require(deleterCalls == 1); +} + +static void TestSharedOwnership() { + alignas(8) unsigned char object[ObjectSize]; + std::memset(object, 0x5a, sizeof(object)); + WriteCount(object, 0, 1); + deleterCalls = 0; + deletedObject = nullptr; + + IntrusivePtr owner; + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1Ev(&owner); + owner.object = object; + owner.deleter = RecordDelete; + + IntrusivePtr argument; + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1ERS7_(&argument, &owner); + Require(_ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEptEv(&argument) == object); + Require(ReadCount(object, 0) == 2); + + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEED1Ev(&argument); + Require(ReadCount(object, 0) == 1); + Require(deleterCalls == 0); + + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEED1Ev(&owner); + Require(ReadCount(object, 0) == 0); + Require(deleterCalls == 1); + Require(deletedObject == object); + Require(_ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEptEv(&owner) == nullptr); +} + +static void TestVectorPushBackReleasesElements() { + int context = 0; + alignas(8) unsigned char object[ObjectSize]; + std::memset(object, 0x5a, sizeof(object)); + WriteCount(object, 0, 1); + deleterCalls = 0; + deletedObject = nullptr; + + IntrusivePtr vector; + _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEEC1EPNS2_10LibContextE(&vector, &context); + Require(vector.object == nullptr); + Require(vector.deleter == nullptr); + Require(vector.libContext == &context); + + const IntrusivePtr empty{nullptr, nullptr, &context}; + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEE8pushBackERKS8_(&vector, &empty) == 0); + Require(vector.object != nullptr); + Require(vector.deleter != nullptr); + + IntrusivePtr filled{object, RecordDelete, &context}; + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEE8pushBackERKS8_(&vector, &filled) == 0); + Require(ReadCount(object, 0) == 2); + + _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEED1Ev(&vector); + Require(vector.object == nullptr); + Require(vector.deleter == nullptr); + Require(ReadCount(object, 0) == 1); + Require(deleterCalls == 0); + + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEED1Ev(&filled); + Require(ReadCount(object, 0) == 0); + Require(deleterCalls == 1); + Require(deletedObject == object); +} + +static void TestAdoptedVectorBodyDestroyedThroughDeleter() { + int context = 0; + alignas(8) unsigned char body[ObjectSize]; + std::memset(body, 0x5a, sizeof(body)); + WriteCount(body, VectorReferenceCountOffset, 1); + deleterCalls = 0; + deletedObject = nullptr; + + IntrusivePtr vector; + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEEC1EPS6_PFvS8_EPNS2_10LibContextE(&vector, body, RecordDelete, &context); + Require(vector.object == body); + Require(vector.deleter == RecordDelete); + Require(vector.libContext == &context); + + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEED1Ev(&vector); + Require(ReadCount(body, VectorReferenceCountOffset) == 0); + Require(deleterCalls == 1); + Require(deletedObject == body); + Require(vector.object == nullptr); +} + +int main() { + Require(sizeof(IntrusivePtr) == 0x18); + + const Construct constructors[] = { + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEC1Ev, + }; + for (const auto construct : constructors) TestConstruct(construct); + + const Access accessors[] = { + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V15EntryEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6BinaryEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEEdeEv, + }; + for (const auto access : accessors) TestAccess(access); + + const Copy copies[] = { + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1ERS7_, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1ERS7_, + }; + for (const auto copy : copies) TestCopy(copy); + + const DestroyCase destructors[] = { + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEED1Ev, VectorReferenceCountOffset}, + }; + for (const auto& testCase : destructors) TestDestroy(testCase); + + TestSharedOwnership(); + TestVectorPushBackReleasesElements(); + TestAdoptedVectorBodyDestroyedThroughDeleter(); + return 0; +} diff --git a/core/libs/tests/GuestNpCppWebApiString.cpp b/core/libs/tests/GuestNpCppWebApiString.cpp new file mode 100644 index 000000000000..18a4dd25a621 --- /dev/null +++ b/core/libs/tests/GuestNpCppWebApiString.cpp @@ -0,0 +1,191 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +extern "C" { +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(void* self, void* libContext); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6String6appendEPKc(void* self, const char* text); +const char* APS5_VABI _ZNK3sce2Np9CppWebApi6Common6String5c_strEv(const void* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEEC1EPNS2_10LibContextE(void* self, void* libContext); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEED1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE8pushBackERKS4_(void* self, const void* element); +} + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +constexpr std::size_t kStringSize = 0x20; +constexpr std::size_t kGuardSize = 0x10; +constexpr std::size_t kReferenceCountOffset = 0x00; +constexpr std::size_t kBufferOffset = 0x08; +constexpr std::size_t kBufferSizeOffset = 0x10; +constexpr std::size_t kLibContextOffset = 0x18; +constexpr unsigned char kFill = 0xA5; + +constexpr std::size_t kVectorSize = 0x18; +constexpr std::size_t kObjectOffset = 0x00; +constexpr std::size_t kBodyMagicOffset = 0x00; +constexpr std::size_t kBodyReferenceCountOffset = 0x30; +constexpr std::size_t kBodyElementKindOffset = 0x34; +constexpr std::size_t kBodyElementsOffset = 0x38; +constexpr std::size_t kBodySizeOffset = 0x40; + +struct Storage { + alignas(8) unsigned char bytes[kStringSize + kGuardSize]; +}; + +struct VectorStorage { + alignas(8) unsigned char bytes[kVectorSize + kGuardSize]; +}; + +template +TValue Load(const Storage& storage, std::size_t offset) { + TValue value; + std::memcpy(&value, storage.bytes + offset, sizeof(value)); + return value; +} + +template +void Store(Storage& storage, std::size_t offset, TValue value) { + std::memcpy(storage.bytes + offset, &value, sizeof(value)); +} + +template +TValue LoadBody(const void* body, std::size_t offset) { + TValue value; + std::memcpy(&value, static_cast(body) + offset, sizeof(value)); + return value; +} + +bool GuardIntact(const Storage& storage) { + for (std::size_t i = kStringSize; i < sizeof(storage.bytes); i++) { + if (storage.bytes[i] != kFill) return false; + } + return true; +} + +bool VectorGuardIntact(const VectorStorage& storage) { + for (std::size_t i = kVectorSize; i < sizeof(storage.bytes); i++) { + if (storage.bytes[i] != kFill) return false; + } + return true; +} + +void TestConstructedStringIsEmpty() { + std::uint64_t libContext = 1; + Storage storage; + std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(storage.bytes, &libContext); + + Require(Load(storage, kReferenceCountOffset) == 0); + Require(Load(storage, kBufferOffset) == nullptr); + Require(Load(storage, kBufferSizeOffset) == 0); + Require(Load(storage, kLibContextOffset) == &libContext); + Require(GuardIntact(storage)); + + const char* text = _ZNK3sce2Np9CppWebApi6Common6String5c_strEv(storage.bytes); + Require(text != nullptr && text[0] == '\0'); + + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(storage.bytes); + Require(Load(storage, kBufferOffset) == nullptr); + Require(Load(storage, kBufferSizeOffset) == 0); + Require(GuardIntact(storage)); +} + +void TestNullLibContextIsStored() { + Storage storage; + std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(storage.bytes, nullptr); + + Require(Load(storage, kLibContextOffset) == nullptr); + Require(Load(storage, kBufferOffset) == nullptr); + Require(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(storage.bytes)[0] == '\0'); + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(storage.bytes); +} + +void TestAppendBuildsAndClearsBuffer() { + std::uint64_t libContext = 1; + Storage storage; + std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(storage.bytes, &libContext); + + Require(_ZN3sce2Np9CppWebApi6Common6String6appendEPKc(storage.bytes, "rank") == 0); + Require(std::strcmp(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(storage.bytes), "rank") == 0); + Require(_ZN3sce2Np9CppWebApi6Common6String6appendEPKc(storage.bytes, "ing") == 0); + Require(std::strcmp(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(storage.bytes), "ranking") == 0); + Require(Load(storage, kBufferOffset) != nullptr); + Require(GuardIntact(storage)); + + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(storage.bytes); + Require(Load(storage, kBufferOffset) == nullptr); + Require(Load(storage, kBufferSizeOffset) == 0); + Require(GuardIntact(storage)); +} + +void TestAppendNullKeepsEmpty() { + std::uint64_t libContext = 1; + Storage storage; + std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(storage.bytes, &libContext); + Require(_ZN3sce2Np9CppWebApi6Common6String6appendEPKc(storage.bytes, nullptr) == 0); + Require(Load(storage, kBufferOffset) == nullptr); + Require(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(storage.bytes)[0] == '\0'); + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(storage.bytes); +} + +void TestVectorPushBackCopiesStringContent() { + std::uint64_t libContext = 1; + VectorStorage vector; + Storage element; + std::memset(vector.bytes, kFill, sizeof(vector.bytes)); + std::memset(element.bytes, kFill, sizeof(element.bytes)); + + _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEEC1EPNS2_10LibContextE(vector.bytes, &libContext); + Require(VectorGuardIntact(vector)); + + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(element.bytes, &libContext); + Require(_ZN3sce2Np9CppWebApi6Common6String6appendEPKc(element.bytes, "privilege") == 0); + char* elementBuffer = Load(element, kBufferOffset); + Require(elementBuffer != nullptr); + + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE8pushBackERKS4_(vector.bytes, element.bytes) == 0); + + void* body; + std::memcpy(&body, vector.bytes + kObjectOffset, sizeof(body)); + Require(body != nullptr); + Require(LoadBody(body, kBodyReferenceCountOffset) == 1); + Require(LoadBody(body, kBodyElementKindOffset) == 2); + Require(LoadBody(body, kBodySizeOffset) == 1); + + void* elements = LoadBody(body, kBodyElementsOffset); + char* storedBuffer; + std::memcpy(&storedBuffer, static_cast(elements) + kBufferOffset, sizeof(storedBuffer)); + Require(storedBuffer != nullptr); + Require(storedBuffer != elementBuffer); + Require(std::strcmp(storedBuffer, "privilege") == 0); + + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(element.bytes); + Require(std::strcmp(storedBuffer, "privilege") == 0); + + _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEED1Ev(vector.bytes); + Require(VectorGuardIntact(vector)); +} + +} + +int main() { + TestConstructedStringIsEmpty(); + TestNullLibContextIsStored(); + TestAppendBuildsAndClearsBuffer(); + TestAppendNullKeepsEmpty(); + TestVectorPushBackCopiesStringContent(); +} diff --git a/core/libs/tests/GuestNpGameIntent.cpp b/core/libs/tests/GuestNpGameIntent.cpp new file mode 100644 index 000000000000..8d63fecbe1e0 --- /dev/null +++ b/core/libs/tests/GuestNpGameIntent.cpp @@ -0,0 +1,67 @@ +#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include +#include + +extern "C" { +int APS5_VABI sceNpGameIntentInitParamInit(NpGameIntentInitParam* param); +int APS5_VABI sceNpGameIntentInfoInit(NpGameIntentInfo* info); +int APS5_VABI sceNpGameIntentInitialize(const void* initParam); +int APS5_VABI sceNpGameIntentReceiveIntent(NpGameIntentInfo* intentInfo); +int APS5_VABI sceNpGameIntentGetPropertyValueString(const NpGameIntentData* intentData, const char* key, char* valueBuf, size_t bufSize); +int APS5_VABI sceNpGameIntentVshInitialize(void); +int APS5_VABI sceNpGameIntentVshTerminate(void); +int APS5_VABI sceNpGameIntentTerminate(void); +} + +static void Require(bool value, const char* what) { + if (value) return; + std::printf("NpGameIntent: %s\n", what); + std::abort(); +} + +template +static bool Throws(TCall call) { + try { + call(); + } catch (const TError&) { + return true; + } + return false; +} + +int main() { + constexpr int kIntentNotFound = -2141898746; + constexpr int kValueNotFound = -2141898745; + + Require(Throws([] { sceNpGameIntentInitParamInit(nullptr); }), + "null param init did not throw"); + + NpGameIntentInitParam initParam{}; + Require(sceNpGameIntentInitParamInit(&initParam) == 0, "param init failed"); + Require(initParam.size == sizeof(NpGameIntentInitParam), "param size mismatch"); + + Require(Throws([] { sceNpGameIntentInfoInit(nullptr); }), + "null info init did not throw"); + + NpGameIntentInfo info{}; + Require(sceNpGameIntentInfoInit(&info) == 0, "info init failed"); + Require(info.size == sizeof(NpGameIntentInfo), "info size mismatch"); + Require(info.user_id == -1, "info user_id mismatch"); + + Require(sceNpGameIntentInitialize(&initParam) == 0, "initialization failed"); + Require(sceNpGameIntentReceiveIntent(&info) == kIntentNotFound, "receive intent mismatch"); + + char buf[64]{}; + Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "key", buf, sizeof(buf)) == kValueNotFound, + "get property value mismatch"); + + Require(sceNpGameIntentVshInitialize() == 0, "vsh init failed"); + Require(sceNpGameIntentVshTerminate() == 0, "vsh terminate failed"); + Require(sceNpGameIntentTerminate() == 0, "terminate failed"); + + std::puts("NpGameIntent tests passed"); + return 0; +} diff --git a/core/libs/tests/GuestNpTus.cpp b/core/libs/tests/GuestNpTus.cpp new file mode 100644 index 000000000000..f2376a4113bf --- /dev/null +++ b/core/libs/tests/GuestNpTus.cpp @@ -0,0 +1,73 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +extern "C" { +int APS5_VABI sceNpTusCreateNpTitleCtxA(std::uint32_t serviceLabel, int userId); +int APS5_VABI sceNpTusCreateRequest(int titleCtxId); +int APS5_VABI sceNpTusDeleteRequest(int reqId); +int APS5_VABI sceNpTusAbortRequest(int reqId); +int APS5_VABI sceNpTusPollAsync(int reqId, int* result); +int APS5_VABI sceNpTusGetMultiSlotVariableAAsync(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option); +int APS5_VABI sceNpTusSetMultiSlotVariableAAsync(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, const std::int64_t* variableArray, int arrayLen, void* option); +int APS5_VABI sceNpTusDeleteMultiSlotVariableAAsync(int reqId, std::uint64_t targetAccountId, const std::int32_t* slotIds, int arrayLen, void* option); +int APS5_VABI sceNpTusAddAndGetVariableAAsync(int reqId, std::uint64_t targetAccountId, std::int32_t slotId, std::int64_t inVariable, const std::uint64_t* isLastChangedAuthor, const void* isLastChangedDate, void* outVariable, std::uint64_t outVariableSize, void* option); +} + +static constexpr int SIGNED_OUT = static_cast(0x80550006); +static constexpr int INVALID_ARGUMENT = static_cast(0x80550704); +static constexpr int ABORTED = static_cast(0x80550707); +static constexpr int INSUFFICIENT_ARGUMENT = static_cast(0x8055070C); +static constexpr int INVALID_ID = static_cast(0x8055070E); +static constexpr int TOO_MANY_SLOTID = static_cast(0x80550718); + +static void Require(bool value) { if (!value) std::abort(); } + +static void RequireFinished(int reqId, int expected) { + int result = 0; + Require(sceNpTusPollAsync(reqId, &result) == 0 && result == expected); +} + +int main() { + const int context = sceNpTusCreateNpTitleCtxA(0, 1); + Require(context > 0); + Require(sceNpTusCreateRequest(context + 1000) == INVALID_ID); + + const int request = sceNpTusCreateRequest(context); + Require(request > 0); + int result = 0; + Require(sceNpTusPollAsync(request, &result) == 1); + + std::int32_t slots[2] = {0, 3}; + std::int64_t values[2] = {5, 7}; + std::uint8_t variables[256] = {}; + Require(sceNpTusGetMultiSlotVariableAAsync(request, 0, nullptr, variables, sizeof(variables), 2, nullptr) == INSUFFICIENT_ARGUMENT); + Require(sceNpTusGetMultiSlotVariableAAsync(request, 0, slots, nullptr, sizeof(variables), 2, nullptr) == INSUFFICIENT_ARGUMENT); + Require(sceNpTusGetMultiSlotVariableAAsync(request, 0, slots, variables, sizeof(variables), 0, nullptr) == INVALID_ARGUMENT); + Require(sceNpTusGetMultiSlotVariableAAsync(request, 0, slots, variables, sizeof(variables), 65, nullptr) == TOO_MANY_SLOTID); + Require(sceNpTusGetMultiSlotVariableAAsync(request, 0, slots, variables, sizeof(variables), 2, variables) == INVALID_ARGUMENT); + std::int32_t negative[1] = {-1}; + Require(sceNpTusDeleteMultiSlotVariableAAsync(request, 0, negative, 1, nullptr) == INVALID_ARGUMENT); + Require(sceNpTusSetMultiSlotVariableAAsync(request, 0, slots, nullptr, 2, nullptr) == INSUFFICIENT_ARGUMENT); + Require(sceNpTusAddAndGetVariableAAsync(request, 0, 0, 1, nullptr, nullptr, nullptr, 0, nullptr) == INSUFFICIENT_ARGUMENT); + Require(sceNpTusAddAndGetVariableAAsync(request, 0, -1, 1, nullptr, nullptr, variables, sizeof(variables), nullptr) == INVALID_ARGUMENT); + + Require(sceNpTusGetMultiSlotVariableAAsync(request, 0, slots, variables, sizeof(variables), 2, nullptr) == 0); + RequireFinished(request, SIGNED_OUT); + Require(sceNpTusSetMultiSlotVariableAAsync(request, 0, slots, values, 2, nullptr) == 0); + RequireFinished(request, SIGNED_OUT); + Require(sceNpTusDeleteMultiSlotVariableAAsync(request, 0, slots, 2, nullptr) == 0); + RequireFinished(request, SIGNED_OUT); + Require(sceNpTusAddAndGetVariableAAsync(request, 0, 0, 1, nullptr, nullptr, variables, sizeof(variables), nullptr) == 0); + RequireFinished(request, SIGNED_OUT); + + Require(sceNpTusAbortRequest(request) == 0); + Require(sceNpTusSetMultiSlotVariableAAsync(request, 0, slots, values, 2, nullptr) == 0); + RequireFinished(request, ABORTED); + + Require(sceNpTusDeleteRequest(request) == 0); + Require(sceNpTusDeleteRequest(request) == INVALID_ID); + Require(sceNpTusAbortRequest(request) == INVALID_ID); + Require(sceNpTusPollAsync(request, &result) == INVALID_ID); + Require(sceNpTusSetMultiSlotVariableAAsync(request, 0, slots, values, 2, nullptr) == INVALID_ID); +} diff --git a/core/libs/tests/GuestPad.cpp b/core/libs/tests/GuestPad.cpp index 61e824535699..ac71cc935371 100644 --- a/core/libs/tests/GuestPad.cpp +++ b/core/libs/tests/GuestPad.cpp @@ -323,6 +323,13 @@ int main() { Require(PadFetchOutput_nid_postfix(&sequence, &snapshot)); Require(snapshot.audioVibrationLarge == 100 && snapshot.audioVibrationSmall == 150); Require(snapshot.vibrationLarge == 12 && snapshot.vibrationSmall == 34); + PadQueueLegacyAudioHaptics_nid_postfix(1, 0, ~std::uint64_t{0}, 200, 20); + Require(PadFetchOutput_nid_postfix(&sequence, &snapshot)); + Require(snapshot.audioVibrationLarge == 200 && snapshot.audioVibrationSmall == 150); + PadClearLegacyAudioHaptics_nid_postfix(1); + Require(PadFetchOutput_nid_postfix(&sequence, &snapshot)); + Require(snapshot.audioVibrationLarge == 100 && snapshot.audioVibrationSmall == 150); + Require(snapshot.vibrationLarge == 12 && snapshot.vibrationSmall == 34); PadSubmitAudioHaptics_nid_postfix(0, 0); std::this_thread::sleep_for(std::chrono::milliseconds(100)); Require(PadFetchOutput_nid_postfix(&sequence, &snapshot)); diff --git a/core/libs/tests/GuestPositionedClose.cpp b/core/libs/tests/GuestPositionedClose.cpp new file mode 100644 index 000000000000..3d44da1f082e --- /dev/null +++ b/core/libs/tests/GuestPositionedClose.cpp @@ -0,0 +1,73 @@ +#include "prx/libc/include/PreciseWait.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libkernel/File/include/FileFlags.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t); +int APS5_VABI sceKernelClose(int); +std::int64_t APS5_VABI sceKernelPread(int, void*, std::size_t, std::int64_t); +} + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "Positioned close check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +static constexpr std::int64_t ErrorEbadf = static_cast(0x80020009u); + +int main() { + const auto root = std::filesystem::path("anyps5-positioned-close-test-" + + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); + Require(std::filesystem::create_directory(root)); + const auto path = (root / "data.bin").string(); + constexpr std::size_t fileSize = 48u << 20; + { + std::ofstream stream(path, std::ios::binary); + const std::vector block(1u << 20, 'x'); + for (std::size_t written = 0; written < fileSize; written += block.size()) stream.write(block.data(), static_cast(block.size())); + Require(stream.good()); + } + std::vector buffer(32u << 20); + int overlapped = 0; + for (int round = 0; round < 60; ++round) { + const int descriptor = sceKernelOpen(path.c_str(), SCE_KERNEL_O_RDONLY, 0); + Require(descriptor >= 0); + std::atomic state{0}; + std::atomic threw{false}; + std::int64_t result = 0; + std::thread reader([&] { + state = 1; + try { + result = sceKernelPread(descriptor, buffer.data(), buffer.size(), 0); + } catch (const std::exception&) { + threw = true; + } + state = 2; + }); + while (state.load() == 0) std::this_thread::yield(); + PreciseSleepUs(300); + const int closed = sceKernelClose(descriptor); + reader.join(); + Require(!threw.load()); + Require(closed == 0); + Require(result == static_cast(buffer.size()) || result == ErrorEbadf); + if (result == static_cast(buffer.size())) ++overlapped; + } + Require(overlapped > 0); + std::filesystem::remove_all(root); + return 0; +} diff --git a/core/libs/tests/GuestPosixScalarIo.cpp b/core/libs/tests/GuestPosixScalarIo.cpp index 938c32f67b98..90cafe8ea9c1 100644 --- a/core/libs/tests/GuestPosixScalarIo.cpp +++ b/core/libs/tests/GuestPosixScalarIo.cpp @@ -191,6 +191,15 @@ int main() { Require(_read_nid_postfix(descriptors[0], pipeBuffer, sizeof(pipeBuffer)) == sizeof(pipeBuffer)); Require(std::memcmp(pipeBuffer, pipePayload, sizeof(pipePayload)) == 0); Require(close_nid_postfix(descriptors[0]) == 0); + int capacityDescriptors[2] = {-1, -1}; + Require(pipe_nid_postfix(capacityDescriptors) == 0); + std::string largePayload(8192, 'x'); + Require(write_nid_postfix(capacityDescriptors[1], largePayload.data(), largePayload.size()) == static_cast(largePayload.size())); + Require(close_nid_postfix(capacityDescriptors[1]) == 0); + std::string largeBuffer(8192, '\0'); + Require(_read_nid_postfix(capacityDescriptors[0], largeBuffer.data(), largeBuffer.size()) == static_cast(largeBuffer.size())); + Require(largeBuffer == largePayload); + Require(close_nid_postfix(capacityDescriptors[0]) == 0); #ifndef _WIN32 int nonBlockingPipe[2] = {-1, -1}; Require(pipe_nid_postfix(nonBlockingPipe) == 0); diff --git a/core/libs/tests/GuestProtectionReport.cpp b/core/libs/tests/GuestProtectionReport.cpp new file mode 100644 index 000000000000..87a3d29b527e --- /dev/null +++ b/core/libs/tests/GuestProtectionReport.cpp @@ -0,0 +1,84 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include + +extern "C" { +int APS5_VABI mprotect_nid_postfix(void*, std::size_t, int) noexcept; +int APS5_VABI sceKernelMapFlexibleMemory(void**, std::size_t, int, int); +int APS5_VABI sceKernelAllocateDirectMemory(std::int64_t, std::int64_t, std::size_t, std::size_t, int, std::int64_t*); +int APS5_VABI sceKernelMapDirectMemory(void**, std::size_t, int, int, std::int64_t, std::size_t); +int APS5_VABI sceKernelMprotect(const void*, std::size_t, int); +int APS5_VABI sceKernelMunmap(std::uint64_t, std::size_t); +int APS5_VABI sceKernelVirtualQuery(const void*, int, VirtualQueryInfo*, std::uint64_t); +int APS5_VABI sceKernelQueryMemoryProtection(void*, void**, void**, int*); +} + +namespace { + +constexpr std::size_t Page = 0x4000; + +void Require(bool condition, const char* what) { + if (condition) return; + std::fprintf(stderr, "%s\n", what); + std::abort(); +} + +void RequireReported(void* address, int expected, const char* what) { + VirtualQueryInfo info{}; + Require(sceKernelVirtualQuery(address, 0, &info, sizeof(info)) == 0, "virtual query of a mapped page failed"); + void* start = nullptr; + void* end = nullptr; + int protection = -1; + Require(sceKernelQueryMemoryProtection(address, &start, &end, &protection) == 0, "protection query of a mapped page failed"); + if (info.protection == expected && protection == expected) return; + std::fprintf(stderr, "%s: reported 0x%x (virtual query) and 0x%x (protection query), expected 0x%x\n", what, info.protection, protection, expected); + std::abort(); +} + +void ProtectedReports() { + void* mapped = nullptr; + Require(sceKernelMapFlexibleMemory(&mapped, Page, 0x2, 0) == 0 && mapped != nullptr, "write-only flexible map failed"); + RequireReported(mapped, 0x3, "a write-only flexible mapping"); + struct Case { + int requested; + int reported; + }; + constexpr Case Cases[] = { + {0x1, 0x1}, {0x2, 0x3}, {0x3, 0x3}, {0x10, 0x10}, {0x12, 0x13}, {0x20, 0x20}, {0x22, 0x23}, {0x30, 0x30}, {0x40, 0x40}, {0x80, 0xc0}, {0x82, 0xc3}, {0x100, 0x100}, {0x200, 0x300}, + {0x202, 0x303}, {0x220, 0x320}, {0x2a2, 0x3e3}, {0xf2, 0xf3}, {0x3f2, 0x3f3}, {static_cast(0xfffffff2u), 0x3f3}, + }; + for (const auto& item : Cases) { + Require(sceKernelMprotect(mapped, Page, item.requested) == 0, "sceKernelMprotect failed"); + char what[64]; + std::snprintf(what, sizeof(what), "sceKernelMprotect 0x%x", static_cast(item.requested)); + RequireReported(mapped, item.reported, what); + } + Require(mprotect_nid_postfix(mapped, Page, 0x2) == 0, "write-only mprotect failed"); + RequireReported(mapped, 0x3, "mprotect with PROT_WRITE"); + Require(sceKernelMunmap(reinterpret_cast(mapped), Page) == 0, "unmap of the flexible mapping failed"); + + std::int64_t physical = 0; + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, Page, 0, 0, &physical) == 0, "direct memory allocation failed"); + void* direct = nullptr; + Require(sceKernelMapDirectMemory(&direct, Page, 0xf2, 0, physical, 0) == 0 && direct != nullptr, "direct map with 0xf2 failed"); + RequireReported(direct, 0xf3, "a direct mapping with 0xf2"); + Require(sceKernelMprotect(direct, Page, static_cast(0xfffffff2u)) == 0, "sceKernelMprotect of the direct mapping failed"); + RequireReported(direct, 0x3f3, "a direct mapping protected with 0xfffffff2"); + Require(sceKernelMunmap(reinterpret_cast(direct), Page) == 0, "unmap of the direct mapping failed"); +} + +} + +int main() { + try { + ProtectedReports(); + } catch (const std::exception& error) { + std::fprintf(stderr, "unexpected exception: %s\n", error.what()); + return 1; + } + std::puts("reported protections include the read bit of every write bit"); + return 0; +} diff --git a/core/libs/tests/GuestQsort.cpp b/core/libs/tests/GuestQsort.cpp index 6124f2540fc1..f27a089c635f 100644 --- a/core/libs/tests/GuestQsort.cpp +++ b/core/libs/tests/GuestQsort.cpp @@ -12,6 +12,8 @@ extern "C" { int APS5_VABI mergesort_nid_postfix(void*, std::size_t, std::size_t, int (APS5_VABI*)(const void*, const void*)); int* APS5_VABI __error_nid_postfix(); void APS5_VABI qsort_nid_postfix(void*, std::size_t, std::size_t, int (APS5_VABI*)(const void*, const void*)); +int APS5_VABI qsort_s_nid_postfix(void*, std::size_t, std::size_t, int (APS5_VABI*)(const void*, const void*, void*), void*); +void* APS5_VABI set_constraint_handler_s_nid_postfix(void*); } namespace { @@ -70,6 +72,86 @@ int APS5_VABI CompareFirstByte(const void* left, const void* right) { return (first > second) - (first < second); } +int APS5_VABI CompareKeysWithDirection(const void* left, const void* right, void* context) { + return *static_cast(context) * CompareKeys(left, right); +} + +bool ExpectOrderS(const char* name, const std::vector& keys, const std::vector& tags) { + std::vector elements; + for (std::size_t index = 0; index < keys.size(); ++index) elements.push_back({keys[index], static_cast(index)}); + int direction = 1; + if (qsort_s_nid_postfix(elements.data(), elements.size(), sizeof(Element), CompareKeysWithDirection, &direction) != 0) { + std::fprintf(stderr, "%s: qsort_s failed\n", name); + return false; + } + for (std::size_t index = 0; index < tags.size(); ++index) { + if (elements[index].tag != tags[index]) { + std::fprintf(stderr, "%s: qsort_s element %zu has tag %d, expected %d\n", name, index, elements[index].tag, tags[index]); + return false; + } + } + return true; +} + +int constraintCalls = 0; +int constraintError = 0; +const char* constraintMessage = nullptr; + +void APS5_VABI CountConstraint(const char* message, void*, int error) { + ++constraintCalls; + constraintMessage = message; + constraintError = error; +} + +bool ExpectConstraint(const char* name, void* base, std::size_t count, std::size_t size, bool comparator) { + int direction = 1; + const int calls = constraintCalls; + const int result = qsort_s_nid_postfix(base, count, size, comparator ? CompareKeysWithDirection : nullptr, &direction); + if (result != 22 || constraintCalls != calls + 1 || constraintError != 22 || !constraintMessage || std::strcmp(constraintMessage, name) != 0) { + std::fprintf(stderr, "qsort_s did not report \"%s\" through the installed handler (result %d, calls %d)\n", name, result, constraintCalls - calls); + return false; + } + return true; +} + +bool ExpectQsortSConstraints() { + void* previous = set_constraint_handler_s_nid_postfix(reinterpret_cast(CountConstraint)); + Element element{}; + constexpr std::size_t huge = (static_cast(-1) >> 1) + 1; + bool correct = ExpectConstraint("qsort_s : n > RSIZE_MAX", &element, huge, sizeof(Element), true); + correct &= ExpectConstraint("qsort_s : es > RSIZE_MAX", &element, 1, huge, true); + correct &= ExpectConstraint("qsort_s : a == NULL", nullptr, 2, sizeof(Element), true); + correct &= ExpectConstraint("qsort_s : cmp == NULL", &element, 2, sizeof(Element), false); + if (set_constraint_handler_s_nid_postfix(previous) != reinterpret_cast(CountConstraint)) { + std::fprintf(stderr, "set_constraint_handler_s did not return the installed handler\n"); + correct = false; + } + return correct; +} + +bool ExpectQsortS() { + std::vector elements; + for (int index = 0; index < 50; ++index) elements.push_back({(index * 13) % 17, index}); + int direction = -1; + if (qsort_s_nid_postfix(elements.data(), elements.size(), sizeof(Element), CompareKeysWithDirection, &direction) != 0) return false; + for (std::size_t index = 1; index < elements.size(); ++index) { + if (elements[index - 1].key < elements[index].key) { + std::fprintf(stderr, "qsort_s with a descending context is not sorted at %zu\n", index); + return false; + } + } + if (qsort_s_nid_postfix(nullptr, 0, sizeof(Element), nullptr, nullptr) != 0) { + std::fprintf(stderr, "qsort_s of no elements failed\n"); + return false; + } + unsigned char bytes[] = {3, 1, 2}; + if (qsort_s_nid_postfix(bytes, 3, 0, CompareKeysWithDirection, &direction) != 0 || bytes[0] != 3 || bytes[1] != 1 || bytes[2] != 2) { + std::fprintf(stderr, "qsort_s with zero-size elements changed them\n"); + return false; + } + return true; +} + bool ExpectOrder(const char* name, const std::vector& keys, const std::vector& tags) { std::vector elements; for (std::size_t index = 0; index < keys.size(); ++index) elements.push_back({keys[index], static_cast(index)}); @@ -140,6 +222,10 @@ int main() { bool correct = true; correct &= ExpectOrder("insertion sort", {1, 0, 1, 0}, {1, 3, 0, 2}); correct &= ExpectOrder("median of three", {2, 1, 2, 0, 1, 2, 0, 1, 0, 2, 1, 0}, {11, 3, 6, 8, 10, 1, 7, 4, 9, 5, 0, 2}); + correct &= ExpectOrderS("insertion sort", {1, 0, 1, 0}, {1, 3, 0, 2}); + correct &= ExpectOrderS("median of three", {2, 1, 2, 0, 1, 2, 0, 1, 0, 2, 1, 0}, {11, 3, 6, 8, 10, 1, 7, 4, 9, 5, 0, 2}); + correct &= ExpectQsortS(); + correct &= ExpectQsortSConstraints(); std::vector keys; for (int index = 0; index < 48; ++index) keys.push_back((index * 7 + 3) % 5); correct &= ExpectOrder("pseudomedian of nine", keys, diff --git a/core/libs/tests/GuestRtc.cpp b/core/libs/tests/GuestRtc.cpp index 94548c3df0cd..a1d820e72d3c 100644 --- a/core/libs/tests/GuestRtc.cpp +++ b/core/libs/tests/GuestRtc.cpp @@ -45,13 +45,22 @@ int APS5_VABI sceRtcFormatRFC3339(char*, const RtcTick*, int); int APS5_VABI sceRtcParseRFC3339(RtcTick*, const char*); int APS5_VABI sceRtcParseDateTime(RtcTick*, const char*); int APS5_VABI sceRtcTickAddTicks(RtcTick*, const RtcTick*, std::int64_t); +int APS5_VABI sceRtcTickAddMicroseconds(RtcTick*, const RtcTick*, std::int64_t); int APS5_VABI sceRtcTickAddSeconds(RtcTick*, const RtcTick*, std::int64_t); +int APS5_VABI sceRtcTickAddMinutes(RtcTick*, const RtcTick*, std::int64_t); +int APS5_VABI sceRtcTickAddHours(RtcTick*, const RtcTick*, std::int32_t); int APS5_VABI sceRtcTickAddDays(RtcTick*, const RtcTick*, std::int32_t); +int APS5_VABI sceRtcTickAddWeeks(RtcTick*, const RtcTick*, std::int32_t); int APS5_VABI sceRtcTickAddMonths(RtcTick*, const RtcTick*, std::int32_t); int APS5_VABI sceRtcTickAddYears(RtcTick*, const RtcTick*, std::int16_t); int APS5_VABI sceRtcFormatRFC3339LocalTime(char*, const RtcTick*); int APS5_VABI sceRtcFormatRFC2822(char*, const RtcTick*, int); int APS5_VABI sceRtcFormatRFC2822LocalTime(char*, const RtcTick*); +int APS5_VABI sceRtcFormatRFC3339Precise(char*, const RtcTick*, int, int); +int APS5_VABI sceRtcFormatRFC3339PreciseLocalTime(char*, const RtcTick*, int); +int APS5_VABI sceRtcCompareTick(const RtcTick*, const RtcTick*); +int APS5_VABI sceRtcGetCurrentAdNetworkTick(RtcTick*); +int APS5_VABI sceRtcSetCurrentTick(const RtcTick*); } static void Require(bool value) { if (!value) std::abort(); } @@ -429,6 +438,23 @@ int main() { source.tick = maxTick; Require(sceRtcTickAddTicks(&result, &source, 1) == invalidValue); Require(sceRtcTickAddTicks(nullptr, &source, 1) == invalidPointer); + source.tick = leapDayTick; + result.tick = 123; + Require(sceRtcTickAddMicroseconds(&result, &source, 1500) == 0 && result.tick == leapDayTick + 1500); + Require(sceRtcTickAddMinutes(&result, &source, 90) == 0 && result.tick == leapDayTick + 5400000000ull); + Require(sceRtcTickAddHours(&result, &source, -2) == 0 && result.tick == leapDayTick - 7200000000ull); + Require(sceRtcTickAddWeeks(&result, &source, 1) == 0 && result.tick == leapDayTick + 604800000000ull); + Require(sceRtcSetTick(&converted, &result) == 0 && converted.month == 3 && converted.day == 7); + result.tick = 123; + Require(sceRtcTickAddMinutes(&result, &source, std::numeric_limits::max()) == invalidValue && result.tick == 123); + Require(sceRtcTickAddWeeks(&result, &source, std::numeric_limits::max()) == invalidValue && result.tick == 123); + Require(sceRtcTickAddHours(nullptr, &source, 1) == invalidPointer); + Require(sceRtcTickAddMicroseconds(&result, nullptr, 1) == invalidPointer); + source.tick = maxTick; + Require(sceRtcTickAddHours(&result, &source, 1) == invalidValue && result.tick == 123); + Require(sceRtcTickAddMicroseconds(&result, &source, 1) == invalidValue && result.tick == 123); + source.tick = 0; + Require(sceRtcTickAddMinutes(&result, &source, -1) == invalidValue && result.tick == 123); const std::uint64_t invalidTicks[] = {maxTick + 1, std::numeric_limits::max()}; for (std::uint64_t invalidTick : invalidTicks) { @@ -561,4 +587,31 @@ int main() { Require(sceRtcFormatRFC2822LocalTime(rfc2822, &tick) == 0 && std::strcmp(rfc2822, "Wed, 31 Dec 1969 23:59:59 +0000") == 0); SetTimeZone("UTC0"); #endif + + { + char precise[40]; + RtcTick first{leapDayTick}, second{leapDayTick + 1}; + Require(sceRtcFormatRFC3339Precise(precise, &first, 0, 0) == 0 && std::strcmp(precise, "2024-02-29T12:34:56Z") == 0); + Require(sceRtcFormatRFC3339Precise(precise, &first, 0, 3) == 0 && std::strcmp(precise, "2024-02-29T12:34:56.789Z") == 0); + Require(sceRtcFormatRFC3339Precise(precise, &first, 0, 6) == 0 && std::strcmp(precise, "2024-02-29T12:34:56.789000Z") == 0); + Require(sceRtcFormatRFC3339Precise(precise, &second, 90, 6) == 0 && std::strcmp(precise, "2024-02-29T14:04:56.789001+01:30") == 0); + Require(sceRtcFormatRFC3339Precise(precise, &first, -300, 1) == 0 && std::strcmp(precise, "2024-02-29T07:34:56.7-05:00") == 0); + Require(sceRtcFormatRFC3339Precise(precise, &first, 0, 7) == invalidValue); + Require(sceRtcFormatRFC3339Precise(precise, &first, 0, -1) == invalidValue); + Require(sceRtcFormatRFC3339Precise(precise, &first, 1440, 0) == invalidValue); + Require(sceRtcFormatRFC3339Precise(precise, &first, -1440, 0) == invalidValue); + Require(sceRtcFormatRFC3339Precise(nullptr, &first, 0, 0) == invalidPointer); + Require(sceRtcFormatRFC3339Precise(precise, nullptr, 0, 6) == 0 && precise[4] == '-'); + SetTimeZone("UTC0"); + Require(sceRtcFormatRFC3339PreciseLocalTime(precise, &first, 2) == 0 && std::strcmp(precise, "2024-02-29T12:34:56.78Z") == 0); + Require(sceRtcFormatRFC3339PreciseLocalTime(precise, &first, 7) == invalidValue); + Require(sceRtcFormatRFC3339PreciseLocalTime(nullptr, &first, 0) == invalidPointer); + Require(sceRtcCompareTick(&first, &second) == -1 && sceRtcCompareTick(&second, &first) == 1 && sceRtcCompareTick(&first, &first) == 0); + Require(sceRtcCompareTick(nullptr, &first) == invalidPointer && sceRtcCompareTick(&first, nullptr) == invalidPointer); + RtcTick current{}; + Require(sceRtcGetCurrentAdNetworkTick(¤t) == 0 && current.tick > unixEpochTick); + Require(sceRtcGetCurrentAdNetworkTick(nullptr) == invalidPointer); + RtcTick tooEarly{unixEpochTick - 1}; + Require(sceRtcSetCurrentTick(&tooEarly) == invalidValue && sceRtcSetCurrentTick(nullptr) == invalidPointer); + } } diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index 9032269a6baa..d162f7202495 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -66,6 +66,8 @@ int APS5_VABI sceNetResolverDestroy(int); int APS5_VABI sceNetResolverGetError(int, int*); int APS5_VABI sceNetCtlGetState(int*); int APS5_VABI sceNetCtlGetIfStat(NetCtlIfStat*); +int APS5_VABI sceNetCtlRegisterCallback(NetCtlCallback, void*, int*); +int APS5_VABI sceNetCtlUnregisterCallback(int); int APS5_VABI sceNetCtlRegisterCallbackV6(NetCtlCallback, void*, int*); int APS5_VABI sceNetCtlUnregisterCallbackV6(int); std::uint64_t NetCtlReadByteCounter_nid_no_patch(const char*); @@ -85,6 +87,8 @@ struct GuestPollEntry { int APS5_VABI poll_nid_postfix(GuestPollEntry*, std::uint32_t, int); int* APS5_VABI __error_nid_postfix(); int APS5_VABI sceNetInetPton(int, const char*, void*); +int APS5_VABI sceNetInetPtonEx(int, const char*, void*, int); +int APS5_VABI sceNetUsleep(int); const char* APS5_VABI sceNetInetNtop(int, const void*, char*, std::uint32_t); int* APS5_VABI sceNetErrnoLoc(void); } @@ -707,6 +711,34 @@ static void CheckAddressText(int family, const char* text) { Require(sceNetInetNtop(family, address.data(), nullptr, output.size()) == nullptr && *sceNetErrnoLoc() == 22); } +static void CheckInetPtonEx() { + std::array address{}; + Require(sceNetInetPtonEx(2, "192.168.1.20", address.data(), 0) == 1); + Require(address[0] == 192 && address[1] == 168 && address[2] == 1 && address[3] == 20); + std::array expected{}; + Require(sceNetInetPton(28, "fe80::1:2", expected.data()) == 1); + address.fill(0); + Require(sceNetInetPtonEx(28, "fe80::1:2", address.data(), 0) == 1 && address == expected); + Require(sceNetInetPtonEx(2, "not an address", address.data(), 0) == 0); + Require(Failed(sceNetInetPtonEx(99, "127.0.0.1", address.data(), 0), 47)); + Require(Failed(sceNetInetPtonEx(2, nullptr, address.data(), 0), 22)); + Require(Failed(sceNetInetPtonEx(2, "127.0.0.1", nullptr, 0), 22)); + for (const int flags : {1, -1}) { + bool threw = false; + try { sceNetInetPtonEx(2, "127.0.0.1", address.data(), flags); } catch (const std::runtime_error&) { threw = true; } + Require(threw); + } +} + +static void CheckUsleep() { + Require(sceNetUsleep(0) == 0); + const auto start = std::chrono::steady_clock::now(); + Require(sceNetUsleep(2000) == 0); + Require(std::chrono::steady_clock::now() - start >= std::chrono::microseconds(2000)); + *sceNetErrnoLoc() = 0; + Require(Failed(sceNetUsleep(-1), 22)); +} + static void CheckEpollIdent() { std::array address{16, 2, 0, 0, 127, 0, 0, 1}; const int receiver = sceNetSocket("epoll-ident", 2, 2, 17); @@ -917,6 +949,71 @@ static void CheckUnspecifiedIpv6() { Require(sceNetSocketClose(receiver) == 0); } +static void CheckAcceptedNonblocking(int family, bool nonblocking) { + const auto addressSize = family == 2 ? 16u : 28u; + std::array address{}; + address[0] = static_cast(addressSize); + address[1] = static_cast(family); + if (family == 2) { address[4] = 127; address[7] = 1; } + else address[23] = 1; + const int listener = sceNetSocket("accept-mode", family, 1, 6); + Require(listener >= 0 && sceNetBind_nid_postfix(listener, address.data(), addressSize) == 0); + Require(sceNetListen(listener, 1) == 0); + const int mode = nonblocking ? 1 : 0; + Require(sceNetSetsockopt(listener, 0xffff, 0x1200, &mode, sizeof(mode)) == 0); + std::uint32_t size = address.size(); + Require(sceNetGetsockname(listener, address.data(), &size) == 0 && size == addressSize); + const int client = sceNetSocket("accept-client", family, 1, 6); + Require(client >= 0 && sceNetConnect(client, address.data(), addressSize) == 0); + int accepted = -1; + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); + do { + accepted = sceNetAccept(listener, nullptr, nullptr); + if (accepted >= 0) break; + Require(nonblocking && Failed(accepted, 35)); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } while (std::chrono::steady_clock::now() < deadline); + Require(accepted >= 0); + int inherited = -1; + size = sizeof(inherited); + Require(sceNetGetsockopt(accepted, 0xffff, 0x1200, &inherited, &size) == 0 && size == sizeof(inherited)); + const int status = fcntl_nid_postfix(accepted, 3); + std::printf("Accepted IPv%d mode=%d SO_NBIO=%d F_GETFL=%d\n", family == 2 ? 4 : 6, mode, inherited, status); + std::fflush(stdout); + Require(inherited == mode && status == (2 | (nonblocking ? 4 : 0))); + const int changed = 1 - mode; + Require(sceNetSetsockopt(listener, 0xffff, 0x1200, &changed, sizeof(changed)) == 0); + size = sizeof(inherited); + Require(sceNetGetsockopt(accepted, 0xffff, 0x1200, &inherited, &size) == 0 && inherited == mode); + struct ReceiveResult { std::int64_t bytes; int error; char value; }; + auto receive = std::async(std::launch::async, [accepted] { + char byte = '?'; + const auto received = sceNetRecv(accepted, &byte, sizeof(byte), 0); + return ReceiveResult{received, *sceNetErrnoLoc(), byte}; + }); + const bool ready = receive.wait_for(nonblocking ? std::chrono::milliseconds(2000) : std::chrono::milliseconds(50)) == std::future_status::ready; + const char payload = 'n'; + Require(sceNetSend(client, &payload, sizeof(payload), 0) == sizeof(payload)); + const auto received = receive.get(); + Require(ready == nonblocking); + if (nonblocking) { + Require(received.bytes == static_cast(0x80410123) && received.error == 35 && received.value == '?'); + char byte = '?'; + std::int64_t count = -1; + const auto arrival = std::chrono::steady_clock::now() + std::chrono::seconds(2); + do { + count = sceNetRecv(accepted, &byte, sizeof(byte), 0); + if (count >= 0) break; + Require(Failed(count, 35)); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } while (std::chrono::steady_clock::now() < arrival); + Require(count == sizeof(byte) && byte == payload); + } else { + Require(received.bytes == sizeof(payload) && received.value == payload); + } + for (const int descriptor : {accepted, client, listener}) Require(sceNetSocketClose(descriptor) == 0); +} + static void CheckIpv6SocketsAreIpv6Only() { const int inet = sceNetSocket("ipv4-any", 2, 2, 17); Require(inet >= 0); @@ -1081,6 +1178,8 @@ int main(int argc, char** argv) { CheckAddressText(2, "255.255.255.255"); CheckAddressText(28, "::1"); CheckAddressText(28, "1234:5678:9abc:def0:1234:5678:9abc:def0"); + CheckInetPtonEx(); + CheckUsleep(); CheckEpollIdent(); CheckLinkLocalAllNodesIpv6(); CheckUnboundSocketName(); @@ -1090,6 +1189,11 @@ int main(int argc, char** argv) { CheckUnboundDatagramTimeoutAfterAddress(); CheckUnspecifiedIpv6(); if (!ipv6Unavailable) CheckIpv6SocketsAreIpv6Only(); + for (const int family : {2, 28}) { + if (family == 28 && ipv6Unavailable) continue; + CheckAcceptedNonblocking(family, false); + CheckAcceptedNonblocking(family, true); + } CheckDatagramDisconnect(); CheckCloseKernelSocket(); CheckKernelSocketOption(); @@ -1457,6 +1561,27 @@ int main(int argc, char** argv) { Require(sceNetCtlUnregisterCallbackV6(i) == 0); } } + { + int cid = -1; + Require(sceNetCtlRegisterCallback(nullptr, nullptr, &cid) == static_cast(0x80412107)); + Require(sceNetCtlRegisterCallback(IgnoreNetCtlEvent, nullptr, nullptr) == static_cast(0x80412107)); + int cids[8] = {}; + for (int i = 0; i < 8; ++i) { + Require(sceNetCtlRegisterCallback(IgnoreNetCtlEvent, nullptr, &cids[i]) == 0); + Require(cids[i] == i); + } + Require(sceNetCtlRegisterCallback(IgnoreNetCtlEvent, nullptr, &cid) == static_cast(0x80412103)); + Require(sceNetCtlRegisterCallbackV6(IgnoreNetCtlEvent, nullptr, &cid) == 0); + Require(sceNetCtlUnregisterCallback(-1) == static_cast(0x80412105)); + Require(sceNetCtlUnregisterCallback(8) == static_cast(0x80412105)); + Require(sceNetCtlUnregisterCallback(3) == 0); + Require(sceNetCtlRegisterCallback(IgnoreNetCtlEvent, nullptr, &cid) == 0); + Require(cid == 3); + for (int i = 0; i < 8; ++i) { + Require(sceNetCtlUnregisterCallback(i) == 0); + } + Require(sceNetCtlUnregisterCallbackV6(0) == 0); + } if (ipv6Unavailable) { std::puts("skipped the IPv6 socket checks, the host has no usable IPv6 loopback"); return 77; diff --git a/core/libs/tests/GuestSocketEvents.cpp b/core/libs/tests/GuestSocketEvents.cpp index 94bf0656db6b..64184d40807c 100644 --- a/core/libs/tests/GuestSocketEvents.cpp +++ b/core/libs/tests/GuestSocketEvents.cpp @@ -104,11 +104,11 @@ int main() { const int reader = accept_nid_postfix(listener, nullptr, nullptr); Require(reader >= 0); Require(sceKernelAddReadEvent(blocking, reader, 1, nullptr) == SCE_OK); + const auto start = std::chrono::steady_clock::now(); std::thread sender([&] { std::this_thread::sleep_for(std::chrono::milliseconds(50)); Require(send_nid_postfix(writer, message, sizeof(message), 0) == sizeof(message)); }); - const auto start = std::chrono::steady_clock::now(); Require(sceKernelWaitEqueue(blocking, events.data(), events.size(), &count, nullptr) == SCE_OK); sender.join(); Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(40)); diff --git a/core/libs/tests/GuestStrings.cpp b/core/libs/tests/GuestStrings.cpp index de2206b7ba2e..85a907e10346 100644 --- a/core/libs/tests/GuestStrings.cpp +++ b/core/libs/tests/GuestStrings.cpp @@ -1,4 +1,5 @@ #include "prx/libc/include/general/VabiMacros.hpp" +#include #include #include #include @@ -7,6 +8,7 @@ #include extern "C" { +void APS5_VABI bcopy_nid_postfix(const void*, void*, std::size_t); char* APS5_VABI basename_nid_postfix(const char*); char* APS5_VABI __inet_ntoa_nid_postfix(unsigned int); char* APS5_VABI dirname_nid_postfix(const char*); @@ -25,6 +27,7 @@ char* APS5_VABI strtok_nid_postfix(char*, const char*); char* APS5_VABI strsep_nid_postfix(char**, const char*); char* APS5_VABI strcasestr_nid_postfix(const char*, const char*); int APS5_VABI strcpy_s_nid_postfix(char*, std::size_t, const char*); +int APS5_VABI strncpy_s_nid_postfix(char*, std::size_t, const char*, std::size_t); int APS5_VABI strcat_s_nid_postfix(char*, std::size_t, const char*); int APS5_VABI strncat_s_nid_postfix(char*, std::size_t, const char*, std::size_t); int APS5_VABI memcpy_s_nid_postfix(void*, std::size_t, const void*, std::size_t); @@ -135,6 +138,33 @@ static void CheckBoundsCheckedFunctions() { Require(snprintf_s_nid_postfix(formatted, sizeof(formatted), "%d-%s", 42, "x") == 4 && std::strcmp(formatted, "42-x") == 0); } +static void CheckSecureStringSizeLimits() { + constexpr std::size_t RsizeMax = SIZE_MAX >> 1; + const std::array original = {'!', 'a', 'b', '\0', 'x', 'y', 'z', '!'}; + for (const auto size : {RsizeMax + 1, SIZE_MAX}) { + for (const char* source : {"cd", "", static_cast(nullptr)}) { + for (const auto copy : {strcpy_s_nid_postfix, strcat_s_nid_postfix}) { + auto bytes = original; + Require(copy(bytes.data() + 1, size, source) != 0); + Require(bytes == original); + } + for (const auto copy : {strncpy_s_nid_postfix, strncat_s_nid_postfix}) { + for (const auto count : {std::size_t{0}, std::size_t{1}, std::size_t{3}}) { + auto bytes = original; + Require(copy(bytes.data() + 1, size, source, count) != 0); + Require(bytes == original); + } + } + } + } + auto bytes = original; + Require(strcpy_s_nid_postfix(bytes.data() + 1, RsizeMax, "cd") == 0); + Require(bytes[0] == '!' && std::strcmp(bytes.data() + 1, "cd") == 0 && bytes[4] == 'x'); + bytes = original; + Require(strcat_s_nid_postfix(bytes.data() + 1, RsizeMax, "cd") == 0); + Require(bytes[0] == '!' && std::strcmp(bytes.data() + 1, "abcd") == 0 && bytes[6] == 'z'); +} + static void CheckCaseInsensitiveComparison() { Require(strcasecmp_nid_postfix("", "") == 0 && strcasecmp_nid_postfix("AbC", "aBc") == 0); Require(strcasecmp_nid_postfix("A", "b") == 'a' - 'b' && strcasecmp_nid_postfix("b", "A") == 'b' - 'a'); @@ -168,7 +198,28 @@ static void CheckSscanfS() { #endif } +static void CheckBcopy() { + constexpr std::array original = {0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187}; + for (std::size_t source = 0; source < original.size(); ++source) { + for (std::size_t destination = 0; destination < original.size(); ++destination) { + for (std::size_t count = 0; count <= original.size() - std::max(source, destination); ++count) { + auto actual = original; + auto expected = original; + for (std::size_t i = 0; i < count; ++i) expected[destination + i] = original[source + i]; + bcopy_nid_postfix(actual.data() + source, actual.data() + destination, count); + Require(actual == expected); + } + } + } + auto destination = original; + destination.fill(0xff); + bcopy_nid_postfix(original.data(), destination.data(), original.size()); + Require(destination == original); + bcopy_nid_postfix(nullptr, nullptr, 0); +} + int main() { + CheckBcopy(); CheckBoundsCheckedFunctions(); CheckCaseInsensitiveComparison(); CheckSscanfS(); @@ -357,5 +408,6 @@ int main() { const char highBytes[] = {static_cast(0xff), 'A', 0}; Require(strcasestr_nid_postfix(highBytes, "a") == highBytes + 1); CheckSecureCopySizeLimits(); + CheckSecureStringSizeLimits(); return CheckMemcpyOverlap() ? 0 : 1; } diff --git a/core/libs/tests/GuestSysarch.cpp b/core/libs/tests/GuestSysarch.cpp index 7167b5003fe5..f2c4551138a7 100644 --- a/core/libs/tests/GuestSysarch.cpp +++ b/core/libs/tests/GuestSysarch.cpp @@ -1,13 +1,16 @@ -#include "prx/libc/include/general/VabiMacros.hpp" +#include "SceTypes.hpp" #include #include +#include #include #include extern "C" { int APS5_VABI sysarch(int number, void* args); int* APS5_VABI __error_nid_postfix(); +int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); +int APS5_VABI scePthreadJoin(Pthread thread, void** retval); } static void Require(bool value) { if (!value) std::abort(); } @@ -21,9 +24,31 @@ template static std::string Thrown(TCall call) { return {}; } +#if defined(__linux__) && defined(__x86_64__) +static void* APS5_VABI ReadGsBase(void* arg) { + auto* read = static_cast(arg); + if (sysarch(130, read) != 0) *read = 0; + return nullptr; +} + +static void RequireLowHalf(int operation, std::uint64_t base) { + unsigned char bytes[8]; + std::memset(bytes, 0xaa, sizeof(bytes)); + Require(sysarch(operation, bytes) == 0); + std::uint32_t low = 0; + std::memcpy(&low, bytes, sizeof(low)); + Require(low == static_cast(base)); + for (int i = 4; i < 8; ++i) Require(bytes[i] == 0xaa); +} +#endif + int main() { Require(Thrown([] { sysarch(129, nullptr); }) == "sysarch: operation 129 not implemented"); - Require(Thrown([] { sysarch(7, nullptr); }) == "sysarch: operation 7 not implemented"); + Require(Thrown([] { sysarch(132, nullptr); }) == "sysarch: operation 132 not implemented"); + for (const int operation : {2, 5, 6, 12, 127, 133, 134, 1000, -1}) { + *__error_nid_postfix() = 0; + Require(sysarch(operation, nullptr) == -1 && *__error_nid_postfix() == 22); + } #if defined(__linux__) && defined(__x86_64__) static std::uint64_t slots[2] = {0x1234567890abcdefull, 0}; std::uint64_t base = reinterpret_cast(slots); @@ -38,6 +63,16 @@ int main() { std::uint64_t self = 0; asm volatile("movq %%fs:0, %0" : "=r"(self)); Require(self == fs); + RequireLowHalf(7, fs); + RequireLowHalf(9, base); + *__error_nid_postfix() = 0; + Require(sysarch(7, nullptr) == -1 && *__error_nid_postfix() == 14); + *__error_nid_postfix() = 0; + Require(sysarch(9, nullptr) == -1 && *__error_nid_postfix() == 14); + std::uint64_t inherited = 0; + Pthread child = nullptr; + Require(scePthreadCreate(&child, nullptr, ReadGsBase, &inherited, "sysarch_gs") == 0); + Require(scePthreadJoin(child, nullptr) == 0 && inherited == base); std::uint64_t outside = 0x0000800000000000ull; *__error_nid_postfix() = 0; Require(sysarch(131, &outside) == -1 && *__error_nid_postfix() == 22); @@ -50,6 +85,7 @@ int main() { #else std::uint64_t base = 0; Require(Thrown([&base] { sysarch(130, &base); }) == "sysarch: segment bases on this host not implemented"); + Require(Thrown([&base] { sysarch(7, &base); }) == "sysarch: segment bases on this host not implemented"); #endif return 0; } diff --git a/core/libs/tests/GuestSysmodule.cpp b/core/libs/tests/GuestSysmodule.cpp index ac2d59886da8..d09901bc2269 100644 --- a/core/libs/tests/GuestSysmodule.cpp +++ b/core/libs/tests/GuestSysmodule.cpp @@ -23,6 +23,7 @@ int APS5_VABI sceSysmoduleLoadModule(std::uint16_t id); int APS5_VABI sceSysmoduleUnloadModule(std::uint16_t id); int APS5_VABI sceSysmoduleLoadModuleInternal(std::uint32_t id); int APS5_VABI sceSysmoduleUnloadModuleInternal(std::uint32_t id); +int APS5_VABI sceSysmoduleIsLoadedInternal(std::uint32_t id); int APS5_VABI sceSysmoduleLoadModuleInternalWithArg(std::uint32_t id, int argc, void* argv, std::uint64_t unk, int* ret); int APS5_VABI sceSysmoduleGetModuleInfoForUnwind(std::uint64_t addr, int flags, ModuleInfoForUnwind* info); int APS5_VABI sceSysmoduleGetModuleHandleInternal(std::uint32_t id, std::int32_t* handle); @@ -51,9 +52,14 @@ constexpr std::uint32_t kInternalFlag = 0x80000000u; constexpr std::uint16_t kAcmModuleId = 0x010b; constexpr std::uint16_t kTrophy2ModuleId = 0x0110; constexpr std::uint16_t kVoiceChatModuleId = 0x0114; +constexpr std::uint16_t kPerfModuleId = 0x0005; +constexpr std::uint16_t kVrSetupDialogModuleId = 0x0130; constexpr std::uint16_t kProprietaryVoiceChatHelperModuleId = 0x0132; constexpr std::uint16_t kPsmlModuleId = 0x0137; +constexpr std::uint32_t kAgcDriverModuleId = 0x80000080; +constexpr std::uint32_t kNgs2InternalModuleId = 0x80000090; constexpr std::uint32_t kAgcModuleId = 0x80000094; +constexpr std::uint32_t kHdrScopesInternalModuleId = 0x800000bd; #ifdef __linux__ struct Mapping { @@ -177,6 +183,7 @@ int main() { Require(sceSysmoduleLoadModule(0) == kInvalidValue); Require(sceSysmoduleUnloadModule(0) == kInvalidValue); for (std::uint32_t id : {0u, kInternalFlag}) { + Require(sceSysmoduleIsLoadedInternal(id) == kInvalidValue); Require(sceSysmoduleLoadModuleInternal(id) == kInvalidValue); Require(sceSysmoduleUnloadModuleInternal(id) == kInvalidValue); int result = 0x5a5a5a5a; @@ -184,8 +191,11 @@ int main() { Require(result == 0x5a5a5a5a); } + Require(ThrowsRuntimeError([] { sceSysmoduleIsLoadedInternal(0x00ffffffu); })); + Require(sceSysmoduleIsLoadedInternal(kFiberModuleId) == kModuleUnloaded); Require(sceSysmoduleIsLoaded(kFiberModuleId) == kModuleUnloaded); Require(sceSysmoduleLoadModuleInternal(kFiberModuleId) == 0); + Require(sceSysmoduleIsLoadedInternal(kFiberModuleId) == 0); Require(sceSysmoduleIsLoaded(kFiberModuleId) == 0); Require(sceSysmoduleLoadModuleInternal(kFiberModuleId) == 0); Require(sceSysmoduleUnloadModuleInternal(kFiberModuleId) == 0); @@ -246,11 +256,46 @@ int main() { Require(sceSysmoduleUnloadModule(kProprietaryVoiceChatHelperModuleId) == 0); Require(sceSysmoduleIsLoaded(kProprietaryVoiceChatHelperModuleId) == kModuleUnloaded); + Require(sceSysmoduleIsLoaded(kPerfModuleId) == kModuleUnloaded); + Require(sceSysmoduleUnloadModule(kPerfModuleId) == kModuleUnloaded); + Require(sceSysmoduleLoadModule(kPerfModuleId) == 0); + Require(sceSysmoduleIsLoaded(kPerfModuleId) == 0); + Require(sceSysmoduleUnloadModule(kPerfModuleId) == 0); + Require(sceSysmoduleIsLoaded(kPerfModuleId) == kModuleUnloaded); + + Require(sceSysmoduleIsLoaded(kVrSetupDialogModuleId) == kModuleUnloaded); + Require(sceSysmoduleUnloadModule(kVrSetupDialogModuleId) == kModuleUnloaded); + Require(sceSysmoduleLoadModule(kVrSetupDialogModuleId) == 0); + Require(sceSysmoduleIsLoaded(kVrSetupDialogModuleId) == 0); + Require(sceSysmoduleUnloadModule(kVrSetupDialogModuleId) == 0); + Require(sceSysmoduleIsLoaded(kVrSetupDialogModuleId) == kModuleUnloaded); + + Require(sceSysmoduleUnloadModuleInternal(kAgcDriverModuleId) == kModuleUnloaded); + Require(sceSysmoduleLoadModuleInternal(kAgcDriverModuleId) == 0); + Require(sceSysmoduleLoadModuleInternal(kAgcDriverModuleId) == 0); + Require(sceSysmoduleUnloadModuleInternal(kAgcDriverModuleId) == 0); + Require(sceSysmoduleUnloadModuleInternal(kAgcDriverModuleId) == 0); + Require(sceSysmoduleUnloadModuleInternal(kAgcDriverModuleId) == kModuleUnloaded); + + Require(sceSysmoduleUnloadModuleInternal(kNgs2InternalModuleId) == kModuleUnloaded); + Require(sceSysmoduleLoadModuleInternal(kNgs2InternalModuleId) == 0); + Require(sceSysmoduleUnloadModuleInternal(kNgs2InternalModuleId) == 0); + Require(sceSysmoduleUnloadModuleInternal(kNgs2InternalModuleId) == kModuleUnloaded); + + Require(sceSysmoduleUnloadModuleInternal(kHdrScopesInternalModuleId) == kModuleUnloaded); + Require(sceSysmoduleLoadModuleInternal(kHdrScopesInternalModuleId) == 0); + Require(sceSysmoduleUnloadModuleInternal(kHdrScopesInternalModuleId) == 0); + Require(sceSysmoduleUnloadModuleInternal(kHdrScopesInternalModuleId) == kModuleUnloaded); + + Require(sceSysmoduleIsLoadedInternal(kAgcModuleId) == kModuleUnloaded); Require(sceSysmoduleUnloadModuleInternal(kAgcModuleId) == kModuleUnloaded); Require(sceSysmoduleLoadModuleInternal(kAgcModuleId) == 0); + Require(sceSysmoduleIsLoadedInternal(kAgcModuleId) == 0); Require(sceSysmoduleLoadModuleInternal(kAgcModuleId) == 0); Require(sceSysmoduleUnloadModuleInternal(kAgcModuleId) == 0); + Require(sceSysmoduleIsLoadedInternal(kAgcModuleId) == 0); Require(sceSysmoduleUnloadModuleInternal(kAgcModuleId) == 0); + Require(sceSysmoduleIsLoadedInternal(kAgcModuleId) == kModuleUnloaded); Require(sceSysmoduleUnloadModuleInternal(kAgcModuleId) == kModuleUnloaded); #if defined(__linux__) || defined(_WIN32) CheckUnwindInfo(); diff --git a/core/libs/tests/GuestSystemConfiguration.cpp b/core/libs/tests/GuestSystemConfiguration.cpp index 32e8dadc1d6b..bed7ab1cbec9 100644 --- a/core/libs/tests/GuestSystemConfiguration.cpp +++ b/core/libs/tests/GuestSystemConfiguration.cpp @@ -28,6 +28,11 @@ int APS5_VABI getpid_nid_postfix(); void* APS5_VABI mmap_nid_postfix(void*, std::size_t, int, int, int, std::int64_t); int APS5_VABI munmap_nid_postfix(void*, std::size_t); std::uint64_t APS5_VABI sceKernelGetTscFrequency(void); +struct GuestRlimitTest { + std::int64_t rlim_cur; + std::int64_t rlim_max; +}; +int APS5_VABI getrlimit_nid_postfix(int resource, GuestRlimitTest* limit); extern char** environ_nid_postfix; } static void Require(bool value) { if (!value) std::abort(); } @@ -505,8 +510,13 @@ int main() { CheckVmmapSysctl(); CheckNameToMib(); *__error_nid_postfix() = 13; + Require(sysconf_nid_postfix(1) == 262144); Require(sysconf_nid_postfix(3) == 128); + GuestRlimitTest nofileLimit{}; + Require(getrlimit_nid_postfix(8, &nofileLimit) == 0); + Require(sysconf_nid_postfix(5) == nofileLimit.rlim_cur); Require(sysconf_nid_postfix(47) == 0x4000); + Require(sysconf_nid_postfix(56) == 1024); Require(getpagesize_nid_postfix() == sysconf_nid_postfix(47)); Require(sysconf_nid_postfix(57) > 0); Require(sysconf_nid_postfix(58) > 0); diff --git a/core/libs/tests/GuestTimeConversion.cpp b/core/libs/tests/GuestTimeConversion.cpp index aff8d5917604..b05b603ae2ba 100644 --- a/core/libs/tests/GuestTimeConversion.cpp +++ b/core/libs/tests/GuestTimeConversion.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include extern "C" { @@ -26,6 +28,7 @@ void APS5_VABI tzset_nid_postfix(void); extern char* tzname_nid_postfix[2]; char* APS5_VABI ctime_nid_postfix(const std::int64_t*); int* APS5_VABI __error_nid_postfix(); +int APS5_VABI setenv_nid_postfix(const char*, const char*, int); double APS5_VABI libc_difftime_nid_postfix(std::int64_t, std::int64_t); double APS5_VABI difftime_nid_postfix(std::int64_t, std::int64_t); } @@ -57,7 +60,7 @@ static void CheckConcurrent(Converter convert, BufferedConverter buffered, const for (std::size_t index = 0; index < workers.size(); ++index) { workers[index] = std::thread([&, index] { start.arrive_and_wait(); - for (int iteration = 0; iteration < 50000; ++iteration) { + for (int iteration = 0; iteration < 10000; ++iteration) { const auto* result = convert(&timers[index]); if (result == nullptr || !Equal(*result, expected[index])) { matches = false; @@ -206,14 +209,99 @@ static void CheckFreeBsdConversions() { Require(strftime_nid_postfix(tooSmall, sizeof(tooSmall), "%k:%l", &guest) == 0, "strftime accepted an undersized buffer"); } -int main() { +static void CheckGuestZone() { + const std::int64_t moment = 172800; + GuestTm result{}; + Require(setenv_nid_postfix("TZ", "UTC-5", 1) == 0, "Could not set the guest time zone"); + Require(localtime_s_nid_postfix(&moment, &result) == &result && result.tm_hour == 5 && result.tm_gmtoff == 18000, + "localtime ignored a guest TZ change made without tzset"); + GuestTm calendar{}; + calendar.tm_year = 70; + calendar.tm_mday = 3; + calendar.tm_hour = 5; + calendar.tm_isdst = -1; + Require(mktime_nid_postfix(&calendar) == moment, "mktime ignored the guest TZ"); + Require(setenv_nid_postfix("TZ", "UTC+3", 1) == 0, "Could not change the guest time zone"); + tzset_nid_postfix(); + Require(localtime_s_nid_postfix(&moment, &result) == &result && result.tm_hour == 21 && result.tm_mday == 2 && result.tm_gmtoff == -10800, + "tzset ignored the guest TZ"); + Require(setenv_nid_postfix("TZ", "", 1) == 0, "Could not empty the guest time zone"); + tzset_nid_postfix(); + Require(localtime_s_nid_postfix(&moment, &result) == &result && result.tm_hour == 0 && result.tm_mday == 3 && result.tm_gmtoff == 0, + "An empty guest TZ was not treated as UTC"); + Require(setenv_nid_postfix("TZ", "UTC-2", 1) == 0, "Could not restore the guest time zone"); + tzset_nid_postfix(); + Require(setenv_nid_postfix("TZ", "EST5", 1) == 0, "Could not set the guest time zone without tzset"); + Require(localtime_s_nid_postfix(&moment, &result) == &result && result.tm_hour == 19 && result.tm_gmtoff == -18000, + "localtime ignored a guest TZ change made without tzset"); + Require(std::strcmp(tzname_nid_postfix[0], "EST") == 0, "Implicit guest TZ update did not refresh tzname"); + Require(setenv_nid_postfix("TZ", "UTC-2", 1) == 0, "Could not restore the guest time zone"); + tzset_nid_postfix(); +} + +static bool ZoneFieldsConsistent(const GuestTm& time, std::int64_t instant, int hour) { + const std::int64_t localSeconds = ((instant + time.tm_gmtoff) % 86400 + 86400) % 86400; + if (time.tm_hour * 3600 + time.tm_min * 60 + time.tm_sec != localSeconds || time.tm_hour != hour) return false; + char expected[16]; + const std::int64_t offset = time.tm_gmtoff < 0 ? -time.tm_gmtoff : time.tm_gmtoff; + std::snprintf(expected, sizeof(expected), "%02d %c%04d", hour, time.tm_gmtoff < 0 ? '-' : '+', static_cast(offset / 3600 * 100)); + char formatted[16]{}; + return strftime_nid_postfix(formatted, sizeof(formatted), "%H %z", &time) == 8 && std::strcmp(formatted, expected) == 0; +} + +static void CheckZoneRace() { + const std::int64_t moment = 172800; + std::atomic stop{false}; + std::atomic torn{0}; + std::thread flipper([&] { + for (bool west = false; !stop.load(); west = !west) setenv_nid_postfix("TZ", west ? "UTC-5" : "UTC+3", 1); + }); + std::array readers; + for (auto& reader : readers) { + reader = std::thread([&] { + for (int iteration = 0; iteration < 2500; ++iteration) { + GuestTm result{}; + if (localtime_s_nid_postfix(&moment, &result) != &result || !ZoneFieldsConsistent(result, moment, result.tm_hour)) ++torn; + GuestTm calendar{}; + calendar.tm_year = 70; + calendar.tm_mday = 3; + calendar.tm_hour = 12; + calendar.tm_isdst = -1; + const std::int64_t instant = mktime_nid_postfix(&calendar); + if (instant == -1 || calendar.tm_mday != 3 || !ZoneFieldsConsistent(calendar, instant, 12)) ++torn; + } + }); + } + for (auto& reader : readers) reader.join(); + stop = true; + flipper.join(); + Require(torn == 0, "A time conversion mixed the fields of two time zones while TZ changed"); + Require(setenv_nid_postfix("TZ", "UTC-2", 1) == 0, "Could not restore the guest time zone"); + tzset_nid_postfix(); +} + #ifdef _WIN32 - _putenv_s("TZ", "UTC-2"); - _tzset(); -#else - setenv("TZ", "UTC-2", 1); - tzset(); +static void CheckRejectedZone() { + const std::int64_t moment = 172800; + const std::string oversized = "UTC-5" + std::string(40000, 'x'); + Require(setenv_nid_postfix("TZ", oversized.c_str(), 1) == 0, "The guest environment rejected a long TZ"); + GuestTm result{}; + bool thrown = false; + try { + localtime_s_nid_postfix(&moment, &result); + } catch (const std::runtime_error&) { + thrown = true; + } + Require(thrown, "localtime accepted a TZ the host cannot hold"); + Require(setenv_nid_postfix("TZ", "UTC-2", 1) == 0, "Could not restore the guest time zone"); + tzset_nid_postfix(); + Require(localtime_s_nid_postfix(&moment, &result) == &result && result.tm_gmtoff == 7200, "localtime did not recover after a rejected TZ"); +} #endif + +int main() { + Require(setenv_nid_postfix("TZ", "UTC-2", 1) == 0, "Could not set the guest time zone"); + tzset_nid_postfix(); constexpr std::array utcCases{{ {0, 70, 0, 1, 0, 0, 0, 4, 0}, {-1, 69, 11, 31, 23, 59, 59, 3, 364}, @@ -287,22 +375,19 @@ int main() { CheckConcurrent(gmtime_nid_postfix, gmtime_s_nid_postfix, "Concurrent gmtime returned another thread's date"); CheckConcurrent(libc_localtime_nid_postfix, localtime_s_nid_postfix, "Concurrent libc_localtime returned another thread's date"); CheckConcurrent(localtime_nid_postfix, localtime_s_nid_postfix, "Concurrent localtime returned another thread's date"); + CheckGuestZone(); + CheckZoneRace(); #ifdef _WIN32 - _putenv_s("TZ", "UTC-5"); -#else - setenv("TZ", "UTC-5", 1); + CheckRejectedZone(); #endif + Require(setenv_nid_postfix("TZ", "UTC-5", 1) == 0, "Could not set the guest time zone"); tzset_nid_postfix(); GuestTm shifted{}; Require(localtime_s_nid_postfix(&epoch, &shifted) == &shifted && shifted.tm_hour == 5 && shifted.tm_gmtoff == 18000, "tzset did not pick up the new TZ"); Require(tzname_nid_postfix[0] != nullptr && std::strcmp(tzname_nid_postfix[0], "UTC") == 0 && tzname_nid_postfix[1] != nullptr, "tzname does not name the zone tzset set"); -#ifdef _WIN32 - _putenv_s("TZ", "EST5EDT"); -#else - setenv("TZ", "EST5EDT", 1); -#endif + Require(setenv_nid_postfix("TZ", "EST5EDT", 1) == 0, "Could not set the guest time zone"); tzset_nid_postfix(); Require(std::strcmp(tzname_nid_postfix[0], "EST") == 0 && std::strcmp(tzname_nid_postfix[1], "EDT") == 0, "tzname does not name the standard and daylight zones"); diff --git a/core/libs/tests/GuestVectorAlgorithms.cpp b/core/libs/tests/GuestVectorAlgorithms.cpp new file mode 100644 index 000000000000..c3b858bac6da --- /dev/null +++ b/core/libs/tests/GuestVectorAlgorithms.cpp @@ -0,0 +1,96 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include + +struct MinMaxPointers { + const void* minimum; + const void* maximum; +}; + +extern "C" { +const void* APS5_VABI __std_find_trivial_2_nid_postfix(const void*, const void*, std::uint16_t); +const void* APS5_VABI __std_find_trivial_unsized_1_nid_postfix(const void*, std::uint8_t); +const void* APS5_VABI __std_find_trivial_unsized_4_nid_postfix(const void*, std::uint32_t); +std::size_t APS5_VABI __std_count_trivial_8_nid_postfix(const void*, const void*, std::uint64_t); +const void* APS5_VABI __std_min_element_1_nid_postfix(const void*, const void*, bool); +const void* APS5_VABI __std_max_element_4_nid_postfix(const void*, const void*, bool); +MinMaxPointers APS5_VABI __std_minmax_element_2_nid_postfix(const void*, const void*, bool); +MinMaxPointers APS5_VABI __std_minmax_element_8_nid_postfix(const void*, const void*, bool); +void APS5_VABI __std_reverse_trivially_swappable_2_nid_postfix(void*, void*); +void APS5_VABI __std_reverse_copy_trivially_copyable_8_nid_postfix(const void*, const void*, void*); +void APS5_VABI __std_swap_ranges_trivially_swappable_noalias_nid_postfix(void*, void*, void*); +} + +static void Require(bool value) { if (!value) std::abort(); } + +static void CheckFindAndCount() { + const std::uint16_t halves[] = {5, 7, 9, 7}; + Require(__std_find_trivial_2_nid_postfix(halves, halves + 4, 7) == halves + 1); + Require(__std_find_trivial_2_nid_postfix(halves, halves + 4, 8) == halves + 4); + Require(__std_find_trivial_2_nid_postfix(halves, halves, 5) == halves); + const std::uint64_t wides[] = {1, 2, 1, 1}; + Require(__std_count_trivial_8_nid_postfix(wides, wides + 4, 1) == 3 && __std_count_trivial_8_nid_postfix(wides, wides, 1) == 0); + alignas(32) std::uint32_t words[16] = {}; + words[3] = 42; + words[12] = 42; + Require(__std_find_trivial_unsized_4_nid_postfix(words + 8, 42) == words + 12); +} + +static void CheckUnsizedRescan() { + alignas(32) unsigned char bytes[96] = {}; + bytes[3] = 'x'; + bytes[70] = 'x'; + Require(__std_find_trivial_unsized_1_nid_postfix(bytes + 8, 'x') == bytes + 3); + bytes[20] = 'x'; + Require(__std_find_trivial_unsized_1_nid_postfix(bytes + 8, 'x') == bytes + 20); + bytes[3] = 0; + bytes[20] = 0; + Require(__std_find_trivial_unsized_1_nid_postfix(bytes + 8, 'x') == bytes + 70); +} + +static void CheckMinMax() { + const unsigned char bytes[] = {3, 0x80, 1, 0x80, 1, 0xff}; + Require(__std_min_element_1_nid_postfix(bytes, bytes + 6, false) == bytes + 2); + Require(__std_min_element_1_nid_postfix(bytes, bytes + 6, true) == bytes + 1); + Require(__std_min_element_1_nid_postfix(bytes, bytes, true) == bytes); + const std::uint32_t words[] = {7, 0xffffffffu, 9, 0xffffffffu, 9}; + Require(__std_max_element_4_nid_postfix(words, words + 5, false) == words + 1); + Require(__std_max_element_4_nid_postfix(words, words + 5, true) == words + 2); + const std::int16_t halves[] = {4, -3, 8, -3, 8, 2}; + const MinMaxPointers signedHalves = __std_minmax_element_2_nid_postfix(halves, halves + 6, true); + Require(signedHalves.minimum == halves + 1 && signedHalves.maximum == halves + 4); + const MinMaxPointers unsignedHalves = __std_minmax_element_2_nid_postfix(halves, halves + 6, false); + Require(unsignedHalves.minimum == halves + 5 && unsignedHalves.maximum == halves + 3); + const std::uint64_t wides[] = {5}; + const MinMaxPointers single = __std_minmax_element_8_nid_postfix(wides, wides + 1, false); + Require(single.minimum == wides && single.maximum == wides); + const MinMaxPointers empty = __std_minmax_element_8_nid_postfix(wides, wides, false); + Require(empty.minimum == wides && empty.maximum == wides); +} + +static void CheckReverseAndSwap() { + std::uint16_t halves[] = {1, 2, 3, 4, 5}; + __std_reverse_trivially_swappable_2_nid_postfix(halves, halves + 5); + Require(halves[0] == 5 && halves[1] == 4 && halves[2] == 3 && halves[3] == 2 && halves[4] == 1); + const std::uint64_t wides[] = {1, 2, 3}; + std::uint64_t reversed[4] = {9, 9, 9, 9}; + __std_reverse_copy_trivially_copyable_8_nid_postfix(wides, wides + 3, reversed); + Require(reversed[0] == 3 && reversed[1] == 2 && reversed[2] == 1 && reversed[3] == 9); + unsigned char left[37], right[37]; + for (int i = 0; i < 37; ++i) { + left[i] = static_cast(i); + right[i] = static_cast(100 + i); + } + __std_swap_ranges_trivially_swappable_noalias_nid_postfix(left, left + 37, right); + for (int i = 0; i < 37; ++i) Require(left[i] == 100 + i && right[i] == i); +} + +int main() { + CheckFindAndCount(); + CheckUnsizedRescan(); + CheckMinMax(); + CheckReverseAndSwap(); + return 0; +} diff --git a/core/libs/tests/GuestVoiceChat.cpp b/core/libs/tests/GuestVoiceChat.cpp new file mode 100644 index 000000000000..0a94bcefa853 --- /dev/null +++ b/core/libs/tests/GuestVoiceChat.cpp @@ -0,0 +1,56 @@ +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include +#include + +extern "C" { +int APS5_VABI sceVoiceChatInitialize(void* param); +int APS5_VABI sceVoiceChatTerminate(void); +int APS5_VABI sceVoiceChatRegisterHandlers(void* a); +int APS5_VABI sceVoiceChatRegisterMicEventHandler(void* a, void* b); +int APS5_VABI sceVoiceChatRegisterChannelEventHandler(void* a, void* b); +int APS5_VABI sceVoiceChatUnregisterMicEventHandler(void* a); +int APS5_VABI sceVoiceChatProcessEvent(void); +int APS5_VABI sceVoiceChatStartChannelEvent(void* a); +int APS5_VABI sceVoiceChatStopChannelEvent(void* a); +int APS5_VABI sceVoiceChatGetMicState(int32_t* state); +int APS5_VABI sceVoiceChatGetUserVoiceVolumeGain(void* a, int32_t* gain); +int APS5_VABI sceVoiceChatSetUserVoiceVolumeGain(void* a, int32_t gain); +int APS5_VABI sceVoiceChatGetActiveChannel(void* channel); +int APS5_VABI sceVoiceChatCreateRequest(void* a, void* b); +int APS5_VABI sceVoiceChatRequestAcsTts(int requestId, void* a, void* b); +int APS5_VABI sceVoiceChatAbortRequest(int requestId); +} + +static void Require(bool value, const char* what) { + if (value) return; + std::printf("VoiceChat: %s\n", what); + std::abort(); +} + +int main() { + constexpr int kSignedOut = static_cast(0x80550006u); + + Require(sceVoiceChatInitialize(nullptr) == 0, "initialization failed"); + Require(sceVoiceChatProcessEvent() == 0, "event processing failed"); + Require(sceVoiceChatRegisterHandlers(nullptr) == 0, "register handlers failed"); + Require(sceVoiceChatRegisterMicEventHandler(nullptr, nullptr) == 0, "register mic event handler failed"); + Require(sceVoiceChatRegisterChannelEventHandler(nullptr, nullptr) == 0, "register channel event handler failed"); + Require(sceVoiceChatUnregisterMicEventHandler(nullptr) == 0, "unregister mic event handler failed"); + Require(sceVoiceChatStartChannelEvent(nullptr) == 0, "start channel event failed"); + Require(sceVoiceChatStopChannelEvent(nullptr) == 0, "stop channel event failed"); + + Require(sceVoiceChatGetMicState(nullptr) == kSignedOut, "get mic state did not return signed out"); + Require(sceVoiceChatGetUserVoiceVolumeGain(nullptr, nullptr) == kSignedOut, "get volume gain did not return signed out"); + Require(sceVoiceChatSetUserVoiceVolumeGain(nullptr, 50) == kSignedOut, "set volume gain did not return signed out"); + + Require(sceVoiceChatGetActiveChannel(nullptr) == kSignedOut, "get active channel did not return signed out"); + Require(sceVoiceChatCreateRequest(nullptr, nullptr) == kSignedOut, "create request did not return signed out"); + Require(sceVoiceChatRequestAcsTts(1, nullptr, nullptr) == kSignedOut, "tts request did not return signed out"); + Require(sceVoiceChatAbortRequest(1) == 0, "abort request failed"); + Require(sceVoiceChatTerminate() == 0, "termination failed"); + + std::puts("VoiceChat tests passed"); + return 0; +} diff --git a/core/libs/tests/SaveDataMemoryMetadata.cpp b/core/libs/tests/SaveDataMemoryMetadata.cpp index 3a6011501280..f54e96b0c35f 100644 --- a/core/libs/tests/SaveDataMemoryMetadata.cpp +++ b/core/libs/tests/SaveDataMemoryMetadata.cpp @@ -52,6 +52,9 @@ int main() { Require(sceSaveDataCreateTransactionResource(1024) == kSaveDataNotInitialized); Require(sceSaveDataDeleteTransactionResource(1) == kSaveDataNotInitialized); + const std::uint32_t syncParamUninit[2] = {7531u, 0u}; + Require(sceSaveDataSyncSaveDataMemory(nullptr) == static_cast(0x809F0000u)); + Require(sceSaveDataSyncSaveDataMemory(syncParamUninit) == static_cast(0x809F0001u)); const auto previous = std::filesystem::current_path(); const auto root = std::filesystem::temp_directory_path() / ("anyps5-metadata-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); diff --git a/core/libs/tests/Wave32WideSubgroup.cpp b/core/libs/tests/Wave32WideSubgroup.cpp index af28fb59d916..751361863277 100644 --- a/core/libs/tests/Wave32WideSubgroup.cpp +++ b/core/libs/tests/Wave32WideSubgroup.cpp @@ -63,6 +63,7 @@ class Subgroup { case spv::OpConstantTrue: value[0] = 1; break; case spv::OpConstantFalse: value[0] = 0; break; case spv::OpLoad: value = arg(3); break; + case spv::OpCopyObject: value = arg(3); break; case spv::OpBitwiseAnd: value[0] = arg(3)[0] & arg(4)[0]; break; case spv::OpBitwiseOr: value[0] = arg(3)[0] | arg(4)[0]; break; case spv::OpBitwiseXor: value[0] = arg(3)[0] ^ arg(4)[0]; break; diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 74f12ba877de..cbd756a8092c 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -63,6 +63,7 @@ add_executable(relinker ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/CallSiteResolver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/CodeInstructionCollector.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/SceCodeExtent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/RelativeJumpTables.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/StrictReachability.cpp @@ -400,8 +401,13 @@ if(BUILD_TESTING) add_test(NAME guest_none_relocations COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_none_relocations.py $) set_tests_properties(guest_none_relocations PROPERTIES TIMEOUT 20) + add_test(NAME guest_relative_section_symbols COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_relative_section_symbols.py $) + set_tests_properties(guest_relative_section_symbols PROPERTIES TIMEOUT 20) add_test(NAME optional_plt COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_optional_plt.py $) + add_test(NAME optional_rela COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_optional_rela.py $) add_test(NAME empty_tls COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_empty_tls.py $) set(tlsCoverageGroups metadata padding register_loads displacement_bounds alu_execution register_address_loads moved_branch rejected) @@ -491,6 +497,9 @@ if(BUILD_TESTING) add_test(NAME macos_executable_exports COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_macos_executable_exports.py $) set_tests_properties(macos_executable_exports PROPERTIES TIMEOUT 30) + add_test(NAME macos_gs_traps COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_macos_gs_traps.py $) + set_tests_properties(macos_gs_traps PROPERTIES TIMEOUT 60) add_test(NAME guest_host_libc COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_host_libc.py $) add_library(guest_module_args_host SHARED elfpatcher/tests/GuestModuleArgsHost.cpp) @@ -661,6 +670,7 @@ if(BUILD_TESTING) add_test_executable(control_flow_graph_tests ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/ControlFlowGraphTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/RelativeJumpTables.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/ControlFlowGraph.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/StrictReachability.cpp ${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/X64InstructionDecoder.cpp diff --git a/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp b/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp index b809478033f4..0dbc39b29fe7 100644 --- a/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp +++ b/core/relinker/elfpatcher/include/elfpatcher/general/ElfConstants.hpp @@ -91,6 +91,9 @@ inline constexpr std::int64_t DT_RELASZ = 8; inline constexpr std::int64_t DT_RELAENT = 9; inline constexpr std::int64_t DT_STRSZ = 10; inline constexpr std::int64_t DT_SYMENT = 11; +inline constexpr std::int64_t DT_REL = 17; +inline constexpr std::int64_t DT_RELSZ = 18; +inline constexpr std::int64_t DT_RELENT = 19; inline constexpr std::int64_t DT_PLTREL = 20; inline constexpr std::int64_t DT_JMPREL = 23; inline constexpr std::int64_t DT_INIT = 12; @@ -103,6 +106,9 @@ inline constexpr std::int64_t DT_RUNPATH = 29; inline constexpr std::int64_t DT_FLAGS = 30; inline constexpr std::int64_t DT_PREINIT_ARRAY = 32; inline constexpr std::int64_t DT_PREINIT_ARRAYSZ = 33; +inline constexpr std::int64_t DT_RELRSZ = 35; +inline constexpr std::int64_t DT_RELR = 36; +inline constexpr std::int64_t DT_RELRENT = 37; inline constexpr std::uint64_t DF_BIND_NOW = 0x8; inline constexpr std::uint64_t DF_SYMBOLIC = 0x2; diff --git a/core/relinker/elfpatcher/src/general/ProgramHeaderLayoutBuilder.cpp b/core/relinker/elfpatcher/src/general/ProgramHeaderLayoutBuilder.cpp index 8f3ecfcb5b03..79d212b58020 100644 --- a/core/relinker/elfpatcher/src/general/ProgramHeaderLayoutBuilder.cpp +++ b/core/relinker/elfpatcher/src/general/ProgramHeaderLayoutBuilder.cpp @@ -125,6 +125,8 @@ std::uint64_t ProgramHeaderLayoutBuilder::GuestLoadPageEnd( if (ph.Type != PT_LOAD) continue; foundLoad = true; + if (ph.FileSize > ph.MemorySize) + throw Domain::RelinkerException("PT_LOAD file size exceeds memory size", ph.Offset); if (ph.MemorySize > std::numeric_limits::max() - ph.MappedAddress) throw Domain::RelinkerException("Load segment virtual range overflowed"); const std::uint64_t vaddrEnd = ph.MappedAddress + ph.MemorySize; @@ -171,8 +173,12 @@ std::uint16_t ProgramHeaderLayoutBuilder::WriteLayout( if (tableEnd > buf.size()) throw Domain::RelinkerException("Program header table exceeds the output file"); const bool tableInExtraBlock = request.PhOff >= request.ExtraBlockOffset; - if (tableInExtraBlock && tableEnd - request.ExtraBlockOffset > request.ExtraBlockSize) - throw Domain::RelinkerException("Program header table does not fit within the extra block"); + if (tableInExtraBlock) { + if (tableEnd - request.ExtraBlockOffset > request.ExtraBlockSize) + throw Domain::RelinkerException("Program header table does not fit within the extra block"); + } else if (tableEnd > request.ExtraBlockOffset) { + throw Domain::RelinkerException("Program header table overlaps the extra block"); + } const std::uint64_t headerBlockSize = tableInExtraBlock ? kElfHeaderSize : tableEnd; if (request.ExtraBlockOffset > buf.size() || request.ExtraBlockSize > buf.size() - request.ExtraBlockOffset) throw Domain::RelinkerException("Extra block exceeds the output file"); diff --git a/core/relinker/elfpatcher/src/general/SectionHeaderTableBuilder.cpp b/core/relinker/elfpatcher/src/general/SectionHeaderTableBuilder.cpp index a7c4cc1f0f78..e07068514fa2 100644 --- a/core/relinker/elfpatcher/src/general/SectionHeaderTableBuilder.cpp +++ b/core/relinker/elfpatcher/src/general/SectionHeaderTableBuilder.cpp @@ -1,8 +1,10 @@ #include #include #include -#include +#include #include +#include +#include namespace Elfpatcher { @@ -41,14 +43,39 @@ void SectionHeaderTableBuilder::WriteTable( std::vector& buf, const SectionHeaderTableRequest& request ) const { + if (buf.size() < kElfHeaderSize) + throw std::runtime_error("Output program header table is out of bounds"); const auto phOffset = Io::ReadU64(buf, kEhdrPhOffOffset); const auto phCount = Io::ReadU16(buf, kEhdrPhNumOffset); const auto phStride = Io::ReadU16(buf, kEhdrPhEntSizeOffset); - if (phStride < 56 || phOffset > buf.size() || phCount > (buf.size() - phOffset) / phStride) + if (phStride < 56 || (phCount > 0 && phOffset < kElfHeaderSize) || phOffset > buf.size() || phCount > (buf.size() - phOffset) / phStride) throw std::runtime_error("Output program header table is out of bounds"); + const auto phTableEnd = phOffset + static_cast(phCount) * phStride; + const std::array, 4> sections{{ + {request.DynStrOffset, request.DynStrSize}, + {request.DynSymOffset, request.DynSymSize}, + {request.DynamicSegmentOffset, request.DynamicSegmentSize}, + {request.StubOffset, request.StubSize}, + }}; + for (std::size_t i = 0; i < sections.size(); ++i) { + if (sections[i].second == 0) continue; + for (std::size_t j = i + 1; j < sections.size(); ++j) { + if (sections[j].second == 0) continue; + const auto startA = sections[i].first; + const auto endA = startA + sections[i].second; + const auto startB = sections[j].first; + const auto endB = startB + sections[j].second; + if (startA < endB && startB < endA) + throw std::runtime_error("Allocated output sections overlap"); + } + } const auto mappedAddress = [&](std::uint64_t offset, std::uint64_t sectionSize) { if (offset > buf.size() || sectionSize > buf.size() - offset) throw std::runtime_error("Allocated output section exceeds file"); + if (offset < kElfHeaderSize && sectionSize > 0) + throw std::runtime_error("Allocated output section overlaps the ELF header"); + if (sectionSize > 0 && phCount > 0 && offset < phTableEnd && phOffset < offset + sectionSize) + throw std::runtime_error("Allocated output section overlaps the program header table"); for (std::uint16_t index = 0; index < phCount; ++index) { const auto header = phOffset + index * phStride; if (Io::ReadU32(buf, header) != PT_LOAD) continue; diff --git a/core/relinker/elfpatcher/src/macos/MacOsImage.cpp b/core/relinker/elfpatcher/src/macos/MacOsImage.cpp index 635d4f93b85d..81fc38d4db65 100644 --- a/core/relinker/elfpatcher/src/macos/MacOsImage.cpp +++ b/core/relinker/elfpatcher/src/macos/MacOsImage.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -194,6 +195,11 @@ void AppendPathCommand(std::vector& out, const std::uint32_t cmd, out.resize(start + size, 0); } +struct GsTrap { + std::uint32_t Rva; + std::size_t Length; +}; + struct TlsAccess { std::uint32_t Rva; std::size_t Length; @@ -212,6 +218,7 @@ struct TlsScan { const Domain::ProgramHeader* Tls = nullptr; std::vector Accesses; std::set BranchTargets; + std::vector GsTraps; }; TlsScan ScanTls(const std::vector& source, const std::vector& headers, const Windows::WindowsLoadImage& image) { @@ -237,6 +244,10 @@ TlsScan ScanTls(const std::vector& source, const std::vector= 0 && target <= std::numeric_limits::max()) scan.BranchTargets.insert(static_cast(target)); } if (info.SegmentPrefix == 0 || (insidePrevious && info.SegmentPrefix != 0x64)) continue; + if (info.SegmentPrefix == 0x65) { + scan.GsTraps.push_back({rva, info.Length}); + continue; + } const auto position = info.OpcodeOffset; bool hasOperandSizePrefix = false; bool supportedPrefixes = info.SegmentPrefix == 0x64; @@ -380,6 +391,23 @@ std::vector WriteMacOsImage(MacOsImageInput& input) { segments.push_back({"__TEXT", 0, LoadRva, VmProtRead | VmProtExecute, {}, {}}); const auto guestSegments = segments.size(); auto sections = image.BuildSections(); + for (const auto& trap : tls.GsTraps) { + if (input.Trampolines != nullptr) { + for (const auto& site : *input.Trampolines) { + const auto siteRva = image.GetRva(site.Address, site.Length); + if (siteRva < trap.Rva + trap.Length && trap.Rva < siteRva + site.Length) + throw Domain::RelinkerException("AMD-only instruction is also a guest GS access", siteRva); + } + } + const auto section = std::find_if(sections.begin(), sections.end(), [&](const auto& candidate) { + return trap.Rva >= candidate.Rva && trap.Rva - candidate.Rva < candidate.Data.size(); + }); + if (section == sections.end()) throw Domain::RelinkerException("Guest GS instruction is outside the image", trap.Rva); + const auto offset = trap.Rva - section->Rva; + if (trap.Length > section->Data.size() - offset) throw Domain::RelinkerException("Guest GS instruction crosses a section boundary", trap.Rva); + std::fill_n(section->Data.begin() + offset, trap.Length, 0x90); + std::copy(std::begin(kStubOpUd2), std::end(kStubOpUd2), section->Data.begin() + offset); + } if (input.Trampolines != nullptr && !input.Trampolines->empty()) { for (const auto& access : tls.Accesses) { for (const auto& site : *input.Trampolines) { diff --git a/core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp b/core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp index 524a4d790cea..510edeac077b 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -25,6 +26,8 @@ PeRelocations WindowsRelocationBuilder::Apply(WindowsLoadImage& image, const Dom const auto symbol = info >> 32; if ((plt && type != 7) || (type != 1 && type != 6 && type != 7 && type != 8 && type != 16 && type != 17 && type != 18)) throw Domain::RelinkerException("Unsupported Windows relocation type " + std::to_string(type), target); + if (target > std::numeric_limits::max() - 8) + throw Domain::RelinkerException("Relocation target address overflow", target); const auto rva = image.GetRva(target, 8); const auto next = ranges.lower_bound(target); if ((next != ranges.end() && next->first < target + 8) || (next != ranges.begin() && std::prev(next)->second > target)) @@ -81,8 +84,14 @@ PeRelocations WindowsRelocationBuilder::Apply(WindowsLoadImage& image, const Dom std::vector WindowsRelocationBuilder::BuildBaseRelocations(const std::vector& targets) const { std::map> pages; - for (const auto target : targets) - pages[target & ~0xfffu].push_back(static_cast(0xa000u | (target & 0xfffu))); + for (const auto target : targets) { + if ((target & 0xfffu) > 0x1000u - 8) + throw Domain::RelinkerException("Base relocation target crosses a page boundary", target); + auto& entries = pages[target & ~0xfffu]; + const auto entry = static_cast(0xa000u | (target & 0xfffu)); + if (std::find(entries.begin(), entries.end(), entry) == entries.end()) + entries.push_back(entry); + } std::vector result; for (const auto& [page, entries] : pages) { Io::AppendU32(result, page); diff --git a/core/relinker/elfpatcher/tests/ProgramLayoutRangeTests.cpp b/core/relinker/elfpatcher/tests/ProgramLayoutRangeTests.cpp index 458cd8bc2d83..1d1bffbe7598 100644 --- a/core/relinker/elfpatcher/tests/ProgramLayoutRangeTests.cpp +++ b/core/relinker/elfpatcher/tests/ProgramLayoutRangeTests.cpp @@ -97,7 +97,7 @@ int main() { case 7: request.InterpOffset = maximum; break; case 8: request.ExtraBlockOffset = maximum; break; case 9: request.ExtraBlockSize = 0x1000; break; - case 10: request.PhOff = 0xF00; request.ExtraBlockVaddr = maximum - 0x1fff; break; + case 10: request.PhOff = 0xF00; break; } std::vector bytes(0x1100, 0xA5); const auto before = bytes; diff --git a/core/relinker/elfpatcher/tests/SectionRangeTests.cpp b/core/relinker/elfpatcher/tests/SectionRangeTests.cpp index 7f718b66ce71..c75c4e977347 100644 --- a/core/relinker/elfpatcher/tests/SectionRangeTests.cpp +++ b/core/relinker/elfpatcher/tests/SectionRangeTests.cpp @@ -29,7 +29,7 @@ int main() { auto empty = valid; empty.StubOffset = 384; empty.StubSize = 0; bytes = Fixture(); builder.WriteTable(bytes, empty); Require(Io::ReadU64(bytes, Io::ReadU64(bytes, 40) + 5 * 64 + 16) == 0x1100); - for (int kind = 0; kind < 8; ++kind) { + for (int kind = 0; kind < 11; ++kind) { bytes = Fixture(); auto request = valid; switch (kind) { case 0: request.DynStrSize = 257; break; @@ -40,12 +40,15 @@ int main() { case 5: Io::WriteU64(bytes, 32, std::numeric_limits::max()); break; case 6: Io::WriteU64(bytes, 80, std::numeric_limits::max() - 4); break; case 7: Io::WriteU16(bytes, 56, 20); break; + case 8: request.DynStrSize = 16; break; + case 9: bytes.resize(40); break; + case 10: Io::WriteU64(bytes, 32, 48); Io::WriteU32(bytes, 48, 1); Io::WriteU64(bytes, 56, 128); break; } const auto before = bytes; bool rejected = false; - try { builder.WriteTable(bytes, request); } catch (const std::exception&) { rejected = true; } + try { builder.WriteTable(bytes, request); } catch (const std::runtime_error&) { rejected = true; } Require(rejected && bytes == before); } - std::cout << "10 section range cases passed\n"; + std::cout << "13 section range cases passed\n"; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; } } diff --git a/core/relinker/main.cpp b/core/relinker/main.cpp index 917a1f4f063a..78e75861e896 100644 --- a/core/relinker/main.cpp +++ b/core/relinker/main.cpp @@ -39,8 +39,6 @@ namespace { -#ifdef _WIN32 - class OutputIdentityDirectory { public: OutputIdentityDirectory(const OutputIdentityDirectory&) = delete; @@ -83,8 +81,6 @@ class OutputIdentityDirectory { std::filesystem::path directory; }; -#endif - bool SameOutputFile(const std::filesystem::path& first, const std::filesystem::path& second) { if (std::filesystem::equivalent(first, second)) return true; #ifdef _WIN32 @@ -129,6 +125,18 @@ bool SameOutputDestination(const std::filesystem::path& first, const std::filesy probe.Remove(); return same; } +#else + if (std::filesystem::exists(firstPath.parent_path()) && std::filesystem::exists(secondPath.parent_path()) && + std::filesystem::equivalent(firstPath.parent_path(), secondPath.parent_path())) { + OutputIdentityDirectory probe(firstPath.parent_path()); + const auto name = probe.GetDirectory() / firstPath.filename(); + const auto alternate = probe.GetDirectory() / secondPath.filename(); + std::filesystem::create_directory(name); + const bool same = std::filesystem::exists(alternate) && std::filesystem::equivalent(name, alternate); + std::filesystem::remove(name); + probe.Remove(); + return same; + } #endif return false; } @@ -313,7 +321,7 @@ int Run(const int argc, char* argv[]) { << Io::Utf8Path(Io::NativePath(absPath).filename()) << "\n" << "libs/\n *.prx\napp0/\n \n"; for (const auto& artifact : guestArtifacts) { - std::string relative = Io::Utf8Path(artifact.Path.lexically_relative(Io::NativePath(absPath).parent_path() / "app0")); + std::string relative = Io::Utf8Path(artifact.Path.lexically_relative(Io::NativePath(absPath).parent_path().lexically_normal() / "app0")); std::replace(relative.begin(), relative.end(), '\\', '/'); std::cout << " " << relative << '\n'; } diff --git a/core/relinker/relinker/include/relinker/analysis/RelativeJumpTables.hpp b/core/relinker/relinker/include/relinker/analysis/RelativeJumpTables.hpp new file mode 100644 index 000000000000..3e8bc1015248 --- /dev/null +++ b/core/relinker/relinker/include/relinker/analysis/RelativeJumpTables.hpp @@ -0,0 +1,24 @@ +#ifndef RELINKER_ANALYSIS_RELATIVEJUMPTABLES_HPP +#define RELINKER_ANALYSIS_RELATIVEJUMPTABLES_HPP + +#include +#include +#include +#include +#include +#include + +namespace Relinker { + +struct RelativeJumpTable { + VirtualAddress Function; + VirtualAddress Begin; + VirtualAddress End; +}; + +std::size_t FindTrailingRelativeJumpTable(std::span code); +std::vector FindRelativeJumpTables(std::span segment, VirtualAddress segmentVAddr, const std::vector>& functions); + +} + +#endif diff --git a/core/relinker/relinker/include/relinker/analysis/UnusedNidFilter/IControlFlowGraph.hpp b/core/relinker/relinker/include/relinker/analysis/UnusedNidFilter/IControlFlowGraph.hpp index 7b1add380f2f..b8386a10b86c 100644 --- a/core/relinker/relinker/include/relinker/analysis/UnusedNidFilter/IControlFlowGraph.hpp +++ b/core/relinker/relinker/include/relinker/analysis/UnusedNidFilter/IControlFlowGraph.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -34,7 +35,8 @@ std::unique_ptr BuildControlFlowGraph( const std::vector& extraEntries, const IRelativeRelocationIndex& relativeRelocations, bool followCodeAddresses, - std::unordered_set& reachable + std::unordered_set& reachable, + const std::function& isData = {} ); std::unique_ptr BuildControlFlowGraph( diff --git a/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp b/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp index d88ef03db62d..0a7b2f56463b 100644 --- a/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp +++ b/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -162,7 +163,9 @@ CodeInstructionSet CodeInstructionCollector::Collect(const std::vector roots; std::map functions; - const auto addRoot = [&](std::uint64_t address) { if (isCode(address)) roots.insert(address); }; + std::map tables; + const auto inTable = [&](std::uint64_t address) { const auto after = tables.upper_bound(address); return after != tables.begin() && address < std::prev(after)->second; }; + const auto addRoot = [&](std::uint64_t address) { if (isCode(address) && !inTable(address)) roots.insert(address); }; const auto addAddressTakenRoot = [&](std::uint64_t address, const std::uint8_t* code, const Codegen::DecodedInstructionInfo& info) { if (!info.HasRipRelativeDisp || code[info.OpcodeOffset] != 0x8d || !(info.RexPrefix & 8)) return; const auto displacement = std::bit_cast(Io::ReadU32(bytes, fileOffset(address, info.Length) + info.RipRelativeDispOffset)); @@ -298,6 +301,19 @@ CodeInstructionSet CodeInstructionCollector::Collect(const std::vector> ranges(functions.begin(), functions.end()); + for (const auto& header : codeHeaders) { + if (header.Type != 1 || (header.Flags & 1) == 0) continue; + const std::span segment(bytes.data() + header.Offset, header.FileSize); + for (const auto& table : FindRelativeJumpTables(segment, header.MappedAddress, ranges)) { + tables.emplace(table.Begin, table.End); + auto& end = functions.at(table.Function); + end = std::min(end, table.Begin); + } + } + std::erase_if(roots, inTable); + } if (roots.empty()) throw Domain::RelinkerException("Code analysis: no code entry points"); AddressBits instructions(headers); const Codegen::X64InstructionDecoder decoder; @@ -413,7 +429,7 @@ CodeInstructionSet CodeInstructionCollector::Collect(const std::vector= header.MappedAddress && root - header.MappedAddress < header.FileSize) entries.push_back(root); if (entries.empty()) continue; const std::span text(bytes.data() + header.Offset, header.FileSize); - const auto graph = UnusedNidFilter::BuildControlFlowGraph(text, header.MappedAddress, entries.front(), entries, pointers, true, sectionReachable[i]); + const auto graph = UnusedNidFilter::BuildControlFlowGraph(text, header.MappedAddress, entries.front(), entries, pointers, true, sectionReachable[i], inTable); for (const auto address : graph->ReachableVaddrs()) { if (!decodedInstructions.Insert(address)) continue; instructions.Insert(address); diff --git a/core/relinker/relinker/src/analysis/RelativeJumpTables.cpp b/core/relinker/relinker/src/analysis/RelativeJumpTables.cpp new file mode 100644 index 000000000000..0ccbc28717a3 --- /dev/null +++ b/core/relinker/relinker/src/analysis/RelativeJumpTables.cpp @@ -0,0 +1,131 @@ +#include +#include +#include +#include +#include + +namespace Relinker { + +namespace { + +constexpr VirtualAddress MaxTableGapAfterFunction = 0x10000; + +struct TableLoad { + std::size_t At; + std::size_t Target; +}; + +bool IsNop(std::span code, std::size_t at, std::size_t length) { + auto position = at; + const auto end = at + length; + while (position < end && (code[position] == 0x66 || code[position] == 0x2e || code[position] == 0x3e)) ++position; + if (position >= end) return false; + if (code[position] == 0x90) return position + 1 == end; + return code[position] == 0x0f && position + 1 < end && code[position + 1] == 0x1f; +} + +bool OnlyPadding(std::span code, std::size_t from, const Codegen::X64InstructionDecoder& decoder) { + for (auto position = from; position < code.size();) { + if (code[position] == 0x00 || code[position] == 0xcc || code[position] == 0x90) { + ++position; + continue; + } + try { + const auto length = decoder.DecodeInstruction(code.data() + position, code.size() - position).Length; + if (length == 0 || length > code.size() - position || !IsNop(code, position, length)) return false; + position += length; + } catch (const Codegen::CodegenException&) { + return false; + } + } + return true; +} + +std::size_t TableEntries(std::span code, std::size_t table, std::size_t end, std::size_t firstTable, const std::vector& instructionStarts) { + std::size_t entries = 0; + while (table + (entries + 1) * 4 <= end) { + std::int32_t relative = 0; + std::memcpy(&relative, code.data() + table + entries * 4, sizeof(relative)); + const auto destination = static_cast(table) + relative; + if (destination < 0 || destination >= static_cast(firstTable) || !instructionStarts[static_cast(destination)]) break; + ++entries; + } + return entries; +} + +std::size_t TablesEnd(std::span code, std::size_t firstTable, const std::vector& loads, const std::vector& instructionStarts) { + auto table = firstTable; + while (table < code.size()) { + auto end = code.size(); + for (const auto& load : loads) + if (load.Target > table && load.Target < end) end = load.Target; + const auto entries = TableEntries(code, table, end, firstTable, instructionStarts); + if (entries == 0 || (table == firstTable && entries < 2)) return table == firstTable ? 0 : table; + table += entries * 4; + if (std::none_of(loads.begin(), loads.end(), [&](const auto& load) { return load.Target == table; })) return table; + } + return table; +} + +} + +std::size_t FindTrailingRelativeJumpTable(std::span code) { + const Codegen::X64InstructionDecoder decoder; + std::vector loads; + std::vector instructionStarts(code.size() + 1, false); + bool flowEnded = false; + bool sawIndirectJump = false; + std::size_t lastIndirectJump = 0; + for (std::size_t position = 0; position < code.size();) { + instructionStarts[position] = true; + if (flowEnded && sawIndirectJump) { + for (const auto& load : loads) { + if (load.Target != position || load.At >= lastIndirectJump) continue; + const auto end = TablesEnd(code, position, loads, instructionStarts); + if (end != 0 && OnlyPadding(code, end, decoder)) return position; + } + } + Codegen::DecodedInstructionInfo info; + try { + info = decoder.DecodeInstruction(code.data() + position, code.size() - position); + } catch (const Codegen::CodegenException&) { + break; + } + if (info.Length == 0 || info.Length > code.size() - position) break; + const auto next = position + info.Length; + if (info.HasRipRelativeDisp && !info.IsTwoByteOpcode && info.OpcodeOffset < info.Length && info.Length >= 4 && code[position + info.OpcodeOffset] == 0x8d) { + std::int32_t displacement = 0; + std::memcpy(&displacement, code.data() + next - 4, sizeof(displacement)); + const auto target = static_cast(next) + displacement; + if (target > static_cast(next) && target < static_cast(code.size())) loads.push_back({position, static_cast(target)}); + } + if (info.FlowKind == Codegen::ControlFlowKind::IndirectJump) { + sawIndirectJump = true; + lastIndirectJump = position; + } + if (!IsNop(code, position, info.Length)) { + const bool directJumpOrCall = (info.Length == 5 && (code[position] == 0xe8 || code[position] == 0xe9)) || (info.Length == 2 && code[position] == 0xeb); + flowEnded = directJumpOrCall || info.FlowKind == Codegen::ControlFlowKind::Return || info.FlowKind == Codegen::ControlFlowKind::IndirectJump || info.FlowKind == Codegen::ControlFlowKind::Trap; + } + position = next; + } + return code.size(); +} + +std::vector FindRelativeJumpTables(std::span segment, VirtualAddress segmentVAddr, const std::vector>& functions) { + std::vector tables; + const auto segmentEnd = segmentVAddr + segment.size(); + for (std::size_t index = 0; index < functions.size(); ++index) { + const auto [begin, end] = functions[index]; + if (begin < segmentVAddr || begin >= segmentEnd || end <= begin) continue; + auto inspectEnd = std::min(end, segmentEnd); + if (index + 1 < functions.size() && functions[index + 1].first > inspectEnd && functions[index + 1].first - inspectEnd <= MaxTableGapAfterFunction) + inspectEnd = std::min(functions[index + 1].first, segmentEnd); + const auto code = segment.subspan(begin - segmentVAddr, inspectEnd - begin); + if (const auto tail = FindTrailingRelativeJumpTable(code); tail != 0 && tail < code.size()) + tables.push_back({begin, begin + tail, inspectEnd}); + } + return tables; +} + +} diff --git a/core/relinker/relinker/src/analysis/SyscallScanner.cpp b/core/relinker/relinker/src/analysis/SyscallScanner.cpp index 5b4e47de6c0f..3e301c843864 100644 --- a/core/relinker/relinker/src/analysis/SyscallScanner.cpp +++ b/core/relinker/relinker/src/analysis/SyscallScanner.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -189,14 +190,18 @@ std::vector SyscallScanner::ScanCodeSectionForSyscalls( const std::vector& codeSection, const FileByteOffset codeSectionOffset, const FileByteOffset codeSectionSize) { - const std::size_t limit = std::min(codeSection.size(), static_cast(codeSectionSize)); + if (codeSectionSize > codeSection.size()) throw RelinkerException("Code section exceeds the input buffer", codeSectionOffset); + if (codeSectionSize > std::numeric_limits::max() - codeSectionOffset) throw RelinkerException("Code section offset overflow", codeSectionOffset); + const auto limit = static_cast(codeSectionSize); RecentInstructions recent; std::vector trapped; const auto scanner = Codegen::MakeInstructionScanner(); scanner->VisitCodeSection(codeSection, codeSectionOffset, codeSectionSize, [&](const Codegen::InstructionMatch& match) { recent.Push(match); + if (match.Offset < codeSectionOffset) throw RelinkerException("Instruction match precedes code section offset", match.Offset); const std::size_t start = static_cast(match.Offset - codeSectionOffset); + if (start > codeSection.size() || match.Length > codeSection.size() - start) throw RelinkerException("Instruction match exceeds code section", match.Offset); const auto opcodeOffset = Codegen::DecodedInstruction{codeSection.data() + start, match.Length}.OpcodeOffset(); const std::size_t opcode = start + opcodeOffset; const std::size_t spanLimit = std::min(limit, start + static_cast(match.Length)); diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/ControlFlowGraph.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/ControlFlowGraph.cpp index e0a4b0c5d977..f7fac0f0132b 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/ControlFlowGraph.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/ControlFlowGraph.cpp @@ -39,7 +39,8 @@ void buildGraph( const IRelativeRelocationIndex& relativeRelocations, const std::vector* data, bool followCodeAddresses, - std::unordered_set& reachable + std::unordered_set& reachable, + const std::function& isData ) { if (text.size() > std::numeric_limits::max() - textVaddr) throw RelinkerException("Text section range exceeds the address space"); @@ -52,6 +53,7 @@ void buildGraph( auto enqueue = [&](VirtualAddress va) { if (reachable.count(va) > 0) return; + if (isData && isData(va)) return; if (va < textVaddr || va - textVaddr >= text.size()) return; const auto offset = static_cast(va - textVaddr); auto& word = visited[offset / 64]; @@ -83,7 +85,7 @@ void buildGraph( VirtualAddress va = worklist.front(); worklist.pop(); - while (va >= textVaddr && va - textVaddr < text.size() && !reachable.contains(va)) { + while (va >= textVaddr && va - textVaddr < text.size() && !reachable.contains(va) && !(isData && isData(va))) { std::size_t bufOff = static_cast(va - textVaddr); std::size_t available = text.size() - bufOff; if (available == 0) throw RelinkerException("CFG: zero available bytes at target", va); @@ -173,9 +175,10 @@ std::unique_ptr BuildControlFlowGraph( const std::vector& extraEntries, const IRelativeRelocationIndex& relativeRelocations, bool followCodeAddresses, - std::unordered_set& reachable + std::unordered_set& reachable, + const std::function& isData ) { - buildGraph(text, textVaddr, entryVaddr, extraEntries, relativeRelocations, nullptr, followCodeAddresses, reachable); + buildGraph(text, textVaddr, entryVaddr, extraEntries, relativeRelocations, nullptr, followCodeAddresses, reachable, isData); return std::make_unique(&reachable); } @@ -212,7 +215,7 @@ std::unique_ptr BuildAddressTakenControlFlowGraph( const std::vector& data ) { std::unordered_set reachable; - buildGraph(std::span(text.data(), text.size()), textVaddr, entryVaddr, extraEntries, relativeRelocations, &data, false, reachable); + buildGraph(std::span(text.data(), text.size()), textVaddr, entryVaddr, extraEntries, relativeRelocations, &data, false, reachable, {}); return std::make_unique(std::move(reachable)); } diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/GotAccessIndex.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/GotAccessIndex.cpp index e9405ca433af..9d1e5a3c2e3b 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/GotAccessIndex.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/GotAccessIndex.cpp @@ -43,7 +43,10 @@ std::unique_ptr BuildGotAccessIndex( VirtualAddress nextVaddr = va + static_cast(info.Length); std::int32_t disp = 0; std::memcpy(&disp, text.data() + bufOff + info.RipRelativeDispOffset, 4); + if (disp < 0 && nextVaddr < static_cast(-static_cast(disp))) continue; + if (disp > 0 && std::numeric_limits::max() - nextVaddr < static_cast(disp)) continue; VirtualAddress target = nextVaddr + static_cast(static_cast(disp)); + if (info.AddressSize32) target = static_cast(target); accessed.insert(target); } diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/PltCompactor.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/PltCompactor.cpp index c21acd03ba55..6632f2e0ae7e 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/PltCompactor.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/PltCompactor.cpp @@ -22,20 +22,29 @@ CompactedPlt CompactPlt(const std::vector& originalReferences, con if (reference.RelocationTypeValue != 7) continue; if (!originalSlots.contains(reference.RelocationAddress) || !newSlots.emplace(reference.RelocationAddress, result.SlotCount).second) throw RelinkerException("Strict filter: invalid retained PLT slot", reference.RelocationAddress); - reference.RelocationTableOffset = tableOffset + static_cast(result.SlotCount++) * 24; + const auto slotOffset = static_cast(result.SlotCount++) * 24; + if (tableOffset > std::numeric_limits::max() - slotOffset) + throw RelinkerException("Strict filter: PLT relocation table offset overflow", tableOffset); + reference.RelocationTableOffset = tableOffset + slotOffset; } std::unordered_set found; for (std::size_t offset = 0; offset + 16 <= text.size(); ++offset) { if (text[offset] != 0xFF || text[offset + 1] != 0x25 || text[offset + 6] != 0x68 || text[offset + 11] != 0xE9) continue; + if (offset + 6 > std::numeric_limits::max() - textVaddr) continue; + const auto next = textVaddr + offset + 6; std::int32_t displacement; std::memcpy(&displacement, text.data() + offset + 2, sizeof(displacement)); - const auto slot = textVaddr + offset + 6 + static_cast(static_cast(displacement)); + if (displacement < 0 && next < static_cast(-static_cast(displacement))) continue; + if (displacement > 0 && std::numeric_limits::max() - next < static_cast(displacement)) continue; + const auto slot = next + static_cast(static_cast(displacement)); const auto original = originalSlots.find(slot); if (original == originalSlots.end()) continue; std::uint32_t index; std::memcpy(&index, text.data() + offset + 7, sizeof(index)); if (index != original->second) throw RelinkerException("Strict filter: PLT thunk index disagrees with its relocation", textVaddr + offset); found.insert(slot); + if (textOffset > std::numeric_limits::max() - 7 || offset > std::numeric_limits::max() - textOffset - 7) + throw RelinkerException("Strict filter: PLT patch offset overflow", textOffset); if (const auto kept = newSlots.find(slot); kept != newSlots.end()) { std::vector bytes(sizeof(std::uint32_t)); std::memcpy(bytes.data(), &kept->second, bytes.size()); diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp index 1d0be749bcbc..d6a5eb9ffe74 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace Relinker::UnusedNidFilter { @@ -90,7 +91,12 @@ void collectRelativeEntries( if (relType != R_X86_64_RELATIVE) continue; - out[static_cast(rOffset)] = static_cast(rAddend); + if (rAddend < 0) + throw RelinkerException("Relative relocation target address is negative", rOffset); + + auto [it, inserted] = out.emplace(static_cast(rOffset), static_cast(rAddend)); + if (!inserted && it->second != static_cast(rAddend)) + throw RelinkerException("Conflicting relative relocation targets for slot", rOffset); } } @@ -155,6 +161,7 @@ std::unique_ptr BuildRelativeRelocationIndex( } std::unordered_map targets; + bool hasDynamic = false; for (std::uint16_t i = 0; i < phCount; ++i) { std::size_t phPos = static_cast(phOff) + i * phEntSize; @@ -162,43 +169,107 @@ std::unique_ptr BuildRelativeRelocationIndex( std::uint32_t type = read32(elfBytes, phPos); if (type != PT_DYNAMIC) continue; + if (hasDynamic) throw RelinkerException("Multiple PT_DYNAMIC segments", phPos); + hasDynamic = true; std::uint64_t segOff = read64(elfBytes, phPos + 8); std::uint64_t segSz = read64(elfBytes, phPos + 32); if (segOff > elfBytes.size() || segSz > elfBytes.size() - segOff) throw RelinkerException("PT_DYNAMIC segment out of bounds"); - - VirtualAddress relaVa = 0; - std::uint64_t relaSz = 0; - VirtualAddress jmprelVa = 0; - std::uint64_t pltrelsz = 0; - bool relaSce = false; - bool jmprelSce = false; + if (segSz % 16 != 0) throw RelinkerException("Invalid dynamic segment size", segOff); + + std::optional relaVa; + std::optional relaSz; + std::optional jmprelVa; + std::optional pltrelsz; + bool hasSysvRela = false; + bool hasOsRela = false; + bool hasSysvRelaSz = false; + bool hasOsRelaSz = false; + bool hasSysvJmprel = false; + bool hasOsJmprel = false; + bool hasSysvPltrelsz = false; + bool hasOsPltrelsz = false; + bool terminated = false; for (std::uint64_t off = 0; off <= segSz && segSz - off >= 16; off += 16) { std::size_t pos = static_cast(segOff + off); std::int64_t tag = static_cast(read64(elfBytes, pos)); std::uint64_t val = read64(elfBytes, pos + 8); - if (tag == DT_NULL) break; - if (tag == DT_RELA || tag == DT_OS_RELA) { + if (tag == DT_NULL) { + terminated = true; + break; + } + if (tag == DT_RELA) { + if (hasOsRela) throw RelinkerException("Both DT_OS_ and DT_ variants present for DT_RELA"); + if (hasSysvRela && relaVa.value() != val) throw RelinkerException("Conflicting duplicate DT_RELA tags"); + hasSysvRela = true; relaVa = val; - relaSce = tag == DT_OS_RELA; } - if (tag == DT_RELASZ || tag == DT_OS_RELASZ) relaSz = val; - if (tag == DT_JMPREL || tag == DT_OS_JMPREL) { + if (tag == DT_OS_RELA) { + if (hasSysvRela) throw RelinkerException("Both DT_OS_ and DT_ variants present for DT_RELA"); + if (hasOsRela && relaVa.value() != val) throw RelinkerException("Conflicting duplicate DT_OS_RELA tags"); + hasOsRela = true; + relaVa = val; + } + if (tag == DT_RELASZ) { + if (hasOsRelaSz) throw RelinkerException("Both DT_OS_ and DT_ variants present for DT_RELASZ"); + if (hasSysvRelaSz && relaSz.value() != val) throw RelinkerException("Conflicting duplicate DT_RELASZ tags"); + hasSysvRelaSz = true; + relaSz = val; + } + if (tag == DT_OS_RELASZ) { + if (hasSysvRelaSz) throw RelinkerException("Both DT_OS_ and DT_ variants present for DT_RELASZ"); + if (hasOsRelaSz && relaSz.value() != val) throw RelinkerException("Conflicting duplicate DT_OS_RELASZ tags"); + hasOsRelaSz = true; + relaSz = val; + } + if (tag == DT_JMPREL) { + if (hasOsJmprel) throw RelinkerException("Both DT_OS_ and DT_ variants present for DT_JMPREL"); + if (hasSysvJmprel && jmprelVa.value() != val) throw RelinkerException("Conflicting duplicate DT_JMPREL tags"); + hasSysvJmprel = true; jmprelVa = val; - jmprelSce = tag == DT_OS_JMPREL; } - if (tag == DT_PLTRELSZ || tag == DT_OS_PLTRELSZ) pltrelsz = val; + if (tag == DT_OS_JMPREL) { + if (hasSysvJmprel) throw RelinkerException("Both DT_OS_ and DT_ variants present for DT_JMPREL"); + if (hasOsJmprel && jmprelVa.value() != val) throw RelinkerException("Conflicting duplicate DT_OS_JMPREL tags"); + hasOsJmprel = true; + jmprelVa = val; + } + if (tag == DT_PLTRELSZ) { + if (hasOsPltrelsz) throw RelinkerException("Both DT_OS_ and DT_ variants present for DT_PLTRELSZ"); + if (hasSysvPltrelsz && pltrelsz.value() != val) throw RelinkerException("Conflicting duplicate DT_PLTRELSZ tags"); + hasSysvPltrelsz = true; + pltrelsz = val; + } + if (tag == DT_OS_PLTRELSZ) { + if (hasSysvPltrelsz) throw RelinkerException("Both DT_OS_ and DT_ variants present for DT_PLTRELSZ"); + if (hasOsPltrelsz && pltrelsz.value() != val) throw RelinkerException("Conflicting duplicate DT_OS_PLTRELSZ tags"); + hasOsPltrelsz = true; + pltrelsz = val; + } } - collectRelativeEntries(elfBytes, loads, relaVa, relaSz, relaSce, sceTableBase, targets); - collectRelativeEntries(elfBytes, loads, jmprelVa, pltrelsz, jmprelSce, sceTableBase, targets); + if (!terminated) throw RelinkerException("Unterminated dynamic segment", segOff); - break; + if ((hasSysvRela && hasOsRelaSz) || (hasOsRela && hasSysvRelaSz)) + throw RelinkerException("Mismatched DT_RELA and DT_RELASZ tag variants"); + if (relaVa.has_value() != relaSz.has_value()) + throw RelinkerException("Incomplete relocation table metadata"); + + if ((hasSysvJmprel && hasOsPltrelsz) || (hasOsJmprel && hasSysvPltrelsz)) + throw RelinkerException("Mismatched DT_JMPREL and DT_PLTRELSZ tag variants"); + if (jmprelVa.has_value() != pltrelsz.has_value()) + throw RelinkerException("Incomplete PLT relocation table metadata"); + + if (relaVa.has_value()) + collectRelativeEntries(elfBytes, loads, *relaVa, *relaSz, hasOsRela, sceTableBase, targets); + if (jmprelVa.has_value()) + collectRelativeEntries(elfBytes, loads, *jmprelVa, *pltrelsz, hasOsJmprel, sceTableBase, targets); } return std::make_unique(std::move(targets)); } } + diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp index 3eede671ebc8..d1d937a7ba13 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp @@ -98,8 +98,8 @@ class StrictImage { for (const auto& [addressTag, sizeTag] : {std::pair{25, 27}, std::pair{26, 28}, std::pair{32, 33}}) { const auto array = value(addressTag, 0x60000000 + addressTag); const auto size = value(sizeTag, 0x60000000 + sizeTag); - if (array.has_value() != size.has_value()) throw RelinkerException("Strict filter: incomplete constructor array metadata"); - if (!array) continue; + if (size && !array) throw RelinkerException("Strict filter: incomplete constructor array metadata"); + if (!size || *size == 0) continue; if (*size % 8 != 0) throw RelinkerException("Strict filter: invalid constructor array size", *array); for (std::uint64_t offset = 0; offset < *size; offset += 8) { const auto slot = *array + offset; diff --git a/core/relinker/relinker/src/guest/GuestImageReader.cpp b/core/relinker/relinker/src/guest/GuestImageReader.cpp index cffd2b4f297a..1a018d30dd76 100644 --- a/core/relinker/relinker/src/guest/GuestImageReader.cpp +++ b/core/relinker/relinker/src/guest/GuestImageReader.cpp @@ -256,7 +256,7 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector image.Symbols.push_back({{}, 0, 0, 0, 0, 0}); continue; } - if (offset != 0 && ((info >> 4) > 2 || (visibility & ~3u) != 0 || ((info & 15) != 0 && (info & 15) != 1 && (info & 15) != 2 && (info & 15) != 6))) fail("Unsupported symbol attributes: " + name); + if (offset != 0 && ((info >> 4) > 2 || (visibility & ~3u) != 0 || ((info & 15) != 0 && (info & 15) != 1 && (info & 15) != 2 && info != 3 && (info & 15) != 6))) fail("Unsupported symbol attributes: " + name); if (section != 0 && (info >> 4) != 0 && visibility != 1 && visibility != 2) { const auto [found, inserted] = exports.emplace(name, image.Symbols.size()); if (name.empty() || (!inserted && (!windows || !IdenticalOrdinaryFunctionExport(image.Symbols[found->second], symbol)))) fail("Duplicate or empty export after stripping #: " + name); @@ -310,7 +310,8 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector if (relocation == 8) { if ((info >> 32) != 0) { const auto& symbol = image.Symbols[info >> 32]; - if (symbol.Section == 0 || addend > static_cast(std::numeric_limits::max()) || symbol.Value != addend) + const bool sectionSymbol = symbol.Info == 3 && symbol.Value == 0; + if (!sectionSymbol && (symbol.Section == 0 || addend > static_cast(std::numeric_limits::max()) || symbol.Value != addend)) fail("RELATIVE relocation has a symbol"); mapped(addend, 0, 0); Io::AppendU64(output, target); diff --git a/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp b/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp index e799b5e0fb64..c164781c75aa 100644 --- a/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp +++ b/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -172,11 +173,25 @@ RelinkResult RelinkerPipeline::Relink(const std::vector& sourceElf } const ByteCount dynJmpRelSize = gotSize; - const FileByteOffset dynRelaOffset = readAsOffset(DT_OS_RELA, DT_RELA, "DT_RELA"); - const ByteCount dynRelaSize = readAsSize(DT_OS_RELASZ, DT_RELASZ, "DT_RELASZ"); + if (hasTag(DT_REL) || hasTag(DT_RELSZ) || hasTag(DT_RELENT)) + throw RelinkerException("DT_REL relocations are not supported"); + if (hasTag(DT_RELR) || hasTag(DT_RELRSZ) || hasTag(DT_RELRENT)) + throw RelinkerException("DT_RELR relocations are not supported"); + + const bool hasRelaTable = hasTag(DT_OS_RELA) || hasTag(DT_RELA) + || hasTag(DT_OS_RELASZ) || hasTag(DT_RELASZ); + FileByteOffset dynRelaOffset = 0; + ByteCount dynRelaSize = 0; + if (hasRelaTable) { + dynRelaOffset = readAsOffset(DT_OS_RELA, DT_RELA, "DT_RELA"); + dynRelaSize = readAsSize(DT_OS_RELASZ, DT_RELASZ, "DT_RELASZ"); + } constexpr std::size_t relaEntSize = 24; - if (readAsSize(DT_OS_RELAENT, DT_RELAENT, "DT_RELAENT") != relaEntSize) - throw RelinkerException("Unsupported DT_RELAENT value"); + if (hasRelaTable || hasTag(DT_OS_RELAENT) || hasTag(DT_RELAENT)) { + const auto relocationEntrySize = readAsSize(DT_OS_RELAENT, DT_RELAENT, "DT_RELAENT"); + if (relocationEntrySize != relaEntSize) + throw RelinkerException("Unsupported DT_RELAENT value: " + std::to_string(relocationEntrySize) + "; expected 24 bytes"); + } if (dynRelaSize % relaEntSize != 0) throw RelinkerException("Invalid DT_RELASZ value"); @@ -312,8 +327,11 @@ RelinkResult RelinkerPipeline::Relink(const std::vector& sourceElf for (const auto& ref : nidRefs) _validationPolicy->ValidateRelocationTypeSupported(ref.RelocationTypeValue, ref.RelocationTableOffset); + std::vector> unwindRanges; if (std::any_of(programHeaders.begin(), programHeaders.end(), [](const ProgramHeader& ph) { return ph.Type == PT_SCE_DYNLIBDATA; })) { const auto functions = UnusedNidFilter::ReadExceptionFunctions(raw, programHeaders, {}, {}); + for (const auto& function : functions) unwindRanges.emplace_back(function.Begin, function.End); + std::sort(unwindRanges.begin(), unwindRanges.end()); for (auto& [segment, segmentVAddr, segmentFileOffset] : executableSegments) { static_cast(segmentFileOffset); if (const auto codeEnd = FindSceCodeEnd(segment, segmentVAddr, functions, gotVAddr, dynJmpRelSize)) @@ -321,13 +339,23 @@ RelinkResult RelinkerPipeline::Relink(const std::vector& sourceElf } } + std::vector> scanSegments; + for (const auto& [segment, segmentVAddr, segmentFileOffset] : executableSegments) { + static_cast(segmentFileOffset); + auto& scanSegment = scanSegments.emplace_back(segment); + for (const auto& table : FindRelativeJumpTables(segment, segmentVAddr, unwindRanges)) + std::fill(scanSegment.begin() + static_cast(table.Begin - segmentVAddr), scanSegment.begin() + static_cast(table.End - segmentVAddr), std::uint8_t{0x90}); + } + std::vector syscallPatches; - for (auto& [segment, segmentVAddr, segmentFileOffset] : executableSegments) { - for (const auto offset : _syscallScanner->ScanCodeSectionForSyscalls(segment, segmentVAddr, segment.size())) { + for (std::size_t index = 0; index < executableSegments.size(); ++index) { + auto& [segment, segmentVAddr, segmentFileOffset] = executableSegments[index]; + auto& scanSegment = scanSegments[index]; + for (const auto offset : _syscallScanner->ScanCodeSectionForSyscalls(scanSegment, segmentVAddr, scanSegment.size())) { if (offset > segment.size() || segment.size() - offset < 2u) throw RelinkerException("Syscall trap patch exceeds executable segment", segmentVAddr + offset); - segment[offset] = 0x0F; - segment[offset + 1] = 0x0B; + segment[offset] = scanSegment[offset] = 0x0F; + segment[offset + 1] = scanSegment[offset + 1] = 0x0B; syscallPatches.push_back({segmentFileOffset + offset, {0x0F, 0x0B}}); } } @@ -472,10 +500,10 @@ RelinkResult RelinkerPipeline::Relink(const std::vector& sourceElf entries.push_back(std::move(entry)); } - for (const auto& [segment, segmentVAddr, segmentFileOffset] : executableSegments) { - static_cast(segmentFileOffset); + for (std::size_t index = 0; index < executableSegments.size(); ++index) { + const auto& segmentVAddr = std::get<1>(executableSegments[index]); for (auto& entry : entries) { - auto segmentSites = _callSiteResolver->ResolveCallSites(segment, segmentVAddr, entry.TargetOffset, 8); + auto segmentSites = _callSiteResolver->ResolveCallSites(scanSegments[index], segmentVAddr, entry.TargetOffset, 8); entry.CallSites.insert(entry.CallSites.end(), segmentSites.begin(), segmentSites.end()); entry.CallSitesResolved = !entry.CallSites.empty(); } diff --git a/core/relinker/relinker/tests/ControlFlowGraphTests.cpp b/core/relinker/relinker/tests/ControlFlowGraphTests.cpp index aa724babbe34..2d0fb562ec0e 100644 --- a/core/relinker/relinker/tests/ControlFlowGraphTests.cpp +++ b/core/relinker/relinker/tests/ControlFlowGraphTests.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -176,6 +177,42 @@ void CheckHighVirtualAddress() { Require(graph->IsReachable(highVaddr + 4), "Missing high branch target address"); } +void CheckRelativeJumpTables() { + constexpr std::size_t tableOffset = 16; + Bytes text(32, 0x90); + text[0] = 0x48; + text[1] = 0x8d; + text[2] = 0x05; + SetDisplacement(text, 3, 7, TextAddress + tableOffset); + text[7] = 0xff; + text[8] = 0xe0; + const std::int32_t targets[]{-16, -9}; + std::memcpy(text.data() + tableOffset, targets, sizeof(targets)); + Require(Relinker::FindTrailingRelativeJumpTable(text) == tableOffset, "Relative jump table was not found"); + + const std::vector> functions{{TextAddress, TextAddress + 9}, {TextAddress + text.size(), TextAddress + text.size() + 8}}; + const auto tables = Relinker::FindRelativeJumpTables(text, TextAddress, functions); + Require(tables.size() == 1, "Expected one relative jump table"); + Require(tables[0].Function == TextAddress && tables[0].Begin == TextAddress + tableOffset && tables[0].End == TextAddress + text.size(), "Relative jump table range was incorrect"); + + Bytes oneEntry = text; + const std::int32_t singleTarget[]{-16}; + std::memcpy(oneEntry.data() + tableOffset, singleTarget, sizeof(singleTarget)); + const std::int32_t invalidSecondTarget[]{0x100}; + std::memcpy(oneEntry.data() + tableOffset + 4, invalidSecondTarget, sizeof(invalidSecondTarget)); + Require(Relinker::FindTrailingRelativeJumpTable(oneEntry) == oneEntry.size(), "Single-entry table was accepted"); + + Bytes invalidTarget = text; + const std::int32_t invalidTargets[]{0x100, 0x104}; + std::memcpy(invalidTarget.data() + tableOffset, invalidTargets, sizeof(invalidTargets)); + Require(Relinker::FindTrailingRelativeJumpTable(invalidTarget) == invalidTarget.size(), "Table with non-instruction targets was accepted"); + + Bytes nonPadding = text; + nonPadding.back() = 0x55; + Require(Relinker::FindTrailingRelativeJumpTable(nonPadding) == nonPadding.size(), "Table followed by non-padding bytes was accepted"); + +} + } int main() { @@ -189,6 +226,7 @@ int main() { CheckBorrowedReachability(); CheckAddressOverflow(); CheckHighVirtualAddress(); + CheckRelativeJumpTables(); std::cout << "Control-flow graph tests passed\n"; return 0; } catch (const std::exception& error) { diff --git a/core/relinker/relinker/tests/SdkMmanSyscallTests.cpp b/core/relinker/relinker/tests/SdkMmanSyscallTests.cpp index 264cb6057c7d..1c73264ae26d 100644 --- a/core/relinker/relinker/tests/SdkMmanSyscallTests.cpp +++ b/core/relinker/relinker/tests/SdkMmanSyscallTests.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -72,6 +73,20 @@ void CheckFixtures() { Require(Rejected(embedded, true, embedded.size()), "SDK matching accepted a prefix that starts inside an immediate"); for (const auto& code : std::vector{{0xB8, 0xDD, 0x01, 0x00, 0x00, 0x0F, 0x05, 0xC3}, {0xCD, 0x80}, {0x0F, 0x34}, {0x0F, 0x07}}) Require(Rejected(code, true, code.size()), "SDK trap mode accepted an unrelated privileged instruction"); + bool overflowRejected = false; + try { + Relinker::MakeSyscallScanner(true)->ScanCodeSectionForSyscalls(Fixtures.front().code, std::numeric_limits::max(), 1); + } catch (const Domain::RelinkerException& error) { + overflowRejected = std::string(error.what()) == "Code section offset overflow"; + } + Require(overflowRejected, "Scanner accepted code section offset overflow"); + bool sizeExceededRejected = false; + try { + Relinker::MakeSyscallScanner(true)->ScanCodeSectionForSyscalls(Fixtures.front().code, 0, Fixtures.front().code.size() + 1); + } catch (const Domain::RelinkerException& error) { + sizeExceededRejected = std::string(error.what()) == "Code section exceeds the input buffer"; + } + Require(sizeExceededRejected, "Scanner accepted code section size exceeding input"); std::cout << "SDK inline syscall checks passed, " << rejectedMutations << " near-match rejections\n"; } diff --git a/core/relinker/relinker/tests/guest_import_fixtures.py b/core/relinker/relinker/tests/guest_import_fixtures.py new file mode 100644 index 000000000000..45c69cbfb420 --- /dev/null +++ b/core/relinker/relinker/tests/guest_import_fixtures.py @@ -0,0 +1,64 @@ +import contextlib +import shutil +import struct +import tempfile +import time + +from test_guest_intel_trampolines import main_fixture + + +@contextlib.contextmanager +def fixture_directory(prefix): + directory = tempfile.mkdtemp(prefix=prefix) + try: + yield directory + finally: + deadline = time.monotonic() + 10 + while True: + try: + shutil.rmtree(directory) + break + except PermissionError: + if time.monotonic() >= deadline: + raise + time.sleep(0.05) + + +def dynamic(image, offset, phoff, tags): + for index, tag in enumerate(tags + [(0, 0)]): + struct.pack_into(' 0; filtered=0" in result.stdout, result.stdout + live_regions = 2 if name == "nonempty" else 1 + assert f"Function graph: {live_regions}/" in result.stdout, result.stdout + if windows and os.name == "nt": + assert subprocess.run([str(output)], capture_output=True, timeout=20).returncode == 42 + + def main(): relinker = Path(sys.argv[1]).resolve() with tempfile.TemporaryDirectory(prefix="anyps5-init-array-") as directory: @@ -105,6 +153,7 @@ def main(): image(DT_INIT_ARRAY, DT_INIT_ARRAYSZ, LOAD_END + 0x100, [FUNCTION], load_size=LOAD_END)) assert result.returncode == 2 and "array is not file-backed" in result.stderr and not output.exists(), ( result.returncode, result.stdout, result.stderr) + strict_array_metadata(relinker, work) print("Init array root tests passed") diff --git a/core/relinker/relinker/tests/test_macos_gs_traps.py b/core/relinker/relinker/tests/test_macos_gs_traps.py new file mode 100644 index 000000000000..8b9c21512d29 --- /dev/null +++ b/core/relinker/relinker/tests/test_macos_gs_traps.py @@ -0,0 +1,55 @@ +"""Check that `relinker --macos` traps guest GS accesses in place.""" +from pathlib import Path +import struct +import subprocess +import sys +import tempfile + +from test_macos_output import MachO +from test_tls_function_coverage import make_image + + +FORMS = { + "gs-load-rcx": bytes.fromhex("65 48 8b 0c 25 78 03 00 00"), + "gs-load-r13": bytes.fromhex("65 4c 8b 2c 25 30 00 00 00"), + "gs-store": bytes.fromhex("65 48 89 04 25 30 00 00 00"), + "gs-alu": bytes.fromhex("65 48 33 04 25 28 00 00 00"), + "gs-short-register-address": bytes.fromhex("65 48 8b 00"), +} + + +def convert(relinker, directory, name, image): + source = Path(directory) / f"{name}.elf" + output = Path(directory) / name + source.write_bytes(bytes(image)) + result = subprocess.run([str(relinker), "--macos", "--skip-sce-module", str(source), str(output)], + capture_output=True, text=True, timeout=60) + if result.returncode != 0: + raise SystemExit(f"{name}: relinker failed: {result.stdout}{result.stderr}") + data = output.read_bytes() + return b"".join(data[lo:hi] for lo, hi in MachO(data).executable_ranges()) + + +def check_trapped(name, code, instruction): + trapped = b"\x0f\x0b" + b"\x90" * (len(instruction) - 2) + b"\xc3" + if trapped not in code: + raise SystemExit(f"{name}: the GS access was not replaced by ud2 and NOP padding") + if instruction + b"\xc3" in code: + raise SystemExit(f"{name}: the GS access was left in the code") + + +def main(): + relinker = Path(sys.argv[1]).resolve() + with tempfile.TemporaryDirectory(prefix="anyps5-macos-gs-") as directory: + for name, instruction in FORMS.items(): + image = make_image("register", "unwind", body=instruction + b"\xc3") + check_trapped(name, convert(relinker, directory, name, image), instruction) + instruction = FORMS["gs-load-rcx"] + image = make_image("register", "unwind", body=instruction + b"\xc3") + struct.pack_into(") + if(BUILD_TESTING AND ANYPS5_ENABLE_SPIRV_TOOLS AND NOT ANYPS5_USE_SYSTEM_DEPS AND NOT TARGET spirv_validator_exit_tests) + add_test_executable(spirv_validator_exit_tests "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/tests/SpirvValidatorExit.cpp") + target_link_libraries(spirv_validator_exit_tests PRIVATE SPIRV-Tools-static) + set_target_properties(spirv_validator_exit_tests PROPERTIES CXX_EXTENSIONS OFF) + configure_windows_unwind(spirv_validator_exit_tests) + add_test(NAME spirv_validator_exit COMMAND spirv_validator_exit_tests) + set_tests_properties(spirv_validator_exit PROPERTIES TIMEOUT 30) + endif() endfunction() diff --git a/core/shader/recompiler/ControlFlow/src/Structurizer.cpp b/core/shader/recompiler/ControlFlow/src/Structurizer.cpp index 3d80b21d8eec..3267461138ca 100644 --- a/core/shader/recompiler/ControlFlow/src/Structurizer.cpp +++ b/core/shader/recompiler/ControlFlow/src/Structurizer.cpp @@ -530,12 +530,27 @@ std::uint32_t mergeBesideReturns(const ControlFlowGraph& graph, const BasicBlock } } const auto count = static_cast(graph.blocks.size()); - const auto returns = [&](std::uint32_t blockId) { - if (!graph.Dominates(header.id, blockId) || !hasLinearPathToTerminal(graph, blockId)) { - return false; + enum class Returning : std::uint8_t { Unknown, Visiting, No, Yes }; + std::vector returning(count, Returning::Unknown); + const auto returns = [&](std::uint32_t start) { + std::vector pending{start}; + while (!pending.empty()) { + const auto blockId = pending.back(); + const auto& block = graph.FindBlock(blockId); + if (returning[blockId] == Returning::Unknown) { + if (block.predecessors.size() == 1u && graph.Dominates(header.id, blockId)) { + returning[blockId] = Returning::Visiting; + std::copy_if(block.successors.begin(), block.successors.end(), std::back_inserter(pending), [&](std::uint32_t successor) { return returning[successor] == Returning::Unknown; }); + continue; + } + returning[blockId] = Returning::No; + } else if (returning[blockId] == Returning::Visiting) { + const bool privateReturn = std::all_of(block.successors.begin(), block.successors.end(), [&](std::uint32_t successor) { return returning[successor] == Returning::Yes; }); + returning[blockId] = privateReturn ? Returning::Yes : Returning::No; + } + pending.pop_back(); } - const auto tail = linearTail(graph, blockId); - return std::all_of(tail.begin(), tail.end(), [&](std::uint32_t member) { return graph.FindBlock(member).predecessors.size() == 1u; }); + return returning[start] == Returning::Yes; }; std::vector> kept(count); for (const auto& block : graph.blocks) { diff --git a/core/shader/recompiler/Optimization/src/ResourceTracker.cpp b/core/shader/recompiler/Optimization/src/ResourceTracker.cpp index 74a307f3679a..0052ebd85cb3 100644 --- a/core/shader/recompiler/Optimization/src/ResourceTracker.cpp +++ b/core/shader/recompiler/Optimization/src/ResourceTracker.cpp @@ -511,7 +511,10 @@ class Tracker { IrValue* heapOffset = nullptr; std::uint32_t immediateOffset = 0; for (std::uint32_t dword = 0; dword < heapReads.size(); dword++) { - heapReads[dword] = handle.Argument(dword)->Resolve(); + heapReads[dword] = ResolveInvariantPhi(m_program.Resources(), handle.Argument(dword)); + if (heapReads[dword] == nullptr) { + return false; + } std::uint32_t memoryIndex = 0; const MemoryInfo* memory = ScalarReadMemory(*heapReads[dword], memoryIndex); if (memory == nullptr || !MemoryIndexBelongsTo(memoryIndex, *heapReads[dword])) { diff --git a/core/shader/recompiler/Optimization/src/TessellationLowering.cpp b/core/shader/recompiler/Optimization/src/TessellationLowering.cpp index 67930aa0ecd6..4969fdc75182 100644 --- a/core/shader/recompiler/Optimization/src/TessellationLowering.cpp +++ b/core/shader/recompiler/Optimization/src/TessellationLowering.cpp @@ -39,6 +39,13 @@ bool Same(const Affine& left, const Affine& right) { return !left.dynamic && !right.dynamic && left == right; } +std::optional CommonMultiple(const Affine& left, const Affine& right) { + if (left.vertex != right.vertex || left.relative != right.relative || left.constant != right.constant) return std::nullopt; + const auto multiple = std::gcd(left.dynamic ? left.dynamicScale : 0, right.dynamic ? right.dynamicScale : 0); + if (multiple == 0) return std::nullopt; + return Affine{left.vertex, left.relative, left.constant, true, multiple, nullptr}; +} + [[noreturn]] void Fail(const char* reason, std::uint32_t pc) { char text[256]; std::snprintf(text, sizeof(text), "tessellation lowering: %s at pc 0x%x", reason, pc); @@ -412,7 +419,7 @@ class AddressEvaluator { case IrOpcode::ShiftLeftLogical32: { const auto lhs = argument(0); const auto shift = scalar(1); - if (!lhs || !shift || *shift >= 31) return std::nullopt; + if (!lhs || !shift || *shift < 0 || *shift >= 31) return std::nullopt; return scale(*lhs, 1ll << *shift); } case IrOpcode::ShiftRightLogical32: { @@ -440,7 +447,7 @@ class AddressEvaluator { const auto source = argument(0); const auto offset = scalar(1); const auto count = scalar(2); - if (!source || !offset || !count) return std::nullopt; + if (!source || !offset || !count || *count < 0) return std::nullopt; if (*count == 0) return Affine{}; const auto shifted = shiftRight(*source, *offset); if (!shifted || *count >= 32) return shifted; @@ -450,18 +457,22 @@ class AddressEvaluator { if (active != nullptr && Implies(active, value->Argument(0))) return argument(1); const auto lhs = argument(1); const auto rhs = argument(2); - return lhs && rhs && Same(*lhs, *rhs) ? lhs : std::nullopt; + if (!lhs || !rhs) return std::nullopt; + if (Same(*lhs, *rhs)) return lhs; + return dynamic ? CommonMultiple(*lhs, *rhs) : std::nullopt; } default: break; } if (!value->IsPhi() || value->ArgumentCount() == 0) return std::nullopt; - const auto first = argument(0); - for (std::size_t index = 1; first && index < value->ArgumentCount(); ++index) { + auto result = argument(0); + for (std::size_t index = 1; result && index < value->ArgumentCount(); ++index) { const auto other = argument(index); - if (!other || !Same(*other, *first)) return std::nullopt; + if (!other) return std::nullopt; + if (Same(*result, *other)) continue; + result = dynamic ? CommonMultiple(*result, *other) : std::nullopt; } - return first && !first->dynamic ? first : std::nullopt; + return result && (dynamic || !result->dynamic) ? result : std::nullopt; } std::uint32_t vertexCount; @@ -796,7 +807,10 @@ class Lowering { } IrValue& componentAddress(const Access& access, std::uint32_t component) { - if (access.address.dynamic) return insert(access, IrOpcode::IAdd32, IrType::U32, {access.addressValue, &constant(access.memoryOffset + 4u * component)}); + if (access.address.dynamic) { + const std::uint32_t byteOffset = access.memoryOffset + 4u * component; + return byteOffset == 0u ? *access.addressValue : insert(access, IrOpcode::IAdd32, IrType::U32, {access.addressValue, &constant(byteOffset)}); + } IrValue& offset = constant(static_cast(access.address.constant) + 4u * component); if (access.kind != TessellationAttribute::ControlInput || access.address.vertex == 0) return offset; IrValue& invocation = insert(access, IrOpcode::GetBuiltin, IrType::U32, {&constant(static_cast(StageInputKind::InvocationId)), &constant(0u)}); diff --git a/core/shader/recompiler/PatchSpirvTools.cmake b/core/shader/recompiler/PatchSpirvTools.cmake index 69cb4bcfc79b..4acc117b3bd8 100644 --- a/core/shader/recompiler/PatchSpirvTools.cmake +++ b/core/shader/recompiler/PatchSpirvTools.cmake @@ -120,3 +120,49 @@ if(NOT spirvToolsReplacements EQUAL 1) message(FATAL_ERROR "SPIRV-Tools optimizer sources have changed; review the local memory fix") endif() set_property(TARGET SPIRV-Tools-opt PROPERTY SOURCES "${spirvToolsPatchedSources}") + +get_target_property(spirvToolsValidatorSourceDir SPIRV-Tools-static SOURCE_DIR) +set(spirvToolsValidatorSource "${spirvToolsValidatorSourceDir}/val/validate_id.cpp") +set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${spirvToolsValidatorSource}") +file(READ "${spirvToolsValidatorSource}" spirvToolsValidator) +string(FIND "${spirvToolsValidator}" "#include " spirvToolsValidatorInclude) +if(spirvToolsValidatorInclude EQUAL -1) + message(FATAL_ERROR "SPIRV-Tools validator includes have changed; review the local lifetime fix") +endif() +string(REPLACE "#include " "#include \n#include " spirvToolsValidator "${spirvToolsValidator}") +foreach(table IN ITEMS allow deny) + set(oldDeclaration "static std::unordered_set instruction_${table}_set{") + set(newDeclaration "static constexpr spv::Op instruction_${table}_set[]{") + set(oldLookup "instruction_${table}_set.count(opcode)") + set(newLookup "(std::find(std::begin(instruction_${table}_set),\n std::end(instruction_${table}_set), opcode) !=\n std::end(instruction_${table}_set))") + string(FIND "${spirvToolsValidator}" "${oldDeclaration}" declarationPosition) + string(FIND "${spirvToolsValidator}" "${oldLookup}" lookupPosition) + if(declarationPosition EQUAL -1 OR lookupPosition EQUAL -1) + message(FATAL_ERROR "SPIRV-Tools validator type-operand tables have changed; review the local lifetime fix") + endif() + string(REPLACE "${oldDeclaration}" "${newDeclaration}" spirvToolsValidator "${spirvToolsValidator}") + string(REPLACE "${oldLookup}" "${newLookup}" spirvToolsValidator "${spirvToolsValidator}") +endforeach() +set(spirvToolsPatchedValidator "${spirvToolsPatchedDir}/validate_id.cpp") +file(CONFIGURE OUTPUT "${spirvToolsPatchedValidator}" CONTENT "${spirvToolsValidator}" @ONLY) +foreach(validatorTarget IN ITEMS SPIRV-Tools-static SPIRV-Tools-shared) + if(NOT TARGET ${validatorTarget}) + continue() + endif() + get_target_property(validatorSources ${validatorTarget} SOURCES) + set(patchedValidatorSources) + set(validatorReplacements 0) + foreach(source IN LISTS validatorSources) + get_filename_component(sourcePath "${source}" ABSOLUTE BASE_DIR "${spirvToolsValidatorSourceDir}") + if(sourcePath STREQUAL spirvToolsValidatorSource) + list(APPEND patchedValidatorSources "${spirvToolsPatchedValidator}") + math(EXPR validatorReplacements "${validatorReplacements} + 1") + else() + list(APPEND patchedValidatorSources "${source}") + endif() + endforeach() + if(NOT validatorReplacements EQUAL 1) + message(FATAL_ERROR "SPIRV-Tools validator sources have changed; review the local lifetime fix") + endif() + set_property(TARGET ${validatorTarget} PROPERTY SOURCES "${patchedValidatorSources}") +endforeach() diff --git a/core/shader/recompiler/ShaderCacheVersion.cmake b/core/shader/recompiler/ShaderCacheVersion.cmake index 81be546106a8..847a51349674 100644 --- a/core/shader/recompiler/ShaderCacheVersion.cmake +++ b/core/shader/recompiler/ShaderCacheVersion.cmake @@ -26,7 +26,6 @@ endforeach() list(SORT sources) list(SORT guard) get_filename_component(rootReal "${ROOT}" REALPATH) -get_filename_component(recompilerReal "${RECOMPILER_DIR}" REALPATH) set(sourcesReal) foreach(source IN LISTS sources) get_filename_component(real "${source}" REALPATH) @@ -70,9 +69,8 @@ foreach(source IN LISTS sources) if(NOT resolved) continue() endif() - string(FIND "${resolved}" "${recompilerReal}/" resolvedInRecompiler) string(FIND "${resolved}" "${rootReal}/3rdparty/" resolvedInThirdParty) - if(resolvedInRecompiler EQUAL 0 OR resolvedInThirdParty EQUAL 0 OR resolved IN_LIST sourcesReal) + if(resolvedInThirdParty EQUAL 0 OR resolved IN_LIST sourcesReal) continue() endif() file(RELATIVE_PATH relativeResolved "${rootReal}" "${resolved}") diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvModule.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvModule.hpp index 48870b3597a6..17e535a57b97 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvModule.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvModule.hpp @@ -56,6 +56,7 @@ class SpirvModule { void AddMemoryModel(std::uint32_t addressingModel, std::uint32_t memoryModel); void AddName(std::uint32_t target, const std::string& name); void AddFunction(std::span words); + void AddSubgroupBallot(std::uint32_t type, std::uint32_t result, std::uint32_t scope, std::uint32_t predicate); void BeginHelperFunction(); void EndHelperFunction(); [[nodiscard]] SpirvDeferredPhi AddDeferredPhi(std::uint32_t type, std::uint32_t result, std::size_t incomingCount); @@ -257,6 +258,12 @@ class SpirvModule { std::vector globalVariables; std::vector functionInstructions; std::vector helperFunctionInstructions; + struct BlockBallots { + std::map, std::uint32_t> results; + std::size_t scanned = 0; + }; + BlockBallots ballots; + BlockBallots helperBallots; bool inHelperFunction = false; }; diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp index 0f214efbfebe..89c98d82a5fc 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp @@ -685,9 +685,7 @@ std::uint32_t EmitConvertU8U32(SpirvEmitterState& state, std::uint32_t arg0) { } std::uint32_t EmitConvertF16F32(SpirvEmitterState& state, std::uint32_t arg0) { - const auto pair = state.module.AllocateId(); - state.module.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(state, 2), pair, arg0, ConstantF32(state, 0u)); - return EmitPackHalf2x16(state, pair); + return EmitF32ToF16BitsRte(state, arg0); } std::uint32_t EmitConvertS32F32(SpirvEmitterState& state, std::uint32_t arg0) { diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp index 19a85371306d..7d4f332fc7ef 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp @@ -126,7 +126,7 @@ std::uint32_t SpirvValueEmitContext::Ballot(const IrValue* predicate) { const auto lowWord = state.module.AllocateId(); const auto highWord = state.module.AllocateId(); const auto ballot = state.module.AllocateId(); - state.module.AddFunction(spv::OpGroupNonUniformBallot, ballotType, high, scope, half == 1u ? Def(predicate) : otherHalf->Def(predicate)); + state.module.AddSubgroupBallot(ballotType, high, scope, half == 1u ? Def(predicate) : otherHalf->Def(predicate)); state.module.AddFunction(spv::OpCompositeExtract, TypeU32(state), lowWord, low, 0u); state.module.AddFunction(spv::OpCompositeExtract, TypeU32(state), highWord, high, 0u); state.module.AddFunction(spv::OpCompositeConstruct, ballotType, ballot, lowWord, highWord, ConstantU32(state, 0u), ConstantU32(state, 0u)); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp index 8b51facdf1e1..bc2f14664a7a 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp @@ -899,6 +899,25 @@ bool CommunicatesThroughMemory(IrOpcode opcode) { return SharedAccessOf(opcode) != SharedAccess::None || buffer == BufferAccess::Write || buffer == BufferAccess::Atomic || address == AddressAccess::Write || address == AddressAccess::Atomic || image == ImageAccess::Write || image == ImageAccess::Atomic; } +bool AccessesMemory(IrOpcode opcode) { + return opcode == IrOpcode::ReadConst || opcode == IrOpcode::ReadConstBuffer || SharedAccessOf(opcode) != SharedAccess::None || BufferAccessOf(opcode) != BufferAccess::None || AddressOpcodeInfoOf(opcode).access != AddressAccess::None || ImageOpcodeInfoOf(opcode).access != ImageAccess::None; +} + +bool PrecedesEveryMemoryAccess(const IrProgram& program, const IrValue& barrier) { + const auto& blocks = program.BlockOrder(); + const IrBlock* block = blocks.empty() ? nullptr : blocks.front(); + for (std::size_t steps = 0; block != nullptr && steps < blocks.size(); ++steps) { + for (const auto* instruction : block->Instructions()) { + if (instruction == &barrier) return true; + if (AccessesMemory(instruction->Opcode())) return false; + } + if (block->Successors().size() != 1u) return false; + block = block->Successors().front(); + if (block->Predecessors().size() != 1u) return false; + } + return false; +} + } void EmitControlFlow(SpirvModule& module, const IrProgram& program) { @@ -955,6 +974,7 @@ void EmitVoid(SpirvValueEmitContext&) { void EmitBarrier(SpirvValueEmitContext& ctx, const IrValue& inst) { auto& state = ctx.state; if (state.program.Resources().stage == IrShaderStage::Vertex) { + if (PrecedesEveryMemoryAccess(state.program, inst)) return; for (const auto* block : state.program.BlockOrder()) { for (const auto* instruction : block->Instructions()) { if (CommunicatesThroughMemory(instruction->Opcode())) ctx.Fail(inst, "s_barrier in a vertex program that accesses LDS or GDS or writes memory is not implemented"); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp index 3651ae73900e..744bc6862bf0 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp @@ -436,6 +436,7 @@ void DefineDescriptors(SpirvEmitterState& state) { break; case DescriptorBindingKind::BdaPagetable: state.bdaPagetableVariable = Define(StorageBufferBlockType(state), "bda_pagetable"); + state.module.AddAnnotation(spv::OpDecorate, state.bdaPagetableVariable, spv::DecorationNonWritable); break; case DescriptorBindingKind::FaultBuffer: state.faultBufferVariable = Define(StorageBufferBlockType(state), "fault_buffer"); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp index 6efac49c44f0..a349d02e80dc 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp @@ -39,7 +39,7 @@ namespace ShaderRecompiler state.module.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4u), result, own, zero, zero, zero); return; } - state.module.AddFunction(spv::OpGroupNonUniformBallot, TypeU32Vector(state, 4u), result, ConstantU32(state, spv::ScopeSubgroup), predicate); + state.module.AddSubgroupBallot(TypeU32Vector(state, 4u), result, ConstantU32(state, spv::ScopeSubgroup), predicate); } void EmitSubgroupBallotFindLsb(SpirvEmitterState& state, std::uint32_t result, std::uint32_t ballot) { diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvModule.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvModule.cpp index db387b77e34f..f10a1cdd9ea9 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvModule.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvModule.cpp @@ -114,12 +114,14 @@ std::vector SpirvModule::Finalize() const { void SpirvModule::BeginHelperFunction() { if (inHelperFunction) throw std::runtime_error("SpirvModule helper functions cannot nest"); std::swap(functionInstructions, helperFunctionInstructions); + std::swap(ballots, helperBallots); inHelperFunction = true; } void SpirvModule::EndHelperFunction() { if (!inHelperFunction) throw std::runtime_error("SpirvModule::EndHelperFunction without a helper function"); std::swap(functionInstructions, helperFunctionInstructions); + std::swap(ballots, helperBallots); inHelperFunction = false; } @@ -221,6 +223,33 @@ void SpirvModule::AddFunction(std::span words) { AppendInstructionWords(functionInstructions, words); } +void SpirvModule::AddSubgroupBallot(std::uint32_t type, std::uint32_t result, std::uint32_t scope, std::uint32_t predicate) { + if (ballots.scanned > functionInstructions.size()) throw std::runtime_error("SpirvModule function instructions shrank under a subgroup ballot"); + for (auto offset = ballots.scanned; offset < functionInstructions.size();) { + const auto wordCount = functionInstructions[offset] >> spv::WordCountShift; + if (wordCount == 0u || wordCount > functionInstructions.size() - offset) throw std::runtime_error("SpirvModule malformed function instruction before a subgroup ballot"); + switch (functionInstructions[offset] & spv::OpCodeMask) { + case spv::OpLabel: + case spv::OpFunction: + case spv::OpFunctionEnd: + case spv::OpFunctionCall: + case spv::OpDemoteToHelperInvocation: + ballots.results.clear(); + break; + default: + break; + } + offset += wordCount; + } + const auto [found, inserted] = ballots.results.try_emplace({type, scope, predicate}, result); + if (inserted) { + appendInstruction(functionInstructions, spv::OpGroupNonUniformBallot, type, result, scope, predicate); + } else { + appendInstruction(functionInstructions, spv::OpCopyObject, type, result, found->second); + } + ballots.scanned = functionInstructions.size(); +} + void SpirvModule::appendString(std::vector& words, const std::string& text) { const auto length = text.size(); const auto wordCount = (length + 1u + 3u) / 4u; diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp index b91213dfe930..7d33733e2aad 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp @@ -666,18 +666,16 @@ void DefineMeshOutputs(SpirvEmitterState& state) { throw std::runtime_error("unsupported mesh output kind"); } const auto type = MeshOutputType(state, output.kind); - output.variableId = MeshArray(state, spv::StorageClassOutput, type, output.kind == StageOutputKind::Layer ? mesh.maxPrimitives : mesh.maxVertices); + const auto layerBlock = output.kind == StageOutputKind::Layer ? state.module.DecoratedType(spv::OpTypeStruct, {{spv::OpMemberDecorate, {0u, spv::DecorationBuiltIn, spv::BuiltInLayer}}, {spv::OpMemberDecorate, {0u, spv::DecorationPerPrimitiveEXT}}, {spv::OpDecorate, {spv::DecorationBlock}}}, type) : 0u; + output.variableId = MeshArray(state, spv::StorageClassOutput, output.kind == StageOutputKind::Layer ? layerBlock : type, output.kind == StageOutputKind::Layer ? mesh.maxPrimitives : mesh.maxVertices); const bool shared = output.kind == StageOutputKind::Layer; output.meshDataVariable = MeshArray(state, shared ? spv::StorageClassWorkgroup : spv::StorageClassPrivate, type, shared ? mesh.maxVertices : state.laneCount); state.interfaceVariables.push_back(output.variableId); state.module.AddName(output.variableId, output.debugName); if (output.kind == StageOutputKind::Parameter) { state.module.AddAnnotation(spv::OpDecorate, output.variableId, spv::DecorationLocation, output.location); - } else { - state.module.AddAnnotation(spv::OpDecorate, output.variableId, spv::DecorationBuiltIn, output.kind == StageOutputKind::Layer ? spv::BuiltInLayer : spv::BuiltInPosition); - } - if (output.kind == StageOutputKind::Layer) { - state.module.AddAnnotation(spv::OpDecorate, output.variableId, spv::DecorationPerPrimitiveEXT); + } else if (output.kind != StageOutputKind::Layer) { + state.module.AddAnnotation(spv::OpDecorate, output.variableId, spv::DecorationBuiltIn, spv::BuiltInPosition); } if (output.kind == StageOutputKind::Position) { state.module.AddAnnotation(spv::OpDecorate, output.variableId, spv::DecorationInvariant); @@ -799,7 +797,8 @@ void EmitMeshEntryPoint(SpirvEmitterState& state) { continue; } const auto layer = MeshLoad(state, output.meshDataVariable, spv::StorageClassWorkgroup, TypeU32(state), vertex.at(state.inputInfo.vertex->mesh.provokingVertex)); - const auto pointer = MeshElement(state, output.variableId, spv::StorageClassOutput, TypeU32(state), index); + const auto pointer = state.module.AllocateId(); + state.module.AddFunction(spv::OpAccessChain, TypePointer(state, spv::StorageClassOutput, TypeU32(state)), pointer, output.variableId, index, ConstantU32(state, 0u)); state.module.AddFunction(spv::OpStore, pointer, layer); } }); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvOptimizer.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvOptimizer.cpp index 079100bb7c51..fffcf9541b6a 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvOptimizer.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvOptimizer.cpp @@ -2,7 +2,9 @@ #include #include #include +#include #include +#include #include #include @@ -56,6 +58,49 @@ bool HasLimitedUseTypes(std::span spirv) { return (int8 && !int8Capability) || (int16 && !int16Capability) || (float16 && !float16Capability); } +std::vector CollapseBlockBallots(std::span spirv) { + if (spirv.size() < 5) throw std::runtime_error("SPIRV-Tools: incomplete module header"); + const bool swapped = spirv[0] != spv::MagicNumber; + const auto native = [swapped](std::uint32_t word) { + return swapped ? (word >> 24u) | ((word >> 8u) & 0x0000ff00u) | + ((word << 8u) & 0x00ff0000u) | (word << 24u) + : word; + }; + std::vector collapsed(spirv.begin(), spirv.begin() + 5); + collapsed.reserve(spirv.size()); + std::map, std::uint32_t> ballots; + for (std::size_t offset = 5; offset < spirv.size();) { + const auto instruction = native(spirv[offset]); + const auto wordCount = instruction >> spv::WordCountShift; + const auto opcode = instruction & spv::OpCodeMask; + if (wordCount == 0 || wordCount > spirv.size() - offset || + (opcode == spv::OpGroupNonUniformBallot && wordCount != 5)) { + throw std::runtime_error("SPIRV-Tools: malformed instruction while collapsing block ballots"); + } + switch (opcode) { + case spv::OpLabel: + case spv::OpFunction: + case spv::OpFunctionEnd: + case spv::OpFunctionCall: + case spv::OpDemoteToHelperInvocation: + ballots.clear(); + break; + case spv::OpGroupNonUniformBallot: { + const auto [found, inserted] = ballots.try_emplace({spirv[offset + 1], spirv[offset + 3], spirv[offset + 4]}, spirv[offset + 2]); + if (!inserted) { + collapsed.insert(collapsed.end(), {native((4u << spv::WordCountShift) | spv::OpCopyObject), spirv[offset + 1], spirv[offset + 2], found->second}); + offset += wordCount; + continue; + } + break; + } + } + collapsed.insert(collapsed.end(), spirv.begin() + offset, spirv.begin() + offset + wordCount); + offset += wordCount; + } + return collapsed; +} + } std::vector ValidateAndOptimizeSpirv(std::span spirv, std::uint32_t vulkanVersion, std::uint32_t spirvVersion, bool allowOffsetTextureOperand, bool optimize, bool specialize) { @@ -92,11 +137,11 @@ std::vector ValidateAndOptimizeSpirv(std::span(spirv.begin(), spirv.end()); + if (!optimize) return CollapseBlockBallots(spirv); spvtools::Optimizer optimizer(environment); optimizer.SetMessageConsumer(consumer); static const char* mode = std::getenv("APS5_SPIRV_OPT"); - if (mode != nullptr && std::string(mode) == "none") return std::vector(spirv.begin(), spirv.end()); + if (mode != nullptr && std::string(mode) == "none") return CollapseBlockBallots(spirv); // The recompiler emits helpers (BDA lookup, fault reporting) as functions called from every memory // access. Exhaustive inlining multiplies module size by ~10x and optimization time by ~20x, so the // default pipeline keeps the performance passes that work per function and leaves inlining to the driver. @@ -174,6 +219,7 @@ std::vector ValidateAndOptimizeSpirv(std::span, 2> bits{readF64Bits(sourceAt(inst, 0u)), readF64Bits(sourceAt(inst, 1u))}; +bool TranslationContext::minMaxF64(const RdnaInstruction& inst, IrOpcode opcode) { + const std::array, 2> bits{flushF64Bits(readF64Bits(sourceAt(inst, 0u)), 0x40u), flushF64Bits(readF64Bits(sourceAt(inst, 1u)), 0x40u)}; const auto isNan = [&](const std::array& value) { IrValue& magnitude = ir.BitwiseAnd(value[1].Value(), ir.Constant(0x7fffffffu)); return &ir.LogicalOr(ir.UGreaterThan(magnitude, ir.Constant(0x7ff00000u)), ir.LogicalAnd(ir.IEqual(magnitude, ir.Constant(0x7ff00000u)), ir.INotEqual(value[0].Value(), ir.Constant(0u)))); }; IrValue& plain = ir.Emit(opcode, IrType::U64, {&ir.ConstructU64(bits[0][0].Value(), bits[0][1].Value()), &ir.ConstructU64(bits[1][0].Value(), bits[1][1].Value())}); + if (ieeeMode) { + writeF64Result(inst.destination, plain); + return true; + } IrValue* lhsNan = isNan(bits[0]); IrValue* rhsNan = isNan(bits[1]); std::array words{}; diff --git a/core/shader/recompiler/Translation/src/FloatInstructions.cpp b/core/shader/recompiler/Translation/src/FloatInstructions.cpp index 012e26a4cfc2..3ff93bb9309d 100644 --- a/core/shader/recompiler/Translation/src/FloatInstructions.cpp +++ b/core/shader/recompiler/Translation/src/FloatInstructions.cpp @@ -124,7 +124,8 @@ bool TranslationContext::float16Unary(const RdnaInstruction& inst, IrOpcode opco const RdnaOperand& operand = sourceAt(inst, 0u); const IrF32 argument = flushF16InputValue(readF16AsF32(operand)); const auto [result, invalid] = unaryFloatSpecials(opcode, argument, IrF32(ir.Emit(opcode, IrType::F32, {&argument.Value()}))); - IrU32 bits = packHalf2x16(applyF16ResultModifiers(inst.destination, clampF16Overflow(result, {&argument.Value()})), IrF32(ir.ConstantF32(0.0f))); + const IrF16 rounded(ir.Emit(IrOpcode::ConvertF16F32, IrType::F16, {&applyF16ResultModifiers(inst.destination, clampF16Overflow(result, {&argument.Value()})).Value()})); + IrU32 bits(ir.Emit(IrOpcode::ConvertU32U16, IrType::U32, {&ir.Emit(IrOpcode::BitCastU16F16, IrType::U16, {&rounded.Value()})})); if (opcode == IrOpcode::FPFract32) { bits = IrU32(ir.Select(ir.IEqual(bits.Value(), ir.Constant(0x3c00u)), ir.Constant(0x3bffu), bits.Value())); } diff --git a/core/shader/recompiler/Translation/src/VectorInstructions.cpp b/core/shader/recompiler/Translation/src/VectorInstructions.cpp index f045f3635aad..9c254b77c078 100644 --- a/core/shader/recompiler/Translation/src/VectorInstructions.cpp +++ b/core/shader/recompiler/Translation/src/VectorInstructions.cpp @@ -955,9 +955,9 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) { case RdnaOpcode::VFmaF64: return float64Operation(inst, IrOpcode::FPFma64); case RdnaOpcode::VMinF64: - return ieeeMode ? float64Operation(inst, IrOpcode::FPMin64) : nonIeeeMinMaxF64(inst, IrOpcode::FPMin64); + return minMaxF64(inst, IrOpcode::FPMin64); case RdnaOpcode::VMaxF64: - return ieeeMode ? float64Operation(inst, IrOpcode::FPMax64) : nonIeeeMinMaxF64(inst, IrOpcode::FPMax64); + return minMaxF64(inst, IrOpcode::FPMax64); case RdnaOpcode::VLdexpF64: return float64Unary(inst, IrOpcode::FPLdexp64); case RdnaOpcode::VTruncF64: diff --git a/core/shader/recompiler/tests/SpirvValidatorExit.cpp b/core/shader/recompiler/tests/SpirvValidatorExit.cpp new file mode 100644 index 000000000000..2bab5af5c967 --- /dev/null +++ b/core/shader/recompiler/tests/SpirvValidatorExit.cpp @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace { + +std::array, 3> modules; + +bool Validate(const std::vector& words) { + spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_2); + return tools.Validate(words); +} + +bool Check(const char* phase) { + const bool function = Validate(modules[0]); + const bool phi = Validate(modules[1]); + const bool invalidRejected = !Validate(modules[2]); + std::fprintf(stderr, "%s: function=%d phi=%d invalidRejected=%d\n", phase, function, phi, invalidRejected); + return function && phi && invalidRejected; +} + +void ValidateAtExit() { + if (!Check("exit validation")) std::_Exit(EXIT_FAILURE); +} + +} + +int main() { + if (std::atexit(ValidateAtExit) != 0) return EXIT_FAILURE; + const std::string prefix = R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +%void = OpTypeVoid +%fn = OpTypeFunction %void +%bool = OpTypeBool +%true = OpConstantTrue %bool +%uint = OpTypeInt 32 0 +%one = OpConstant %uint 1 +%two = OpConstant %uint 2 +)"; + const std::string function = "%main = OpFunction %void None %fn\n%entry = OpLabel\n"; + const std::string tail = "OpReturn\nOpFunctionEnd\n"; + const std::string phi = R"(OpSelectionMerge %merge None +OpBranchConditional %true %left %right +%left = OpLabel +OpBranch %merge +%right = OpLabel +OpBranch %merge +%merge = OpLabel +%value = OpPhi %uint %one %left %two %right +)"; + spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_2); + if (!tools.Assemble(prefix + function + tail, &modules[0]) || + !tools.Assemble(prefix + function + phi + tail, &modules[1]) || + !tools.Assemble(prefix + "%main = OpFunction %void None %void\n%entry = OpLabel\n" + tail, &modules[2])) return EXIT_FAILURE; + return Check("warm validation") ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/docs/dev/ARCHITECTURE.md b/docs/dev/ARCHITECTURE.md index 96b44b2fdb27..d034ea364755 100644 --- a/docs/dev/ARCHITECTURE.md +++ b/docs/dev/ARCHITECTURE.md @@ -93,6 +93,7 @@ flowchart LR - The shader registry maps a code address to every header registered for that code, in registration order. Static, graphics and rectangle ABI resolution pick the header at the bound address (or one equal to it, `SameHeader`, which also takes a hull header fused by `sceAgcFuseShaderHalves` (type 3) as the registered back half (type 7) it copies, the fused registers aside; graphics ABI resolution then uses the fused header's shader registers); draws and dispatches pick the program by the stage's binary type (`RegisteredProgram`), and a front entry matching both type 2 and type 4 throws. Within a type the last registration wins; re-registering an identical earlier header makes its snapshot current again without preparing it again, and changed code drops the older headers. - The snapshot is published as pending. `SourceHandleFor`, `InvocationFor` and `ShaderPreparationTransaction::Edit` / `Read` wait for the pending job before reading `PreparedShaders`, and rethrow its failure. `APS5_SYNC_SHADER_PREPARE=1` prepares on the registering thread instead. - A draw or dispatch whose state matches no artifact prepared at registration (a title that builds its static ABI state itself, without `sceAgcDriverResolve*Abi`) gets one prepared at use by `SourceHandleFor` or `InvocationFor`, logged once per artifact and kept in the snapshot for the next use. +- A stage that `sceAgcDriverResolve*Abi` cannot prepare for the state the title creates its pipeline with (a compute shader that calls user-data pointers, a descriptor the resource plan rejects) is left to its first use like a stage deferred at registration: `ResolveShaderAbi` and `PrepareGraphicsStages` catch the failure inside their own preparation transaction, so the `ShaderPreparationScope` the title's call runs in still commits, log it once per shader, mark the shader deferred and record the ABI key, so a repeated call prepares nothing; `ResolvePreparedGraphics` skips a deferred stage's rectangles until a draw has prepared it. A draw or dispatch that cannot prepare the stage either fails as before. Driver failures, `ProcessShutdown` and a failure stored by the shader's registration preparation still propagate from the ABI call. - A compute shader whose `s_swappc_b64` calls a user-data pointer cannot be linked at registration, because the callee's address is only known at dispatch. `InvocationFor` in [`ShaderRegistry.cpp`](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) reads each callee from guest memory, appends one copy per call after the caller (`LinkUserDataCalls`) and prepares that linked code, so a different callee gets its own prepared entry. - A draw's `Graphics::Pipeline` comes from `CachedPipeline`. When the device has `VK_EXT_graphics_pipeline_library` with fast linking, `VK_EXT_extended_dynamic_state` and `VK_KHR_dynamic_rendering`, draws render with `vkCmdBeginRenderingKHR` instead of render passes and the pipeline is linked from four libraries (vertex input, pre-rasterization shaders, fragment shader, fragment output) kept per device in [`PipelineLibrary.cpp`](../../core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp). A library built for one pipeline is reused by the next: another blend, vertex layout or target format builds only that part (the shader libraries name no attachment format), and cull mode, front face, depth and stencil state are dynamic, so changing them only links. The fast link is what the draw waits for; a background thread then links the same libraries with link-time optimization and the pipeline binds that one from the next draw on. Push constants then use a 256-byte block with a 128-byte slot per side: the pre-rasterization stages stack from byte 0, the pixel shader always starts at byte 128 (`StagePushOffset`), so its library does not depend on how much push data its vertex shader uses. Pipelines with per-face depth bias, a separate alpha blend into an 8_8 target with the alternate component order (both draw twice), rect lists, geometry shaders or a stage without a variant id are created whole, still with dynamic rendering; devices without the extensions (or with `APS5_NO_GPL=1`) keep render passes and whole pipelines. - A draw whose `PA_SC_AA_CONFIG` selects 2, 4 or 8 samples renders into host-only multisampled images. [`MultisampleTarget.cpp`](../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp) keeps one image per device, surface address, format, extent and sample count (`MultisampleTargetView`); the pipeline, render pass and library keys carry `State::samples`, and a multisampled depth target is a multisampled depth surface. The guest sample planes and FMASK are never read or written, so the registry also guards the target's guest range: a CPU read (`GuestMemory::SetReadGuard`), a DMA copy, and a sampled or storage image read of it throw. A `CB_COLOR_CONTROL` resolve mode over a multisampled CB0 runs as a render pass whose resolve attachment is CB1's resident image (`ResolveMultisampleTarget`). A zero fill of the target's CMASK (`NoteColorMetadataFill`, from fill kernels and CP DMA fills) clears the image at its next use. diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index bf7da58a3387..d73506d651b6 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -799,5 +799,4 @@ Throughout the project, every function at every stage either **does exactly what - [sceKernelVirtualQuery](../../core/libs/prx/libkernel/DirectMemory/Export.cpp) returns [VirtualQueryInfo](../../core/libs/SceTypes.hpp) with a one-byte flag allocation unit so Windows and SysV hosts return the same 72-byte representation, with the name at byte 33 and GPU mask at byte 65. This preserves the existing SysV representation; the console offsets beyond the address, protection and size fields observed in Astro Bot's AGC calls are not hardware-measured. Size and offset assertions plus a raw-byte named-mapping regression guard the host ABI. - [kqueue and kevent](../../core/libs/prx/libkernel/Equeue/src/Kqueue.cpp) (libkernel) follow FreeBSD 11's [`kern_event.c`](https://github.com/freebsd/freebsd-src/blob/34bd34750187bae02a0086402c78c031c59dd8b3/sys/kern/kern_event.c) (`kqueue_kevent`, `kqueue_register`, `kqueue_scan`, the timer and user filters) over the event queue; nothing is measured on a console. The descriptor is a host null device registered as the kqueue: `close` and `sceKernelClose` close the queue and `dup`/`dup2` of it throw, but `read`, `write` and `fstat` act on the null device instead of failing with `EOPNOTSUPP` or reporting `S_IFIFO`. Read and write events are supported on guest sockets only, plus the timer, user and [vnode](../../core/libs/prx/libkernel/Equeue/src/FileEvent.cpp) filters; other filters, `NOTE_LOWAT`, `EV_CLEAR` on a socket, `EVFILT_READ` on another kqueue, a `NOTE_NSECONDS` period above one second with a fraction (11.0 converts the fraction with the microsecond macro) and any change to an existing timer (11.0 keeps the armed expiry and applies the new period after it, 11.4's `filt_timertouch` restarts it) throw. `EV_DISABLE`, `EV_DISPATCH` and `EV_FORCEONESHOT` follow kern_event.c: a disabled knote stays registered, stops polling or firing and is not reported, a trigger raised while it is disabled is kept and reported after `EV_ENABLE`, where `knote` would drop it, `EV_DISPATCH` reports once and then disables the knote, and `EV_FORCEONESHOT` is ignored on a new knote while on an existing one it sets `EV_ONESHOT` and fires the knote; a negative `nevents` fails with `EINVAL`. A new user event keeps its `fflags` unmasked and is not triggered by `NOTE_TRIGGER`, because `filt_userattach` reads the already cleared `kn_fflags`. The timer limit `kq_calloutmax` (`ENOMEM`) is not enforced, and closing a kqueue wakes a thread waiting on it in `kevent` with `EBADF`, where `kqueue_drain` makes the close wait for that thread. A level-triggered event is reported once per call and moved behind the other events, as in `kqueue_scan`; the `sceKernel*Equeue` calls keep their own collection loop - [NGS2 render buffer capacity](../../core/libs/prx/libSceNgs2.native/src/Render.cpp) is treated as available storage for one configured grain. PPSA01880 passes 303104 bytes for 256 stereo float frames (2048 bytes); rendering writes or clears only those frames and preserves the remaining capacity. Non-null silent outputs now undergo the same supported-format and minimum-size checks as audible outputs, with channel counts limited to 1 through 8. Unused null outputs remain ignored. The console's handling of surplus capacity and invalid silent output descriptors has not been measured; invalid descriptors throw rather than return guessed SDK errors. - - +- Guest `TZ` changes reach libc `tzset`, `localtime`, `localtime_s`, `ctime`, `mktime` and `strftime` by rewriting the host process `TZ`, so they also shift every other host consumer of `TZ` (for example libSceRtc local time) once a guest changes it. The zone switch and the conversion run under one lock, but host code outside libc does not take it. The zone grammar is the host C runtime's, not FreeBSD's: Windows accepts only the `std offset [dst]` form and treats an empty guest `TZ` as `UTC0`, a `:Europe/Berlin` style name is read as a zone abbreviation there and yields a wrong zone name (for example `rop`), and zone file names are honoured on Linux only. A guest `TZ` that the guest environment accepts but the host rejects (on Windows, longer than the host environment limit) makes the time functions throw `std::runtime_error`. `tm_zone` of a `tm` computed before a `TZ` change can point at storage that now holds the new zone name, so its `%Z` output is stale or changed. diff --git a/docs/dev/debt/libSceAgc.md b/docs/dev/debt/libSceAgc.md index 985c36c8bb1e..75a88e3386b2 100644 --- a/docs/dev/debt/libSceAgc.md +++ b/docs/dev/debt/libSceAgc.md @@ -24,6 +24,8 @@ - [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 - [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 - [sceAgcAsyncCondExecPatchSetCommandAddress, sceAgcAsyncCondExecPatchSetEnd](../../../core/libs/prx/libSceAgc/Patch/src/CondExec.cpp) (libSceAgc) - no reference has a body for them; they are assumed to patch the COND_EXEC packet like `sceAgcCondExecPatchSetCommandAddress` and `sceAgcCondExecPatchSetEnd`, since `sceAgcAcbCondExec` writes the same packet as `sceAgcDcbCondExec`, as `sceAgcAsyncRewindPatchSetRewindState` does for REWIND - [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 diff --git a/docs/dev/debt/libSceAgcDriver.md b/docs/dev/debt/libSceAgcDriver.md index ab62e0cbe0ec..66a9ea56429a 100644 --- a/docs/dev/debt/libSceAgcDriver.md +++ b/docs/dev/debt/libSceAgcDriver.md @@ -7,6 +7,10 @@ ### Unknown function info - [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. +- [sceAgcDriverSubmitCommandBuffer, sceAgcDriverSubmitMultiCommandBuffers](../../../core/libs/prx/libSceAgcDriver/Submit/src/CommandBuffer.cpp) (libSceAgcDriver) - `queue_context` is treated as a pointer to context whose queue identifier is stored 4 bytes in, matching KytyPS5 (`src/libs/agc.cpp` `AgcDriverSubmitCommandBuffer` / `get_driver_queue`). A null context returns `EINVAL` (22). Whether the console validates other context fields before queue dispatch is unverified + +- [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. + - [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. @@ -31,6 +35,7 @@ - [Strided buffer overhang](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) - a strided buffer descriptor that ends less than one stride past the end of the readable registered allocation holding its base is bound only up to that allocation's end, even when another readable allocation starts right there. This assumes the bytes past the end are the rounded-up `num_records` of the last record (RADV rounds it up), which no fetch inside the buffer reads; a guest that really keeps a vertex split across two adjacent allocations would read zeros for the cut tail. Taken from the RetroArch Dolphin core case in #2912, not measured on hardware. - [Buffer descriptor CACHE_SWIZZLE hint](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) - bit 30 of word 1 in a buffer resource descriptor (`words[1] & 0x40000000u`) is normalized to zero when binding guest buffers in `ShaderResources::addGuestBuffer`. On GFX10 (gfx1013), bit 30 of V# word 1 is `CACHE_SWIZZLE` (Mesa's `gfx10-rsrc.json`: STRIDE [29:16], CACHE_SWIZZLE [30], SWIZZLE_ENABLE [31]), a cache-bank hint with no effect on the address or data, which is dropped here. - [Buffer descriptors past the range limit](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) - a buffer V# whose size is over `maxStorageBufferRange` is bound only up to the end of the readable registered allocation holding its base when that allocation ends before the limit, and as the empty placeholder buffer, like a null V#, when no registered allocation holds its base; a V# over an allocation that reaches the limit still throws. Titles leave such words in descriptor slots their shaders do not reach on the path taken (PPSA10528 binds `c8010002 2c6bec20 c0053c00 04000013`, about 33 TB from an address above the user address space). What the console returns for an access past the bound range is not known; here the device's robust buffer access decides. Follows the review of #3202, not measured on hardware. +- [Vertex fetch overhang](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) - a zero-padded vertex fetch whose valid range (`num_records * stride`) ends less than one stride past the end of the readable memory at its base copies only the readable bytes and reads the rest as zeros, instead of failing the draw; a longer overhang still fails. This extends the strided buffer overhang rule above (the last record is the rounded-up `num_records`) to the staging copy and is not measured on hardware. PPSA26223 draws a fetch of 14858 records of 44 bytes that ends 8 bytes past the end of the 0xa0000-byte mapping holding it - [Zero-stride vertex fetch range](../../../core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp) (libSceAgcDriver) - for a stride-0 V# with OOB_SELECT 2 the whole fetch reads memory unless NUM_RECORDS is 0; with OOB_SELECT 3 it reads zero when NUM_RECORDS is 0 and throws when the fetch only partly fits. Rules taken from the GFX10 OOB_SELECT table in Mesa [ac_descriptors.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/amd/common/ac_descriptors.c), not measured on hardware. Whether OOB_SELECT 3 checks each dword of a multi-dword fetch separately is unknown. OOB_SELECT 0 and 1 keep the structured record checks. - [Resource registration](../../../core/libs/prx/libSceAgcDriver/Resource/src/Registration.cpp) (libSceAgcDriver) - registration always fails with `RESOURCE_REGISTRATION_UNAVAILABLE`, so the owner and resource queries, `sceAgcDriverSetResourceUserData`, `sceAgcDriverFindResourcesPublic` and `sceAgcDriverUnregisterAllResourcesForOwner` return the same error without reading their arguments. Their parameter lists follow the libSceGnmDriver counterparts and are unverified. `sceAgcDriverGetResourceRegistrationMaxNameLength` writes 0xfc and returns 0, as [prosper](https://github.com/mattias800/prosper) does from a title's registration configuration; the `uint32_t*` parameter matches the SDK wrapper `sce::Agc::ResourceRegistration::getMaxNameLength(unsigned int*)`. sharpemu writes 256 and KytyPS5 returns `RESOURCE_REGISTRATION_UNAVAILABLE`, so the value is unverified; a null output throws - [sceAgcDriverRegisterMultipleResources](../../../core/libs/prx/libSceAgcDriver/Resource/src/Registration.cpp) uses the existing unavailable-registration state and leaves caller memory untouched. Its four arguments (uint32 handle output, uint32 owner, descriptor pointer, uint32 count) follow the original PPSA34502 / 01.010.000 codzm call. The caller builds 40-byte descriptors and explicitly accepts 0x8A6C9018 with an invalid output handle. Enabled registration and console argument-validation precedence are unverified. @@ -103,8 +108,9 @@ - [WRITE_DATA to thread-trace user-data registers](../../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (libSceAgcDriver) updates the queue's UCONFIG values for register destinations `0xc340..0xc347`, including repeated writes to one address; these are not guest-memory labels. The range follows AMD's GFX10 `SQ_THREAD_TRACE_USERDATA_0..7` definitions in [the Linux register header](https://github.com/torvalds/linux/blob/master/drivers/gpu/drm/amd/include/asic_reg/gc/gc_10_1_0_offset.h) and the packets emitted by PPSA21564's bundled AGC driver before its EOP flip. Other register destinations throw. GPU thread-trace capture and its user-data stream are not emitted; only the final register state is retained. Console trace behavior is unverified. - [WRITE_DATA with DST_SEL 3](../../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (libSceAgcDriver) writes guest memory like DST_SEL 5. AMD's GFX10 header names 3 the GDS, but Killzone: Liberation (PPSA11142) writes its frame fence with `sceAgcDcbWriteData(dst 6, cache policy 3, write confirm)`, which the SPRX-confirmed encoding ([OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/main/src/cb_builders.c)) turns into DST_SEL 3 with a 40-bit label address in the title's own memory that `RELEASE_MEM` and `WAIT_REG_MEM` then use; the console must write that label or the title would wait on it forever. Whether the console routes it through the GDS or a cache is unknown - [SET_*_REG_INDIRECT](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp) (libSceAgcDriver) reads its register list when the command buffer is submitted, with the packets. The console's graphics PFP reads it when it reaches the packet, so a list written after the submission (by the CPU, or by GPU work ahead of the packet behind PFP_SYNC_ME) is read here before that write. -- The [shader float mode](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (`FLOAT_MODE`, `DX10_CLAMP`, `IEEE_MODE` and `FP16_OVFL` of `COMPUTE_PGM_RSRC1` and `SPI_SHADER_PGM_RSRC1_PS/GS/HS`, bit positions from Mesa's `gfx10.json` and `gfx103.json`) is decoded into `GuestContext::floatMode` and is part of the shader cache keys and the serialized request (#981). `IEEE_MODE` is read by `v_min_f32`, `v_max_f32`, `v_min3_f32`, `v_max3_f32` and `v_med3_f32`, their f16 forms, `v_min_f64` and `v_max_f64`, `IEEE_MODE` and the `FLOAT_MODE` output denormal bits by the output modifier, `DX10_CLAMP` by the clamp modifier, `FP16_OVFL` by every instruction with an f16 result (see its entry), and the whole mode by the 16-bit interpolation instructions (see their entries). With IEEE=1 a signaling NaN operand returns that NaN quieted (the first operand checked first, min3/max3 as two nested pairs, med3 as min3 when any operand is NaN), measured on RDNA2 (gfx1035) over every combination of ±0, ±1, ±inf, denormals, quiet and signaling NaNs with f32 denormals kept and flushed. IEEE=0 and unknown modes keep the previous f32 lowering, which matches IEEE=0. The f16 forms choose their NaN results on the bits: a NaN operand returns the other operand unchanged (src0 when both are NaN), and with IEEE=1 a signaling NaN operand returns that NaN quieted, in the same order as f32. `v_min_f64` and `v_max_f64` quiet a signaling NaN operand the same way with IEEE=1; with IEEE=0 they return src0 unchanged when src1 is NaN, otherwise src1 when src0 is NaN. Both were measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1 with f32 denormals kept and flushed and f16/f64 denormals kept; RDNA2 has no f64 min3, max3 or med3. Unknown modes follow IEEE=0. `v_min_f16` and `v_max_f16` flush each denormal input to its signed zero when bit 0 of the f16/f64 denormal field is clear (0 and 2); fields 1 and 3 preserve inputs, even when output flushing is enabled. Measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1, including signed zeros, NaNs and neg/abs modifiers. Unknown modes keep f16 denormals. With f16/f64 denormals flushed the hardware also flushes denormal inputs of f64 min/max, which those lowerings still do not. NaN quieting in f16 arithmetic (the unary instructions and the f32 arithmetic follow it, see below) and `FLOAT_MODE` (f32 denormals flushed before the min/max compare in Astro's Playroom's 0xc0) still follow a single mode, as the entries below record. The mode comes from the registered shader header's `sh_registers`, the same source at registration and at draw/dispatch so the prepared artifact keys match. A title that rewrites RSRC1 at run time, or a raw compute program with no header, compiles with that mode or none. +- The [shader float mode](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (`FLOAT_MODE`, `DX10_CLAMP`, `IEEE_MODE` and `FP16_OVFL` of `COMPUTE_PGM_RSRC1` and `SPI_SHADER_PGM_RSRC1_PS/GS/HS`, bit positions from Mesa's `gfx10.json` and `gfx103.json`) is decoded into `GuestContext::floatMode` and is part of the shader cache keys and the serialized request (#981). `IEEE_MODE` is read by `v_min_f32`, `v_max_f32`, `v_min3_f32`, `v_max3_f32` and `v_med3_f32`, their f16 forms, `v_min_f64` and `v_max_f64`, `IEEE_MODE` and the `FLOAT_MODE` output denormal bits by the output modifier, `DX10_CLAMP` by the clamp modifier, `FP16_OVFL` by every instruction with an f16 result (see its entry), and the whole mode by the 16-bit interpolation instructions (see their entries). With IEEE=1 a signaling NaN operand returns that NaN quieted (the first operand checked first, min3/max3 as two nested pairs, med3 as min3 when any operand is NaN), measured on RDNA2 (gfx1035) over every combination of ±0, ±1, ±inf, denormals, quiet and signaling NaNs with f32 denormals kept and flushed. IEEE=0 and unknown modes keep the previous f32 lowering, which matches IEEE=0. The f16 forms choose their NaN results on the bits: a NaN operand returns the other operand unchanged (src0 when both are NaN), and with IEEE=1 a signaling NaN operand returns that NaN quieted, in the same order as f32. `v_min_f64` and `v_max_f64` quiet a signaling NaN operand the same way with IEEE=1; with IEEE=0 they return src0 unchanged when src1 is NaN, otherwise src1 when src0 is NaN. Both were measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1 with f32 denormals kept and flushed and f16/f64 denormals kept; RDNA2 has no f64 min3, max3 or med3. Unknown modes follow IEEE=0. `v_min_f16` and `v_max_f16` flush each denormal input to its signed zero when bit 0 of the f16/f64 denormal field is clear (0 and 2); fields 1 and 3 preserve inputs, even when output flushing is enabled. Measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1, including signed zeros, NaNs and neg/abs modifiers. Unknown modes keep f16 denormals. `v_min_f64` and `v_max_f64` flush subnormal inputs to signed zero when bit 0 of the f16/f64 denormal field is clear (0 and 2); fields 1 and 3, and an unknown mode, preserve inputs. The fully flushed and preserved f64 behavior was [measured on gfx1036](https://github.com/boykopovar/AnyPS5/pull/2036); split-mode selection follows [LLVM's denormal-mode enumeration](https://llvm.org/docs/AMDGPUUsage.html#floating-point-denorm-mode-enumeration-values). Other f64 arithmetic does not model these denormal controls. NaN quieting in f16 arithmetic (the unary instructions and the f32 arithmetic follow it, see below) and `FLOAT_MODE` (f32 denormals flushed before the min/max compare in Astro's Playroom's 0xc0) still follow a single mode, as the entries below record. The mode comes from the registered shader header's `sh_registers`, the same source at registration and at draw/dispatch so the prepared artifact keys match. A title that rewrites RSRC1 at run time, or a raw compute program with no header, compiles with that mode or none. - A [WAIT_REG_MEM](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp) (libSceAgcDriver) that a compute queue passes on another queue's recorded label store, before the GPU has executed it, is landed before the queue's next dispatch, draw, COND_EXEC or predicated packet reads guest memory: the worker polls until memory satisfies the wait or no unfinished batch writes the label. The graphics queue (queue 0) is exempt, as landing its waits costs PPSA01341 about a quarter of its frame rate; a title that fills memory a graphics command buffer reads after it submits the buffer behind such a wait reads it early there. `APS5_LAND_WAITS_ALL_QUEUES=1` lands the graphics queue's waits too. +- [CPU reads behind an address-based build](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp) (libSceAgcDriver) - an address-based store (FLAT/GLOBAL, or through a descriptor the recompiler cannot resolve to a static buffer) is not recorded as a pending write: its batch holds a lease on the registered allocations instead, and the flush hook does not wait for it. A DISPATCH_INDIRECT whose arguments are read on the CPU first finishes the batches recorded with a lease (`Graphics::FinishLeaseWork`). These reads do not, so they can see guest memory from before such a build ran: the indirect draw arguments and draw count of a draw resolved on the CPU (`Pm4::ReadDrawArguments`, `Pm4::ReadDrawCount`), the count record of the [copy kernel](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferCopy.cpp) HLE (guarded only by its shape check), and the checks in [Submission.cpp](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp) that decide whether a queue awaits a label only the title writes (`awaitsTitle`, `orderReleased`). No test covers them and no title has been seen to read such memory there. - The [dispatches that follow a compute queue's wait](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp) (libSceAgcDriver), up to the next wait that memory does not satisfy, a packet that writes guest memory other than a label, or a conditional packet, are resolved (memory captured, shader recompiled) when the wait passes, as the CP reads them right after it; the graphics queue is exempt (`APS5_GROUP_CAPTURE_ALL_QUEUES=1` includes it). The device still reads guest memory when the dispatch is issued; when that memory no longer matches the capture, the dispatch is resolved again from current memory. - [PA_CL_CLIP_CNTL](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - `DX_LINEAR_ATTR_CLIP_ENA` (bit 24) clear is drawn like bit 24 set, with Vulkan's clipping of `NoPerspective` outputs; what a clear bit changes on the console is undocumented. - [10_11_11 unorm color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) render into R32_UINT attachments: the pixel shader packs each export, rounded to nearest even like format-30 image stores, which is assumed to match the CB's conversion. @@ -115,7 +121,7 @@ - [DB_SHADER_CONTROL.CONSERVATIVE_Z_EXPORT](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp) (libSceAgcDriver) becomes `DepthLess`/`DepthGreater`. For a pixel shader that breaks the promise, the host driver decides whether its interpolated Z is depth-tested before shading; what the console does then was not measured. - [DB_SHADER_CONTROL.STENCIL_TEST_VAL_EXPORT_ENABLE](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) becomes `FragStencilRefEXT` when available, taken from the second MRTZ channel of the 32_GR and 32_ABGR formats as Mesa's AMD drivers write it ([`emit_ps_mrtz_export`](https://gitlab.freedesktop.org/mesa/mesa/-/blob/3e32b0e1f132231b7a972c1b0b5b06c7e16cf8b9/src/amd/common/nir/ac_nir_lower_ps_late.c#L194)); the console was not measured. The host value is the test value of both faces and what `REPLACE_TEST` writes, so a stencil operation that writes all ones or the operation value throws with it, as do a stencil clear draw, the UINT16_ABGR layout and `STENCIL_OP_VAL_EXPORT_ENABLE`. A device without the extension uses the bounded replay described above. On such a device a draw first drops the export when the reference cannot change its result (`StencilReferenceUnused`: no stencil test or attachment, or on both faces a compare that ignores it and no reachable `REPLACE` through a non-zero write mask), and its pixel shader is then compiled at its first draw, not when its ABI is resolved. A device with the extension keeps the export, so each such shader keeps one pixel variant. - [DB_SHADER_CONTROL.DEPTH_BEFORE_SHADER](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp) (libSceAgcDriver) becomes `EarlyFragmentTests`, and so does `Z_ORDER` `EARLY_Z_THEN_LATE_Z` or `EARLY_Z_THEN_RE_Z` without kill, Z export, mask export or stencil export; with alpha-to-coverage the tests stay late. The meaning of the bit and of `Z_ORDER` 3 follows Mesa (`src/amd/registers/gfx10.json`; RADV sets the bit for `early_fragment_tests` in `src/amd/vulkan/radv_shader.c`). What the console does with `DEPTH_BEFORE_SHADER` and a Z export, a mask export, a stencil export or alpha-to-coverage together was not measured. -- [PA_SC_CONSERVATIVE_RASTERIZATION_CNTL](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - 0x6001 uses Vulkan overestimation on devices that add at most 1/256 pixel and keep degenerate triangles; the console's uncertainty region and degenerate handling are unmeasured. A value with `OVER_RAST_ENABLE` and `UNDER_RAST_ENABLE` clear is decoded as disabled even when `UNCERTAINTY_REGION_MODE` or the `OUTER_`/`INNER_UNCERTAINTY_EDGERULE_OVERRIDE` bits (19:16) are set, as PPSA01474 writes 0xe6000; that these fields have no effect without over- or underestimation is assumed from their names, not measured. +- [PA_SC_CONSERVATIVE_RASTERIZATION_CNTL](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - 0x6001 uses Vulkan overestimation on devices that add at most 1/256 pixel and keep degenerate triangles; the console's uncertainty region and degenerate handling are unmeasured. A value with `OVER_RAST_ENABLE` and `UNDER_RAST_ENABLE` clear is decoded as disabled even when `UNCERTAINTY_REGION_MODE` or the `OUTER_`/`INNER_UNCERTAINTY_EDGERULE_OVERRIDE` bits (19:16) are set, as PPSA01474 writes 0xe6000; that these fields have no effect without over- or underestimation is assumed from their names, not measured. With `PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES` = 0 the `PERSP_CENTROID` and `LINEAR_CENTROID` inputs get the pixel-centre barycentrics, as without conservative rasterization ([shader debt](shader.md)): the one rasterized sample sits at the pixel centre, so every centroid the rasterizer could pick for an overestimated pixel is that point; that it does not move the centroid towards the primitive when the sample is uncovered is inferred, not measured. With more rasterized samples, and when a single-sample draw is raised to a multisampled color target, centroid inputs throw. - [Descriptor pool chain](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) - each pool holds two storage buffer descriptors more than a set can take, as a workaround: RADV (Mesa 26.0 and later) lets a `FREE_DESCRIPTOR_SET` pool place a set up to 32 bytes past the pool's buffer ([`util_vma_heap_init`](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/amd/vulkan/radv_descriptor_pool.c) is given `bo_size + RADV_POOL_HEAP_OFFSET` as a size), and with a page-multiple pool size those bytes are an unmapped page - [CMASK fast clears](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) - each fast-cleared CMASK tile (code 0) is stored as `CB_COLOR_CLEAR_WORD` into its 8x8 texels at the first draw into the target or at its fast-clear eliminate or DCC decompress pass, not as the color block writes the tile, and the CMASK is then stored as expanded (0xF): a texture or copy read of the target after that draw and before the title's eliminate sees the clear value in tiles no draw wrote, where the console keeps their previous bytes. Tile addresses follow the pipe-aligned SW_64KB_Z_X CMASK equation for 16 pipes; other CMASK swizzle modes, a cleared tile of a DCC target and a fast clear of a 3D or array color target throw; base mip 0 of a 2D mipmapped color target is supported (offset follows AMD Addrlib `Gfx10Lib::HwlComputeCmaskInfo` reverse mip-chain ordering preceding base mip 0; mipmapped targets with 8 or 16 bpp throw until `dsMipmapHtileFix` mip-tail shrinkage is modelled). A tile with a code other than 0 and 0xF keeps its texels like an expanded tile: PPSA18391 (engine: Clickteam Fusion) fast clears a target whose CMASK it never initialized, so some of its nibbles hold leftover values such as 1 and 10; what the console does with such codes on a single-sample target is unverified on hardware. Single-sample views with `viewMip > 0` skip CMASK fast-clear decoding since CMASK metadata only covers base mip 0 on AMD hardware (Mesa `si_state.c` similarly clears `FAST_CLEAR` for level > 0). - [HTILE depth fast clears](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) - HTILE is not modeled. Only RADV's `clear_htile_mask` compute kernel (17 GFX10 words, 64x1x1 groups) is recognized, and only when it leaves ZMASK at 0 (`user[4]` and `user[5]` low nibbles both 0); the dispatch still runs, and the host depth image is cleared to `DB_DEPTH_CLEAR` the next time the surface with that `DB_HTILE_DATA_BASE` address is bound. The address match is exact, so a clear of a different HTILE range does not hit. HTILE initialization, the stencil-only update, and fast clears made by the SDK's own paths, CP DMA fills or other kernels are not covered and leave the depth image as it was. The kernel words are the ones the PR author saw RADV submit; they are not compared with Mesa's source. A second kernel, the 9-word dword fill PPSA01802 (Kena: Bridge of Spirits) clears its depth HTILE with (64x1x1 groups, one `buffer_store_format_x` per thread through the stride-4 V# in `user[0..3]`, the value read by `s_buffer_load_dword` through the V# in `user[4..7]`), is taken as a fill of that range when the store format is 32_UINT, 32_SINT or 32_FLOAT and the value V# covers the dword (NUM_RECORDS times its stride, or NUM_RECORDS bytes when the stride is 0) ([`MatchMemoryValueFill`](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp), then the whole-HTILE and ZMask rules of `NoteDepthMetadataFill`). The value is read from guest memory when the dispatch is decoded, so a value that earlier GPU work in the same submission has not written back yet is not seen. When the [fill HLE](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferFill.cpp) takes the dispatch instead (an output descriptor of one 32-bit value per element, a value descriptor that covers the loaded dword, a 16-byte aligned range), the value is read again when the fill is recorded, after the labels queued before it, and the HTILE fill is noted with that value. diff --git a/docs/dev/debt/libSceAjm.native.md b/docs/dev/debt/libSceAjm.native.md index bcdfde9b1e48..ca2298edef0d 100644 --- a/docs/dev/debt/libSceAjm.native.md +++ b/docs/dev/debt/libSceAjm.native.md @@ -8,7 +8,7 @@ - [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: the layer field is not checked and MPEG-2.5 stops at 64 kbps; the original file length (`parse_ofl`) layout (Xing/Info + LAME tag, VBRI, 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 - [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 decoders other than MP3, ATRAC9, M4AAC and Opus](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - decoding throws for every other codec id. Broken Sword - Shadow of the Templars: Reforged (PPSA21480) stops at codec 23 (`AJM: decoding codec 23 is not implemented`). The ids known here are MP3 0, ATRAC9 1, M4AAC 2 and Opus 24; id 23 is not among them and which codec it is, is unknown. +- [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`; 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 is assumed to clear the context as sceAjmBatchJobClearContext does diff --git a/docs/dev/debt/libSceAppInstUtil.md b/docs/dev/debt/libSceAppInstUtil.md new file mode 100644 index 000000000000..ecf5fb2a82b1 --- /dev/null +++ b/docs/dev/debt/libSceAppInstUtil.md @@ -0,0 +1,5 @@ +# libSceAppInstUtil technical debt + +### Unknown function info + +- [sceAppInstUtilAppIsInUpdating, sceAppInstUtilAppGetSize, sceAppInstUtilAppInstallPkg, sceAppInstUtilAppPrepareOverwritePkg, sceAppInstUtilAppRecoverApp, sceAppInstUtilAppUnInstallAddcont, sceAppInstUtilAppUnInstallPat, sceAppInstUtilAppUnInstallTheme, sceAppInstUtilAppUnInstallTypes, sceAppInstUtilGetInstallProgressInfo, sceAppInstUtilGetInstallProgress and sceAppInstUtilGetPrimaryAppSlot](../../../core/libs/prx/libSceAppInstUtil/Export.cpp) (libSceAppInstUtil) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAppInstUtil.c), but no credible prototype was found. They are declared without parameters only to resolve the known imports; this is not an ABI claim and callers must not call them until a prototype is verified. The other seven declarations follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/AppInstUtil/AppInstUtil.cs) diff --git a/docs/dev/debt/libSceAudioOut.md b/docs/dev/debt/libSceAudioOut.md index 01bb47513905..a480c3077bf7 100644 --- a/docs/dev/debt/libSceAudioOut.md +++ b/docs/dev/debt/libSceAudioOut.md @@ -18,6 +18,8 @@ - [sceAudioOut2PortCreate](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) (libSceAudioOut) - 12 channels are assumed to be L R C LFE Ls Rs Lb Rb Ltf Rtf Ltb Rtb as in KytyPS5's 12-to-8 fold, and 6 and 8 channels to be its first 6 and 8; no title or SDK header confirms the order - [sceAudioOut2PortCreate](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) (libSceAudioOut) - the stereo fold is assumed, not the console's downmix: C at -3 dB into both sides, Ls/Rs, Lb/Rb and Ltf/Rtf at -3 dB and Ltb/Rtb at -6 dB into their own side, LFE dropped - [sceAudioOut2PortSetAttributes](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) (libSceAudioOut) - each PCM buffer set (attribute 0) is taken as one grain: buffers set between two pushes are queued and played one per grain, at most 16 queued. A grain with no new buffer repeats the last one only when the port has always been given that same buffer (rewritten in place); a port whose buffers move on plays silence instead. Inferred from PPSA12544, whose object port (type 6) is given one grain of a 64-grain ring per set, often two or three between pushes and then none for a few; the console behaviour for several sets per push and for a push without a new buffer is not documented +- [Legacy vibration ports](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp): PPSA99902's [public input wrapper](https://github.com/Nazky/UnleashedRecomp-Prospero/blob/2ce067a1d51bb58670c7ee4a3aed893c504da805/UnleashedRecomp/hid/driver/sdl_hid.cpp#L316) opens type 10 with 256 stereo S16 or F32 frames at 48000 Hz. Mono duplicates its channel as AudioOut2 does; wider vibration layouts throw. Native controller output uses AudioOut2's four-channel layout, or its six-channel PulseAudio mapping. Without a controller sound card the existing RMS motor transform is applied to segments of at most 256 frames, scheduled by the legacy queue's frame count and rate; drain and close remove only that port's segments. Concurrent sources combine by the maximum per motor, and the pad consumer uses this approximation only for controllers other than DualSense. This host timing and motor model is not a console measurement or waveform reproduction; legacy native output is unmeasured on hardware, and user routing retains the single-controller limitation. AudioOut2 retains its separate 80 ms peak hold. + - [sceAudioOutDeviceIdOpen](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - imported by PPSA01317, which fails to load without it; declared without parameters, signature unknown. It opens a port on a given audio device; which device ids exist and how they map to host outputs is unknown, so it throws - [sceAudioOut2PortGetState](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) and [sceAudioOut2GetSpeakerInfo](../../../core/libs/prx/libSceAudioOut/src/AudioOut2SpeakerArray.cpp) (libSceAudioOut) - the port state reports the channel count of the port's data format, output main, volume 127 and a zero reroute counter and flags. Taking the channel count from the data format follows [SharpEmu](https://github.com/sharpemu/sharpemu/blob/main/src/SharpEmu.Libs/Audio/AudioOut2Exports.cs), another emulator. A payload process on a PS5 (firmware 13.60), which is not the foreground application, read output 0, volume -1 and reroute counter 5 for its main ports, and 6 channels for an 8-channel port on a 5.1 system, so a title's values are unverified, including the output of a pad port, the volume after `sceAudioOut2PortSetAttributes` and the reroute counter on a device change. The speaker info reports type 0 with available bits `0x3`, the front left speaker at +30 degrees and the front right one at -30, and -360 for the azimuth and elevation of every other slot. On that console the type followed the HDMI device type and the available bits its channels: type 0 with the device set to TV, 1 to AV amplifier and 2 to sound bar, which [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/AudioOut2.cs) names television, AV receiver and sound bar (3 is headphones there), and available bits `0x3` for 2 channels and `0x3F` for 5.1 (centre at 0, surround pair at +110 and -110, -360 for the LFE). The front pair and the empty slots read the same in every setting; 7.1 on the AV amplifier reported 2 channels on that console's HDMI chain. The host output is always reported as a stereo television here. The console also returns `0x80268006` before `sceAudioOut2Initialize`, `0x80268001` for flags above 1 and `0x8026800C` for a null pointer, which are not modelled. ASTRO BOT and ASTRO's PLAYROOM reportedly stop on an assertion that depends on these values (#4317), but which field matters has not been isolated diff --git a/docs/dev/debt/libSceAutoMounterClient.md b/docs/dev/debt/libSceAutoMounterClient.md new file mode 100644 index 000000000000..10f8710cf9a3 --- /dev/null +++ b/docs/dev/debt/libSceAutoMounterClient.md @@ -0,0 +1,5 @@ +# libSceAutoMounterClient technical debt + +### Unknown function info + +- [libSceAutoMounterClient](../../../core/libs/prx/libSceAutoMounterClient/Export.cpp) - the three names and NIDs are in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAutoMounterClient.c), but no credible prototypes were found. The declarations have no parameters solely to resolve known imports; this is not an ABI claim and the exports must not be called until their signatures are verified diff --git a/docs/dev/debt/libSceBgft.md b/docs/dev/debt/libSceBgft.md new file mode 100644 index 000000000000..8d8e3ccbe7cb --- /dev/null +++ b/docs/dev/debt/libSceBgft.md @@ -0,0 +1,5 @@ +# libSceBgft technical debt + +### Unknown function info + +- [sceBgftServiceDownloadPauseTaskAll, sceBgftServiceDownloadResumeTaskAll, sceBgftServiceDownloadStartTaskAll, sceBgftServiceDownloadStopTaskAll, sceBgftServiceDownloadFindActivePatchTask, sceBgftServiceDownloadFindActivePupTask, sceBgftServiceIntDownloadFindActiveGameAndGameAcTask, sceBgftServiceIntDownloadFindActiveTask, sceBgftServiceIntDownloadGetGameAndGameAcProgress, sceBgftServiceIntDownloadGetPatchProgress, sceBgftServiceIntDownloadGetPlayGoProgress, sceBgftServiceIntDownloadGetProgress, sceBgftServiceIntDownloadGetTaskInfo, sceBgftServiceIntDownloadPauseTask, sceBgftServiceIntDownloadRegisterTaskByStorageEx, sceBgftServiceIntDownloadResumeTask, sceBgftServiceIntDownloadStopTask, sceBgftServiceIntDownloadStartTask, sceBgftServiceIntDownloadUnregisterTask and sceBgftServiceIntInit](../../../core/libs/prx/libSceBgft/Export.cpp) (libSceBgft) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceBgft.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. The eight task-control declarations not listed here follow [SharpProspero's DownloadService bindings](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Platform/DownloadService.cs) diff --git a/docs/dev/debt/libSceContentDelete.md b/docs/dev/debt/libSceContentDelete.md index e771fdeec761..018189415586 100644 --- a/docs/dev/debt/libSceContentDelete.md +++ b/docs/dev/debt/libSceContentDelete.md @@ -3,3 +3,8 @@ ### Unknown function info - [sceContentDeleteById](../../../core/libs/prx/libSceContentDelete/Export.cpp) (libSceContentDelete) - imported by PPSA06465, which fails to load without it; added without an implementation because its signature is unknown +- [sceContentDeleteById and sceContentDeleteByPath](../../../core/libs/prx/libSceContentDelete/Export.cpp) (libSceContentDelete) delete from a content library that is empty on the host, as nothing is exported to it, so both throw for every content id and path once the arguments and the initialization are checked: no reference gives the console's error for content that is not in the library ([SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Content/ContentDelete.cs#L25-L42) names only `0x809D5006`, no permission). The signatures, a 64-bit content id and a UTF-8 path, follow [Orbital](https://github.com/AlexAltea/orbital/blob/cc408367da6a6ebbf5ac1ac559fefeadc73b2831/tools/ida/db_types.json#L541-L542) and SharpProspero, which agree. A null path and a call outside `sceContentDeleteInitialize` and `sceContentDeleteTerminate` throw, as the console's errors for them are unknown, and the order of the two checks is unverified. + +### Unknown function info + +- [sceContentDeleteByIdListForSf, sceContentDeleteGlobalMstat, sceContentDeleteMemoryInitialize, sceContentDeleteMemoryTerminate](../../../core/libs/prx/libSceContentDelete/Export.cpp) (libSceContentDelete) - exported symbols from decrypted PS5 firmware 1.00–3.20 libSceContentDelete.sprx; parameter lists and return values are unknown/unverified and no reference (KytyPS5, shadPS4, fpPS4, SharpProspero) documents their signatures. Declared as stubs that throw via `NotImplemented_nid_no_patch` diff --git a/docs/dev/debt/libSceFontFt.md b/docs/dev/debt/libSceFontFt.md new file mode 100644 index 000000000000..618ebd832261 --- /dev/null +++ b/docs/dev/debt/libSceFontFt.md @@ -0,0 +1,5 @@ +# libSceFontFt technical debt + +### Unknown function info + +- [sceFontFtSupportBdf, sceFontFtSupportCid, sceFontFtSupportOpenType, sceFontFtSupportOpenTypeOtf, sceFontFtSupportOpenTypeTtf, sceFontFtSupportPcf, sceFontFtSupportPfr, sceFontFtSupportTrueType, sceFontFtSupportType1, sceFontFtSupportType42 and sceFontFtSupportWinFonts](../../../core/libs/prx/libSceFontFt/Export.cpp) (libSceFontFt) are declared without parameters, signatures unknown; [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/font/fontft.cpp), another emulator, declares them the same way and only logs. They are assumed to enable one font format in the FontFt library, which here always registers FreeType's default modules, so each call returns 0 when the host FreeType has the module (`bdf`, `t1cid`, `truetype`, `cff`, `pcf`, `pfr`, `type1`, `type42`, `winfonts`; OpenType needs `cff` and `truetype`) and throws when it does not. The mapping of Sony's format names to FreeType modules, the return value and the error codes of the console are unverified, and no title is known to call them. diff --git a/docs/dev/debt/libSceHmd2.md b/docs/dev/debt/libSceHmd2.md index 9b27109ad467..04db13ece297 100644 --- a/docs/dev/debt/libSceHmd2.md +++ b/docs/dev/debt/libSceHmd2.md @@ -11,6 +11,7 @@ - [sceHmd2Initialize](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - the error the console returns when the PS VR2 runtime is unavailable is unknown; it returns libSceHmd's `UNSUPPORTED_FEATURE` (0x81110016). PPSA01317 passes a pointer to 16 zeroed bytes and only tests the result for non-zero - PPSA01317 imports declared without parameters, signatures unknown: the remaining [libSceHmd2](../../../core/libs/prx/libSceHmd2/Export.cpp) and [libSceVrTracker2](../../../core/libs/prx/libSceVrTracker2/Export.cpp) exports. Names from the aerolib NID list - PPSA15878 imports declared without parameters, signatures unknown: the [libSceHmd2](../../../core/libs/prx/libSceHmd2/Export.cpp) reprojection and gaze exports, `sceVrTracker2Finalize`, `sceVrTracker2GetPlayAreaOrientedBoundingBox` and `sceVrTracker2LocateCoordinateSystem` in [libSceVrTracker2](../../../core/libs/prx/libSceVrTracker2/Export.cpp), and the `scePadVrController` trigger effect exports in [libScePad](../../../core/libs/prx/libScePad/Export.cpp). Names from the aerolib NID list +- [libSceHmd2 gaze and reprojection exports](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - `sceHmd2GazeGetCalibrationData`, `sceHmd2GazeGetCalibrationDataMaxSize`, `sceHmd2ReprojectionClearUserEventEnd`, `sceHmd2ReprojectionClearUserEventStart`, `sceHmd2ReprojectionGetCurrentOutput`, `sceHmd2ReprojectionSetUserEventEnd`, `sceHmd2ReprojectionSetUserEventStart`, `sceHmd2GazeActivateGazeTracking`, `sceHmd2GazeGetDeviceStatus`, `sceHmd2GazeGetUserCalibrationStatus`, `sceHmd2GazeInsertRawDataWithHandle`, `sceHmd2GazeSetEnabledEye`, `sceHmd2GazeSetUserCalibrationData`, `sceHmd2GazeSetUserCalibrationPoint`, `sceHmd2GazeStartUserCalibration`, `sceHmd2GazeStopUserCalibration`, `sceHmd2ReprojectionSeeThroughDraw`, `sceHmd2ReprojectionSeeThroughIC`, `sceHmd2ReprojectionVideoOutEq`, `sceHmd2ReprojectionWaitShader`, `sceHmd2SeeThroughCallbackGetPoseDataFunc`, `sceHmd2GazeObjectSelectionGetSelectedObject`, `sceHmd2GazeObjectSelectionInitialize`, `sceHmd2GazeObjectSelectionQueryMemory`, `sceHmd2GazeObjectSelectionTerminate`, `sceHmd2ReprojectionClear`, `sceHmd2ReprojectionSet`, `sceHmd2ReprojectionSetEventGpuEnd`, `sceHmd2ReprojectionSetEventGpuStart`, `sceHmd2RpeorjectionSetParam`, `sceHmd2SetFieldOfView`, `sceHmd2SetIpdOffset` are declared without parameters from the firmware export database (aerolib.csv); their guest signatures are unverified on hardware and every call throws ### Functional diff --git a/docs/dev/debt/libSceHttp.md b/docs/dev/debt/libSceHttp.md index d59da9042b20..cfcbd069b677 100644 --- a/docs/dev/debt/libSceHttp.md +++ b/docs/dev/debt/libSceHttp.md @@ -24,4 +24,5 @@ - [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. +- [sceHttpAuthCacheFlush, sceHttpCookieFlush](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the context-id signatures follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Net/Http.cs), and the NIDs `zzB0StvRab4` and `seCvUt91WHY` match [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.cpp). The offline backend never stores authentication credentials or cookies, so flushing a live context succeeds with empty caches. Calls return `BEFORE_INIT` (0x80431001) when no context is alive and `INVALID_ID` (0x80431100) for an unknown, deleted or non-context handle, following the existing context lifecycle checks. These error codes and their precedence have not been measured on PS5 hardware; shadPS4 only stubs these calls. Network-backed caches remain unimplemented. - [sceHttpAbortWaitRequest, sceHttpWaitRequest](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the abort takes only the epoll handle, as in [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json) and [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Net/Http.cs); behaviour and error codes follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.cpp) (PS4), not confirmed on a PS5 title or hardware. An abort stays set: every later wait on the handle, a poll included, returns `SCE_HTTP_ERROR_ABORTED` (0x80431080) until the handle is destroyed. A wait woken by `sceHttpDestroyEpoll`, or by the `sceHttpTerm` that ends the last context, returns 0 events. The epoll calls return `BEFORE_INIT` (0x80431001) while no `sceHttpInit` context is alive and `INVALID_ID` (0x80431100) for an unknown context or handle, and `sceHttpTerm` returns the same errors instead of succeeding for any id. Handles are pointers, so a stale handle whose address a newer epoll reuses is taken as that epoll diff --git a/docs/dev/debt/libSceJson2.md b/docs/dev/debt/libSceJson2.md index 92f10a90079c..94400d6f22e4 100644 --- a/docs/dev/debt/libSceJson2.md +++ b/docs/dev/debt/libSceJson2.md @@ -8,10 +8,11 @@ - [sce::Json::Parser::parse](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - a null text pointer or zero size returns `JsonErrorParse` (0x80920101) without modifying the destination Value as observed when PPSA26785 passes a null document after its legal screen; whether the console distinguishes a null pointer from a syntax error with a separate parameter error code is unknown. A number that overflows a double (e.g. `1e309`) throws: whether the console rejects the document or stores an infinite real is unknown - [sce::Json::InitParameter2, InitParameterRtti and InitParameterRtti2](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - layouts inferred, not known. `InitParameter2` is taken as the 40-byte object of [SharpEmu](https://github.com/sharpemu/sharpemu/blob/1811cef94435db7655209fb39f65ddf258b93fd6/src/SharpEmu.Libs/Json/JsonExports.cs) and [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/eda6ba37ece127ab0396378404d99182e3e8a530/src/libs/libJson2.cpp): the allocator, its user data, the file buffer size, the 32-bit special float format type at 0x18 and 12 bytes both zero, and both constructors zero all of it. `InitParameterRtti2::setAllocatorRtti` writes the allocation parameters and user data where `setAllocator` writes the allocator, as both references do; whether the console also marks the object as using RTTI allocation is unknown. `InitParameterRtti`'s constructor writes the allocation parameters, user data and file buffer size to the first 24 bytes, an order inferred from its mangled arguments. The values of `SpecialFloatFormatType` and what they select are unknown, so `setSpecialFloatFormatType` stores any value and nothing reads it: `Initializer::initialize(InitParameter2)` ignores every field as `initialize(InitParameter)` does, serializing a non-finite real throws whatever the type, and whether the type lets the parser accept NaN or infinity is unknown. The setters return nothing; whether the console's return `this` is unknown - [sce::Json::Object::Pair and sce::Json::Array::iterator members](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) imported by PPSA30601 - the signatures follow the mangled names, with `Array::iterator::operator++(int)` returning its copy through a result pointer before `this` as `Array::begin` does, since the iterator has a non-trivial destructor. `Pair(const String&, const Value&)` deep-copies both 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. The default-constructed iterator is singular +- [sce::Json::Value::setTypeMismatchHandler, setElementAccessFailureHandler and setSpecialFloatHandler](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - members of `Value` that return an int, as PPSA07411 calls them: on its root `Value` before parsing into it, with the handler and its context, taking 0 or 0x80848104 as success; they return 0. The PS Vita library's `Value::setNullAccessCallback` is such a member too ([vitasdk](https://github.com/vitasdk/vita-headers/blob/15605617cbbb23d266f88af28899fdb573e2355e/include/psp2/json.h#L638)). The handler signatures follow the mangled names. A handler applies to the `Value` it is set on and to the values that `Value` holds then, and `Parser::parse` gives the values it creates the handlers of the `Value` it parses into, since the title reads the errors its handlers record after accessing parsed members; copies, `push_back` and `Object::operator[]` do not carry them, and how the console finds the handler of an accessed value is unknown. A typed getter on a non-null value of another type calls the type mismatch handler with the requested type, the value, null and the context, and returns the value the handler returns if it has the requested type, else the getter's default; what the console passes as the second `Value`, and whether the accessed value comes first, are unknown. The element access failure handler is not called: a failed `operator[]` or `getValue` on a value carrying it throws, as the error code the console passes, which the title records as its error, is unknown; without one the access still reaches the null-access callback. The special float handler is not called: serializing a non-finite real throws with or without it, as the `FunctionType` values and the `bool*` output are unknown, and the title's handler returns a different replacement for each of its two types - [sce::Json::MemAllocator::notifyError](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - the base class's virtual does nothing. A title's allocator that does not override it calls it through its own vtable; the library never calls the title's allocator, so it never reports an allocation failure. Whether the console's base implementation logs anything is unknown ### Functional -- PPSA07411 imports declared without parameters, signatures unknown, added without an implementation because the title fails to load without them: [four sce::Json::Value members (setElementAccessFailureHandler, setSpecialFloatHandler, setTypeMismatchHandler, operator bool)](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2), [sceConvertKeycodeGetCharacter](../../../core/libs/prx/libSceConvertKeycode/Export.cpp) (libSceConvertKeycode) and [sceNpSessionSignalingGetGroupFromSessionId](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) +- PPSA07411 imports declared without parameters, signatures unknown, added without an implementation because the title fails to load without them: [sce::Json::Value::operator bool](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2), [sceConvertKeycodeGetCharacter](../../../core/libs/prx/libSceConvertKeycode/Export.cpp) (libSceConvertKeycode) and [sceNpSessionSignalingGetGroupFromSessionId](../../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - PPSA30601 imports declared without parameters, signatures unknown, added without an implementation because the title fails to load without them: [sce::Json::Object::insert](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2), whose return type, and so whether a result pointer precedes `this`, is unknown - [sce::Json::InitParameter2 base-object constructor](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - forwards to the complete-object constructor and uses its inferred layout and default field values; constructor equivalence and initialization bytes remain unverified on the console diff --git a/docs/dev/debt/libSceNet.md b/docs/dev/debt/libSceNet.md index 3782ee84d16b..769d959f1520 100644 --- a/docs/dev/debt/libSceNet.md +++ b/docs/dev/debt/libSceNet.md @@ -7,10 +7,181 @@ ### Unknown function info - [sceHttp2GetMemoryPoolStats, sceSslGetMemoryPoolStats, sceNetGetMemoryPoolStats](../../../core/libs/prx/libSceNet/Export.cpp) (libSceHttp2, libSceSsl, libSceNet) - the signatures `(int id, stats*)`, the layouts (pool size, peak and current use, and for Http2 and Ssl a 32-bit reserved field) and the error codes were measured on a PS5 (firmware 13.60). The pool size is the one given at init or pool creation. Http2 and Net allocate nothing from these pools. Ssl counts its certificate backend allocations and returned data against its pool; C++ bookkeeping is outside that budget. All three initially report zero use, where the console already reports 0x15C0 bytes for Ssl, 0x1640 for Http2 and 4 for a Net pool. Ssl's allocation sizes and peak usage do not reproduce the console allocator +- [sceNetClearDnsCache](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetStatisticsInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetInterfaceStats](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetSetDnsInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetShowIfconfigWithMemory](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetShowPolicy](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetShowPolicyWithMemory](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetShowRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetShowRouteWithMemory](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetResolverStartAton6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetResolverConnectAbort](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetResolverConnectCreate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetResolverConnect](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetResolverConnectDestroy](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetSockInfo6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetSetDns6Info](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetShowRoute6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetShowRoute6WithMemory](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetAllocateAllRouteInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetAllocateAllRouteInfo6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetControl](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDbgInit](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetEventCallbackCreate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetEventCallbackDestroy](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetEventCallbackGetError](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetEventCallbackWaitCB](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetFreeAllRouteInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetArpInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetAppMemInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetDnsInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetIfList](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetIfListEx](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetIfListOnce](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetIfName](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetIfnameNumList](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetNameToIndex](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetRandom](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetRouteInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetsockoptEx](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetSystemTime](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetSystemMemInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetInitParam](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetInetPtonEx](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the `(int af, const char* src, void* dst, int flags)` signature follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/net/net.cpp), which forwards it to `sceNetInetPton` and does not know the flags either. Flags 0 converts as `sceNetInetPton` does; any other flags throw, since their meaning (an IPv6 scope or otherwise) is not known. Unverified on hardware +- [sceNetIoctl](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetMemoryAllocate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetMemoryFree](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetSetDnsInfoToKernel](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetSetsockoptEx](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetSocketInternal](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetSyncCreate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetSyncDestroy](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetSyncGet](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetSyncSignal](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetSyncWait](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetSysctl](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetThreadCreate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetThreadExit](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetThreadJoin](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetUsleep](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the `(int microseconds)` signature and the `EINVAL` failure for a negative count follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/net/net.cpp); other counts sleep through libkernel's `sceKernelUsleep`, a cancellation point, and return 0. Whether the console takes the count as signed, and what it returns, is unverified on hardware +- [sceNetShowIfconfigForBuffer](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetShowRouteForBuffer](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetShowNetstatForBuffer](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetShowNetstatEx](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetShowNetstatExForBuffer](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetInetNtopWithScopeId](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetInetPtonWithScopeId](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetDns6Info](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetSetDns6InfoToKernel](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetShowRoute6ForBuffer](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigGetIfFlags](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigSetIfFlags](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigUnsetIfFlags](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigDelIfaddr](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigDownInterface](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigSetDefaultRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigSetDefaultRouteDev](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigSetIfaddr](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigSetIfLinkLocalAddr6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigUpInterface](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigUpInterfaceWithFlags](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigCleanUpAllInterfaces](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigFlushRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigFlushRoute6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigAddIfaddr](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigGetIfaddr](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigSetIfmtu](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigAddArp](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigAddArpWithInterface](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigAddMRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigAddRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigAddRouteWithInterface](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigDelArp](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigDelArpWithInterface](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigDelDefaultRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigDelDefaultRouteDev](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigDelMRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigDelRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigGetDefaultRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDhcpGetAutoipInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDhcpGetInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDhcpGetInfoEx](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDhcpStart](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDhcpStop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigAddIfaddr6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigDelIfaddr6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigDelDefaultRoute6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigSetIfaddr6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigSetIfaddr6WithFlags](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigSetDefaultRoute6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigGetIfaddr6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigGetDefaultRoute6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigDelRoute6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigAddRoute6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetAddrConfig6Start](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetAddrConfig6Stop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetAddrConfig6GetInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetInfoDumpStart](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetInfoDumpStop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDhcpdStart](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDhcpdStop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDuplicateIpStart](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDuplicateIpStop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanAdhocClearWakeOnWlan](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanAdhocCreate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanAdhocGetWakeOnWlanInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanAdhocJoin](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanAdhocLeave](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanAdhocPspEmuClearWakeOnWlan](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanAdhocPspEmuGetWakeOnWlanInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanAdhocPspEmuSetWakeOnWlan](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanAdhocScanJoin](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanAdhocSetExtInfoElement](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanAdhocSetWakeOnWlan](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanApStart](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanApStop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanBackgroundScanQuery](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanBackgroundScanStart](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanBackgroundScanStop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanDiagGetDeviceInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanDiagSetAntenna](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanDiagSetTxFixedRate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanGetDeviceConfig](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanInfraGetRssiInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanInfraLeave](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanInfraScanJoin](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanScan](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigWlanSetDeviceConfig](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigEtherGetLinkMode](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigEtherPostPlugInOutEvent](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigEtherSetLinkMode](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetPppoeStart](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetPppoeStop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetBandwidthControlSetDefaultParam](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetBandwidthControlGetDefaultParam](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetBandwidthControlGetDataTraffic](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigSetDefaultScope](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigRoutingStart](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigRoutingStop](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigRoutingShowRoutingConfig](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetConfigRoutingShowtCtlVar](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetGetStatisticsInfoInternal](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDumpAbort](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDumpCreate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDumpSetFilterParam](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDumpDestroy](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetDumpRead](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetEmulationDebugSettingsSet](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetEmulationDebugSettingsGet](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetEmulationSet](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNetEmulationGet](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature ### Functional - [sceNetSocket and sceNetSetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) map peer socket types 6 and 10 to host UDP and TCP transports and accept the PS5 broadcast option `0x20000` as `SO_BROADCAST`. Loopback transport is tested; console peer negotiation and peer virtual-port routing are not implemented. +- [sceNetAccept](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) inherits the listener's non-blocking mode through the existing `SO_NBIO` setter, as the libkernel socket path does and FreeBSD's [`sonewconn`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/uipc_socket.c#L571) copies the socket state. The mode is sampled when accept starts; changes between connection creation and accept are not modelled. Not measured on a console. - [sceNetConnect to AF_UNSPEC](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) dissolves a datagram association as FreeBSD 11.4's `udp_disconnect()` does, which resets the local address to the wildcard address and keeps the port. On Linux a socket bound to an explicit port keeps its local address as well (`127.0.0.1:` stays `127.0.0.1:`), because a bound socket cannot be bound again; one bound to port 0 is bound again to the wildcard address with the port it had - [sceNetSetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) sets `SO_RCVTIMEO` and `SO_SNDTIMEO` as the host socket's timeouts. On Windows a receive or send on a stream socket that times out fails with `ETIMEDOUT`, where FreeBSD returns `EAGAIN` or the partial count: Winsock leaves the connection in an indeterminate state after a timeout and reports no partial count ([SOL_SOCKET options](https://learn.microsoft.com/en-us/windows/win32/winsock/sol-socket-socket-options)), so FreeBSD's behaviour needs the timeout waited for without the host option. A receive on a datagram socket that times out fails with `EAGAIN` - [sceNetSetsockopt, sceNetGetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) take `SO_SNDTIMEO` and `SO_RCVTIMEO` (0x1005, 0x1006) as a 16-byte `struct timeval`, as FreeBSD 11.4's [`sosetopt` and `sogetopt`](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/sys/kern/uipc_socket.c#L2657-L2690) do, and Sony's 0x1105 and 0x1106 as an `int` of microseconds. A [console log of ProsperoLight](https://github.com/blackbearreloaded/ProsperoLight/issues/49) (firmware 12.20) shows receives timing out every 100 ms after it gave 0x1006 a `{0, 100000}` timeval. The errors for a value shorter than a timeval (`EINVAL`) or out of range (`EDOM`), and `sceNetGetsockopt` cutting the timeval to the caller's length, follow FreeBSD's `sooptcopyin` and `sooptcopyout` and are not measured on a console diff --git a/docs/dev/debt/libSceNetCtl.md b/docs/dev/debt/libSceNetCtl.md index 427e526f3531..396309b1daee 100644 --- a/docs/dev/debt/libSceNetCtl.md +++ b/docs/dev/debt/libSceNetCtl.md @@ -7,3 +7,8 @@ ### Unknown function info - [sceNetCtlGetInfo](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - answers from the host interface that holds an IPv4 address, preferring the one that carries the default route: its address, netmask, gateway, MTU and first two IPv4 DNS servers. The device is reported as wired, the link as connected, the IP configuration as DHCP and the HTTP proxy as off, whatever the host uses, and the Ethernet address is the one `sceNetGetMacAddress` reports. A gateway or DNS server the interface does not have is reported as `0.0.0.0`; what a console reports then is unknown. The DHCP host name (12), the PPPoE user name (13), the HTTP proxy server (20) and the Wi-Fi BSSID and SSID (5, 6) are empty strings, as that configuration sets none of them; the remaining Wi-Fi codes (7-10) and the HTTP proxy port (21) report 0 +- [sceNetCtlGetInfo](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - answers from the host interface that holds an IPv4 address, preferring the one that carries the default route: its address, netmask, gateway, MTU and first two IPv4 DNS servers. The device is reported as wired, the link as connected, the IP configuration as DHCP and the HTTP proxy as off, whatever the host uses, and the Ethernet address is the one `sceNetGetMacAddress` reports. A gateway or DNS server the interface does not have is reported as `0.0.0.0`; what a console reports then is unknown. The DHCP host name (12), the PPPoE user name (13), the HTTP proxy server (20) and the Wi-Fi BSSID and SSID (5, 6) are empty strings, as that configuration sets none of them; the remaining Wi-Fi codes (7-10) and the HTTP proxy port (21) report 0; this follows [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/network.cpp) and is not measured on hardware + +### Functional + +- [sceNetCtlCheckCallback](../../../core/libs/prx/libSceNetCtl/Export.cpp) (libSceNetCtl) - the state comes from the host interfaces, as in `sceNetCtlGetState`: `IPOBTAINED` (3) while the host has a non-loopback IPv4 or IPv6 address, `DISCONNECTED` (1) otherwise; the event values follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/network/net_ctl_codes.h#L27-L29), and no other event is delivered. Each callback registered with `sceNetCtlRegisterCallback` is called on the checking thread with its registration argument when the state differs from the last one it received, so a new registration receives the current state at the next check. This edge-triggered model follows [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/9e41d8320a2774b303fbad344c2347e8d0292e83/src/libs/network.cpp#L3648-L3661); [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/network/net_ctl_obj.cpp#L47-L57) delivers the state on every check instead, and neither is measured on hardware. KytyPS5 calls the callbacks under its lock; here they are called after the lock is released, from a copy of their slots, so a `sceNetCtlUnregisterCallback` or `sceNetCtlTerm` on another thread that returns while a check is in flight does not stop that check from calling the callback once more with its old argument. Callbacks registered with `sceNetCtlRegisterCallbackV6` keep their argument but receive no event, because no source says which call delivers them diff --git a/docs/dev/debt/libSceNgs2.native.md b/docs/dev/debt/libSceNgs2.native.md index f32a66cf47ce..59361e933cd5 100644 --- a/docs/dev/debt/libSceNgs2.native.md +++ b/docs/dev/debt/libSceNgs2.native.md @@ -2,6 +2,7 @@ ### Unknown function info +- [NGS2 voice validation](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) returns `0x804A8203` for an invalid voice in Control, RunCommands, GetState, GetStateFlags, GetPortInfo and QueryInfo, following the type-4 handle error in [prosper's PS5 module analysis](https://github.com/mattias800/prosper/blob/d61eb2c0ad2de879ef74df9c4aa050c512804a18/prosper/src/hle/audio/hle_audio.cpp#L4478). Control checks a null parameter list first (`0x804A8360`); GetState resolves the voice before output validation, and GetState rejects sizes below 8 before a null output. Existing rack-specific exact state sizes, other argument precedence and command layouts are retained; arbitrary-sized state copies and full console behavior remain unverified. These values follow module analysis, not new console execution. - [NGS2 render buffer capacity](../../../core/libs/prx/libSceNgs2.native/src/Render.cpp) is treated as available storage for one configured grain. PPSA01880 passes 303104 bytes for 256 stereo float frames (2048 bytes); rendering writes or clears only those frames and preserves the remaining capacity. Non-null silent outputs now undergo the same supported-format and minimum-size checks as audible outputs, with channel counts limited to 1 through 8. Unused null outputs remain ignored. The console's handling of surplus capacity and invalid silent output descriptors has not been measured; invalid descriptors throw rather than return guessed SDK errors. - [NGS2 ATRAC9 waveform loops](../../../core/libs/prx/libSceNgs2.native/src/Atrac9.cpp) accepts one integer forward `smpl` loop. Inclusive RIFF endpoints and encoder-delay adjustment follow [vgmstream riff.c](https://github.com/vgmstream/vgmstream/blob/7dc938fa2f210943b37c7b6511852b516ef432ab/src/meta/riff.c). PPSA03747 supplies a 60-byte sampler chunk with one loop, `samplerData` 24, start 256, inclusive end 299224 and delay 256 for 298969 audible samples. The chunk size and loop count bound the records; `samplerData` is not interpreted. Output positions are assumed to use audible samples and an exclusive end. Blocks split into an optional intro, a repeated range and an optional tail; zero play count means infinite repeats, positive play count includes the first pass. ATRAC9 ranges include a preceding codec frame for decoder history. The output block layout, repeat-count mapping and error codes are unverified on hardware. Multiple, reverse, alternating and fractional loops throw. - [NGS2 submixer UserFx](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (control `0x20000004`) invokes the title's callback after input mixing on every playing grain, including silence. The 40-byte control and 48-byte process context follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/945dbc3cc3eee80ac3e053b438502ed936fa6bb2/src/core/libraries/ngs2/ngs2_submixer.h). PPSA03747 installs a peak-meter callback; its code confirms the channel pointers, user data 0, channel count, grain size and sample-rate offsets and returns 0. Other user data words are preserved. Flags are assumed to be 1 after registration/restart and 0 thereafter, following UserFx2's first-process convention; this callback does not inspect them. Null handlers disable the effect, setup clears it, and nonzero callback results throw. These lifecycle and error semantics are unverified on console hardware. @@ -28,6 +29,7 @@ - [sceNgs2PanInit / sceNgs2PanGetVolumeMatrix](../../../core/libs/prx/libSceNgs2.native/src/Pan.cpp) (libSceNgs2.native) - follow the PS4 firmware 11.00 `libSceNgs2.sprx`, read and checked against its output for 2, 4, 5 and 7 speakers into 1, 2, 6 and 8 channels; not yet confirmed on the PS5 library. Speakers are taken in the order L R, L R Ls Rs, L R C Ls Rs or L R C Ls Rs Lb Rb and kept in a fixed circular order, not sorted; without angles the defaults are 90/270, 40/110/250/320, 0/40/110/250/320 and 0/40/110/150/210/250/320 degrees. As in that library, defaults converted to radians keep the wrap-around entry in degrees, so angles past the last speaker pan almost fully to it. Error codes are not known for the PS5, so invalid arguments throw, including a speaker angle equal to the unit angle, on which that library loops forever. Angles so large that adding or subtracting a turn leaves them unchanged throw for the same reason - [sceNgs2GeomResetListenerParam, sceNgs2GeomResetSourceParam](../../../core/libs/prx/libSceNgs2.native/src/Geom.cpp) (libSceNgs2.native) - the structures (12, 16, 16, 60, 96, 108, 32 and 308 bytes) come from [KytyPS5](https://github.com/KytyPS5/KytyPS5)'s `ngs2.cpp` and match [shadPS4](https://github.com/shadps4-emu/shadPS4)'s `ngs2_geom.h` field for field, but only KytyPS5 gives the behaviour - shadPS4 logs and returns success without writing the output. The defaults (orient front +Z, orient up +Y, sound speed 343, cone 1/360/1/360, rolloff 0/1000000/1/1, doppler factor, full-band, LFE and maximum level 1, two speakers and matrix format 2) are unverified on a PS5 title; KytyPS5 and iStark/PS5PCEM agree on them. PPSA15552 (Dead Cells) calls `sceNgs2GeomResetSourceParam` after creating its racks. A null output address returns `INVALID_OUT_ADDRESS` as elsewhere in the library rather than throwing as KytyPS5 does. `sceNgs2GeomApply` is now a minimal pass-through that follows [KytyPS5](https://github.com/KytyPS5/KytyPS5)'s `Ngs2GeomApply` (shadPS4 only stubs it): pitch 1, source position, volume the larger of the source minimum and maximum level, and a diagonal of the source maximum level (1 when it is not positive) over `matrix_format` channels (0 is two, at most 8). The listener and `flags` are ignored, so source attenuation, distance rolloff, panning and Doppler stay unimplemented; neither KytyPS5 nor this has been measured on a PS5, and a null output address returns `INVALID_OUT_ADDRESS` where KytyPS5 exits - [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. Unknown flags, zero orientation vectors, nonfinite input and an unrepresentable translation still throw without changing the output; 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 reaches this call after the custom sampler rack in #4334. This does not implement source attenuation, panning or Doppler in `sceNgs2GeomApply`. +- [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. Unknown flags, 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 diff --git a/docs/dev/debt/libSceNpCppWebApi.md b/docs/dev/debt/libSceNpCppWebApi.md new file mode 100644 index 000000000000..4ea6a15b4ea2 --- /dev/null +++ b/docs/dev/debt/libSceNpCppWebApi.md @@ -0,0 +1,16 @@ +# libSceNpCppWebApi technical debt + +### 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::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 + +### Unknown function info + +- [sce::Np::CppWebApi](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - every request entry point (API calls, transaction `start`/`getResponse`/`readData`, the `*Factory::create`s) returns `SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN` offline. The constant's name and value follow the reference tables in shadPS4 `np_error.h` and fpPS4 `np_error.pas`, where `0x80552907` is `ORBIS_NP_WEBAPI_ERROR_NOT_SIGNED_IN` and `0x80552901` is `OUT_OF_MEMORY` (no `UNAVAILABLE` exists in the `0x80552901`-`0x8055291F` range); it is the code a signed-out console returns from these entry points. The earlier `0x80552901` value came from the module the previous implementation shipped, which used the same incorrect constant +- [sce::Np::CppWebApi::Common::IntrusivePtr](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the 0x18-byte layout (object, deleter, `LibContext*`) and the offset of the 32-bit reference count in the pointee (0x00, 0x30 for `Vector`) come from the module bundled with one title (PPSA21564); other SDK versions are not checked. The destructor throws on the last reference of a pointer without a deleter: destroying the object needs its destructor and the `LibContext` allocator, which are not implemented +- [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 diff --git a/docs/dev/debt/libSceNpGameIntent.md b/docs/dev/debt/libSceNpGameIntent.md new file mode 100644 index 000000000000..681c6dbf8fe9 --- /dev/null +++ b/docs/dev/debt/libSceNpGameIntent.md @@ -0,0 +1,10 @@ +# libSceNpGameIntent technical debt + +### Silent stubs + +- [sceNpGameIntentVshInitialize, sceNpGameIntentVshTerminate](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - VSH initialization and termination return 0 without performing shell operations, matching `sceNpGameIntentInitialize` and `sceNpGameIntentTerminate` + +### Unknown function info + +- [sceNpGameIntentInitParamInit](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - struct layout matches [fpPS4](https://github.com/red-prig/fpPS4/blob/master/src/np/ps4_libscenpgameintent.pas#L17) `SceNpGameIntentInitParam`; setting `size`, zeroing reserved bytes, and throwing on null pointer follow Sony SDK `*ParamInit` conventions but are unverified on retail hardware +- [sceNpGameIntentInfoInit](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - struct layout matches [fpPS4](https://github.com/red-prig/fpPS4/blob/master/src/np/ps4_libscenpgameintent.pas#L28) `SceNpGameIntentInfo`; setting `size`, `user_id = -1`, zeroing fields, and throwing on null pointer match `sceNpGameIntentReceiveIntent` defaults but are unverified on retail hardware diff --git a/docs/dev/debt/libSceRegMgr.md b/docs/dev/debt/libSceRegMgr.md index 467ad2850e96..e599cc296dbe 100644 --- a/docs/dev/debt/libSceRegMgr.md +++ b/docs/dev/debt/libSceRegMgr.md @@ -2,4 +2,5 @@ ### Unknown function info -- [sceRegMgrNonSysGetInt](../../../core/libs/prx/libSceRegMgr/Export.cpp) (libSceRegMgr, NID `dKeshzt29G4`) - imported by Astro Bot (PPSA21564), which fails to load without libSceRegMgr.prx (issue 4720); the `(int64_t key, int32_t unk, int32_t* data)` signature follows [ChonkyStation4](https://github.com/liuk7071/ChonkyStation4/blob/c101a23ecbc15e56c6e30d5dd24d3ed8880a957a/ChonkyStation4/OS/Libraries/SceRegMgr/SceRegMgr.cpp#L38-L47), not measured on a console, so every call throws +- [sceRegMgrNonSysGetInt](../../../core/libs/prx/libSceRegMgr/Export.cpp) (libSceRegMgr, NID `dKeshzt29G4`) - observed as an import needed for module loading (issue 4720); its `(int64_t key, int32_t unk, int32_t* data)` signature follows [ChonkyStation4](https://github.com/liuk7071/ChonkyStation4/blob/c101a23ecbc15e56c6e30d5dd24d3ed8880a957a/ChonkyStation4/OS/Libraries/SceRegMgr/SceRegMgr.cpp#L38-L47), not measured on a console, so every call throws +- [sceRegMgrGetBin and sceRegMgrSetBin](../../../core/libs/prx/libSceRegMgr/Export.cpp) (libSceRegMgr) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceRegMgr.c), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim. The string and integer prototypes follow [SharpProspero's SystemSettings bindings](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Platform/SystemSettings.cs) diff --git a/docs/dev/debt/libSceRtc.md b/docs/dev/debt/libSceRtc.md index 62ca3e81dcc7..8f90cdda7fb2 100644 --- a/docs/dev/debt/libSceRtc.md +++ b/docs/dev/debt/libSceRtc.md @@ -1,5 +1,9 @@ # libSceRtc technical debt +### Silent stubs + +- [sceRtcInit, sceRtcEnd](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - return 0 and do nothing, as the console functions do: both are a bare `xor eax, eax; ret` in libSceRtc.sprx on firmware 13.60 + ### Unknown function info - [sceRtcParseDateTime](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - the console's zone name table is only known from the names measured on PS5 firmware 13.60 and 13.42; any other name is assumed to take the military letter offset of its first character, as every measured name outside the table does (`HST`, `MSK`, `XYZ`), so a name the console's table holds but nobody measured gets the wrong offset @@ -7,3 +11,7 @@ - [sceRtcFormatRFC2822, sceRtcFormatRFC2822LocalTime, sceRtcFormatRFC3339LocalTime](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - only shadPS4 (PS4) has bodies. A null tick returns `INVALID_POINTER` like the existing `sceRtcFormatRFC3339`, where shadPS4 formats the current time instead; offsets outside ±1439 minutes return `INVALID_VALUE` like `sceRtcFormatRFC3339`; the local-time variants use the host's offset at the given instant (shadPS4 uses `sceKernelGettimezone`, the current offset) - [sceRtcGetDosTime, sceRtcSetDosTime](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - bit layout from shadPS4 (PS4, the standard FAT date/time). A year outside 1980-2107 makes `GetDosTime` return `INVALID_YEAR` after writing 0 for an earlier one and `0xff9fbf7d` (2107-12-31 23:59:58, the largest encodable time) for a later one, and `SetDosTime` decodes the year as the unsigned 7-bit FAT field (1980-2107) without validating the fields, as the disassembly-derived [fpPS4](https://github.com/red-prig/fpPS4) reimplementation of the PS4 library does; not confirmed on a PS5 title, and shadPS4 instead masks out-of-range years into the packed value and sign-extends the 16-bit date - [sceRtcSetYear, sceRtcSetMonth, sceRtcSetDay, sceRtcSetHour, sceRtcSetMinute, sceRtcSetSecond, sceRtcSetMicrosecond](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - component setters validate component boundaries independently without cross-field calendar validation +- [sceRtcGetCurrentRawNetworkTick, sceRtcGetCurrentAdNetworkTick, sceRtcGetCurrentDebugNetworkTick](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - the console reads clocks 0x13, 0x12 and 0x11 (the ad and debug clocks fall back to the network clock 0x10) and returns the clock time plus the Unix epoch in ticks, or `0x80B50001` when the clock is unsupported (errno 5). Those console clocks are not modelled; all three return the system clock like `sceRtcGetCurrentNetworkTick`. Read from the disassembly of libSceRtc.sprx on firmware 13.60, not measured on a console +- [sceRtcSetCurrentTick, sceRtcSetCurrentNetworkTick, sceRtcSetCurrentAdNetworkTick, sceRtcSetCurrentDebugNetworkTick](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - the console rejects a null pointer with `0x80B50002` and a tick before the Unix epoch with `0x80B50003`, then sets the system clock (clock 0, 0x10, 0x12 or 0x11) from the tick. A valid tick throws: the host clock is not set. sceRtcSetConf passes its third and fourth arguments to the same clock call with clock 0 and throws; what the pair selects is unknown +- [sceRtcFormatRFC3339Precise, sceRtcFormatRFC3339PreciseLocalTime](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - read from the disassembly of libSceRtc.sprx on firmware 13.60: a null tick means the current tick, a precision above 6 or a zone outside -1439 to 1439 minutes returns `0x80B50003`, and the fraction is the microseconds truncated to the requested number of digits. Not compared with console output. The output buffer size is unknown; at most 33 bytes are written +- [sceRtcGetYear, sceRtcGetMonth, sceRtcGetDay, sceRtcGetHour, sceRtcGetMinute, sceRtcGetSecond, sceRtcGetMicrosecond, sceRtcSetYear, sceRtcSetMonth, sceRtcSetDay, sceRtcSetHour, sceRtcSetMinute, sceRtcSetSecond, sceRtcSetMicrosecond](../../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - inferred component behavior without confirmed hardware trace. Component setters validate individual boundaries (e.g. day 1..31) independently without cross-field calendar validation; getters return `0x80B50002` (`SCE_RTC_ERROR_INVALID_POINTER`) for a null pointer diff --git a/docs/dev/debt/libSceScreenShot.md b/docs/dev/debt/libSceScreenShot.md new file mode 100644 index 000000000000..89867a84f60f --- /dev/null +++ b/docs/dev/debt/libSceScreenShot.md @@ -0,0 +1,5 @@ +# libSceScreenShot technical debt + +### Unknown function info + +- [sceScreenShotSetOverlayImage and sceScreenShotSetOverlayImageWithOrigin](../../../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). The PS4 compatibility implementation also exposes the names but declares no parameters; no PS5 prototype was found. The declarations have no parameters solely to resolve known imports and make no ABI claim diff --git a/docs/dev/debt/libSceSysCore.md b/docs/dev/debt/libSceSysCore.md new file mode 100644 index 000000000000..0ebd352ebdd5 --- /dev/null +++ b/docs/dev/debt/libSceSysCore.md @@ -0,0 +1,5 @@ +# libSceSysCore technical debt + +### Unknown function info + +- [sceApplicationGetAppInfoByAppId](../../../core/libs/prx/libSceSysCore/Export.cpp) (libSceSysCore) - the name and NID are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSysCore.c), but no credible prototype was found. The declaration has no parameters solely to resolve a known import and makes no ABI claim diff --git a/docs/dev/debt/libSceSysmodule.md b/docs/dev/debt/libSceSysmodule.md index 80f91983aade..b25f89b71048 100644 --- a/docs/dev/debt/libSceSysmodule.md +++ b/docs/dev/debt/libSceSysmodule.md @@ -4,6 +4,14 @@ - [kModuleTable](../../../core/libs/prx/libSceSysmodule/ModuleTable.hpp) (libSceSysmodule) - module ID 0x00000132 maps to `libSceProprietaryVoiceChatHelperPrx` as verified in [#5017](https://github.com/boykopovar/AnyPS5/pull/5017) and reported in Discussion [#6344](https://github.com/boykopovar/AnyPS5/discussions/6344) for EA SPORTS FC 27 (PPSA34015). +### Known modules + +- [kModuleTable](../../../core/libs/prx/libSceSysmodule/ModuleTable.hpp) (libSceSysmodule) - module ID 0x00000132 maps to `libSceProprietaryVoiceChatHelperPrx` as verified in [#5017](https://github.com/boykopovar/AnyPS5/pull/5017) and reported in Discussion [#6344](https://github.com/boykopovar/AnyPS5/discussions/6344) for EA SPORTS FC 27 (PPSA34015). +- [kModuleTable](../../../core/libs/prx/libSceSysmodule/ModuleTable.hpp) (libSceSysmodule) - module id 0x00000132 is named `libSceProprietaryVoiceChatHelperPrx`, the name of the built module, because `sceSysmoduleGetModuleHandleInternal` opens `libs/.prx`. The name the console's table uses for this id is not verified +- [kModuleTable](../../../core/libs/prx/libSceSysmodule/ModuleTable.hpp) (libSceSysmodule) - module IDs registered from decrypted PS5 firmware 1.00–3.20 sysmodule lookup tables: + - User-space IDs (0x00000005, 0x0000010a, 0x0000010e, 0x0000011b, 0x00000125, 0x00000126, 0x00000127, 0x00000128, 0x0000012a, 0x0000012b, 0x00000130) and internal IDs (0x8000007b, 0x8000007e, 0x8000007f, 0x80000080, 0x80000082, 0x80000083, 0x80000084, 0x80000085, 0x80000086, 0x80000087, 0x80000088, 0x80000089, 0x8000008e, 0x80000090, 0x80000091, 0x80000092, 0x80000093, 0x80000095, 0x80000097, 0x80000099, 0x8000009b, 0x8000009c, 0x8000009e, 0x8000009f, 0x800000a0, 0x800000a1, 0x800000a2, 0x800000a3, 0x800000a4, 0x800000a6, 0x800000a7, 0x800000a8, 0x800000a9, 0x800000ad, 0x800000ae, 0x800000af, 0x800000b0, 0x800000b2, 0x800000b3, 0x800000b4, 0x800000b5, 0x800000b6, 0x800000b7, 0x800000b9, 0x800000ba, 0x800000bb, 0x800000bc, 0x800000bd, 0x800000be, 0x800000bf, 0x800000c3); whether all mapped PRX modules exist as separate runtime files or are embedded in system daemons on retail consoles is unverified + - Dual IDs for `libSceNgs2` (0x0000000b user / 0x80000090 internal) and `libSceHdrScopes` (0x00000109 user / 0x800000bd internal) reflect distinct public and internal sysmodule identifiers defined in PS5 firmware SDKs + ### Functional - [sceSysmoduleGetModuleInfoForUnwind](../../../core/libs/prx/libSceSysmodule/Export.cpp) (libSceSysmodule) - on Windows the region and file name of an address inside a loaded image are cached after the first lookup, because `VirtualQuery` on a large image section is slow. An entry is used only while the loader still reports the same image for the address (same base, time stamp and size). A page protection change inside the image after the first lookup, which would split or merge the region `VirtualQuery` reports, is not noticed. Addresses outside loaded images are queried every time diff --git a/docs/dev/debt/libSceVideoRecordingP.md b/docs/dev/debt/libSceVideoRecordingP.md new file mode 100644 index 000000000000..79bb5888e16c --- /dev/null +++ b/docs/dev/debt/libSceVideoRecordingP.md @@ -0,0 +1,14 @@ +# libSceVideoRecordingP technical debt + +### Unknown function info + +- [_sceVideoRecordingQueryParam_nid_postfix](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - declared with `void(void* param)` signature following [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/VideoRecording.cs#L22); unimplemented and throws `NotImplemented_nid_no_patch` +- [sceVideoRecordingCloseByHandle, sceVideoRecordingGetInfoByHandle](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - inferred handle-based recording control signatures taking an integer recording handle; unimplemented and throw `NotImplemented_nid_no_patch` +- [sceVideoRecordingCopyBGRAtoNV12](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - inferred pixel buffer conversion signature `(void* dst, const void* src, uint32_t width, uint32_t height)`; unimplemented and throws `NotImplemented_nid_no_patch` +- [sceVideoRecordingGetSupportInfo, sceVideoRecordingGetVideoOutMode](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - query signatures taking an output pointer; exact structure layouts and return values are unverified and throw `NotImplemented_nid_no_patch` +- [sceVideoRecordingInit, sceVideoRecordingTerm](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - parameterless initialization and termination stubs; unimplemented and throw `NotImplemented_nid_no_patch` +- [sceVideoRecordingInitialize](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - initialization stub taking an optional parameter structure pointer; unverified and throws `NotImplemented_nid_no_patch` +- [sceVideoRecordingOpen2](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - extended recording open signature; parameters inferred from `sceVideoRecordingOpen` and [fpPS4](https://github.com/red-prig/fpPS4); unimplemented and throws `NotImplemented_nid_no_patch` +- [sceVideoRecordingQueryHddSize2, sceVideoRecordingQueryMemSize2](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - memory and disk size query signatures taking parameter pointer; unverified and throw `NotImplemented_nid_no_patch` +- [sceVideoRecordingRestModeProcessGetStatus, sceVideoRecordingRestModeProcessStart](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - rest mode recording interfaces; unverified and throw `NotImplemented_nid_no_patch` +- [sceVideoRecordingSendCommand](../../../core/libs/prx/libSceVideoRecordingP/Export.cpp) (libSceVideoRecordingP) - command dispatch signature taking integer command ID and parameter pointer; unverified and throws `NotImplemented_nid_no_patch` diff --git a/docs/dev/debt/libSceVoiceChat.md b/docs/dev/debt/libSceVoiceChat.md index f112d0dff5c6..60ad4dc3bb5f 100644 --- a/docs/dev/debt/libSceVoiceChat.md +++ b/docs/dev/debt/libSceVoiceChat.md @@ -1,6 +1,10 @@ # libSceVoiceChat technical debt +### Silent stubs + +- [sceVoiceChatAbortRequest, sceVoiceChatRegisterChannelEventHandler, sceVoiceChatRegisterDaemonEventHandler, sceVoiceChatRegisterMemberEventHandler, sceVoiceChatRegisterPartyUserStateChangedHandler, sceVoiceChatRegisterVoiceEventHandler, sceVoiceChatStartAccessibilityEvent, sceVoiceChatStartChannelEvent, sceVoiceChatStartDaemonEvent, sceVoiceChatStartMemberEvent, sceVoiceChatStartRangedMemberEvent, sceVoiceChatStartVoiceEvent, sceVoiceChatStopAccessibilityEvent, sceVoiceChatStopChannelEvent, sceVoiceChatStopDaemonEvent, sceVoiceChatStopMemberEvent, sceVoiceChatStopRangedMemberEvent, sceVoiceChatStopVoiceEvent, sceVoiceChatUnregisterAccessibilityEventHandler, sceVoiceChatUnregisterChannelEventHandler, sceVoiceChatUnregisterDaemonEventHandler, sceVoiceChatUnregisterMemberEventHandler, sceVoiceChatUnregisterMicEventHandler, sceVoiceChatUnregisterPartyUserStateChangedHandler, sceVoiceChatUnregisterRangedMemberEventHandler, sceVoiceChatUnregisterVoiceEventHandler](../../../core/libs/prx/libSceVoiceChat/Export.cpp) (libSceVoiceChat) - offline handler registration and event controls return 0 without connecting to a voice chat daemon, because the host environment does not run a PSN voice chat service; titles that register event listeners or start/stop audio capture events proceed safely without throwing + ### Unknown function info -- [libSceVoiceChat](../../../core/libs/prx/libSceVoiceChat/Export.cpp) (libSceVoiceChat) - voice chat channel management, audio event, and gain control interfaces; stubs throw until voice chat daemon subsystem is implemented +- [sceVoiceChatAbortRequest, sceVoiceChatGetActiveChannel, sceVoiceChatGetChannelMemberActiveChannelMatchingState, sceVoiceChatGetChannelMemberVoiceConnectionState, sceVoiceChatGetChatChannelMemberInfoList, sceVoiceChatGetChatGroupMemberInfoList, sceVoiceChatGetMicState, sceVoiceChatGetUserVoiceVolumeGain, sceVoiceChatRegisterChannelEventHandler, sceVoiceChatRegisterDaemonEventHandler, sceVoiceChatRegisterMemberEventHandler, sceVoiceChatRegisterPartyUserStateChangedHandler, sceVoiceChatRegisterVoiceEventHandler, sceVoiceChatRequest0, sceVoiceChatRequest1, sceVoiceChatRequestAcsTts, sceVoiceChatRequestAction, sceVoiceChatRequestSetMemberVolume, sceVoiceChatRequestUpdateGameSessionVoiceChatChannelName, sceVoiceChatRequestUpdatePlayerSessionVoiceChatChannelName, sceVoiceChatSetUserVoiceVolumeGain, sceVoiceChatStartAccessibilityEvent, sceVoiceChatStartChannelEvent, sceVoiceChatStartDaemonEvent, sceVoiceChatStartMemberEvent, sceVoiceChatStartRangedMemberEvent, sceVoiceChatStartVoiceEvent, sceVoiceChatStopAccessibilityEvent, sceVoiceChatStopChannelEvent, sceVoiceChatStopDaemonEvent, sceVoiceChatStopMemberEvent, sceVoiceChatStopRangedMemberEvent, sceVoiceChatStopVoiceEvent, sceVoiceChatUnregisterAccessibilityEventHandler, sceVoiceChatUnregisterChannelEventHandler, sceVoiceChatUnregisterDaemonEventHandler, sceVoiceChatUnregisterMemberEventHandler, sceVoiceChatUnregisterMicEventHandler, sceVoiceChatUnregisterPartyUserStateChangedHandler, sceVoiceChatUnregisterRangedMemberEventHandler, sceVoiceChatUnregisterVoiceEventHandler](../../../core/libs/prx/libSceVoiceChat/Export.cpp) (libSceVoiceChat) - parameter layouts are inferred; offline, handlers and event controls return 0, query calls, volume gain controls, and requests return `SIGNED_OUT` (0x80550006) without modifying guest buffers - [sceVoiceChatRequestCreateVoiceChatGroup, sceVoiceChatRequestJoinVoiceChatGroup, sceVoiceChatRequestLeaveVoiceChatGroup, sceVoiceChatRequestDeleteVoiceChatGroup](../../../core/libs/prx/libSceVoiceChat/Export.cpp) (libSceVoiceChat) - assumed to take a request id and two pointers like the other `sceVoiceChatRequest*` calls; offline, every argument is ignored and `SIGNED_OUT` is returned, as for those diff --git a/docs/dev/debt/libSceVrTracker.md b/docs/dev/debt/libSceVrTracker.md new file mode 100644 index 000000000000..918f6cc2d434 --- /dev/null +++ b/docs/dev/debt/libSceVrTracker.md @@ -0,0 +1,5 @@ +# libSceVrTracker technical debt + +### Unknown function info + +- [sceVrTrackerCpuProcess, sceVrTrackerGetResult, sceVrTrackerGetTime, sceVrTrackerGpuSubmit, sceVrTrackerGpuWait and sceVrTrackerInit](../../../core/libs/prx/libSceVrTracker/Export.cpp) (libSceVrTracker) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceVrTracker.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/libc.md b/docs/dev/debt/libc.md index 6798ff76443f..31f3b6eac175 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -3,10 +3,11 @@ ### Unknown function info - [fdopen](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) validates descriptor access modes as FreeBSD 11.4 [fdopen.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdio/fdopen.c) does: a requested mode incompatible with a descriptor not opened for both reading and writing returns null with `EINVAL`, leaving the descriptor open. Windows reads the handle's granted access through the same host helper as `fcntl(F_GETFL)`; a closed descriptor returns null with `EBADF`. Console behavior is unverified. -- [scriptingGetMem, scriptingFreeMem](../../../core/libs/prx/libc/src/ApplicationHeap.cpp) (libc) - runtime allocator hooks for Unity titles. The allocation signature `(alignment, size)`, minimum alignment 16 and null result for a non-power-of-two alignment follow [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/df5b9b92be7dfd463b47756b707d6d356a9f0550/src/libs/libKernel.cpp); the free hook's one-pointer signature follows [Raeen's recorded contract](https://github.com/Whoisraeen/Raeen/blob/2db56923204b89719916b95e29c2f3c4e1d9f90b/docs/unity-dlsym-blocker.md). Both use the registered application allocator. No console measurement; null allocation returns null and other allocator errors throw. +- [scriptingGetMem, scriptingFreeMem](../../../core/libs/prx/libc/src/ApplicationHeap.cpp) (libc) - runtime allocator hooks for Unity titles. The allocation signature `(alignment, size)`, minimum alignment 16 and null result for a non-power-of-two alignment follow [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/df5b9b92be7dfd463b47756b707d6d356a9f0550/src/libs/libKernel.cpp); the free hook's one-pointer signature follows [Raeen's recorded contract](https://github.com/Whoisraeen/Raeen/blob/2db56923204b89719916b95e29c2f3c4e1d9f90b/docs/unity-dlsym-blocker.md). Both use libc's own guest heap, not the title's registered allocator. A title that ships its own `libc.prx` (Metal Gear Solid Master Collection, PPSA18194) has already initialized that allocator, and reaching it from here would run its initializer a second time. That title's IL2CPP runtime looks up only `scriptingGetMem`, by name from `Il2cppUserAssemblies.prx`, and asks it for 256 KiB blocks aligned to 4 KiB. Which console library exports the hooks is unknown, and there is no console measurement. Allocation failure and sizes that overflow return null. - [_Mtx_trylock](../../../core/libs/prx/libc/src/Threads.cpp) (libc, NID `k6pGNMwJB08`) - returns busy for a non-recursive mutex already owned by the caller, assumed to match `_Mtx_lock`'s ownership handling; exact console behavior is unverified. - [nearbyintf](../../../core/libs/prx/libc/src/Math.cpp) (libc, NID `c+4r-T-tEIc`) uses the host rounding mode, not the console's; console rounding mode state remains unverified. +- [__std_find_trivial_unsized_N](../../../core/libs/prx/libc/src/VectorAlgorithms.cpp) (libc) - matches the `libc.prx` bundled with PPSA08427 (disassembly, and that binary run side by side on random inputs): when nothing matches between `first` and the end of its 32-byte-aligned block, the console rescans that whole block without masking and so can return a match up to 31 bytes before `first` - [_Thrd_sleep](../../../core/libs/prx/libc/src/Threads.cpp) (libc, NID `jfRI3snge3o`) follows the Dinkumware threads library shipped with [Visual C++ 2013](https://github.com/adityaramesh/concurrency_runtime/tree/master/VS2013_SP2/crt/src/thr) (`xtime.c`) and C11 Annex K, without console verification. It reads a 16-byte xtime (64-bit seconds and nanoseconds) on the guest `CLOCK_REALTIME`, normalizes the nanoseconds and sleeps through `nanosleep`, retrying on `EINTR`, so it is a cancellation point; a null xtime or one whose normalization overflows throws. The console's `_Thrd_sleep` (disassembled from a title's `libc.prx`) also takes an optional `xtime*` of remaining time and returns `int`; here the sleep is never cut short, so it returns 0 and leaves the remaining time untouched. - [_Mtx_init_with_name](../../../core/libs/prx/libc/src/Threads.cpp) (libc, NID `tgioGpKtmbE`) forwards to `_Mtx_init` and ignores the mutex name parameter. - [_Cnd_timedwait](../../../core/libs/prx/libc/src/Threads.cpp) (libc, NID `McaImWKXong`) follows the console's `_Cnd_timedwait` (disassembled from a title's `libc.prx`), where xtime is an absolute deadline converted against the current time: it returns `_Thrd_timedout` (2) on expiration and `_Thrd_success` (0) on notification. The console returns 4 for other internal pthread errors while this implementation throws. @@ -22,6 +23,7 @@ - [sceLibcMspaceReallocalign](../../../core/libs/prx/libc/src/Mspace.cpp) (libSceLibcInternal, implemented in libc) takes `(void* heap, void* block, size_t alignment, size_t size)`, the order of the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x13880, whose internal routine at 0x12390 treats the third argument as the alignment and, for a null block, passes it to the routine `sceLibcMspaceMemalign` uses). Measured on a PS5 (firmware 13.60) from a homebrew payload: a null block with alignment 4096 and size 100 returns a 4096-aligned block; an alignment of 0 or 3 succeeds and 100 gives a 128-aligned block, as the routine raises it to at least 32 and to the next power of two; an alignment of 2^63 or more returns null, keeps the block and leaves `errno` alone; and a size of 0 returns a block (the same one when it is already suitable) instead of freeing it. A block that is aligned and large enough is kept where it is, without giving back the bytes past the new size as the console's in-place resize does - [acosl](../../../core/libs/prx/libc/src/Math.cpp) (libc) - computed by the host's math library, like the other libc math functions here, not by the console's (FreeBSD msun ld80 code in Sony's `libc.prx`): results can differ from the console's in the last bit, and on Linux glibc sets `errno` to `EDOM` for `acosl` outside [-1, 1] where msun only raises the floating-point exception. `long double` is the x87 80-bit format, as Sony's `frexpl` uses it (`fldt 0x8(%rsp)`, result in `st(0)`) - [atanl, sinl, cosl, tanl, sinhl, coshl, tanhl, expl, exp2l, logl, log10l and powl](../../../core/libs/prx/libc/src/Math.cpp) (libc) - computed by the host's math library, as `acosl` is, not by the console's FreeBSD msun ld80 code: results can differ from the console's in the last bit (glibc's `log10l(1000)` is not exactly 3), and on Linux glibc sets `errno` to `EDOM` or `ERANGE` where msun only raises the floating-point exception. `ldexpl` and `truncl` are exact operations and give the console's result; `logb` and `remainder` give the console's value too, but on Linux glibc sets `errno` (`ERANGE` for `logb(0)`, `EDOM` for `remainder` by zero or of an infinity) where msun only raises the floating-point exception +- [Dinkumware atomics](../../../core/libs/prx/libc/src/DinkumwareAtomics.cpp) (libc) - every operation is sequentially consistent whatever memory order it is given, which is never weaker than the console. Matching the `libc.prx` bundled with PPSA08427 (that binary run side by side on every order from 0 to 7), `_Atomic_load_8` returns 0 for a release, acq_rel or invalid order and the 8-byte exchange and fetch operations return 0 without touching memory for an order above `memory_order_seq_cst`; the generic `_Atomic_copy`, `_Atomic_exchange` and `_Atomic_compare_exchange_*` lock the byte flag passed first, as the console does - [std_execute_once](../../../core/libs/prx/libc/Export.cpp) (libc) - assumed to be the plain-named twin of `std::_Execute_once` (`_ZSt13_Execute_onceRSt9once_flagPFiPvS1_PS1_ES1_`, the callee of Dinkumware `call_once`): it delegates there and gets the [thread.once.callonce] semantics already verified for that symbol (one invocation per flag, concurrent callers block until it completes, and a failing or throwing invocation leaves the flag unready so the next caller runs it). The name equivalence is not confirmed on a title, and the callback convention (called with (null, arg, null), non-zero return meaning success) is the twin's, unverified for this symbol - [__atomic_load, __atomic_store, __atomic_exchange and __atomic_compare_exchange](../../../core/libs/prx/libc/src/AtomicBuiltins.cpp) (libc) - the generic, size-parameterised libatomic entry points are a single `ud2` in the `libc.prx` bundled with PPSA08427 (disassembly), so the console traps on any call; these throw instead - [Dinkumware mutexes and conditions](../../../core/libs/prx/libc/src/Threads.cpp) (libc) use pointer-to-handle arguments, matching the console aerolib signatures reported in [#3079](https://github.com/boykopovar/AnyPS5/pull/3079). The title's `_Mtx_lock` maps `0` to `_Thrd_success` (0), `0x8002000b` to `_Thrd_busy` (3), and other failures to `_Thrd_error` (4); its internal mutex init recognizes recursive flag `0x100`, while every other type uses default attributes. The observed relock failure for a plain mutex suggests those defaults are error-checking, but that has not been confirmed on hardware. The title's `_Mtx_unlock` and `_Cnd_wait` return success regardless of their internal result. The host implementation treats invalid ownership preconditions as no-ops, and throws for invalid handles, host wait errors and destroying a locked mutex as debug guards; those cases are not confirmed against the console. @@ -39,7 +41,8 @@ - [snprintf_s and vsnprintf_s](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) - follow C11 K.3.5.3.5 and K.3.5.3.12 with `RSIZE_MAX` assumed to be `SIZE_MAX >> 1`, as `snwprintf_s` does: a null buffer or format, `size` of 0 or above `RSIZE_MAX`, any `%n` and a null `%s`, `%ls` or `%S` argument return a negative value and set `buffer[0]` to zero when the buffer and size allow it; otherwise the output is truncated to the buffer and the full length is returned. A violation only returns: the constraint handler is not invoked, and the console's error value is unverified. Not confirmed on a PS5 title - [vsscanf_s](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) - returns the count of items converted before an undersized `%s`/`%c`/`%[` destination (0 when it is the first), not EOF. Not confirmed on a PS5 title - [localeconv](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, re-exported by libSceAmpr) - the 0x90-byte Dinkumware struct (empty monetary strings, `CHAR_MAX` for the 14 `char` fields, `decimal_point` ".", `_False`/`_True` "false"/"true") is read from the `libc.prx` that Astro's Playroom ships. It is a fixed C-locale table: whether the console changes it with `setlocale` is not known, and `_Frac_grouping`, `_Frac_sep`, `_No`, `_Yes` are empty strings as in that dump -- [set_constraint_handler_s](../../../core/libs/prx/libc/src/WideConversion.cpp) (libc, re-exported by libSceAmpr) - the default handler is assumed to abort; each module keeps its own handler state, so a handler set through one library is not seen by the other. `ctime_s` and `asctime_s` follow the Dinkumware 26-byte format with `EINVAL` for a buffer under 26 bytes, a null pointer or an out-of-range weekday/month; `ctime_s` uses the guest time zone of `localtime_s`, and the console's exact checks are not verified +- [set_constraint_handler_s](../../../core/libs/prx/libc/src/WideConversion.cpp) (libc, re-exported by libSceAmpr) - the default handler is assumed to abort; each module keeps its own handler state, so a handler set through one library is not seen by the other. Within a module every `_s` function calls the installed handler, with the guest's calling convention. `ctime_s` and `asctime_s` follow the Dinkumware 26-byte format with `EINVAL` for a buffer under 26 bytes, a null pointer or an out-of-range weekday/month; `ctime_s` uses the guest time zone of `localtime_s`, and the console's exact checks are not verified +- [qsort_s](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) sorts as `qsort` does and passes its context as the comparator's third argument, as FreeBSD 13's [`qsort_s`](https://github.com/freebsd/freebsd-src/blob/release/13.0.0/lib/libc/stdlib/qsort.c#L97-L127) (C11 Annex K) does. It returns `EINVAL` after calling the constraint handler when `n` or `es` exceeds `RSIZE_MAX`, or when `n` is not 0 and `a` or `cmp` is null; FreeBSD 11, which the rest of libc follows, has no `qsort_s`. An element size of 0 sorts nothing, where FreeBSD loops over the same address. The console's behaviour is unverified; the Quake homebrew (#6698) imports it from its `libOSMesa.so.8` - [__udivti3](../../../core/libs/prx/libc/src/RuntimeSupport.cpp) (libc, re-exported by libSceAmpr) - the hardware traps on a zero divisor; this throws instead - [__umodti3](../../../core/libs/prx/libc/src/RuntimeSupport.cpp) (libc) - the hardware traps on a zero divisor; this throws instead @@ -58,6 +61,8 @@ - [Wide-character conversions](../../../core/libs/prx/libc/src/Multibyte.cpp) (libc) - The wide `wcsto*` functions parse the ASCII prefix of the guest string, as the C locale accepts only ASCII white space and digits. [`wcsrtombs`](../../../core/libs/prx/libc/src/Multibyte.cpp) uses the guest's single-byte C locale through `wcrtomb`; other encodings and shift states are not supported - [realpath](../../../core/libs/prx/libc/src/RealPath.cpp) (libc) follows FreeBSD 11's [`realpath.c`](https://github.com/freebsd/freebsd-src/blob/stable/11/lib/libc/stdlib/realpath.c) except for symlinks. The console's guest filesystem has none, so a host symlink under the guest root is followed for existence and type but not expanded, and `..` drops the previous component lexically, as `Resolve` does for every guest path. Through a host link `linkdir -> sub/deep`, `linkdir/../x` gives `x` beside the link, not beside its target. - [strtod, strtof, strtold](../../../core/libs/prx/libc/src/Strings.cpp) (libc) - `NAN(n-char-sequence)` converts to the quiet NaN that `nan` and `nanf` return, negated after a minus sign, and ignores the sequence as they ignore their tag. This is inferred from C17 7.12.11.2p2, which makes `nan("n")` equivalent to `strtod("NAN(n)", NULL)`, and not observed on the console. No console `nanl` was observed, so `strtold` returns the x87 default quiet NaN (exponent `0x7fff`, significand `0xc000000000000000`) +- [remquol](../../../core/libs/prx/libc/src/MathExact.cpp) (libc) leaves `*quo` unchanged when the result is NaN; the `libc.prx` bundled with PPSA08427 writes a value that depends on earlier x87 state (`remquol(1, 0)` stored 0 on a first call and 4 on later ones) +- [Long double math in MathExact.cpp](../../../core/libs/prx/libc/src/MathExact.cpp) (libc) was compared with the `libc.prx` bundled with PPSA08427 only for canonical 80-bit encodings; unnormals, pseudo-denormals and pseudo-infinities passed to arithmetic functions (not the classifiers) follow the host x87 instead of the console - [nan and nanf](../../../core/libs/prx/libc/src/Math.cpp) (libc) return fixed quiet NaN values (`0x7fc00000` and `0x7ff8000000000000`), ignoring the tag argument as observed in the console's `libc.prx` (both exports load a global constant via `mov rax,[rip+..]; vmovsd xmm0,[rax]; ret`); VLC (PPSA85300) stops on nanf at start-up after strsep. - [seed48 and lcong48](../../../core/libs/prx/libc/src/Random.cpp) (libSceLibcInternal, implemented in libc) follow the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x6F460 and 0x6F420): the multiplier and addend are shared by the whole `rand48` family, including `erand48`, `nrand48` and `jrand48` on a caller's seed; `lcong48` sets them with the seed, and `srand48` and `seed48` restore 0x5DEECE66D and 0xB. Unlike FreeBSD, `seed48` returns a static copy of the new seed rather than the previous one. A null argument, which the console dereferences, throws - [lgamma](../../../core/libs/prx/libc/src/Math.cpp) (libc) returns the host's `lgamma` but does not store the sign of Γ(x) in the guest's `signgam`, as FreeBSD 11's [`e_lgamma.c`](https://github.com/freebsd/freebsd-src/blob/stable/11/lib/msun/src/e_lgamma.c) does through `__ieee754_lgamma_r`, because no guest `signgam` is exported. PPSA99004 imports it. Console behaviour is unverified @@ -75,6 +80,8 @@ - [sceKernelSync](../../../core/libs/prx/libc/src/General.cpp) (libkernel) on Windows skips a recorded file it cannot open for writing (read-only, or held open by a handle that denies write sharing), since `FlushFileBuffers` needs a writable handle. On Linux, it skips a recorded file it cannot open for reading. Such a file's data may not reach the disk before a host crash; the guest reads the same data either way - [`fopen` streams](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) on Windows share delete access, like the descriptors [`sceKernelOpen`](../../../core/libs/prx/libkernel/File/src/Open.cpp) opens, so a file the guest holds open can be removed or renamed. A stream reopened with `freopen` still goes through `_wfreopen`, which does not share delete access. On Windows, opening a directory in a read mode fails with `EACCES`, where FreeBSD opens it; directory streams are not emulated - The [`_Unwind_Context`](../../../core/libs/prx/libc/include/exceptions/Unwind.hpp) that `_Unwind_Backtrace` passes to its callback starts with a table laid out like LLVM libunwind's `AbstractUnwindCursor` virtual table, because homebrew built with the ps5-payload-dev SDK links libunwind statically and reads the frames with its own `_Unwind_GetIP` (`__unw_get_reg`), which calls through that table. Its [slots](../../../core/libs/prx/libc/src/exception/Unwind.cpp) answer as `UnwindCursor` of [LLVM 18.1.8](https://github.com/llvm/llvm-project/blob/llvmorg-18.1.8/libunwind/src/UnwindCursor.hpp) does with DWARF unwind information: `getReg` and `setReg` of a register number outside -2 (`UNW_REG_SP`) to 16 (`rip`), `getFloatReg` and `setFloatReg` print libunwind's message and abort, as `_LIBUNWIND_ABORT` does; the destructors do nothing; `getInfo` gives the function's range, LSDA and personality from its FDE, `DW_CFA_GNU_args_size` as `gp`, `UNWIND_X86_64_MODE_DWARF` as `format`, the FDE as `unwind_info` and the start of the ELF segment or PE section holding the IP as `extra`, and all zero when no FDE covers the IP. Differences: `step` walks through Windows host frames that have no FDE with `RtlVirtualUnwind`, as `_Unwind_Backtrace` does, and `getInfo` reports them with no range, as `_Unwind_GetRegionStart` does; `step` looks a signal frame up at its IP instead of the IP minus one and ends at a frame whose rules leave `rsp` and `rip` unchanged, where libunwind would return that frame again; `getFunctionName` asks `dladdr` on Linux and macOS but takes the nearest export at or below the IP in the image holding it on Windows, since PE exports carry no size; `jumpto` restores the registers as `_Unwind_RaiseException` installs a landing pad, without `r10` and `r11`. The layout follows libunwind's `UnwindCursor.hpp`, not the console's own `libSceLibcInternal`, which is not checked +- [`fopen` of /dev/random and /dev/urandom](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) in a read mode returns a stream over the host null device whose `fread` fills the buffer with host OS entropy, as the libkernel [random device](../../../core/libs/prx/libkernel/File/src/RandomDevice.cpp) descriptors do. Only the exact paths are recognised, the stream has no descriptor, `setvbuf`, `feof`, `ferror`, `clearerr`, `flockfile`, `ftrylockfile`, `funlockfile` and `fclose` work on it, `fflush` returns 0 as on a FreeBSD read-only stream, and every other stream function throws, as does opening the paths for writing. `freopen` onto these paths still fails with `ENOENT`. ProsperoTV (PPSA99003) reads its phone pairing code this way +- The [`_Unwind_Context`](../../../core/libs/prx/libc/include/exceptions/Unwind.hpp) that `_Unwind_Backtrace` passes to its callback starts with a table laid out like LLVM libunwind's `AbstractUnwindCursor` virtual table, because homebrew built with the ps5-payload-dev SDK links libunwind statically and reads the frames with its own `_Unwind_GetIP` (`__unw_get_reg`), which calls through that table. Only register reads and writes (`validReg`, `getReg`, `setReg`, with `UNW_REG_IP` and `UNW_REG_SP`), `validFloatReg` (always false) and `isSignalFrame` are answered; the other slots throw. The layout follows libunwind's `UnwindCursor.hpp`, not the console's own `libSceLibcInternal`, which is not checked - The Windows libc [exception unwinder](../../../core/libs/prx/libc/src/exception/Unwind.cpp) steps through frames of modules without the project's `.ehmeta` and `.ehfram` tables (system DLLs, and WinLibs GCC 15.2's `libstdc++-6.dll` and `libwinpthread-1.dll`, whose `.eh_frame` sections are empty) with their PE unwind data through `RtlVirtualUnwind`, but does not run Windows exception or termination handlers: it stops at a frame whose unwind data has one, such as a C++ cleanup through `__gxx_personality_seh0` in `libstdc++-6.dll` or a system DLL function like `qsort` in `ucrtbase.dll` (Windows 10 22H2), and the exception is then reported as unhandled - The libc [SSE4a trap emulation](../../../core/libs/prx/libc/src/specifics/windows/Sse4aEmulation.hpp) on Windows is superseded by `--to-intel` and remains only until the relinked title has been verified without it. - [getifaddrs](../../../core/libs/prx/libc/src/Interfaces.cpp) (libc) lists the host's IPv4 and IPv6 addresses in FreeBSD's `ifaddrs` and sockaddr layouts. FreeBSD's per-interface AF_LINK entries (MAC, index, MTU) and their `if_data` are not produced, so interfaces without an address are omitted. Names are the host's (Linux `lo`, `eth0`; Windows NDIS names such as `ethernet_32769`, or `if` when one does not fit 15 bytes), not the console's. A Linux point-to-point address without a peer loses `IFF_POINTOPOINT`, and an IPv4 broadcast address the host omits is computed from the netmask. On Windows the flags are only `IFF_UP`, `IFF_RUNNING`, `IFF_LOOPBACK`, `IFF_BROADCAST` and `IFF_MULTICAST`, derived from the adapter: PPP and tunnel adapters get neither `IFF_BROADCAST` nor `IFF_POINTOPOINT` because their peer address is not reported, and an address whose prefix length the host reports as unspecified gets a host mask. @@ -85,6 +92,10 @@ - [%p and %lp](../../../core/libs/prx/libc/include/WindowsFormatting.hpp) (libc) follow the console's `libSceLibcInternal.sprx` measured from a homebrew payload on firmware 12.70 ([#7549](https://github.com/boykopovar/AnyPS5/issues/7549)), not FreeBSD: bare lowercase hex with no `0x` prefix even under `#`, `0` for a null pointer, `+` and the space flag ignored, the width counting only the digits, `-` padding on the right, `0` padding with zeros when there is no precision, the precision as the minimum number of digits, nothing for a null pointer under `%.0p`, and `l` accepted. Only `snprintf` and `swprintf` of that module were measured, from a payload process rather than a title process, so a width beside `%.0p`, the `0` flag beside a precision (which C17 ignores) and `%.0p` of a non-null pointer follow this formatter's own rules and are not measured; `swprintf` was not run with `%lp`, so the wide family still refuses that conversion and returns -1. Linux routes `%p` and `%lp` to this formatter because glibc prints the FreeBSD form and `(nil)` for a null pointer, which means a format that combines `%p` with a conversion this formatter refuses, such as an argument position above 99, throws there instead of reaching glibc - [collate and collate _Getcat](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc) build the facet for the C locale only and throw for any other locale name, the wide entry point following the narrow one it sits next to. The wide facet carries its own vtable, whose `compare`, `transform` and `hash` take the guest's 16-bit characters, so the length of a range is a count of characters: `compare` orders by 16-bit value (the C locale, as Dinkumware's `char_traits::compare` does), `transform` copies the characters into the result as the guest's bytes and sizes the result in bytes (two per character, a 16-bit terminator, the 7 characters that still fit next to it and `length * 2 | 15` plus two bytes of storage beyond that), and `hash` mixes one whole character per round, so wide text below 0x100 hashes like its narrow spelling and a character above 0xff hashes as one value instead of the two bytes it holds. The console's `libc.prx` exports both `_Getcat` (NIDs BSVJqITGCyI and 2wz4rthdiy8) with identical 0x19d-byte bodies, believed to build the same 0x20-byte facet; the console bodies themselves are unreadable (obfuscated dynsym, and the text of the relinked `libc.prx` is zeroed), so the wide entries follow the narrow algorithm and the C locale instead of the console's wide code, and no console run checks the two. The console's `libc.prx` stores pointers to its collation state tables at offsets 0x10 and 0x18 of the facet; ours leaves them null, as only the facet's own functions read them. `do_transform` throws for input with embedded null characters, which the console drops depending on the output buffer size. The narrow result capacity (`length | 15`) and the over-aligned storage of results of 4080 characters or more follow the console's `libc.prx` code and are not checked on hardware. No module of the PPSA59322, PPSA76677 or PPSA97358 dumps in this tree imports either `_Getcat`, so neither has been exercised by a title. - [The scanf family](../../../core/libs/prx/libc/src/Formatting.cpp) (libc), the `_s` forms included, on Linux reads the 16-bit wide conversions (`%ls`, `%lc`, `%l[`, `%S`, `%C`) in the guest scanner instead of glibc, whose `wchar_t` is 32 bits: each input byte is stored as one 16-bit unit of the same value, as the guest's C-locale `mbrtowc` converts it, and a format without them still goes to the host. `sscanf_s` and `vsscanf_s` take this path on both platforms; Windows hands `%ls`, `%lc` and `%l[` of `sscanf`, `vsscanf` and `fscanf` to the host, whose `wchar_t` is 16 bits. A format that reaches the guest scanner throws for a conversion outside C17's (`%2$C`, `%'ls`). Bytes above 0x7F and locales other than C are not measured on the console +- [The narrow printf family](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) converts 16-bit guest wide arguments to UTF-8 on both platforms, independently of the host locale. It does not use the guest's single-byte `wcrtomb`; the console's treatment of invalid UTF-16 and locale-specific encoding remains unverified. Linux retains host formatting for formats without wide arguments or `%m`, so a narrow `%p` and a NaN there still follow the host (glibc prints a null pointer as `(nil)`, where FreeBSD prints `0x0`, and a NaN with its sign, `-nan`, and with the `+` and space flags, where FreeBSD prints `nan`); argument positions above 99 (FreeBSD's `NL_ARGMAX`) throw when a format requires the guest formatter +- [collate and collate _Getcat](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc) build the facet for the C locale only and throw for any other locale name, the wide entry point following the narrow one it sits next to. The wide facet carries its own vtable, whose `compare`, `transform` and `hash` take the guest's 16-bit characters, so the length of a range is a count of characters: `compare` orders by 16-bit value (the C locale, as Dinkumware's `char_traits::compare` does), `transform` copies the characters into the result and records size and capacity in 16-bit elements (inline capacity 7, heap capacity `length | 7`, `(capacity + 1) * 2` bytes including the terminator), following Dinkumware's [VC12 `basic_string`](https://github.com/ganboing/crt.vc12/blob/f1d47126bf13d1c9dccaa6c53b72217fd0474968/src/xstring#L500-L531), and `hash` mixes one whole character per round, so wide text below 0x100 hashes like its narrow spelling and a character above 0xff hashes as one value instead of the two bytes it holds. The console's `libc.prx` exports both `_Getcat` (NIDs BSVJqITGCyI and 2wz4rthdiy8) with identical 0x19d-byte bodies, believed to build the same 0x20-byte facet; the console bodies themselves are unreadable (obfuscated dynsym, and the text of the relinked `libc.prx` is zeroed), so the wide entries follow the narrow algorithm and the C locale instead of the console's wide code, and no console run checks the two. The console's `libc.prx` stores pointers to its collation state tables at offsets 0x10 and 0x18 of the facet; ours leaves them null, as only the facet's own functions read them. `do_transform` throws for input with embedded null characters, which the console drops depending on the output buffer size. The narrow result capacity (`length | 15`) and the over-aligned storage of results of 4080 characters or more follow the console's `libc.prx` code and are not checked on hardware. No module of the PPSA59322, PPSA76677 or PPSA97358 dumps in this tree imports either `_Getcat`, so neither has been exercised by a title. +- [std::__throw_bad_variant_access](../../../core/libs/prx/libc/src/exception/StandardExceptions.cpp) (libc) throws `std::bad_variant_access` with the `what()` string `bad_variant_access`, the one [LLVM libc++](https://github.com/llvm/llvm-project/blob/main/libcxx/src/variant.cpp) returns, since the export carries libc++'s `__throw_bad_variant_access` name; the console's string is unverified. Onimusha: Way of the Sword (PPSA27836 01.000.001) imports it from `libc`, and its bundled `libc.prx` does not export it. +- [The scanf family](../../../core/libs/prx/libc/src/Formatting.cpp) (libc), the `_s` forms included, on Linux throws for the 16-bit wide conversions (`%ls`, `%lc`, `%l[`, `%S`, `%C`): glibc would store a 32-bit `wchar_t` per character into the guest's 16-bit buffer. Windows hands `%ls`, `%lc` and `%l[` to the host, whose `wchar_t` is 16 bits - [The scanf family](../../../core/libs/prx/libc/include/WindowsScanning.hpp) (libc) on Windows returns 0, as C17 and FreeBSD do, when input fails after only suppressed conversions have completed (`sscanf("12", "%*d%d", &value)`). Linux hands the format to glibc, which returns `EOF` there. On Windows the host stops a `%x`, `%X`, `%i` or `%p` after the `x` of a `0x` that no hex digit follows (or that the width cuts off), and a float conversion (`%e`, `%f`, `%g`, `%a`) after the `e` of an exponent with no digits (`1e`), where C17 and FreeBSD stop before the `x` or the `e` - [Guest path resolution](../../../core/libs/prx/libc/src/General.cpp) falls back to ASCII case-insensitive component lookup, following [shadPS4's host filesystem](https://github.com/shadps4-emu/shadPS4/blob/945dbc3cc3eee80ac3e053b438502ed936fa6bb2/src/core/file_sys/backends/host_fs.cpp). PPSA03747 requests `Skyrim.INI`, `Data/Video` and lowercase archive/plugin names that differ from the dump's filenames. Exact host names take precedence; multiple fallback matches throw instead of selecting an arbitrary file. Existing parent names are resolved when creating a new file, and guest mount aliases use the same ASCII comparison. Results are not cached, so renames and newly created files remain visible. Unicode case folding and PS5 behaviour for case-colliding host entries are unverified; host roots are used as supplied. Explicit native Windows drive and UNC paths bypass guest parent-component validation.. A relinked module's `.guest.prx` is matched the same way when the original module is not beside it: Borderlands 4 (PPSA01494) loads `/app0/prx/libRenoirCore.PS5.prx`, which it ships as `librenoircore.ps5.prx` - [tzname](../../../core/libs/prx/libc/src/Time.cpp) (libc) points at the host's zone names, read at load and again by `tzset`: the abbreviations a `TZ` string gives (`EST5EDT` is `EST` and `EDT` on both hosts), but on Windows without `TZ` the CRT's full names such as `GMT Standard Time`, which ICU's [uprv_tzname](https://github.com/unicode-org/icu/blob/457157a92aa053e632cc7fcfd0e12f8a943b2d11/icu4c/source/common/putil.cpp#L1244-L1264) (ES-DE in PPSA99169) cannot map to a zone ID. A zone with no daylight-saving time has `tzname[1]` empty on Windows and equal to `tzname[0]` on Linux, where FreeBSD leaves three spaces. The console's names are not known diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 6f8a93affe3a..12065718c0bd 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -15,10 +15,11 @@ - [physhm_open, physhm_unlink](../../../core/libs/prx/libkernel/Unimplemented.cpp) (libkernel, NIDs `n371J5cP+uo` and `AUqJNkobQ1c`) - declared as throwing stubs: the PS5 physical shared memory API is not documented, and [shadPS4's module generator](https://github.com/shadps4-emu/moduleGenerator/blob/main/libraries/kernel.cpp) also declares both with no arguments and returns `ORBIS_OK`, so the signature, the handle a success would return and the console's behaviour are unmeasured. The no-argument `int` signature follows shadPS4. Forever Launcher (PPSA99030) imports both and stops at load on `physhm_open`, and the relinker resolves no call site for either in that title, so no handle is fabricated and both throw - [__aps5_get_pending_module_args_nid_no_patch](../../../core/libs/prx/libkernel/Module/src/Module.cpp) (libkernel) - returns a pointer to a default 16-byte argument block `{size: 0x10, version: 0x200, context: nullptr}` when no arguments are pending (`pendingModuleArgs.argp` is null). Unity's `UnifyPS5Plugin.prx` (Cult of the Lamb PPSA06464, #5621) is loaded as a needed library at process start, where the dynamic linker executes its `DT_INIT` before `sceKernelLoadStartModule` runs; its entry function reads `argp[0]` before checking `args` and expects a block of `{0x10, 0x200, ptr}` or `{0x58, 0x103}`, so a null pointer caused a segmentation fault. The console's default module argument block and its contents for imported modules are unverified. - [sceKernelError](../../../core/libs/prx/libkernel/System/src/Error.cpp) follows [shadPS4's kernel implementation](https://github.com/shadps4-emu/shadPS4/blob/23c41333822fe8eca4c609b43119180af63ae4ac/src/core/libraries/kernel/kernel.cpp): zero returns zero; other guest POSIX error values receive the `0x80020000` bias without modifying errno. Arithmetic is defined modulo 32 bits to avoid signed overflow for inputs outside the errno domain. Console behavior for those inputs and PS5 equivalence have not been measured. +- [physhm_open, physhm_unlink](../../../core/libs/prx/libkernel/Unimplemented.cpp) (libkernel, NIDs `n371J5cP+uo` and `AUqJNkobQ1c`) - declared as throwing stubs: the PS5 physical shared memory API is not documented, and [shadPS4's module generator](https://github.com/shadps4-emu/moduleGenerator/blob/main/libraries/kernel.cpp) also declares both with no arguments and returns `ORBIS_OK`, so the signature, the handle a success would return and the console's behaviour are unmeasured. The no-argument `int` signature follows shadPS4. Forever Launcher (PPSA99030) imports both and stops at load on `physhm_open`, and the relinker resolves no call site for either in that title, so no handle is fabricated and both throw - [getlogin_r](../../../core/libs/prx/libkernel/System/src/Login.cpp) (libkernel) returns the host effective account name: `GetUserNameW` as UTF-8 on Windows and `getpwuid_r(geteuid())` on POSIX. A valid account returns a name even without a login session; host `getlogin_r`, `USER` and `LOGNAME` are not consulted. The console's login name and behaviour without a session are unknown. Account lookup or encoding failures, a missing or empty account name, and POSIX account records exceeding the 16 KiB lookup buffer throw; there is no replacement name or retry. A short guest buffer returns `ERANGE` without modifying it, and a null output returns `EFAULT`. - [sceKernelGetSystemSwVersion](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) reports baseline system software version `0x01000000` ("01.000.000") for ProsperoStore (PPSA99000); the console's firmware version is not modeled. Measured on PS5 firmware 13.42: record values 0, 24, 39, 40, 41, 48 and 64 in the leading 64-bit field are accepted and preserved, the 28-byte string at offset 8 and packed version at 0x24 are filled, and bytes from 0x28 onward remain untouched. This API reports "13.590.001" / `0x13590001` in that environment, while the Prospero variant reports "13.420.000" / `0x13420000`; their values must not be assumed identical. A null pointer still returns `SCE_KERNEL_ERROR_EFAULT`, which was not measured; other firmware versions and record values remain unverified. - [kern.rng_pseudo](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `sysctlbyname` fills the whole output buffer with bytes from the host's `std::random_device` and leaves the length unchanged; a write returns -1 with `EPERM` and a call without an output buffer throws. The only caller seen is mbedTLS's `mbedtls_hardware_poll` in the catalog's ProsperoLight boilerplate, which asks for 64-byte blocks and fails if fewer come back. The console's generator, its per-call maximum and its answer to a size query are unknown -- [sysconf](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `_SC_CLK_TCK` returns 128, FreeBSD's `CLK_TCK` as its [`sysconf`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/sysconf.c#L100-L101) returns it ([`time.h`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/include/time.h#L48-L54)); the console's value is not checked, and no `times` reports ticks at that rate. CPython's `posix` module stops its import with "cannot read ticks_per_second" when the value is below 1, as the Python in PS5MC (PPSA99420) does. Names other than `_SC_CLK_TCK`, `_SC_PAGESIZE`, `_SC_NPROCESSORS_CONF`, `_SC_NPROCESSORS_ONLN` and `_SC_PHYS_PAGES` fail with `EINVAL` +- [sysconf](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `_SC_CLK_TCK` returns 128, FreeBSD's `CLK_TCK` as its [`sysconf`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/sysconf.c#L100-L101) returns it ([`time.h`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/include/time.h#L48-L54)); the console's value is not checked, and no `times` reports ticks at that rate. CPython's `posix` module stops its import with "cannot read ticks_per_second" when the value is below 1, as the Python in PS5MC (PPSA99420) does. `_SC_ARG_MAX` returns 262144 (FreeBSD's `NCARGS`), `_SC_OPEN_MAX` returns the `RLIMIT_NOFILE` soft limit via `getrlimit` ([`sysconf.c`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/sysconf.c#L106-L115)), and `_SC_IOV_MAX` returns 1024 (FreeBSD's `UIO_MAXIOV`). Names other than `_SC_ARG_MAX`, `_SC_CLK_TCK`, `_SC_OPEN_MAX`, `_SC_PAGESIZE`, `_SC_IOV_MAX`, `_SC_NPROCESSORS_CONF`, `_SC_NPROCESSORS_ONLN` and `_SC_PHYS_PAGES` fail with `EINVAL` - [hw.realmem](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `sysctl` {`CTL_HW`, 12} and `sysctlbyname` report the same 8-byte value as `hw.physmem` (the `_SC_PHYS_PAGES` pages of 16 KiB), read-only with `EPERM` for a write and `ENOMEM` for a short buffer, as FreeBSD's [`sysctl_hw_realmem`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_mib.c#L191-L204) does. FreeBSD reports there the memory the firmware describes, which is at least `hw.physmem`; the console's values are not known. Dolphin reads it as the machine's memory size in [`MemPhysical`](https://github.com/dolphin-emu/dolphin/blob/8d2ad1a662472955497e5bb79d7b9ec6999e1f0d/Source/Core/Common/MemoryUtil.cpp#L253-L273) (RetroArch PPSA99169's `dolphin_libretro` core) - [vm.overcommit](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `sysctlbyname` reports the 4-byte value 0, the default FreeBSD gives its [`vm.overcommit`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/vm/swap_pager.c#L158-L161) `int`, with `ENOMEM` for a short buffer; the console's value is not known and a write throws. mimalloc reads it in [`unix_detect_overcommit`](https://github.com/microsoft/mimalloc/blob/31d034d94cdb8e22f7d7ed55967f581a2d6e831d/src/prim/unix/prim.c#L129-L153) and takes 0 as no overcommit, as PS5MC (PPSA99420) does at start-up - [kern.sdk_version](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `sysctlbyname` reports the system software version as the 4-byte value `0x01000000` (1.00), the value `sceKernelGetSystemSwVersion` reports; a write returns -1 with `EPERM`. The major version is byte 3 and the minor version byte 2, in hex digits that read as decimal (13.60 is `0x1360xxxx`), as [Capy Mobile](https://github.com/khalifa007/CapyMobile/blob/2758c016/src/ps5/platform.c#L64-L71) (PPSA92280), [remote_lua_loader](https://github.com/n0llptr/remote_lua_loader/blob/7c2840e475/savedata/misc.lua#L195-L209) and the [lapse payload](https://github.com/NetflixNHack/Netflix-N-Hack/blob/605eb93ba5/PS4/payloads/lapse.js#L436-L450) read it. Capy Mobile only displays it. The console's firmware version is not modeled and the low two bytes are unknown @@ -57,6 +58,7 @@ - [sceKernelSyncOnAddressWait8 / sceKernelSyncOnAddressWait16 / sceKernelSyncOnAddressWait32 / sceKernelSyncOnAddressWait64](../../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - libc's `__std_atomic_wait_direct_8/16/32/64` pass a microsecond timeout or null and ignore the result; the by-value comparand, return values and the 2- and 8-byte alignment of Wait16 and Wait64 are assumed - [scePthreadRwlockDestroy, pthread_rwlock_destroy](../../../core/libs/prx/libkernel/Pthread/src/Rwlock.cpp) (libkernel) - measured on a PS5 (firmware 13.60): destroying a rwlock returns 0 whether it is free or held for reading or writing, and leaves 1 in the handle; every call on that handle then returns `EINVAL` until it is initialized again, and a zero handle is initialized on first use by the `scePthreadRwlock` calls as by the POSIX ones. A rwlock destroyed while held is not freed, so its memory leaks. Destroying a rwlock while another thread is acquiring it is not guarded - [scePthreadCondWait / scePthreadCondTimedwait](../../../core/libs/prx/libkernel/Pthread/src/Cond.cpp) (libkernel) - before waiting, FreeBSD libthr's [`cond_wait_common`](https://github.com/freebsd/freebsd-src/blob/main/lib/libthr/thread/thr_cond.c#L1992-L2029) checks ownership through [`_mutex_owned`](https://github.com/freebsd/freebsd-src/blob/main/lib/libthr/thread/thr_mutex.c#L1166-L1178): an unowned mutex returns `EPERM`, while its destroyed marker returns `EINVAL`. An untouched adaptive static initializer (slot value 1) therefore returns `EPERM`; the test covers this, an uninitialized slot, a mutex owned by another thread, and the destroyed marker for both timed and untimed sce and POSIX waits. A recursive mutex locked several times is released completely for the wait and relocked to the same depth afterwards, as FreeBSD libthr does (`_mutex_cv_unlock`, `_mutex_cv_lock`). These FreeBSD-derived behaviors remain unverified on console hardware +- [sceKernelSetEventFlag](../../../core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp) (libkernel) - multi-waiter satisfaction follows fpPS4 in-order evaluation: registered waiters are evaluated in FIFO order and each satisfied waiter's clear mode is applied immediately to the pattern before evaluating subsequent waiters; this ordering is unverified on PS5 console hardware - [sceKernelAioSubmitReadCommandsMultiple / sceKernelAioSubmitWriteCommandsMultiple / sceKernelAioWaitRequests / sceKernelAioPollRequests / sceKernelAioCancelRequest / sceKernelAioCancelRequests / sceKernelAioDeleteRequests](../../../core/libs/prx/libkernel/Aio/src/Aio.cpp) (libkernel) - behaviour from shadPS4 (one id per request for the Multiple submits, cancel turns any request aborted and id 0 reports processing, wait mode 2 returns once one request completed, poll copies every state without waiting); the 128 requests or ids per batch limit is from SharpProspero, with an unknown error, so larger batches throw; wait modes other than 1 (and) and 2 (or) throw; null pointers, negative counts and invalid ids return the codes the single-request siblings use, checked before any state is written - [scePthreadAttr setters and getters](../../../core/libs/prx/libkernel/Pthread/src/Attr.cpp) (libkernel) - measured on a PS5 (firmware 13.60): a null or uninitialized attribute returns `EINVAL`, as does a null output of the stack size, detach state and scheduling parameter getters; a policy other than 1 and 3, an inheritance other than 0 and 4, or a priority outside 256 to 767 (or a null parameter) returns `ENOTSUP` (`0x8002002D`); a detach state other than 0 and 1 and a stack size below 16384 return `EINVAL`; any affinity mask is accepted. A null attribute pointer to `scePthreadAttrInit` and a null output of `scePthreadAttrGetaffinity` crashed the payload, so they still throw. A new attribute holds a 64 KiB stack size on the console and 1 MiB here, which the host code that runs on guest threads needs - [sceKernelGetAvailableCpumask](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) - returns the default thread affinity, 0x1FFF (CPUs 0 to 12), as KytyPS5 does. Titles pin threads within it (PPSA02664 passes 0x1FFB, Hades II PPSA36082 0x3 and 0x3F); whether CPU 13 is available to a title is not confirmed @@ -112,9 +114,9 @@ - [Guest thread scheduling](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) - the policies and priorities that `sched_get_priority_max`, `sched_get_priority_min`, `pthread_setschedparam` and `scePthreadSetschedparam` accept, and those a created thread takes, follow a PS5 (firmware 13.60), but they are only recorded and reported: they are not applied to host threads, so a title's priority order has no effect on host scheduling - [sceKernelMprotect, sceKernelMtypeprotect](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) (libkernel) over a range with a gap that is not registered in the allocation tracker protect the registered parts and skip the gap, and return 0, as FreeBSD 11.4's [`vm_map_protect`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/vm/vm_map.c) does (it has no check for holes); a range whose start is unmapped (also when later pages are mapped) returns `SCE_KERNEL_ERROR_EINVAL` (`mprotect`: `EINVAL`) because an existing test pins that for an unmapped start; FreeBSD answers 0 there too (`vm_map_protect` takes the next entry), so this errno is not taken from the source and was not changed. `sceKernelMprotect` threw for a gap before, which ended the title. Every part is checked for a GPU or image pin before the first one is protected; the parts are then protected one after the other, so a host protection failure in a later part leaves the earlier parts changed (FreeBSD's `vm_map_protect` cannot fail in its second pass). The behaviour on the console is not known. None of this was measured on a console. -- [sceKernelMprotect protection bits](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - bits outside `0x3F7` are dropped instead of refused, as FreeBSD 11's [`sys_mprotect`](https://github.com/freebsd/freebsd-src/blob/34bd34750187bae02a0086402c78c031c59dd8b3/sys/vm/vm_mmap.c#L606) does with `VM_PROT_ALL`. Onimusha: Way of the Sword (PPSA27836 01.000.001) maps direct memory with `0xf2` and then protects it with `0xfffffff2`. Not measured on a console; the map calls and `sceKernelMtypeprotect` still refuse undefined bits -- [JIT shared memory](../../../core/libs/prx/libkernel/DirectMemory/Jit.cpp) (libkernel) - `sceKernelJitCreateSharedMemory`, `sceKernelJitCreateAliasOfSharedMemory` and `sceKernelJitMapSharedMemory` take the signatures of [Orbital](https://github.com/AlexAltea/orbital)'s `tools/ida/db_types.json` (name, length, maximum protection, descriptor out; descriptor, maximum protection, descriptor out; descriptor, protection, address in and out), which ps4sdk, ps5-jar-loader and the call sites in RetroArch PPSA99169, XPSemu PPSA97358 and VLC PPSA85300 match; SharpProspero declares the alias as returning the descriptor. The object is a host anonymous file shared by its descriptors (a `memfd` on Linux, a delete-on-close temporary file mapped as an executable section on Windows), the alias is a duplicate descriptor, and every map maps the whole object, so writes through one view show through the others; a non-null address is a hint, as in `mmap` without `MAP_FIXED`. Not measured on the console: a length that is zero or not a multiple of 16 KiB and protection bits other than CPU read, write and execute are `EINVAL`; an alias or a map asking for more than the descriptor's maximum protection (write and execute counting as read) is `EACCES`, as FreeBSD 11's `vm_map_protect` treats a protection past `max_protection`; a descriptor that is not a JIT object is `EBADF`. The name is ignored, the objects do not count against the flexible memory budget, `mprotect` of a view is not limited to the maximum protection, and `mmap` of the descriptors is unsupported, as for `shm_open`. On Linux the `memfd` is created without `MFD_EXEC`, which Linux 6.3 added and older kernels reject with `EINVAL`, and relies on the protection of the mapping: the flag only sets the file's execute permission bits, and the direct memory `memfd` is mapped executable the same way; a host with `vm.memfd_noexec` set to 2 refuses the creation and the call throws -- [kqueue and kevent](../../../core/libs/prx/libkernel/Equeue/src/Kqueue.cpp) (libkernel) follow FreeBSD 11's [`kern_event.c`](https://github.com/freebsd/freebsd-src/blob/34bd34750187bae02a0086402c78c031c59dd8b3/sys/kern/kern_event.c) (`kqueue_kevent`, `kqueue_register`, `kqueue_scan`, the timer and user filters) over the event queue; nothing is measured on a console. The descriptor is a host null device registered as the kqueue: `close` and `sceKernelClose` close the queue and `dup`/`dup2` of it throw, but `read`, `write` and `fstat` act on the null device instead of failing with `EOPNOTSUPP` or reporting `S_IFIFO`. Read and write events are supported on guest sockets only, plus `EVFILT_READ` on the read end of a pipe (see the kevent on a pipe entry) and the timer, user and [vnode](../../../core/libs/prx/libkernel/Equeue/src/FileEvent.cpp) filters; other filters, `EV_CLEAR` on a socket or a pipe, `EVFILT_READ` on another kqueue, a `NOTE_NSECONDS` period above one second with a fraction (11.0 converts the fraction with the microsecond macro) and any change to an existing timer (11.0 keeps the armed expiry and applies the new period after it, 11.4's `filt_timertouch` restarts it) throw. `EV_DISABLE`, `EV_DISPATCH` and `EV_FORCEONESHOT` follow kern_event.c: a disabled knote stays registered, stops polling or firing and is not reported, a trigger raised while it is disabled is kept and reported after `EV_ENABLE`, where `knote` would drop it, `EV_DISPATCH` reports once and then disables the knote, and `EV_FORCEONESHOT` is ignored on a new knote while on an existing one it sets `EV_ONESHOT` and fires the knote; a negative `nevents` fails with `EINVAL`. A new user event keeps its `fflags` unmasked and is not triggered by `NOTE_TRIGGER`, because `filt_userattach` reads the already cleared `kn_fflags`. The timer limit `kq_calloutmax` (`ENOMEM`) is not enforced, and closing a kqueue wakes a thread waiting on it in `kevent` with `EBADF`, where `kqueue_drain` makes the close wait for that thread. A level-triggered event is reported once per call and moved behind the other events, as in `kqueue_scan`; the `sceKernel*Equeue` calls keep their own collection loop. `NOTE_LOWAT` on a socket sets the low-water mark as FreeBSD 11's [`filt_soread` and `filt_sowrite`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/kern/uipc_socket.c) do, and a later change of the knote replaces it as `kqueue_register` replaces `kn_sdata`: a read event fires once the buffered bytes reach the mark, or at end of file or on a listening socket. A read mark above 1 is taken on stream sockets only: on a datagram socket Linux's `FIONREAD` reports the next datagram where FreeBSD's `sbavail` counts every queued byte, so it throws. A write event takes a mark of 1 only, where the host's writability stands in for one free byte, since the host does not report the send buffer's free space; other write marks and marks below 1, which make FreeBSD's read filter always fire, throw +- [sceKernelMprotect protection bits](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - bits outside `0x3F7` are dropped instead of refused, as a PS5 (firmware 13.60) drops them: Onimusha: Way of the Sword (PPSA27836 01.000.001) maps direct memory with `0xf2` and then protects it with `0xfffffff2`, which the console reports as `0xf3` and then `0x3f3`. In a payload process the console also drops the CPU execute bit (`0x4`) of flexible, direct and JIT memory, and POSIX `mprotect` with write and execute reports write only (`0x2`). A title process may keep execute, so it is kept here. The map calls and `sceKernelMtypeprotect` refuse undefined bits on the console with `EINVAL` (`0x80020016`), and the direct map calls refuse execute; here both throw for undefined bits, and the direct map calls map execute +- [JIT shared memory](../../../core/libs/prx/libkernel/DirectMemory/Jit.cpp) (libkernel) - `sceKernelJitCreateSharedMemory`, `sceKernelJitCreateAliasOfSharedMemory` and `sceKernelJitMapSharedMemory` take the signatures of [Orbital](https://github.com/AlexAltea/orbital)'s `tools/ida/db_types.json` (name, length, maximum protection, descriptor out; descriptor, maximum protection, descriptor out; descriptor, protection, address in and out), which ps4sdk, ps5-jar-loader and the call sites in RetroArch PPSA99169, XPSemu PPSA97358 and VLC PPSA85300 match; SharpProspero declares the alias as returning the descriptor. The object is a host anonymous file shared by its descriptors (a `memfd` on Linux, a delete-on-close temporary file mapped as an executable section on Windows), the alias is a duplicate descriptor, and every map maps the whole object, so writes through one view show through the others; a non-null address is a hint, as in `mmap` without `MAP_FIXED`. Not measured on the console: a length that is zero or not a multiple of 16 KiB and protection bits other than CPU read, write and execute are `EINVAL`; an alias or a map asking for more than the descriptor's maximum protection (write and execute counting as read) is `EACCES`, as FreeBSD 11's `vm_map_protect` treats a protection past `max_protection`; a descriptor that is not a JIT object is `EBADF`. The name is ignored, the objects do not count against the flexible memory budget, `mprotect` of a view is not limited to the maximum protection, and `mmap` of the descriptors maps the object as a [regular file](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp), so its views and the JIT views see each other's writes. On Linux the `memfd` is created without `MFD_EXEC`, which Linux 6.3 added and older kernels reject with `EINVAL`, and relies on the protection of the mapping: the flag only sets the file's execute permission bits, and the direct memory `memfd` is mapped executable the same way; a host with `vm.memfd_noexec` set to 2 refuses the creation and the call throws +- [kqueue and kevent](../../../core/libs/prx/libkernel/Equeue/src/Kqueue.cpp) (libkernel) follow FreeBSD 11's [`kern_event.c`](https://github.com/freebsd/freebsd-src/blob/34bd34750187bae02a0086402c78c031c59dd8b3/sys/kern/kern_event.c) (`kqueue_kevent`, `kqueue_register`, `kqueue_scan`, the timer and user filters) over the event queue; nothing is measured on a console. The descriptor is a host null device registered as the kqueue: `close` and `sceKernelClose` close the queue and `dup`/`dup2` of it throw, but `read`, `write` and `fstat` act on the null device instead of failing with `EOPNOTSUPP` or reporting `S_IFIFO`. Read and write events are supported on guest sockets only, plus `EVFILT_READ` on the read end of a pipe (see the kevent on a pipe entry) and the timer, user and [vnode](../../../core/libs/prx/libkernel/Equeue/src/FileEvent.cpp) filters and a read event on the host stream on descriptor 0, which never triggers, as `select` never reports descriptor 0 readable; PPSA34015 registers descriptor 0 for reading in a new kqueue at start-up and does not check the result; other filters, `EV_CLEAR` on a socket or a pipe, `EVFILT_READ` on another kqueue, a `NOTE_NSECONDS` period above one second with a fraction (11.0 converts the fraction with the microsecond macro) and any change to an existing timer (11.0 keeps the armed expiry and applies the new period after it, 11.4's `filt_timertouch` restarts it) throw. `EV_DISABLE`, `EV_DISPATCH` and `EV_FORCEONESHOT` follow kern_event.c: a disabled knote stays registered, stops polling or firing and is not reported, a trigger raised while it is disabled is kept and reported after `EV_ENABLE`, where `knote` would drop it, `EV_DISPATCH` reports once and then disables the knote, and `EV_FORCEONESHOT` is ignored on a new knote while on an existing one it sets `EV_ONESHOT` and fires the knote; a negative `nevents` fails with `EINVAL`. A new user event keeps its `fflags` unmasked and is not triggered by `NOTE_TRIGGER`, because `filt_userattach` reads the already cleared `kn_fflags`. The timer limit `kq_calloutmax` (`ENOMEM`) is not enforced, and closing a kqueue wakes a thread waiting on it in `kevent` with `EBADF`, where `kqueue_drain` makes the close wait for that thread. A level-triggered event is reported once per call and moved behind the other events, as in `kqueue_scan`; the `sceKernel*Equeue` calls keep their own collection loop. `NOTE_LOWAT` on a socket sets the low-water mark as FreeBSD 11's [`filt_soread` and `filt_sowrite`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/kern/uipc_socket.c) do, and a later change of the knote replaces it as `kqueue_register` replaces `kn_sdata`: a read event fires once the buffered bytes reach the mark, or at end of file or on a listening socket. A read mark above 1 is taken on stream sockets only: on a datagram socket Linux's `FIONREAD` reports the next datagram where FreeBSD's `sbavail` counts every queued byte, so it throws. A write event takes a mark of 1 only, where the host's writability stands in for one free byte, since the host does not report the send buffer's free space; other write marks and marks below 1, which make FreeBSD's read filter always fire, throw - [EVFILT_VNODE, sceKernelAddFileEvent and sceKernelDeleteFileEvent](../../../core/libs/prx/libkernel/Equeue/src/FileEvent.cpp) (libkernel) watch the host file or directory behind the descriptor and turn host changes into FreeBSD 11 vnode hints, which [`filt_vfsvnode`](https://github.com/freebsd/freebsd-src/blob/34bd34750187bae02a0086402c78c031c59dd8b3/sys/kern/vfs_subr.c#L5032) adds whole when they share a bit with the requested `fflags`: a write that grows a file is `NOTE_WRITE | NOTE_EXTEND`, another write `NOTE_WRITE`, a shrinking truncate or an attribute change `NOTE_ATTRIB`, a higher link count `NOTE_LINK`, a lower one or a pending delete `NOTE_DELETE`, a new path `NOTE_RENAME`; in a directory, an entry created, removed or renamed is `NOTE_WRITE`, with `NOTE_LINK` for a subdirectory, and on Linux an entry moved from or to another directory adds `NOTE_EXTEND`; inotify queues the two halves of a rename one after the other, so a watcher woken by the first can read it alone, and a half read without the other is classified only after a `getdents64` on the watched directory, which takes the directory lock that `rename` holds until both halves are queued ([`do_renameat2`](https://github.com/torvalds/linux/blob/v6.8/fs/namei.c#L4975) around `vfs_rename` and its `fsnotify_move`, [`iterate_dir`](https://github.com/torvalds/linux/blob/v6.8/fs/readdir.c#L103)), an ordering taken from the kernel source, not from inotify(7), which promises none; a directory registered with `NOTE_EXTEND` on Windows is accepted, as `vfs_kqfilter` does not check it, but never receives it, because the directory handle cannot tell a move between directories from a removal plus an addition. FreeBSD's hints depend on the filesystem and the console's filesystems are not known: a growing `ftruncate` reports `NOTE_WRITE | NOTE_EXTEND` where `vop_setattr_post` reports `NOTE_ATTRIB`, and on Windows `utimes` on a file reports `NOTE_WRITE`. The three Windows notifications are told apart in the error, which names the `parent`, `entries` or `subdirectories` watch, the path it was given and `GetLastError`, but a `FindFirstChangeNotification` that fails still throws rather than returning an error to the guest: the EVFILT_VNODE attach path is [`vfs_kqfilter`](https://github.com/freebsd/freebsd-src/blob/34bd34750187bae02a0086402c78c031c59dd8b3/sys/kern/vfs_subr.c#L4936), which returns `EINVAL`, `ENOMEM` or 0 after `fget` has turned a descriptor that is not open into `EBADF`, and consults no directory permission, so no FreeBSD errno stands for a host that refuses to notify, and what the console does on such a host is not measured. Only the `parent` watch can fail this way in practice: `entries` and `subdirectories` need the `FILE_LIST_DIRECTORY` that opening the watched directory already took. The parent handle produces no hint of its own, as `RunHostWatch` only calls `FindNextChangeNotification` on it, so a later change may leave the watch on its 50 ms poll instead of throwing; that is not done here. Windows uses `FindFirstChangeNotification` on the parent directory (and on a watched directory itself) and compares `GetFileInformationByHandleEx` snapshots at least every 50 ms, because a write through an open handle is reported only when the handle is flushed or closed, so file hints can arrive 50 ms late, and a file deleted while watched, which the POSIX delete of Windows 10 1903 moves into `$Extend\$Deleted` until its last handle closes, reports `NOTE_DELETE` without `NOTE_RENAME` and keeps its handle without a parent directory watch, as that directory cannot be watched; Linux uses inotify on the file and is not run. `NOTE_REVOKE`, `NOTE_OPEN`, `NOTE_CLOSE`, `NOTE_CLOSE_WRITE`, `NOTE_READ`, unknown bits and descriptors other than files, directories, pipes and sockets throw. `sceKernelAddFileEvent(eq, fd, watch, udata)` and `sceKernelDeleteFileEvent(eq, fd)` take the [OpenOrbis](https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/blob/0a1aaf9d/include/orbis/libkernel.h#L35) and [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0/src/SharpProspero/Interop/Kernel/KernelEqueue.cs#L164) signatures; `watch` is taken as the `fflags` and the event is added with `EV_CLEAR`, neither confirmed on a title (feat(libkernel): implement kevent EV_DISABLE, EV_DISPATCH and EV_FORCEONESHOT) (fix(libkernel): follow kqueue_register for EV_FORCEONESHOT and modify flags) - [sem_timedwait](../../../core/libs/prx/libkernel/Pthread/Posix/Sem.cpp) (libkernel) consumes an available count before checking the timeout and waits indefinitely for a null timeout, following FreeBSD 11's `sem_timedwait`. Console behavior for null and invalid timeouts when a count is available is unverified. - [tcsendbreak, tcdrain, tcflush, tcflow, tcgetpgrp, tcsetpgrp](../../../core/libs/prx/libkernel/File/src/Terminal.cpp) (libkernel) follow FreeBSD 11.4's [`termios.c`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/termios.c): `tcflush` answers `EINVAL` for a queue other than `TCIFLUSH`, `TCOFLUSH` or `TCIOFLUSH` and `tcflow` for an action outside `TCOOFF`..`TCION` before the descriptor is looked at, and every other call fails as its terminal ioctl does on a descriptor that is not a terminal, with the errors `tcgetattr` gives, so `tcsendbreak` never reaches its 0.4 s pause. FreeBSD's `pipe_ioctl` answers `TIOCGPGRP` and `TIOCSPGRP` on a pipe or FIFO with the descriptor's SIGIO owner, which is not modelled, so `tcgetpgrp` and `tcsetpgrp` throw on a host pipe or FIFO above descriptor 2; descriptors 0 to 2 are taken as non-terminals that are not pipes, whatever the host attaches to them, so a pipe the guest creates on a freed descriptor 0 to 2 gets `ENOTTY` there. The PS5 `libkernel` is assumed to use this FreeBSD code, and `tcdrain` is not a cancellation point here @@ -161,7 +163,7 @@ - [waitpid](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) returns -1 with `ECHILD` (`EINVAL` for unknown options): nothing here creates a child process; child processes would need a real `waitpid`. - [execv](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) throws: replacing the process image is not supported, the same as `execvp`. PPSA97358 imports it and fails to load without it. - [execve](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) throws like `execvp`: replacing the process image is not supported. The console behavior for invalid arguments and executable path lookups is not modeled. -- [sysarch](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - `AMD64_GET_FSBASE`, `AMD64_GET_GSBASE` and `AMD64_SET_GSBASE` follow FreeBSD 11's amd64 [`sysarch`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/amd64/amd64/sys_machdep.c) on Linux x86-64 hosts, through the host's `arch_prctl`. The GS base belongs to the calling thread, a base at or above `0x800000000000` (`VM_MAXUSER_ADDRESS`) returns `EINVAL`, and a null argument returns `EFAULT`. `AMD64_SET_FSBASE` throws, since guest and host code share the thread's FS base for their TLS. The other operations, and every operation on other hosts (Windows keeps its TEB in GS), throw. What a new thread's GS base starts as is not modelled: Linux threads inherit their creator's +- [sysarch](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - `AMD64_GET_FSBASE`, `AMD64_GET_GSBASE` and `AMD64_SET_GSBASE` follow FreeBSD 11's amd64 [`sysarch`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/amd64/amd64/sys_machdep.c) on Linux x86-64 hosts, through the host's `arch_prctl`. The GS base belongs to the calling thread, a base at or above `0x800000000000` (`VM_MAXUSER_ADDRESS`) returns `EINVAL`, and a null argument returns `EFAULT`. `AMD64_SET_FSBASE` throws, since guest and host code share the thread's FS base for their TLS. In a payload process on a PS5 (firmware 12.70), `I386_GET_FSBASE` (7) and `I386_GET_GSBASE` (9) wrote 4 bytes, the low 32 bits of the FS and GS bases, and failed with `EFAULT` for a null argument; here they do the same on Linux x86-64 hosts. There a GS base set by one thread left the main thread's unchanged, and threads made with `pthread_create` and `scePthreadCreate` started with their creator's GS base; here a guest thread's host thread is created by the creating thread, and Linux's [`copy_thread`](https://github.com/torvalds/linux/blob/v6.6/arch/x86/kernel/process.c#L182-L186) gives a new thread its creator's GS base. The operation numbers FreeBSD 11's amd64 `sysarch` has no case for returned `EINVAL` there for the nine tried, each with a null argument (2, 5, 6, 12, 127, 133, 134, 1000 and -1; 6 is `I386_VM86`, which its header names), as its [default case](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/amd64/amd64/sys_machdep.c#L324-L325) does; here every such number returns `EINVAL` on every host; the numbers other than those nine follow that default case and were not measured. `AMD64_GET_XFPUSTATE` (132) returned 0 there for lengths 0, 64, 128, 256 and 512 (512 was also the largest length a search accepted) and failed with `EINVAL` for 1024, 2048, 4096 and 8192 and with `EFAULT` for a null argument or address, and `I386_GET_XFPUSTATE` (11) failed with `EFAULT` for a null argument; both throw here. The other operations FreeBSD 11's amd64 `sysarch` handles, and every operation it handles on other hosts (Windows keeps its TEB in GS), throw. - [kern.proc.pathname](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) returns `/app0/eboot.bin` for pid -1 and the calling process, following FreeBSD's `sysctl_kern_proc_pathname`; the exact pathname on retail consoles remains unverified. - [kern.proc.vmmap](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - `sysctl` {`CTL_KERN`, `KERN_PROC`, `KERN_PROC_VMMAP`, pid} returns packed FreeBSD 11 `kinfo_vmentry` records (144 bytes each, an empty path) for the used ranges of the guest address-space arena, in address order, as [`kinfo_getvmmap`](https://github.com/freebsd/freebsd-src/blob/releng/11.0/lib/libutil/kinfo_getvmmap.c) reads them. A range is one record: the type is always `KVME_TYPE_DEFAULT`, the protection is `sceKernelVirtualQuery`'s for the range's first page (0 where it reports none), and offset, object, resident page counts, flags and path are 0. Only the calling process's pid is accepted (`ESRCH` otherwise, -1 included, as FreeBSD's `pget` gives); a write returns `EPERM` and a short buffer `ENOMEM`. Ranges outside the arena, which guest code cannot map, are not reported. Whether a PS5 application may read its own map, and what types and paths the console reports, is unverified - [machdep.cacheline_size](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) returns -1 with `ENOENT`, matching FreeBSD amd64 where this sysctl OID is not defined. diff --git a/docs/dev/debt/libkernel_sys.md b/docs/dev/debt/libkernel_sys.md index b45c18af4c19..4590e03b1476 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -13,3 +13,4 @@ - [renameat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `rename`. Any other relative path is joined to its descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to relative descriptors, 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. Console behaviour is unverified - [fork](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) throws: creating a child process is not supported. PPSA19111 imports it and fails to load without it. Its engine calls `fork` in `Posix_Daemonize`, only with `-daemonize`, and in `Platform_ShellExecute` before `execvp("xdg-open")`, which the main menu's Previews button reaches, so pressing Previews ends in this throw. [setsid](../../../core/libs/prx/libkernel_sys/Export.cpp) fails with `EPERM`, as FreeBSD's does for a process group leader, since the guest is taken to lead its own process group (see `kill`). [umask](../../../core/libs/prx/libkernel_sys/Export.cpp) keeps the guest's mask (initially 022, masked to 07777) and returns the previous one, Linux named shared-memory creation applies it, but ordinary files are still created with the host process's mask - [chroot](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) follows FreeBSD 11.4's [`sys_chroot`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/vfs_syscalls.c#L776-L807) and [`pwd_chroot`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L3059-L3085) for a root caller: `EFAULT` for a null path, `ENOENT` for an empty or missing one, `ENAMETOOLONG` for a path of 1024 bytes or more or a component over 255, and `ENOTDIR` for a path that is not a directory or passes through a file. A directory that is the guest root (`/`, `/..`, or `.` after `fchdir` to a descriptor of `/`) succeeds and changes nothing, as the root stays the same directory; the guest is taken never to have changed its root, so open directory descriptors do not make it fail with `EPERM` (`kern.chroot_allow_open_directories` 1). Any other directory throws, naming the path: the guest path resolver has a fixed root and mount aliases (`/app0`, `/savedata0`) that do not move with it. On the console a title starts in a sandbox root, where `pwd_chroot` refuses with `EPERM` while a directory is open; whether that holds for titles, and whether a title holds `PRIV_VFS_CHROOT`, are unverified. Porpoise (PPSA99764) weak-imports it and calls it only as `fchdir` to its saved descriptor of `/` then `chroot(".")`, to put its sandbox root back after a jailbreak daemon replaced it ([`put_root_back`](https://github.com/elripalda/Porpoise-Dolphin-Emulator-for-PS5/blob/2ed1ab5a06995aa198f7bccd4da0f90c3fc29d39/src/porpoise_jailbreak.cpp#L303-L331)) +- [fchown](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - ownership is not modelled: the guest's own owner and group (0) and the unchanged value (-1) are accepted, a descriptor that is not open fails with `EBADF`, and any other owner or group throws, naming it. The check reads the descriptor with `fstat`, ignoring the CRT's invalid-parameter handler on Windows as `DescriptorControl` does. The console's ownership rules are unverified. Morrowind (`mshivam019/morrowind-ps5`) imports it diff --git a/docs/dev/debt/shader.md b/docs/dev/debt/shader.md index b2917b84776c..e2b3e0418b21 100644 --- a/docs/dev/debt/shader.md +++ b/docs/dev/debt/shader.md @@ -21,7 +21,7 @@ - [s_sethalt](../../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp) (core/shader/recompiler) - with SIMM16[0] clear it is a no-op: the RDNA 2 ISA guide describes that value as resuming a halted wave, and a wave that executes it is not halted; measured on a gfx1035 (RDNA 2) wave: `s_sethalt 0` followed by a VALU op completes and gives the normal result. Only SIMM16[0] is read; with it set the instruction throws - [s_subvector_loop_begin and s_subvector_loop_end](../../../core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp) (core/shader/recompiler) - follow AMD's RDNA 2 instruction set reference and a gfx1036 (wave64) measurement, pinned in [SubvectorLoop.cpp](../../../core/libs/prx/libSceAgcDriver/tests/execution/SubvectorLoop.cpp); `S0` in the end instruction's `elif(S0 == 0)` is read as its SGPR operand. A wave32 shader and a destination other than an SGPR throw because neither is measured. Measured on PC RDNA2 (Radeon 610M), not on a PS5. The [specialization pass](../../../core/shader/recompiler/SpirvBackend/src/SpirvSpecialization.cpp) keeps a constant-condition branch whose untaken target is a loop header, since folding it removes the loop's back edge, which SPIR-V requires exactly once per loop header (SPIRV-Tools rejects the module and the NVIDIA driver 615.78.08 crashes compiling it); a loop whose back-edge block becomes unreachable in another way is not handled - [Vertex entry merged wave info](../../../core/shader/recompiler/Translation/src/InstructionTranslator.cpp) (recompiler) - `s3` of every vertex-path shader is set to `waveSize | waveSize << 8` (vertex and primitive count of a full wave, wave index 0), or to `32 | 32 << 8` for a wave64 program on a 32-lane host subgroup, which runs it at one lane per invocation and holds no lanes 32-63. On a device whose `subgroupSupportedStages` lacks the vertex stage (lavapipe, MoltenVK), vertex and local programs run as one-lane waves: `s3` is `1 | 1 << 8`, the lane id is 0, a ballot is the invocation's own bit, a shuffle or broadcast returns the invocation's value, an elect is true and a subgroup reduction is the value itself, so no `GroupNonUniform` capability is declared. Layout taken from the prologue Mesa's ACO emits for NGG vertex shaders (`s_bfe_u64 exec` from `s3`), not measured on hardware. The driver gives every graphics program user data from `s8`, so the condition does not tell NGG from legacy vertex shaders: they just never read `s3`. A draw whose last wave is partial still reports a full wave, and so does a host wave with idle invocations (an RTX 5070 ran every vertex warp of a non-indexed triangle list with 30 of its 32 invocations). -- [s_barrier in vertex-path programs](../../../core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp) (recompiler) - emits no instruction when the program has no LDS or GDS access and no memory write or atomic, so no wave of the subgroup can observe another; the `GS_ALLOC_REQ` such a barrier orders on NGG passthrough programs is already dropped on the vertex path. The Vertex execution model has no workgroup barrier. Programs with LDS, GDS or memory writes throw. Not measured on hardware +- [s_barrier in vertex-path programs](../../../core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp) (recompiler) - emits no instruction when the program has no LDS or GDS access and no memory write or atomic, so no wave of the subgroup can observe another, or when the barrier runs before every memory access of the program (it is on the entry's straight-line path with no load, store, atomic, image or LDS access ahead of it), so it orders nothing; the `GS_ALLOC_REQ` such a barrier orders on NGG passthrough programs is already dropped on the vertex path. The Vertex execution model has no workgroup barrier. Other programs with LDS, GDS or memory writes throw. Not measured on hardware - [Vertex clip and cull distances](../../../core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp) (libSceAgcDriver) - `PA_CL_VS_OUT_CNTL` enable bit `s` of `CLIP_DIST_ENA`/`CULL_DIST_ENA` is taken to select component `s` of the `pos1`/`pos2` exports, with the Vulkan array index counted by the enabled bits below it, and `VS_OUT_CCDIST0_VEC_ENA` as bit 22; this follows the recompiler's existing position-export mapping, not hardware or documentation. The guest clip distances share one `gl_ClipDistance` array with the all-zero clip position distance, which takes the index after them. A vertex program whose own code has no `pos1`..`pos3` export is compiled and prepared without these bits (`PositionExportLayout`), so registration and draws agree on its artifact; an export in a subroutine it calls is not seen by that scan - [32_AR color exports](../../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) (shader recompiler) - with `SPI_SHADER_COL_FORMAT` 3 the red value is export channel 0 and the alpha is export channel 1, the GFX10 layout of Mesa's [`emit_ps_color_export`](https://gitlab.freedesktop.org/mesa/mesa/-/blob/3e32b0e1f132231b7a972c1b0b5b06c7e16cf8b9/src/amd/common/nir/ac_nir_lower_ps_late.c#L400-L411), ACO and radeonsi in Mesa 22.0 ([aco_instruction_selection.cpp](https://gitlab.freedesktop.org/mesa/mesa/-/blob/a82920af6efa4a7a66919885c6477fbd8fdc7b9d/src/amd/compiler/aco_instruction_selection.cpp#L11087-L11096), [si_shader_llvm_ps.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/a82920af6efa4a7a66919885c6477fbd8fdc7b9d/src/gallium/drivers/radeonsi/si_shader_llvm_ps.c#L323-L333)) and AMD's [LLPC](https://github.com/GPUOpen-Drivers/llpc/blob/40cb8d95ad8d6f7f1652e3fd47d39667594cce08/lgc/lowering/FragmentColorExport.cpp#L157-L168); before GFX10 the alpha was channel 3. Channels 2 and 3, which none of these compilers enables, are ignored; green and blue are 0 and the alpha of an export without channel 1 is 1.0, the defaults of the other formats, which no source gives. Not measured on hardware, neither on gfx1013 nor on a PC RDNA 2 GPU; pinned in [Pixel32ArExport.cpp](../../../core/libs/prx/libSceAgcDriver/tests/execution/Pixel32ArExport.cpp) @@ -62,7 +62,7 @@ - [MIMG PCK2/PCK4 loads](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) (`image_load[_mip]_pck2/pck4`, opcodes 0x70, 0x71, 0x73 and 0x74) were measured on RDNA2 (gfx1035) with the [hardware oracle](../HW_ORACLE.md) for 2D images with identity swizzle. With data mask 0x1, they write one dword: the raw bits of the N texels (PCK2: 2, PCK4: 4) from `x & ~(N - 1)` on row y, concatenated (element k at bit `k * texel bits`) and zero-extended. The dword is zero when the mip level is past the last level, `x + N - 1` is outside the level's width (unaligned x, unsigned), or y is outside its height. Data only comes back when N texels fit in 32 bits: R8, R16 or RG8 for PCK2, R8 for PCK4. Wider texels (R32, RG16, RGBA8) and masks other than 0x1 write zeros on hardware. Those masks throw at decode, and formats outside UNORM/UINT/SINT R8, R16 and RG8 throw. #1314's earlier measurement saw only zeros because it used masks 0x3 and 0xf. The stores (0x76, 0x77, 0x79 and 0x7a) decode but throw at translation. They are not measured, and opcode 0x76 on 32_32_32_32_FLOAT hung the GPU (#1314). - [3D image accesses bound to a 2D or 2D-array T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) follow CouchRiv's [hardware oracle](../HW_ORACLE.md) measurement on RDNA2 (gfx1036, #2023) and adriadam10's (gfx1035, #2140), not the PS5's gfx1013. On a 2D T#, `image_sample_lz` ignores r and reads the (u,v) texel, `image_load` and `image_load_mip` read the (x,y) texel when z is 0 and return zero otherwise, and `image_get_resinfo` returns (width, height, 1, levels). On a 2D-array T#, these accesses behave like dim 2D-array: r or z is the layer, and `image_get_resinfo` returns the layer count as its third component. Every other access of such an image, including samples with an LOD, bias, derivatives, offset or comparison, gathers, `image_get_lod` and stores, keeps throwing because it is not measured. - [2D-array samples through a 3D T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) (`image_sample`, `image_sample_l` and `image_sample_lz` with DIM 2D_ARRAY) sample the volume as DIM 3D does: the third address VGPR is the normalized r coordinate, which the sampler's Z mode wraps or clamps, not a layer index, and a plain sample in a pixel shader takes its LOD from the derivatives of all three coordinates. Measured on RDNA2 (RX 6700 XT, gfx1031, driver 32.0.21045.5002, Windows 11) with the [hardware oracle](../HW_ORACLE.md) template run through the driver's comgr and HIP runtime, not on the PS5's gfx1013: on 16x16x4 R32_FLOAT 3D T#s with one and three levels, the three instructions with DIM 2D_ARRAY returned the same value as with DIM 3D in all 256 lanes of 66 cases (point, bilinear and z-linear filters, wrap addressing, r from -0.125 to 3, quad steps of 1 to 4 texels in u and v and of 0 to 4 slices in r, LOD 1). Other samples of such a pair (bias, LOD clamp, derivatives, offsets, comparison, 16-bit addresses), gathers, `image_get_lod` and a sample mixed with a load of the same image keep throwing because they are not measured. The host sampler ignores `Z_FILTER`, as for every 3D sample, so r is filtered with the XY filter. -- [2D accesses through a cube T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) (#4139) read the T#'s `BASE_ARRAY` face as a cube access with face coordinate 0 does, measured on RDNA1 (gfx1010, RX 5700 XT) with the [hardware oracle](../HW_ORACLE.md) template run through the Windows driver's comgr and HIP runtime on 8x8 and 32x32 32_FLOAT cubes (`DEPTH` 5 and 11, `BASE_ARRAY` 0 and 6): `image_sample_lz`, plain `image_sample` (one level, and six levels in 2x2 compute quads) and `image_sample_l` (the third address VGPR is the LOD, integer and fractional) with DIM 2D equal DIM CUBE with face 0 on every row, with point and bilinear filters, wrap, clamp and mirror addressing and `DISABLE_CUBE_WRAP` 0 and 1, and the VGPR after the two coordinates is not read; `image_load` with DIM 2D reads texel (x, y) of the `BASE_ARRAY` face. So the face coordinates are cube coordinates in [1, 2]: the texel is `floor((u - 1) * width)`. They are translated through the cube mode DIM CUBE accesses use, which samples the face of the 2D-array view at (u - 1, v - 1) with the S# addressing. That matches the hardware inside the face, except within half a texel of its edges with a bilinear filter and `DISABLE_CUBE_WRAP` 0, where the hardware blends texels of the neighbouring faces (also measured on RDNA2, gfx1035, 8x8 cubes with `BASE_ARRAY` 0 and 6). Outside it the hardware clamps a coordinate below 1 to the first texel under every filter, wrap mode and `DISABLE_CUBE_WRAP` setting measured, clamps a coordinate of 2 or more to the last texel when `DISABLE_CUBE_WRAP` is 0 (point filter) and applies the S# addressing to it when it is 1; the translation does none of this, as for DIM CUBE. Only `image_sample_lz`, plain `image_sample`, `image_sample_l`, mip-less `image_load`, and mip-less `image_store` to an image the program only stores to take this mode. Such a store writes texel (x, y) of the `BASE_ARRAY` face, measured on RDNA2 (gfx1036, [by simonbbby](https://github.com/boykopovar/AnyPS5/pull/6799#issuecomment-6217609589), not the PS5's gfx1013) with the [hardware oracle](../HW_ORACLE.md) on 8x8 mip-less cube arrays (`DEPTH` 11) of 32_UINT, 32_FLOAT and 16_16_16_16_FLOAT ([the last also by simonbbby](https://github.com/boykopovar/AnyPS5/pull/6799#issuecomment-6224023050), with exactly representable half values) with `BASE_ARRAY` 0 and 6, wave32 and wave64: the 64 stored texels land on that face and every other byte of the 64 KiB backing is unchanged. Other formats, and Crimson Desert's own 256x256 tiled descriptor, are not measured. Other DIM 2D accesses through a cube T# (gathers, bias, comparison, offsets, derivatives, atomics, stores to an image the program also reads, stores of formats written through a conversion such as 32_SINT, mip loads and stores) still throw. +- [2D accesses through a cube T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) (#4139) read the T#'s `BASE_ARRAY` face as a cube access with face coordinate 0 does, measured on RDNA1 (gfx1010, RX 5700 XT) with the [hardware oracle](../HW_ORACLE.md) template run through the Windows driver's comgr and HIP runtime on 8x8 and 32x32 32_FLOAT cubes (`DEPTH` 5 and 11, `BASE_ARRAY` 0 and 6): `image_sample_lz`, plain `image_sample` (one level, and six levels in 2x2 compute quads) and `image_sample_l` (the third address VGPR is the LOD, integer and fractional) with DIM 2D equal DIM CUBE with face 0 on every row, with point and bilinear filters, wrap, clamp and mirror addressing and `DISABLE_CUBE_WRAP` 0 and 1, and the VGPR after the two coordinates is not read; `image_load` with DIM 2D reads texel (x, y) of the `BASE_ARRAY` face. So the face coordinates are cube coordinates in [1, 2]: the texel is `floor((u - 1) * width)`. They are translated through the cube mode DIM CUBE accesses use, which samples the face of the 2D-array view at (u - 1, v - 1) with the S# addressing. That matches the hardware inside the face, except within half a texel of its edges with a bilinear filter and `DISABLE_CUBE_WRAP` 0, where the hardware blends texels of the neighbouring faces (also measured on RDNA2, gfx1035, 8x8 cubes with `BASE_ARRAY` 0 and 6). Outside it the hardware clamps a coordinate below 1 to the first texel under every filter, wrap mode and `DISABLE_CUBE_WRAP` setting measured, clamps a coordinate of 2 or more to the last texel when `DISABLE_CUBE_WRAP` is 0 (point filter) and applies the S# addressing to it when it is 1; the translation does none of this, as for DIM CUBE. Only `image_sample_lz`, plain `image_sample`, `image_sample_l`, mip-less `image_load`, and mip-less `image_store` to an image the program only stores to take this mode. Such a store writes texel (x, y) of the `BASE_ARRAY` face, the face the measured `image_load` reads; stores were not measured on hardware. Other DIM 2D accesses through a cube T# (gathers, bias, comparison, offsets, derivatives, atomics, stores to an image the program also reads, stores of formats written through a conversion such as 32_SINT, mip loads and stores) still throw. - [Unnormalized S#s](../../../core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp) are accepted only when direct and used only by explicit-LOD `image_sample` without comparison, derivatives or `_a`, on a single-level, single-layer 1D or 2D view of mip 0, even when the image is also sampled through a normalized S#. - [Unnormalized texel offsets](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) are added to the f32 coordinates: exact on RDNA2 (gfx1035), which applies the offset before the clamp ([measured by adriadam10](https://github.com/boykopovar/AnyPS5/pull/822#issuecomment-6033192725)). - [Cube image gradients](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) (shader recompiler) - `image_sample_d` and `image_sample_cd` on a cube pass their gradients to the host sample without the -1.0 that moves the face coordinates from [1, 2] to [0, 1], as Mesa's AMD lowering builds them (`src/amd/common/nir/ac_nir_lower_tex_coords.c`, `prepare_cube_coords`: face-space derivatives with no offset, coordinates offset by 1.5). Measured on RDNA2 (gfx1035) with the [hardware oracle](../HW_ORACLE.md): on a 16x16 32_FLOAT cube with five levels, point mip filter, `image_sample_d` reads the level of `round(log2(16 * max(|ddx|, |ddy|)))` for gradients of 2^(level ± 0.3) texels on each of the four components, both signs, mixed components, every face and two coordinates (1032 rows). `image_sample_cd` takes one level per quad of lanes, from the gradients of its first lane; the host sample computes it per lane. @@ -95,6 +95,7 @@ - On a host subgroup narrower than the wave, [`v_readlane_b32`](../../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp) of a constant lane outside it builds only as lane 63 of a half-wave scan (DPP row_shr 1/2/4/8, `v_permlanex16_b32`, EXEC -1) or, in a program whose entry EXEC is every lane (vertex, local and tessellation stages), as 0 when the value is 0 in lanes 32-63; other such reads throw. A value is 0 there when it is built from 0 through selects under a lane index (lane id or `v_mbcnt` of -1) below at most 32, `v_cmp_ne_u32` against 0, OR, DPP and permlanes; the counts in `s3` give such a bound on a 32-lane host. On a 32-lane host a half-wave scan may also run under `s_mov_b64 exec, -1`, use bound_ctrl:1 steps for an OR, add or unsigned max, and take keys that are 0 in lanes 32-63; its lane 31 is then the subgroup reduction of the host's invocations (a shuffle of the scan on a device without subgroup arithmetic): idle invocations add the identity, where the console would add the keys of the lanes `s3` claims. - [NGG mesh entry](../../../core/shader/recompiler/Translation/src/InstructionTranslator.cpp) uses the guest wave size (32 or 64) for wave indices, per-wave vertex and primitive counts, and the workgroup wave count in `s3`. The four-bit wave fields follow Mesa's [get_wave_id_in_tg, get_tgsize and get_thread_id_in_tg](https://chromium.googlesource.com/chromiumos/third_party/mesa/+/refs/heads/stabilize-13982.70.B-chromeos-amd/src/gallium/drivers/radeonsi/gfx10_shader_ngg.c); partial waves and groups exceeding 15 waves throw. Indexed and non-indexed wave32 mesh rendering with 32, 64 and 128 threads was checked by pixel readback on Windows with an NVIDIA RTX 4070 Laptop GPU. The guest register values have not been compared with console hardware. - [Mesh shader grid size](../../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) (shader recompiler) - every mesh shader loads `NumWorkgroups.x` and declares no vertices when it is 0, which no dispatch produces. The read is a workaround for RADV (Mesa 26.2.4, seen on an RX 9070 XT, GFX12): binding a mesh pipeline whose shader reads neither `NumWorkgroups` nor `DrawIndex` keeps the user SGPR count and draw-id flag of the vertex pipeline bound before it in the same command buffer (`radv_bind_pre_rast_shader` updates them only when the shader has the `AC_UD_VS_BASE_VERTEX_START_INSTANCE` slot), so `radv_emit_userdata_mesh` opens a 3-register `SET_SH_REG` and writes one value, the CP takes the following `DISPATCH_MESH_DIRECT` as register data and the GPU hangs (gfx ring timeout, MODE1 reset). Kena: Bridge of Spirits (PPSA01802) hits it on its layered volume draws. Reading `NumWorkgroups` gives every mesh shader the grid-size slot, so the count and the values match; drop the read once a fixed Mesa is the minimum +- [Mesh layer output](../../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) (shader recompiler) - a mesh shader writes each primitive's layer through a one-member `Block` struct array whose member is decorated `BuiltIn Layer` and `PerPrimitiveEXT`, as glslang declares `gl_Layer` inside `gl_MeshPerPrimitiveEXT`, not through a standalone `PerPrimitiveEXT` `Layer` array. Both forms pass `spirv-val`, but AMD's Windows driver (32.0.21045.5002, RX 6700 XT) crashes in `amdvlk64.dll` creating a pipeline whose mesh shader writes the standalone form, whether the layer is signed or unsigned and whatever the target, while the block form draws, also with a pixel shader that reads the layer. - [Shader recompilation](../../../core/shader/recompiler/Recompiler.cpp) currently occurs right before it was transferred to Vulkan with caching, but should be moved to the [relinker](../../../core/relinker/main.cpp) stage. For this purpose, [shader/recompiler](../../../core/shader/recompiler) was written completely independently from [libs/prx](../../../core/libs/prx). - [v_interp_p1ll_f16, v_interp_p1lv_f16 and v_interp_p2_f16](../../../core/shader/recompiler/Translation/src/AttributeInstructions.cpp) read the 16-bit halves of each vertex's parameter through `VK_KHR_fragment_shader_barycentric`, as fixed-function interpolation cannot split a 32-bit attribute. Without the extension the pixel shader takes the [barycentric emulation](../../../core/shader/recompiler/SpirvBackend/src/BarycentricGeometryShader.cpp), whose generated geometry stage passes each vertex's parameter to it as a flat input, so the halves arrive bit for bit; a device without geometry shaders throws when that stage is built. P10 and P20 are the vertex differences rounded to f16 to nearest even, which is not confirmed on hardware. v_interp_p1ll_f16 and v_interp_p1lv_f16 round the exact `P10 * I + P0` once to f32 and v_interp_p2_f16 rounds the exact `P20 * J + S2` once to f16, as measured on RDNA2 (#697); the exact sums are computed in f64, so these instructions need the Float64 capability. They follow the shader's float mode, measured on RDNA2 (gfx1035) in all eight combinations of `IEEE_MODE` and kept/flushed f32 and f16 denormals over 768 special and 1,024 random rows. With IEEE=1 a NaN operand is returned quieted; with IEEE=0 it is returned unquieted, except that v_interp_p2_f16's f16 result is always quiet. Flushed f32 denormals flush I/J, v_interp_p2_f16's f32 S2 and the f32 result; flushed f16 denormals flush P0, P10/P20, v_interp_p1lv_f16's f16 S2 and the f16 result. Denormal modes 1 and 2 (flush only inputs or only outputs) and rounding other than to nearest even are not measured and throw. An unknown mode follows IEEE=0 with denormals kept. Lavapipe has no barycentrics, so the draw test is skipped in CI; `agc_driver_interpolation_modes` checks the arithmetic in every mode against the hardware rows on any device with Float64. - Without `VK_KHR_fragment_shader_barycentric`, a pixel shader whose I/J VGPRs [only feed `v_interp_p1_f32`/`v_interp_p2_f32` of their own pair](../../../core/shader/recompiler/Optimization/src/HostInterpolationChecker.cpp) reads the attribute Vulkan interpolated. Any other read of them or of a raw vertex parameter takes the weights and the three vertex values from a [generated geometry stage](../../../core/shader/recompiler/SpirvBackend/src/BarycentricGeometryShader.cpp), which needs `geometryShader` and a vertex shader before it: tessellation, mesh and rect-list pipelines, primitives other than triangles, more than 32 fragment input locations and interfaces beyond the device's geometry limits throw. diff --git a/tools/check_conventions.py b/tools/check_conventions.py index c3c186cc7426..e36545afcdeb 100644 --- a/tools/check_conventions.py +++ b/tools/check_conventions.py @@ -27,6 +27,7 @@ "commit-subject": ("error", "docs/dev/CONVENTIONS.md", "Commit subject is not Conventional Commits"), "pr-title": ("error", "docs/dev/CONVENTIONS.md", "Pull request title is not Conventional Commits"), "pr-template": ("error", ".github/pull_request_template.md", "Fill in the pull request template"), + "debt-length": ("warning", "CONTRIBUTING.md#documentation", "Technical debt entry over 1000 characters: state the gap first (what differs from the console, what is unverified, what throws) and keep only the behaviour it needs"), } CPP = {".c", ".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp", ".hxx", ".inl", ".ipp"} @@ -39,6 +40,7 @@ DEBT = "docs/dev/TechnicalDebt.md" DEBT_DIR = "docs/dev/debt/" DEBT_TARGET = re.compile(r"\]\((?:\.\./)+(core/[^)#\s]+)") +DEBT_ENTRY_LIMIT = 1000 CONVENTIONAL = re.compile(r"^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([^)]+\))?!?: \S") STRINGS = re.compile(r'"(?:\\.|[^"\\])*"|\'(?:\\.|[^\'\\])*\'') @@ -244,6 +246,12 @@ def docs(self): continue self.report("doc-link", path, number, target) + def debt_entries(self): + for path, lines in self.files(lambda p: is_debt(p.as_posix())): + for number, text in lines: + if long_debt_entry(text): + self.report("debt-length", path, number, f"{len(text)} characters") + def workflows(self): for path in sorted(self.status): if self.status[path] == "D" or not re.fullmatch(r"\.github/workflows/[^/]+\.ya?ml", path): @@ -313,6 +321,10 @@ def is_debt(path): return path == DEBT or (path.startswith(DEBT_DIR) and path.endswith(".md") and "/" not in path[len(DEBT_DIR):]) +def long_debt_entry(text): + return text.startswith("- ") and len(text) > DEBT_ENTRY_LIMIT + + def debt_name(text): match = DEBT_TARGET.search(text) if not match: @@ -473,6 +485,7 @@ def output(check, repo): check.shaders() check.repository() check.docs() + check.debt_entries() check.workflows() check.commits() pr = json.load(open(args.event, encoding="utf-8")).get("pull_request") if args.event else None diff --git a/tools/tests/test_check_conventions_debt_length.py b/tools/tests/test_check_conventions_debt_length.py new file mode 100644 index 000000000000..50dad5328935 --- /dev/null +++ b/tools/tests/test_check_conventions_debt_length.py @@ -0,0 +1,64 @@ +import io +import os +import subprocess +import sys +import tempfile +import unittest +from contextlib import redirect_stdout +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) +import check_conventions +from check_conventions import Check + +DEBT = "docs/dev/debt/libSceExample.md" +SHORT = "- [sceShort](../../../core/libs/prx/libSceExample/Export.cpp) (libSceExample) - layout unknown" +LONG = "- [sceLong](../../../core/libs/prx/libSceExample/Export.cpp) (libSceExample) - " + "x" * 1000 +PROSE = "Not an entry. " + "y" * 1000 + + +class DebtLengthTests(unittest.TestCase): + def test_long_debt_entry(self): + self.assertFalse(check_conventions.long_debt_entry(SHORT)) + self.assertFalse(check_conventions.long_debt_entry("- " + "z" * (check_conventions.DEBT_ENTRY_LIMIT - 2))) + self.assertTrue(check_conventions.long_debt_entry("- " + "z" * (check_conventions.DEBT_ENTRY_LIMIT - 1))) + self.assertFalse(check_conventions.long_debt_entry(PROSE)) + + def test_added_long_entry_warns_without_failing(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + + def git(*args): + return subprocess.run(["git", "-c", f"safe.directory={root}", "-c", "user.name=Conventions Test", + "-c", "user.email=test@example.com", "-c", "commit.gpgsign=false", *args], + cwd=root, capture_output=True, check=True, encoding="utf-8").stdout + + def write(text): + (root / DEBT).parent.mkdir(parents=True, exist_ok=True) + (root / DEBT).write_text(text, encoding="utf-8") + + git("init", "-q") + write(f"# libSceExample technical debt\n\n### Functional\n\n{SHORT}\n") + git("add", ".") + git("commit", "-qm", "docs: add base") + write(f"# libSceExample technical debt\n\n{PROSE}\n\n### Functional\n\n{SHORT}\n{LONG}\n") + git("commit", "-qam", "docs(debt): add a long entry") + head = git("rev-parse", "HEAD").strip() + base = git("rev-parse", "HEAD~1").strip() + + previous = Path.cwd() + try: + os.chdir(root) + check = Check(base, head) + check.debt_entries() + with redirect_stdout(io.StringIO()): + status = check_conventions.output(check, "example/repo") + finally: + os.chdir(previous) + + self.assertEqual(check.findings, [("debt-length", DEBT, 8, f"{len(LONG)} characters")]) + self.assertEqual(status, 0) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/tests/test_shader_cache_runtime_sources.py b/tools/tests/test_shader_cache_runtime_sources.py new file mode 100644 index 000000000000..6b11c5d5048c --- /dev/null +++ b/tools/tests/test_shader_cache_runtime_sources.py @@ -0,0 +1,182 @@ +from pathlib import Path +import os +import re +import shutil +import subprocess +import sys +import tempfile +import unittest + + +SOURCE = Path(sys.argv.pop(1)) +CMAKE = sys.argv.pop(1) + + +class Fixture: + def __init__(self, root, spirv_tools=False): + self.root = root + self.recompiler = root / 'core/shader/recompiler' + self.build = root / 'build' + self.calls = 0 + for directory, directories, files in os.walk(SOURCE, followlinks=False): + for path in (Path(directory), *(Path(directory) / name for name in directories + files)): + if path.is_symlink() or getattr(path.lstat(), 'st_file_attributes', 0) & 0x400: + raise AssertionError('The source fixture contains a reparse point') + shutil.copytree(SOURCE, self.recompiler) + header = root / 'core/libs/prx/libc/include/HostThreadLocal.hpp' + header.parent.mkdir(parents=True) + header.write_text('struct FixtureThreadLocal {};\n', encoding='utf-8') + self.guard = root / 'driver.cpp' + self.guard.write_text('int fixture_driver;\n', encoding='utf-8') + compiler = root / ('compiler.cmd' if os.name == 'nt' else 'compiler') + if os.name == 'nt': + compiler.write_text('@echo off\nif "%*"=="-print-sysroot" exit /b 0\nif "%*"=="--version" exit /b 0\nif "%*"=="-v --help" exit /b 0\nif "%*"=="-Wl,--help" exit /b 0\nif "%*"=="-Wl,--version" exit /b 0\n>>"%~dp0compiler-invoked" echo %*\nexit /b 99\n', encoding='utf-8') + else: + compiler.write_text('#!/bin/sh\nif [ "$#" -eq 1 ]; then case "$1" in -print-sysroot|--version|-Wl,--help|-Wl,--version) exit 0;; esac; fi\nif [ "$#" -eq 2 ] && [ "$1" = "-v" ] && [ "$2" = "--help" ]; then exit 0; fi\nprintf "%s\\n" "$*" >> "$(dirname "$0")/compiler-invoked"\nexit 99\n', encoding='utf-8') + compiler.chmod(0o755) + mode = 'ON' if spirv_tools else 'OFF' + (root / 'CMakeLists.txt').write_text(f'''cmake_minimum_required(VERSION 3.20) +set(CMAKE_CXX_COMPILER "{compiler.as_posix()}") +set(CMAKE_CXX_COMPILER_ID GNU) +set(CMAKE_CXX_COMPILER_VERSION 15.2.0) +set(CMAKE_CXX_COMPILER_ID_RUN TRUE) +set(CMAKE_CXX_COMPILER_FORCED TRUE) +set(CMAKE_CXX_COMPILER_WORKS TRUE) +set(CMAKE_CXX_ABI_COMPILED TRUE) +set(CMAKE_CXX_STANDARD_COMPUTED_DEFAULT 17) +set(CMAKE_CXX_EXTENSIONS_COMPUTED_DEFAULT ON) +set(CMAKE_CXX_COMPILE_FEATURES cxx_std_20) +project(ShaderCacheSourceFixture LANGUAGES CXX) +set(CMAKE_BUILD_TYPE Release) +set(ANYPS5_ENABLE_SPIRV_TOOLS {mode}) +set(ANYPS5_USE_SYSTEM_DEPS ON) +set(ANYPS5_SYSTEM_DEPS_STAMP fixture) +add_library(fixture_runtime INTERFACE) +add_library(glslang INTERFACE) +add_library(SPIRV INTERFACE) +add_library(SPIRV-Tools-opt INTERFACE) +add_library(SPIRV-Tools-static INTERFACE) +include("${{CMAKE_CURRENT_SOURCE_DIR}}/core/shader/recompiler/CMakeLists.txt") +add_shader_recompiler(shader_recompiler_probe fixture_runtime GUARD_SOURCES "${{CMAKE_CURRENT_SOURCE_DIR}}/driver.cpp") +get_target_property(runtimeSources shader_recompiler_probe SOURCES) +list(JOIN runtimeSources "\\n" runtimeSources) +file(WRITE "${{CMAKE_CURRENT_BINARY_DIR}}/runtime-sources.txt" "${{runtimeSources}}\\n") +''', encoding='utf-8') + self.configure() + + def configure(self, flags=''): + result = subprocess.run([CMAKE, '-S', str(self.root), '-B', str(self.build), '-G', 'Ninja', f'-DCMAKE_CXX_FLAGS={flags}'], + capture_output=True, encoding='utf-8', errors='replace', timeout=15) + if result.returncode: + raise AssertionError(result.stdout + result.stderr) + if (self.root / 'compiler-invoked').exists(): + raise AssertionError('The generator fixture invoked a compiler: ' + (self.root / 'compiler-invoked').read_text(encoding='utf-8')) + self.generated = self.build / 'shader_recompiler_probe_generated' + self.listing = self.generated / 'sources.txt' + ninja = (self.build / 'build.ninja').read_text(encoding='utf-8') + command = next(line for line in ninja.splitlines() if line.startswith(' COMMAND = ') and 'ShaderCacheVersion.cmake' in line) + self.extra = re.search(r'"-DEXTRA=([^"]*)"', command).group(1) + self.compiled = [] + for line in ninja.splitlines(): + match = re.match(r'build .*: CXX_COMPILER__shader_recompiler_probe_[^ ]+ (.*?) \|\|', line) + if match: + self.compiled.append(Path(match.group(1).replace('$:', ':').replace('$ ', ' '))) + + def version(self, success=True): + self.calls += 1 + output = self.build / f'probe-version-{self.calls}.cpp' + result = subprocess.run([CMAKE, f'-DROOT={self.root.as_posix()}', + f'-DRECOMPILER_DIR={self.recompiler.as_posix()}', + f'-DLIST_FILE={self.listing.as_posix()}', f'-DOUTPUT={output.as_posix()}', + f'-DEXTRA={self.extra}', '-P', str(self.recompiler / 'ShaderCacheVersion.cmake')], + capture_output=True, encoding='utf-8', errors='replace', timeout=10) + if not success: + if result.returncode == 0 or output.exists(): + raise AssertionError('The generator accepted an unkeyed dependency') + return result.stdout + result.stderr + if result.returncode: + raise AssertionError(result.stdout + result.stderr) + return re.search(r'SourceVersion = 0x([0-9a-f]+)ull', output.read_text(encoding='utf-8')).group(1) + + def sources(self): + return {Path(line.split(' ', 1)[1]) for line in self.listing.read_text(encoding='utf-8').splitlines() if line.startswith('sources ')} + + +class ShaderCacheRuntimeSourceTests(unittest.TestCase): + def fixture(self, spirv_tools=False): + temporary = tempfile.TemporaryDirectory(prefix='shader_cache_sources_') + self.addCleanup(temporary.cleanup) + return Fixture(Path(temporary.name), spirv_tools) + + def test_runtime_source_graph_off_and_on(self): + for enabled in (False, True): + with self.subTest(spirv_tools=enabled): + fixture = self.fixture(enabled) + generated = fixture.generated / 'ShaderCacheVersion.cpp' + selected = {Path(line) for line in (fixture.build / 'runtime-sources.txt').read_text(encoding='utf-8').splitlines()} + self.assertEqual(set(fixture.compiled), selected) + self.assertNotIn(generated, fixture.sources()) + self.assertEqual({path for path in fixture.sources() if path.suffix in ('.cpp', '.cc', '.cxx')}, selected - {generated}) + self.assertIn(fixture.recompiler / 'tests/DummyShaders.cpp', fixture.sources()) + self.assertEqual(fixture.recompiler / 'SpirvBackend/src/SpirvOptimizer.cpp' in fixture.sources(), enabled) + fixture.version() + + def test_uncompiled_test_sources_do_not_change_version(self): + fixture = self.fixture() + previous = fixture.version() + for name in ('CacheFileReadTests.cpp', 'RequestSerializerTests.cpp'): + path = fixture.recompiler / 'tests' / name + self.assertNotIn(path, fixture.sources()) + path.write_bytes(path.read_bytes() + b'\n') + self.assertEqual(fixture.version(), previous, name) + (fixture.recompiler / 'tests/NewFixture.cpp').write_text('int new_fixture;\n', encoding='utf-8') + fixture.configure() + self.assertEqual(fixture.version(), previous) + + def test_runtime_sources_headers_and_cmake_change_version(self): + fixture = self.fixture() + previous = fixture.version() + for name in ('Recompiler.cpp', 'tests/DummyShaders.cpp', 'tests/DummyShaders.hpp', 'BdaAbi.hpp', 'CMakeLists.txt', 'ShaderCacheVersion.cmake'): + with self.subTest(source=name): + path = fixture.recompiler / name + before = path.read_bytes() + path.write_bytes(before + b'\n') + self.assertNotEqual(fixture.version(), previous) + path.write_bytes(before) + self.assertEqual(fixture.version(), previous) + + def test_flags_change_version(self): + fixture = self.fixture() + previous = fixture.version() + fixture.configure('-DSHADER_CACHE_FIXTURE=1') + self.assertNotEqual(fixture.version(), previous) + + def test_optimizer_content_only_changes_enabled_version(self): + for enabled in (False, True): + with self.subTest(spirv_tools=enabled): + fixture = self.fixture(enabled) + previous = fixture.version() + optimizer = fixture.recompiler / 'SpirvBackend/src/SpirvOptimizer.cpp' + optimizer.write_bytes(optimizer.read_bytes() + b'\n') + self.assertEqual(fixture.version() == previous, not enabled) + + def test_uncompiled_internal_include_is_rejected(self): + fixture = self.fixture() + source = fixture.recompiler / 'Recompiler.cpp' + source.write_text('#include "tests/CacheFileReadTests.cpp"\n' + source.read_text(encoding='utf-8'), encoding='utf-8') + self.assertIn('does not hash', fixture.version(False)) + + def test_unhashed_external_include_is_rejected(self): + fixture = self.fixture() + (fixture.root / 'external.hpp').write_text('int external_fixture;\n', encoding='utf-8') + source = fixture.recompiler / 'Recompiler.cpp' + source.write_text('#include "../../../external.hpp"\n' + source.read_text(encoding='utf-8'), encoding='utf-8') + self.assertIn('does not hash', fixture.version(False)) + + def test_unreviewed_driver_call_is_rejected(self): + fixture = self.fixture() + fixture.guard.write_text('ShaderRecompiler::UnreviewedFixtureCall();\n', encoding='utf-8') + self.assertIn('has not been reviewed', fixture.version(False)) + + +unittest.main()