diff --git a/.github/workflows/pr-intake.yml b/.github/workflows/pr-intake.yml index 3225af812b..42ea8d8bae 100644 --- a/.github/workflows/pr-intake.yml +++ b/.github/workflows/pr-intake.yml @@ -23,6 +23,16 @@ jobs: script: | const { owner, repo } = context.repo; const pull_number = context.payload.pull_request.number; + const reopened = context.payload.action === 'reopened'; + const removeLimitLabel = async () => { + try { + await github.rest.issues.removeLabel({ owner, repo, issue_number: pull_number, name: 'open PR limit' }); + } catch (error) { + if (error.status !== 404) { + core.warning(`Could not remove the open PR limit label: ${error.message}`); + } + } + }; const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number }); if (pr.state !== 'open' || pr.base.ref !== 'main') { return; @@ -38,13 +48,7 @@ jobs: core.warning(`Could not check the permission of ${context.payload.sender.login}, so the open PR limit applies: ${error.message}`); } if (maintainer) { - try { - await github.rest.issues.removeLabel({ owner, repo, issue_number: pull_number, name: 'open PR limit' }); - } catch (error) { - if (error.status !== 404) { - core.warning(`Could not remove the open PR limit label: ${error.message}`); - } - } + await removeLimitLabel(); return; } } @@ -57,18 +61,23 @@ jobs: } const merged = mergedPRs.total_count; if (merged >= 20) { + if (reopened) { + await removeLimitLabel(); + } return; } const allowedOpen = merged >= 1 ? 20 : 1; const openPRs = await github.paginate(github.rest.pulls.list, { owner, repo, state: 'open', base: 'main', per_page: 100 }); - const reopened = context.payload.action === 'reopened'; const open = new Set(openPRs .filter((p) => p.user.login === pr.user.login && (reopened || p.number < pull_number)) .map((p) => p.number)); open.add(pull_number); if (open.size <= allowedOpen) { + if (reopened) { + await removeLimitLabel(); + } return; } const name = 'open PR limit'; diff --git a/.gitmodules b/.gitmodules index e811a81b6c..34556e3e22 100644 --- a/.gitmodules +++ b/.gitmodules @@ -76,3 +76,6 @@ [submodule "3rdparty/quickjs"] path = 3rdparty/quickjs url = https://github.com/quickjs-ng/quickjs.git +[submodule "3rdparty/sqlite"] + path = 3rdparty/sqlite + url = https://github.com/sqlite/sqlite.git diff --git a/3rdparty/sqlite b/3rdparty/sqlite new file mode 160000 index 0000000000..57520227c9 --- /dev/null +++ b/3rdparty/sqlite @@ -0,0 +1 @@ +Subproject commit 57520227c95396ce39ba6ecaac8d475b78dd3964 diff --git a/CMakeLists.txt b/CMakeLists.txt index 25abb6896a..c96a38e336 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -362,6 +362,9 @@ if(BUILD_TESTING) add_test(NAME check_skips_ctest COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_skips_ctest.py) set_tests_properties(check_skips_ctest PROPERTIES TIMEOUT 30) + add_test(NAME pr_intake_reopen COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_intake_workflow.py) + set_tests_properties(pr_intake_reopen PROPERTIES TIMEOUT 60) add_test(NAME tools_pe_inventory COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/tests/test_pe_symbol_inventory.py) set_tests_properties(tools_pe_inventory PROPERTIES TIMEOUT 20) add_test(NAME check_skips COMMAND ${Python3_EXECUTABLE} @@ -466,7 +469,7 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_package_release.py) add_test(NAME web_release COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_web_release.py) - set_tests_properties(web_release PROPERTIES TIMEOUT 20) + set_tests_properties(web_release PROPERTIES TIMEOUT 120) add_test(NAME web_library_assets COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_build_web.py) set_tests_properties(web_library_assets PROPERTIES TIMEOUT 20) @@ -703,7 +706,8 @@ set_target_properties(usb-1.0 PROPERTIES POSITION_INDEPENDENT_CODE ON) include(cmake/ZArchive.cmake) include(cmake/Opus.cmake) -if(WIN32 OR APPLE) +include(cmake/Sqlite.cmake) +if(WIN32 OR APPLE OR (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND NOT ANYPS5_ENABLE_ASAN AND NOT ANYPS5_ENABLE_TSAN)) set(MI_BUILD_SHARED ON CACHE BOOL "" FORCE) set(MI_BUILD_STATIC OFF CACHE BOOL "" FORCE) set(MI_BUILD_OBJECT OFF CACHE BOOL "" FORCE) @@ -717,6 +721,8 @@ if(WIN32 OR APPLE) target_link_options(mimalloc PRIVATE -static LINKER:--wrap=malloc,--wrap=calloc,--wrap=realloc,--wrap=free,--wrap=strdup,--wrap=_strdup) target_sources(mimalloc PRIVATE ${CMAKE_SOURCE_DIR}/core/libs/allocator/RuntimeHeap.c) + elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") + target_link_options(mimalloc PRIVATE LINKER:--as-needed) endif() endif() diff --git a/cmake/Jxr.cmake b/cmake/Jxr.cmake index 9e20294e31..109b12a74d 100644 --- a/cmake/Jxr.cmake +++ b/cmake/Jxr.cmake @@ -39,6 +39,8 @@ add_library(jxr STATIC EXCLUDE_FROM_ALL ${JXR_SOURCE_DIR}/jxrgluelib/JXRGluePFC.c ${JXR_SOURCE_DIR}/jxrgluelib/JXRMeta.c) configure_jxr_native(jxr) +set_source_files_properties(${JXR_SOURCE_DIR}/image/sys/strcodec.c ${JXR_SOURCE_DIR}/image/decode/segdec.c + PROPERTIES COMPILE_DEFINITIONS _M_IA64) if(UNIX) target_link_libraries(jxr PRIVATE m) endif() diff --git a/cmake/Sqlite.cmake b/cmake/Sqlite.cmake new file mode 100644 index 0000000000..f5a1e05e64 --- /dev/null +++ b/cmake/Sqlite.cmake @@ -0,0 +1,68 @@ +enable_language(C) + +set(SQLITE_SOURCE_DIR ${CMAKE_SOURCE_DIR}/3rdparty/sqlite) +set(sqliteGen ${CMAKE_BINARY_DIR}/sqlite) +file(MAKE_DIRECTORY ${sqliteGen}) + +foreach(tool IN ITEMS lemon mkkeywordhash mksourceid) + add_executable(sqlite_${tool} EXCLUDE_FROM_ALL ${SQLITE_SOURCE_DIR}/tool/${tool}.c) + set_target_properties(sqlite_${tool} PROPERTIES OUTPUT_NAME ${tool} RUNTIME_OUTPUT_DIRECTORY ${sqliteGen}) +endforeach() +add_executable(sqlite_jimsh EXCLUDE_FROM_ALL ${SQLITE_SOURCE_DIR}/autosetup/jimsh0.c) +if(WIN32) + target_compile_definitions(sqlite_jimsh PRIVATE HAVE__FULLPATH) +else() + target_compile_definitions(sqlite_jimsh PRIVATE HAVE_REALPATH) +endif() +set_target_properties(sqlite_jimsh PROPERTIES OUTPUT_NAME jimsh RUNTIME_OUTPUT_DIRECTORY ${sqliteGen}) +set(sqliteTcl $) + +add_custom_command(OUTPUT ${sqliteGen}/sqlite3.h + COMMAND ${sqliteTcl} ${SQLITE_SOURCE_DIR}/tool/mksqlite3h.tcl ${SQLITE_SOURCE_DIR} -o ${sqliteGen}/sqlite3.h + WORKING_DIRECTORY ${sqliteGen} + DEPENDS sqlite_jimsh sqlite_mksourceid ${SQLITE_SOURCE_DIR}/src/sqlite.h.in ${SQLITE_SOURCE_DIR}/VERSION) +add_custom_command(OUTPUT ${sqliteGen}/parse.c ${sqliteGen}/parse.h + COMMAND ${CMAKE_COMMAND} -E copy ${SQLITE_SOURCE_DIR}/src/parse.y ${SQLITE_SOURCE_DIR}/tool/lempar.c ${sqliteGen} + COMMAND $ -S parse.y + WORKING_DIRECTORY ${sqliteGen} + DEPENDS sqlite_lemon ${SQLITE_SOURCE_DIR}/src/parse.y ${SQLITE_SOURCE_DIR}/tool/lempar.c) +add_custom_command(OUTPUT ${sqliteGen}/opcodes.h + COMMAND ${CMAKE_COMMAND} -E cat ${sqliteGen}/parse.h ${SQLITE_SOURCE_DIR}/src/vdbe.c > ${sqliteGen}/opcodes.in + COMMAND ${sqliteTcl} ${SQLITE_SOURCE_DIR}/tool/mkopcodeh.tcl < ${sqliteGen}/opcodes.in > ${sqliteGen}/opcodes.h + WORKING_DIRECTORY ${sqliteGen} + DEPENDS sqlite_jimsh ${sqliteGen}/parse.h ${SQLITE_SOURCE_DIR}/src/vdbe.c) +add_custom_command(OUTPUT ${sqliteGen}/opcodes.c + COMMAND ${sqliteTcl} ${SQLITE_SOURCE_DIR}/tool/mkopcodec.tcl ${sqliteGen}/opcodes.h > ${sqliteGen}/opcodes.c + WORKING_DIRECTORY ${sqliteGen} + DEPENDS sqlite_jimsh ${sqliteGen}/opcodes.h) +add_custom_command(OUTPUT ${sqliteGen}/keywordhash.h + COMMAND $ > ${sqliteGen}/keywordhash.h + WORKING_DIRECTORY ${sqliteGen} + DEPENDS sqlite_mkkeywordhash) +add_custom_command(OUTPUT ${sqliteGen}/pragma.h + COMMAND ${sqliteTcl} ${SQLITE_SOURCE_DIR}/tool/mkpragmatab.tcl ${sqliteGen}/pragma.h + WORKING_DIRECTORY ${sqliteGen} + DEPENDS sqlite_jimsh ${SQLITE_SOURCE_DIR}/tool/mkpragmatab.tcl) +add_custom_command(OUTPUT ${sqliteGen}/ctime.c + COMMAND ${sqliteTcl} ${SQLITE_SOURCE_DIR}/tool/mkctimec.tcl ${sqliteGen}/ctime.c + WORKING_DIRECTORY ${sqliteGen} + DEPENDS sqlite_jimsh ${SQLITE_SOURCE_DIR}/tool/mkctimec.tcl) + +set(sqliteSources) +foreach(name IN ITEMS alter analyze attach auth backup bitvec btmutex btree build callback carray complete date dbpage + dbstat delete expr fault fkey func global hash insert json legacy loadext main malloc mem0 mem1 mem2 mem3 + mem5 memdb memjournal mutex mutex_noop mutex_unix mutex_w32 notify os os_kv os_unix os_win pager pcache + pcache1 pragma prepare printf random resolve rowset select status table threads tokenize treeview trigger + utf update upsert util vacuum vdbe vdbeapi vdbeaux vdbeblob vdbemem vdbesort vdbetrace vdbevtab vtab wal + walker where wherecode whereexpr window) + list(APPEND sqliteSources ${SQLITE_SOURCE_DIR}/src/${name}.c) +endforeach() +add_library(sqlite3 STATIC EXCLUDE_FROM_ALL ${sqliteSources} ${sqliteGen}/parse.c ${sqliteGen}/opcodes.c + ${sqliteGen}/ctime.c ${sqliteGen}/sqlite3.h ${sqliteGen}/keywordhash.h ${sqliteGen}/pragma.h ${sqliteGen}/opcodes.h) +target_include_directories(sqlite3 PRIVATE ${sqliteGen} ${SQLITE_SOURCE_DIR}/src) +target_include_directories(sqlite3 SYSTEM INTERFACE ${sqliteGen}) +target_compile_definitions(sqlite3 PRIVATE SQLITE_THREADSAFE=1 SQLITE_OMIT_LOAD_EXTENSION SQLITE_CORE) +set_target_properties(sqlite3 PROPERTIES C_STANDARD 99 POSITION_INDEPENDENT_CODE ON C_VISIBILITY_PRESET hidden) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + target_link_libraries(sqlite3 PRIVATE m pthread) +endif() diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index 740258e498..4aa409f731 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -152,7 +152,8 @@ add_custom_command( COMMENT "Patching NIDs for libc" ) -file(GLOB LIBS_DIRS LIST_DIRECTORIES true ${CMAKE_CURRENT_SOURCE_DIR}/prx/*) +file(GLOB LIBS_DIRS LIST_DIRECTORIES true CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/prx/*) +file(GLOB LIBS_CMAKE_LISTS CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/prx/*/CMakeLists.txt) list(REMOVE_ITEM LIBS_DIRS ${LIBC_DIR}) set(PATCHED_OUTPUTS ${LIBC_PATCHED}) @@ -289,13 +290,14 @@ if(TARGET mimalloc AND WIN32) ) list(APPEND PATCHED_OUTPUTS ${PATCHED_DIR}/mimalloc.dll ${PATCHED_DIR}/mimalloc-redirect.dll) elseif(TARGET mimalloc) + set(MIMALLOC_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}mimalloc${CMAKE_SHARED_LIBRARY_SUFFIX}) add_custom_command( - OUTPUT ${PATCHED_DIR}/libmimalloc.dylib - COMMAND ${CMAKE_COMMAND} -E copy $ ${PATCHED_DIR}/libmimalloc.dylib + OUTPUT ${PATCHED_DIR}/${MIMALLOC_NAME} + COMMAND ${CMAKE_COMMAND} -E copy $ ${PATCHED_DIR}/${MIMALLOC_NAME} DEPENDS mimalloc - COMMENT "Copying libmimalloc.dylib beside the libraries" + COMMENT "Copying ${MIMALLOC_NAME} beside the libraries" ) - list(APPEND PATCHED_OUTPUTS ${PATCHED_DIR}/libmimalloc.dylib) + list(APPEND PATCHED_OUTPUTS ${PATCHED_DIR}/${MIMALLOC_NAME}) endif() include(prx/libSceFreeType/BuildInfo.cmake) @@ -535,6 +537,46 @@ if(BUILD_TESTING) configure_windows_unwind(guest_kernel_sys_barrier_tests) add_test(NAME guest_kernel_sys_barrier COMMAND guest_kernel_sys_barrier_tests) set_tests_properties(guest_kernel_sys_barrier PROPERTIES TIMEOUT 20) + add_test_executable(guest_kernel_sys_execve_tests tests/GuestKernelSysExecve.cpp) + target_include_directories(guest_kernel_sys_execve_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_execve_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_execve_tests) + add_test(NAME guest_kernel_sys_execve COMMAND guest_kernel_sys_execve_tests) + set_tests_properties(guest_kernel_sys_execve PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_kernel_sys_execve PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_kernel_sys_execvpe_tests tests/GuestKernelSysExecvpe.cpp) + target_include_directories(guest_kernel_sys_execvpe_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_execvpe_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_execvpe_tests) + add_test(NAME guest_kernel_sys_execvpe COMMAND guest_kernel_sys_execvpe_tests) + set_tests_properties(guest_kernel_sys_execvpe PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_kernel_sys_execvpe PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_kernel_sys_nanosleep_tests tests/GuestKernelSysNanosleep.cpp) + target_include_directories(guest_kernel_sys_nanosleep_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_nanosleep_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_nanosleep_tests) + add_test(NAME guest_kernel_sys_nanosleep COMMAND guest_kernel_sys_nanosleep_tests) + set_tests_properties(guest_kernel_sys_nanosleep PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_kernel_sys_nanosleep PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_kernel_sys_adjtime_tests tests/GuestKernelSysAdjtime.cpp) + target_include_directories(guest_kernel_sys_adjtime_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_adjtime_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_adjtime_tests) + add_test(NAME guest_kernel_sys_adjtime COMMAND guest_kernel_sys_adjtime_tests) + set_tests_properties(guest_kernel_sys_adjtime PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_kernel_sys_adjtime PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_kernel_sys_pthread_tests tests/GuestKernelSysPthread.cpp) target_include_directories(guest_kernel_sys_pthread_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(guest_kernel_sys_pthread_tests PRIVATE libkernel_sys libkernel libc) @@ -566,6 +608,16 @@ if(BUILD_TESTING) set_tests_properties(guest_firmware_constant_returns PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$;PATH=path_list_prepend:$;PATH=path_list_prepend:$;PATH=path_list_prepend:$;PATH=path_list_prepend:$;PATH=path_list_prepend:$;PATH=path_list_prepend:$;PATH=path_list_prepend:$;PATH=path_list_prepend:$") endif() + add_test_executable(guest_bemp2sys_tests tests/GuestBemp2sys.cpp) + target_include_directories(guest_bemp2sys_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_bemp2sys_tests PRIVATE libSceBemp2sys libkernel libc) + configure_windows_unwind(guest_bemp2sys_tests) + add_test(NAME guest_bemp2sys COMMAND guest_bemp2sys_tests) + set_tests_properties(guest_bemp2sys PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_bemp2sys PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_playready_tests tests/GuestPlayReady.cpp) target_include_directories(guest_playready_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(guest_playready_tests PRIVATE libScePlayReady libkernel libc) @@ -1072,16 +1124,6 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() - add_test_executable(guest_sys_util_notification_tests tests/GuestSysUtilNotification.cpp) - target_include_directories(guest_sys_util_notification_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) - target_link_libraries(guest_sys_util_notification_tests PRIVATE libSceSysUtil libkernel libc) - configure_windows_unwind(guest_sys_util_notification_tests) - add_test(NAME guest_sys_util_notification COMMAND guest_sys_util_notification_tests) - if(WIN32) - set_tests_properties(guest_sys_util_notification PROPERTIES ENVIRONMENT_MODIFICATION - "PATH=path_list_prepend:$") - endif() - if(UNIX AND (CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64)$" OR CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64")) add_subdirectory(tests/register_context) endif() diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver.hpp index e539ff385b..bb21344ce1 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver.hpp @@ -34,6 +34,7 @@ extern "C" void AgcDriverRequestScreenshot_nid_postfix(); extern "C" void AgcDriverSuspendPoint_nid_postfix(); extern "C" void AgcDriverSerialProgress_nid_postfix(std::uint64_t* accepted, std::uint64_t* completed); extern "C" std::size_t AgcDriverAwaitedWaits_nid_postfix(AgcDriver::AwaitedWait* waits, std::size_t capacity); +extern "C" void AgcDriverWaitForPipelineJournal_nid_postfix(); extern "C" std::uint64_t AgcDriverDispatchesAdoptedAhead_nid_postfix(); extern "C" std::uint64_t AgcDriverDispatchesRejectedAhead_nid_postfix(); extern "C" void AgcDriverSetAheadAdoptHook_nid_postfix(void (*hook)()); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Dispatch/DispatchCache.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Dispatch/DispatchCache.hpp index c7cf99a0a1..47863c8276 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Dispatch/DispatchCache.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Dispatch/DispatchCache.hpp @@ -26,23 +26,18 @@ namespace AgcDriver::DriverDetail { inline bool CaptureRegisteredViewsCurrent(std::span views) { if (views.empty()) return true; - for (std::uint32_t attempt = 0; attempt < 2u; ++attempt) { - const auto before = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); - bool current = true; - for (const auto& view : views) { - const auto range = GuestAllocations::GuestAllocationsLookup_nid_postfix(static_cast(view.address), false); - if (view.hasRange != (range != nullptr)) { - current = false; - break; - } - if (range != nullptr && (range->address != view.rangeAddress || range->bytes != view.rangeBytes || range->allocationAddress != view.allocationAddress || range->allocationBytes != view.allocationBytes || range->backingSerial != view.backingSerial || range->readable != view.readable || range->writable != view.writable || range->gpu != view.gpu)) { - current = false; - break; - } - } - if (before == GuestAllocations::GuestAllocationsGeneration_nid_postfix()) return current; + std::vector addresses; + addresses.reserve(views.size()); + for (const auto& view : views) addresses.push_back(static_cast(view.address)); + std::vector> ranges(views.size()); + GuestAllocations::GuestAllocationsLookupEach_nid_postfix(addresses.data(), addresses.size(), ranges.data()); + for (std::size_t index = 0; index < views.size(); ++index) { + const auto& view = views[index]; + const auto& range = ranges[index]; + if (view.hasRange != (range != nullptr)) return false; + if (range != nullptr && (range->address != view.rangeAddress || range->bytes != view.rangeBytes || range->allocationAddress != view.allocationAddress || range->allocationBytes != view.allocationBytes || range->backingSerial != view.backingSerial || range->readable != view.readable || range->writable != view.writable || range->gpu != view.gpu)) return false; } - return false; + return true; } inline constexpr std::size_t MaxDispatchVariants = 8; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp index d32168c0ed..db5745139e 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp @@ -78,6 +78,7 @@ public: std::pair SerialProgress(); std::uint64_t FlipSubmissions() const; std::vector AwaitedWaits(); + void WaitForPipelineJournal(); bool WaitIdleFor(std::chrono::milliseconds timeout); void RegisterVideoOutput(std::uint32_t handle, const std::shared_ptr& output); void UnregisterVideoOutput(std::uint32_t handle, const std::shared_ptr& output); @@ -269,7 +270,8 @@ private: 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 sampledRead(); + static void setSampledRead(bool sampled); static bool writeEvidenceEnabled(); static bool writerKeyedEvidence(); static bool foreignWriters(); @@ -469,7 +471,7 @@ private: std::map queues; std::map> outputs; struct EopFlip { - std::uint32_t handle; + FlipInfo info; std::shared_ptr request; }; std::map eopFlips; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/ThreadScratch.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/ThreadScratch.hpp new file mode 100644 index 0000000000..b6268d9489 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Execution/include/ThreadScratch.hpp @@ -0,0 +1,21 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_THREADSCRATCH_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_THREADSCRATCH_HPP + +#include "prx/libc/include/HostThreadSlot.hpp" +#include "ThreadOwned.hpp" + +namespace AgcDriver { + +template +T& ThreadScratch() { + auto* object = HostThreadSlot::Get(); + if (object == nullptr) { + ShaderRecompiler::ThreadOwned(object); + HostThreadSlot::Set(object); + } + return *object; +} + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/VideoOutput.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/VideoOutput.hpp index c642371e8e..95696a705f 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/VideoOutput.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/VideoOutput.hpp @@ -32,6 +32,11 @@ struct FlipInfo { std::int64_t argument; }; +class EopFlipAlreadyReserved final : public std::runtime_error { +public: + EopFlipAlreadyReserved() : std::runtime_error("EOP flip token is already reserved for this flip") {} +}; + class IFlipRequest { public: virtual ~IFlipRequest() = default; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/EntryPoints.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/EntryPoints.cpp index 0e3c3dd8ad..5b33337563 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/EntryPoints.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/EntryPoints.cpp @@ -161,6 +161,10 @@ extern "C" std::size_t AgcDriverAwaitedWaits_nid_postfix(AgcDriver::AwaitedWait* return count; } +extern "C" void AgcDriverWaitForPipelineJournal_nid_postfix() { + AgcDriver::DriverDetail::Driver::Get().WaitForPipelineJournal(); +} + extern "C" void AgcDriverSerialProgress_nid_postfix(std::uint64_t* accepted, std::uint64_t* completed) { const auto [submitted, done] = AgcDriver::DriverDetail::Driver::Get().SerialProgress(); *accepted = submitted; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Lifecycle.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Lifecycle.cpp index 585f029413..feca3ead52 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Lifecycle.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Lifecycle.cpp @@ -297,6 +297,16 @@ std::vector Driver::AwaitedWaits() { return result; } +void Driver::WaitForPipelineJournal() { + std::shared_ptr localDevice = device.Load(); + if (localDevice == nullptr) { + std::lock_guard gpuLock(GuestMemory::GpuMutex()); + if (device == nullptr) device = std::make_shared(); + localDevice = device; + } + localDevice->WaitForPipelineJournalReplay(); +} + std::pair Driver::SerialProgress() { std::lock_guard lock(mutex); return {accepted, completed}; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WriteEvidence.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WriteEvidence.cpp index cc60e9ca09..33019e1f0c 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WriteEvidence.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WriteEvidence.cpp @@ -2,16 +2,17 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Memory/WriteEvidence.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libc/include/HostThreadSlot.hpp" #include #include namespace AgcDriver::DriverDetail { SampledReadScope::SampledReadScope(std::atomic& reads) : previous(Driver::sampledRead()) { - Driver::sampledRead() = Driver::writeEvidenceEnabled() && reads.fetch_add(1, std::memory_order_relaxed) % Driver::writeEvidenceSampleEvery() == 0; + Driver::setSampledRead(Driver::writeEvidenceEnabled() && reads.fetch_add(1, std::memory_order_relaxed) % Driver::writeEvidenceSampleEvery() == 0); } -SampledReadScope::~SampledReadScope() { Driver::sampledRead() = previous; } +SampledReadScope::~SampledReadScope() { Driver::setSampledRead(previous); } void PendingView::Load() { if (Driver::validateLegacy()) return; @@ -26,9 +27,18 @@ bool PendingView::Overlaps(std::uint64_t address, std::size_t bytes) const { return Driver::validateLegacy() ? Graphics::Recorder::SnapshotWriteOverlaps(address, bytes) : Graphics::Recorder::SnapshotOverlaps(snapshot.get(), address, bytes); } -bool& Driver::sampledRead() { - static thread_local bool sampled = false; - return sampled; +namespace { + +struct SampledReadTag; + +} + +bool Driver::sampledRead() { + return HostThreadSlot::Get(); +} + +void Driver::setSampledRead(bool sampled) { + HostThreadSlot::Set(sampled); } bool Driver::writeEvidenceEnabled() { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Presentation.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Presentation.cpp index d607e1ae53..efbef80f39 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Presentation.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Presentation.cpp @@ -24,12 +24,17 @@ std::uint32_t Driver::ReserveEopFlipAt(const FlipInfo& info, std::uint32_t token std::uint32_t Driver::reserveEopFlip(const FlipInfo& info, std::uint32_t token) { rethrowFailure(); checkStopping(); - require(token >= EopFlipTokenBase && token <= EopFlipTokenLimit && !eopFlips.contains(token), "EOP flip token is not free"); + require(token >= EopFlipTokenBase && token <= EopFlipTokenLimit, "EOP flip token is out of range"); const auto output = outputs.find(info.handle); require(output != outputs.end(), "EOP flip references an unregistered video output"); + if (const auto found = eopFlips.find(token); found != eopFlips.end()) { + const auto& reserved = found->second.info; + require(reserved.handle == info.handle && reserved.index == info.index && reserved.mode == info.mode && reserved.argument == info.argument, "EOP flip token is reserved for a different flip"); + throw EopFlipAlreadyReserved{}; + } auto request = output->second->TryReserve(info); if (!request) return 0; - eopFlips.emplace(token, EopFlip{info.handle, std::move(request)}); + eopFlips.emplace(token, EopFlip{info, std::move(request)}); nextEopToken = std::max(nextEopToken, token + 1); return token; } @@ -53,7 +58,7 @@ void Driver::UnregisterVideoOutput(std::uint32_t handle, const std::shared_ptrsecond == output, "video output registration mismatch"); - std::erase_if(eopFlips, [handle](const auto& entry) { return entry.second.handle == handle; }); + std::erase_if(eopFlips, [handle](const auto& entry) { return entry.second.info.handle == handle; }); outputs.erase(it); } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/QueueWorker.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/QueueWorker.cpp index a50fc98699..bcf2489750 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/QueueWorker.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/QueueWorker.cpp @@ -50,6 +50,8 @@ FrameTiming* Driver::includeTimingSubmission(const Submission& submission, bool } void Driver::markCompleted(std::uint64_t serial) { + static const bool traceGpu = std::getenv("APS5_TRACE_GPU") != nullptr; + if (traceGpu) std::fprintf(stderr, "[gpu] %.1f completed serial=%llu (in order up to %llu)\n", TraceMs(), static_cast(serial), static_cast(completed)); completedOutOfOrder.insert(serial); while (!completedOutOfOrder.empty() && *completedOutOfOrder.begin() == completed + 1) { completed = *completedOutOfOrder.begin(); @@ -70,6 +72,8 @@ void Driver::deferCompletion(const Submission& submission) { queue0Uncommitted.push_back(received); } completionDeferred() = true; + static const bool traceGpu = std::getenv("APS5_TRACE_GPU") != nullptr; + if (traceGpu) std::fprintf(stderr, "[gpu] %.1f deferred completion serial=%llu to the draw pipeline\n", TraceMs(), static_cast(submission.serial)); DrawPipeline::Queue0().Enqueue([this, serial = submission.serial, received, labelWrites = submission.labelWrites, progress = submission.progress, holdsFlip = submission.holdsFlip] { completeSubmission(serial, received, labelWrites, progress, holdsFlip); }, {}); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp index 0551e52ba3..0cf8663d3d 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp @@ -542,6 +542,8 @@ void Driver::Submit(const Packet* packet, std::uint32_t queue, bool cpuFlip) { } submission.shaders = shaders; submission.serial = accepted + 1; + if (trace) std::fprintf(stderr, "[gpu] %.1f accepted serial=%llu queue=0x%x dwords=%zu at %p%s\n", TraceMs(), static_cast(submission.serial), queue, submission.commands.size(), + static_cast(descriptor.addr), cpuFlip ? " (cpu flip)" : ""); submission.received = ++eventSerial; const auto now = std::chrono::steady_clock::now(); @@ -599,6 +601,8 @@ void Driver::SuspendPoint() { Submission boundary{}; boundary.serial = accepted + 1; boundary.suspend = true; + static const bool trace = std::getenv("APS5_TRACE_GPU") != nullptr; + if (trace) std::fprintf(stderr, "[gpu] %.1f accepted serial=%llu suspend point\n", TraceMs(), static_cast(boundary.serial)); boundary.queue = 0; boundary.receivedAt = receivedAt; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp index c4fc580f50..cc5269db98 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp @@ -358,7 +358,11 @@ void Validate(std::span packet, std::uint32_t queue) { } case 0x3f: size(4); break; case 0x13: case 0x2f: graphics(); size(2); break; - case 0x26: graphics(); size(3); break; + case 0x26: + graphics(); + size(3); + require((packet[1] & 1u) == 0 && packet[2] <= 0xffffu, "unsupported index base alignment or address bits"); + break; case 0x27: graphics(); size(6); @@ -400,7 +404,7 @@ void Validate(std::span packet, std::uint32_t queue) { require((packet[2] >> 16u) == 0 && (packet[3] >> 16u) == 0, "start-index location semantics are not implemented"); require(drawLocation(packet[2]) && drawLocation(packet[3]) && drawLocation(packet[4] & 0xffffu), "invalid indirect draw register location"); require((packet[4] & ~(0xffffu | (1u << 30u) | (1u << 31u))) == 0, "indirect multi-draw control bits are not implemented"); - if ((packet[4] & (1u << 30u)) != 0) require((packet[6] & 3u) == 0 && address(packet[6], packet[7]) != 0, "invalid indirect draw count address"); + if ((packet[4] & (1u << 30u)) != 0) require((packet[6] & 3u) == 0 && address(packet[6], packet[7]) != 0 && packet[7] <= 0xffffu, "invalid indirect draw count address"); else require(address(packet[6], packet[7]) == 0, "count address without an indirect draw count"); require((packet[8] & 3u) == 0 && packet[8] >= (opcode == 0x2c ? 16u : 20u), "invalid indirect draw stride"); require((packet[9] & ~0x20u) == (opcode == 0x2c ? 2u : 0u), "unsupported indirect draw initiator"); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Screenshot.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Screenshot.cpp index 9e60cbac8c..133f0ea8b7 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Screenshot.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Screenshot.cpp @@ -3,14 +3,24 @@ #include "SDL_filesystem.h" #include "SDL_error.h" #include +#include +#include #include #include -#include #include #include #include #include +#ifdef _WIN32 +#include +#include +#include +#else +#include +#include +#endif + namespace { std::atomic requested{false}; @@ -40,10 +50,52 @@ std::string localTimestamp() { return text; } -std::filesystem::path unusedPath(const std::filesystem::path& folder, const std::string& stem) { +int createExclusive(const std::filesystem::path& path) { +#ifdef _WIN32 + return ::_wopen(path.c_str(), _O_WRONLY | _O_CREAT | _O_EXCL | _O_BINARY | _O_NOINHERIT, _S_IREAD | _S_IWRITE); +#else + return ::open(path.c_str(), O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0666); +#endif +} + +bool writeAll(int descriptor, const std::uint8_t* data, std::size_t size) { + while (size != 0) { +#ifdef _WIN32 + const unsigned int chunk = size > INT_MAX ? INT_MAX : static_cast(size); + const int written = ::_write(descriptor, data, chunk); +#else + const auto written = ::write(descriptor, data, size); +#endif + if (written < 0 && errno == EINTR) continue; + if (written <= 0) return false; + data += written; + size -= static_cast(written); + } + return true; +} + +bool closeFile(int descriptor) { +#ifdef _WIN32 + return ::_close(descriptor) == 0; +#else + return ::close(descriptor) == 0; +#endif +} + +std::filesystem::path writeUnusedPng(const std::filesystem::path& folder, const std::string& stem, const std::vector& png) { for (int index = 0; index < 1000; ++index) { const auto path = folder / (index == 0 ? stem + ".png" : stem + "-" + std::to_string(index) + ".png"); - if (!std::filesystem::exists(path)) return path; + const int descriptor = createExclusive(path); + if (descriptor < 0) { + if (errno == EEXIST) continue; + throw std::runtime_error("Screenshot: cannot create output file"); + } + const bool written = writeAll(descriptor, png.data(), png.size()); + const bool closed = closeFile(descriptor); + if (written && closed) return path; + std::error_code error; + std::filesystem::remove(path, error); + throw std::runtime_error("Screenshot: cannot write output file"); } throw std::runtime_error("Screenshot: no unused file name for " + stem + " in " + folder.string()); } @@ -71,11 +123,5 @@ std::filesystem::path AgcDriver::Screenshot::Write(std::string_view titleId, std const auto png = Decoder::Png::Encode(rgb, width, height, 3); const auto folder = directory(); std::filesystem::create_directories(folder); - const auto path = unusedPath(folder, std::string(titleId) + "-" + localTimestamp()); - std::ofstream file; - file.exceptions(std::ios::failbit | std::ios::badbit); - file.open(path, std::ios::binary); - file.write(reinterpret_cast(png.data()), static_cast(png.size())); - file.close(); - return path; + return writeUnusedPng(folder, std::string(titleId) + "-" + localTimestamp(), png); } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index 9386864e5e..8ffb1adcb3 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -2444,7 +2444,8 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : state->oversizedBuffer = std::make_unique(graphicsContext(), Graphics::EmptyBufferBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); std::ranges::fill(state->oversizedBuffer->Bytes(), std::byte{0}); if (!compileOnly && Pm4::GdsImportable() && Graphics::HostImportFor(graphicsContext(), Pm4::GdsAddress(), Pm4::GdsBytes, Graphics::ImportRequester::Map) == nullptr) Pm4::RejectGdsImport(); - state->pipelineCache = std::make_unique(graphicsContext(), state->properties, window != nullptr || compileOnly); + static const bool headlessPipelineCache = std::getenv("APS5_HEADLESS_PIPELINE_CACHE") != nullptr; + state->pipelineCache = std::make_unique(graphicsContext(), state->properties, window != nullptr || compileOnly || headlessPipelineCache); state->detiler = std::make_unique(graphicsContext()); state->textureCache = std::make_unique(graphicsContext()); state->colorTransfer = std::make_unique(graphicsContext()); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Sampler.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Sampler.hpp index 34dd9a2774..65384895fd 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Sampler.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Sampler.hpp @@ -26,6 +26,7 @@ public: bool ReadsOpaqueBlackBorder() const; bool RequiresNonSeamlessCube() const; static void Validate(const Context& context, const GuestSamplerResource& descriptor); + static bool ShaderAppliesLodBias(const GuestSamplerResource& descriptor); static bool ReadsOpaqueBlackBorder(const GuestSamplerResource& descriptor); private: diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp index 7f069456ca..e74026aa63 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp @@ -2412,6 +2412,7 @@ const HostImport* importRunAt(const Context& context, HostImports& state, Import const bool exhausted = result == VK_ERROR_OUT_OF_HOST_MEMORY || result == VK_ERROR_OUT_OF_DEVICE_MEMORY; if (std::string_view(step) == "vkAllocateMemory") { state.allocationFailed = true; + if (!exhausted) state.allocationFailures = 0; if (exhausted && (++state.allocationFailures >= 3 || state.liveBytes + bytes + budget / 8 >= budget)) { state.ceiling = std::max(state.liveBytes, 1); std::fprintf(stderr, "[gpu] host import budget lowered to the %.1f MiB live after vkAllocateMemory failed %s\n", state.liveBytes / 1048576.0, state.allocationFailures >= 3 ? "3 times in a row" : "within an eighth of the budget"); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp index 91cf189859..50d522e1df 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp @@ -1,5 +1,6 @@ #include "prx/libSceAgcDriver/Graphics/include/Sampler.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp" +#include "PipelineSpecialization.hpp" #include #include #include @@ -7,10 +8,14 @@ namespace AgcDriver::Graphics { + bool Sampler::ShaderAppliesLodBias(const GuestSamplerResource& descriptor) { + return descriptor.lodBias < -ShaderRecompiler::PipelineSpecialization::SamplerLodBiasShaderThreshold || descriptor.lodBias > ShaderRecompiler::PipelineSpecialization::SamplerLodBiasShaderThreshold; + } + void Sampler::Validate(const Context& context, const GuestSamplerResource& descriptor) { Require(!descriptor.anisotropyEnable || context.samplerAnisotropy, "guest sampler descriptor requests anisotropic filtering which the device does not support"); Require(descriptor.maxAnisotropy <= context.limits.maxSamplerAnisotropy, "guest sampler descriptor requests an anisotropy ratio beyond the device limit"); - Require(descriptor.lodBias >= -context.limits.maxSamplerLodBias && descriptor.lodBias <= context.limits.maxSamplerLodBias, "guest sampler descriptor requests a LOD bias beyond the device limit"); + Require(ShaderAppliesLodBias(descriptor) || (descriptor.lodBias >= -context.limits.maxSamplerLodBias && descriptor.lodBias <= context.limits.maxSamplerLodBias), "guest sampler descriptor requests a LOD bias beyond the device limit"); const auto mirrorsOnce = [](VkSamplerAddressMode mode) { return mode == VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE; }; Require(context.samplerMirrorClampToEdge || !(mirrorsOnce(descriptor.addressModeU) || mirrorsOnce(descriptor.addressModeV) || mirrorsOnce(descriptor.addressModeW)), "guest sampler descriptor uses a mirror-once clamp mode which the device does not support"); Require(!(descriptor.unnormalizedCoordinates && descriptor.compareEnable), "guest sampler descriptor with unnormalized coordinates enables depth comparison, which is not implemented"); @@ -36,7 +41,7 @@ namespace AgcDriver::Graphics { info.addressModeU = descriptor.addressModeU; info.addressModeV = descriptor.addressModeV; info.addressModeW = descriptor.addressModeW; - info.mipLodBias = descriptor.lodBias; + info.mipLodBias = ShaderAppliesLodBias(descriptor) ? 0.0f : descriptor.lodBias; info.anisotropyEnable = descriptor.anisotropyEnable ? VK_TRUE : VK_FALSE; info.maxAnisotropy = descriptor.maxAnisotropy; info.compareEnable = descriptor.compareEnable ? VK_TRUE : VK_FALSE; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index ea0ca7232b..554e209edd 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -2,6 +2,7 @@ #include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp" #include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp" #include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" +#include "prx/libSceAgcDriver/Execution/include/ThreadScratch.hpp" #include "ThreadOwned.hpp" #include "prx/libc/include/HostThreadLocal.hpp" #include "prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp" @@ -58,24 +59,18 @@ bool overlap(std::uint64_t first, std::size_t firstSize, std::uint64_t second, s bool registeredImageViewsCurrent(std::span views) { if (views.empty()) return true; - for (std::uint32_t attempt = 0; attempt < 2u; ++attempt) { - const auto before = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); - bool current = true; - for (const auto& view : views) { - const auto range = GuestAllocations::GuestAllocationsLookup_nid_postfix(static_cast(view.address), false); - if (view.hasRange != (range != nullptr)) { - current = false; - break; - } - if (range != nullptr && (range->address != view.rangeAddress || range->bytes != view.rangeBytes || range->allocationAddress != view.allocationAddress || range->allocationBytes != view.allocationBytes || range->backingSerial != view.backingSerial || range->readable != view.readable || range->writable != view.writable || range->gpu != view.gpu)) { - current = false; - break; - } - } - const auto after = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); - if (before == after) return current; + std::vector addresses; + addresses.reserve(views.size()); + for (const auto& view : views) addresses.push_back(static_cast(view.address)); + std::vector> ranges(views.size()); + GuestAllocations::GuestAllocationsLookupEach_nid_postfix(addresses.data(), addresses.size(), ranges.data()); + for (std::size_t index = 0; index < views.size(); ++index) { + const auto& view = views[index]; + const auto& range = ranges[index]; + if (view.hasRange != (range != nullptr)) return false; + if (range != nullptr && (range->address != view.rangeAddress || range->bytes != view.rangeBytes || range->allocationAddress != view.allocationAddress || range->allocationBytes != view.allocationBytes || range->backingSerial != view.backingSerial || range->readable != view.readable || range->writable != view.writable || range->gpu != view.gpu)) return false; } - return false; + return true; } VkComponentSwizzle ComponentSwizzleFor(std::uint8_t dstSel) { @@ -3409,17 +3404,17 @@ bool ShaderResources::fastRevalidate(std::uint64_t serialBefore, std::span* queriesSlot = nullptr; - auto& queries = ShaderRecompiler::ThreadOwned(queriesSlot); - thread_local std::vector* pendingSlot = nullptr; - auto& pending = ShaderRecompiler::ThreadOwned(pendingSlot); + struct QueriesTag {}; + auto& queries = ThreadScratch, QueriesTag>(); + struct PendingTag {}; + auto& pending = ThreadScratch, PendingTag>(); // The element each pending query stands for, in query order. - thread_local std::vector* ownersSlot = nullptr; - auto& owners = ShaderRecompiler::ThreadOwned(ownersSlot); - thread_local std::vector* imagesSlot = nullptr; - auto& images = ShaderRecompiler::ThreadOwned(imagesSlot); - thread_local std::vector* scannedKeysSlot = nullptr; - auto& scannedKeys = ShaderRecompiler::ThreadOwned(scannedKeysSlot); + struct OwnersTag {}; + auto& owners = ThreadScratch, OwnersTag>(); + struct ImagesTag {}; + auto& images = ThreadScratch, ImagesTag>(); + struct ScannedKeysTag {}; + auto& scannedKeys = ThreadScratch, ScannedKeysTag>(); queries.clear(); pending.clear(); owners.clear(); @@ -3923,10 +3918,10 @@ bool ShaderResources::revalidateFull(std::span shaders, Pr captureValidation(); return true; }; - thread_local std::vector* overlappingSlot = nullptr; - auto& overlapping = ShaderRecompiler::ThreadOwned(overlappingSlot); - thread_local std::vector* refreshedSlot = nullptr; - auto& refreshed = ShaderRecompiler::ThreadOwned(refreshedSlot); + struct OverlappingTag {}; + auto& overlapping = ThreadScratch, OverlappingTag>(); + struct RefreshedTag {}; + auto& refreshed = ThreadScratch, RefreshedTag>(); refreshed.clear(); FastFail reason = FastFail::Count; bool accepted = false; @@ -3983,8 +3978,8 @@ bool ShaderResources::revalidateFull(std::span shaders, Pr // The walk after the refresh, or beside a proof that accepted a foreign overlap, must // find every object in place and current: another object, or an upload (a moved // content version), is a decision the proof got wrong. - thread_local std::vector>* versionsSlot = nullptr; - auto& versions = ShaderRecompiler::ThreadOwned(versionsSlot); + struct VersionsTag {}; + auto& versions = ThreadScratch>, VersionsTag>(); versions.clear(); for (const auto& surface : validatedTextures) { if (surface.source != nullptr) versions.emplace_back(surface.source, surface.source->Version()); @@ -4021,8 +4016,8 @@ bool ShaderResources::revalidateFull(std::span shaders, Pr }; if (EpochRevalidate()) { if ((!directRegions.empty() || !guestMemory.Writes().empty()) && !(pendingSerialSeen != 0 && pendingSerialSeen == StorageTexture::PendingSerial())) { - thread_local std::vector* regionsSlot = nullptr; - auto& regions = ShaderRecompiler::ThreadOwned(regionsSlot); + struct RegionsTag {}; + auto& regions = ThreadScratch, RegionsTag>(); regions.clear(); for (const auto& region : directRegions) regions.push_back({region.begin, region.end, nullptr, nullptr, false}); for (const auto& [begin, end] : guestMemory.Writes()) regions.push_back({begin, end, nullptr, nullptr, false}); @@ -5467,6 +5462,7 @@ void ShaderResources::addSamplerTable(const ShaderRecompiler::DescriptorBinding& auto resource = DecodeSamplerResource(elementWords); resource.compareEnable = binding.samplerDepthCompare.at(element); Sampler::Validate(context, resource); + Require(!Sampler::ShaderAppliesLodBias(resource), "a sampler table entry with a LOD bias beyond the sampler limit needs a per-entry shader bias, which is not implemented"); Require(!Sampler::ReadsOpaqueBlackBorder(resource), "a sampler table entry with an opaque black border needs dynamic texture BC-swizzle pairing, which is not implemented"); Require(resource.addressModeU != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER && resource.addressModeV != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER && resource.addressModeW != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, "a sampler table entry with a border needs dynamic texture numeric-class pairing, which is not implemented"); Require(!resource.nonSeamlessCube || context.nonSeamlessCubeMap, "a sampler table entry that disables seamless cube filtering needs VK_EXT_non_seamless_cube_map because dynamic texture/sampler pairing is not implemented"); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index 3f1f0b4b70..772b923d32 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -10,6 +10,7 @@ #include "prx/libc/include/General.hpp" #include "prx/libc/include/general/AtomicSharedPtr.hpp" #include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp" +#include "prx/libc/include/HostThreadSlot.hpp" #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" #include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" @@ -1849,7 +1850,8 @@ bool FillClearColor(VkFormat format, std::uint32_t elementBytes, std::span; void FlushHook(std::uint64_t address, std::size_t bytes) { StorageTexture::FlushPending(address, bytes); @@ -2261,11 +2263,11 @@ bool StorageTexture::refresh(std::uint32_t firstLayer, std::uint32_t lastLayer) refreshProof = {}; struct Exempt { const StorageTexture* previous; - ~Exempt() { refreshing = previous; } - } exempt{refreshing}; + ~Exempt() { Refreshing::Set(previous); } + } exempt{Refreshing::Get()}; // This image's own pending results stay on the GPU, where the next dispatch wants them (the // flush below and the compare through the hook skip it). - refreshing = this; + Refreshing::Set(this); NoteProved(); // Results of other images pending in this memory must reach it first, except an alias's: its // units are taken on the device below (borrowUnits), so it stays pending. The keys read for @@ -2832,9 +2834,9 @@ bool StorageTexture::compareUntracked(std::uint64_t address, std::size_t bytes, if (!originalHolds(address, bytes)) return false; struct Exempt { const StorageTexture* previous; - ~Exempt() { refreshing = previous; } - } exempt{refreshing}; - refreshing = this; + ~Exempt() { Refreshing::Set(previous); } + } exempt{Refreshing::Get()}; + Refreshing::Set(this); Require(address >= descriptor.baseAddress && bytes <= guestBytes && address - descriptor.baseAddress <= guestBytes - bytes, "texture comparison exceeds its snapshot"); Require(original.size() == guestBytes, "texture comparison has no complete snapshot"); GuestMemory::FlushGpuWrites(address, bytes); @@ -4584,7 +4586,7 @@ bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, cons if (pending.flushingCount.load(std::memory_order_acquire) != 0) return false; if (!pending.textures.SurelyClear(address, bytes)) { std::lock_guard lock(pending.mutex); - const auto* exempt = refreshing; + const auto* exempt = Refreshing::Get(); if (pending.textures.MayOverlap(address, bytes) && std::any_of(pending.textures.begin(), pending.textures.end(), [&](const StorageTexture* texture) { return texture != except && texture != exempt && texture->overlaps(address, bytes) && texture->pendingUnitInside(address, bytes); })) return false; if (pending.flushingCount.load(std::memory_order_acquire) != 0) return false; } @@ -4614,7 +4616,7 @@ bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, cons { auto& pending = Pending(); std::lock_guard lock(pending.mutex); - const auto* exempt = refreshing; + const auto* exempt = Refreshing::Get(); for (auto it = pending.textures.MayOverlap(address, bytes) ? pending.textures.begin() : pending.textures.end(); it != pending.textures.end();) { auto* texture = *it; if (texture != except && texture != exempt && (device == VK_NULL_HANDLE || texture->context.device == device) && texture->overlaps(address, bytes)) { @@ -4762,7 +4764,7 @@ bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, cons bool StorageTexture::WaitForFlushing(std::uint64_t address, std::size_t bytes, const StorageTexture* except) { auto& pending = Pending(); if (pending.flushingCount.load(std::memory_order_acquire) == 0 || GuestMemory::GpuMutex().HeldByThisThread()) return false; - const auto* exempt = refreshing; + const auto* exempt = Refreshing::Get(); for (bool waited = false;; waited = true) { { std::lock_guard lock(pending.mutex); @@ -5415,7 +5417,7 @@ std::vector> StorageTexture::overlappingPending( 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; + const auto* exempt = Refreshing::Get(); for (auto* texture : pending.textures) { if (texture == exempt || !texture->overlaps(address, bytes) || !texture->pendingUnitInside(address, bytes)) continue; if (auto alive = texture->weak_from_this().lock()) overlapping.push_back(std::move(alive)); @@ -5597,7 +5599,7 @@ std::size_t StorageTexture::DiscardPendingInside(std::uint64_t address, std::siz std::lock_guard lock(pending.mutex); std::size_t discarded = 0; const auto end = address + bytes; - const auto* exempt = refreshing; + const auto* exempt = Refreshing::Get(); for (auto it = pending.textures.begin(); it != pending.textures.end();) { auto* texture = *it; const auto begin = texture->descriptor.baseAddress; diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp b/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp index 8b01cc19db..42f03743e8 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp @@ -2033,6 +2033,48 @@ void importAllocationErrorTests(const Context& context, VkResult failure, bool n ::operator delete(block, std::align_val_t{piece}); } +void importMixedAllocationErrorTests(const Context& context) { + constexpr std::size_t piece = 65536; + constexpr std::size_t count = 16; + auto* block = static_cast(::operator new(2 * count * piece, std::align_val_t{piece})); + auto importing = context; + importing.hostImportAlignment = piece; + ImportAllocationProbe probe(importing); + SetHostImportWindow(0); + ClearHostImports(importing.device); + { + GuestAllocations::Mutation mutation; + for (std::size_t index = 0; index < count; ++index) mutation.Add(block + 2 * index * piece, piece, true, true, true); + } + SetHostImportBudget(64 * piece); + std::size_t next = 0; + const auto lookup = [&] { return HostImportFor(importing, reinterpret_cast(block + 2 * next++ * piece), 16) != nullptr; }; + const auto counted = [] { return HostImportCounters().requesters[static_cast(ImportRequester::Access)]; }; + Require(lookup(), "the mixed allocation error test's first import was not made"); + const auto before = counted(); + for (const auto failure : {VK_ERROR_OUT_OF_DEVICE_MEMORY, VK_ERROR_OUT_OF_HOST_MEMORY, VK_ERROR_INVALID_EXTERNAL_HANDLE, VK_ERROR_OUT_OF_DEVICE_MEMORY, VK_ERROR_UNKNOWN, VK_ERROR_OUT_OF_HOST_MEMORY}) { + probe.failure = failure; + Require(!lookup(), "an import with an injected mixed allocation failure was made"); + } + const auto after = counted(); + Require(after.failed - before.failed == 6 && after.refused == before.refused, "interrupted OOM failures lowered the import ceiling"); + probe.failure = VK_SUCCESS; + Require(lookup(), "nonconsecutive OOM failures prevented a later successful import"); + for (const auto failure : {VK_ERROR_OUT_OF_HOST_MEMORY, VK_ERROR_OUT_OF_DEVICE_MEMORY, VK_ERROR_OUT_OF_HOST_MEMORY}) { + probe.failure = failure; + Require(!lookup(), "an import with an injected consecutive OOM was made"); + } + probe.failure = VK_SUCCESS; + Require(!lookup(), "three consecutive OOM failures did not lower the import ceiling"); + { + GuestAllocations::Mutation mutation; + for (std::size_t index = 0; index < count; ++index) mutation.Remove(block + 2 * index * piece); + } + ClearHostImports(importing.device); + SetHostImportBudget(0); + ::operator delete(block, std::align_val_t{piece}); +} + void importCeilingTests(const Context& context, const BdaTestAccess& access, bool nearBudget) { constexpr std::size_t piece = 65536; constexpr std::size_t count = 24; @@ -4814,6 +4856,7 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { evictingRefusalJoinTests(context); evictingRefusalStepDownTests(context); importDeviceBudgetTests(); + importMixedAllocationErrorTests(context); importCeilingTests(context, access, false); importCeilingTests(context, access, true); for (const auto failure : {VK_ERROR_OUT_OF_HOST_MEMORY, VK_ERROR_OUT_OF_DEVICE_MEMORY, VK_ERROR_INVALID_EXTERNAL_HANDLE, VK_ERROR_UNKNOWN}) { diff --git a/core/libs/prx/libSceAgcDriver/tests/FrameReplay.cpp b/core/libs/prx/libSceAgcDriver/tests/FrameReplay.cpp index 9b557243eb..2915200a3b 100644 --- a/core/libs/prx/libSceAgcDriver/tests/FrameReplay.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/FrameReplay.cpp @@ -1,3 +1,4 @@ +#include "execution/VulkanTestDevice.hpp" #include "prx/libSceAgc/DcbFlow/include/Memory.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/FrameCapture.hpp" @@ -134,7 +135,7 @@ bool equal(const std::byte* bytes, const std::vector& expected) { return std::memcmp(bytes, expected.data(), expected.size()) == 0; } -void testCaptureAndReplay(const std::filesystem::path& tool) { +void testCaptureAndReplay(const std::filesystem::path& tool, bool pipelines) { const char* directory = std::getenv("APS5_FRAME_CAPTURE"); check(directory != nullptr && *directory != '\0', "APS5_FRAME_CAPTURE is not set"); const std::filesystem::path capture(directory); @@ -186,17 +187,40 @@ void testCaptureAndReplay(const std::filesystem::path& tool) { check(recorded(late.base), "the block committed during the capture is not in its ranges"); check(reader.Header().find("\"late_ranges\":1") != std::string::npos, "the capture header does not count one range committed during the capture: " + reader.Header()); - const auto firstDump = capture / "first.bin"; - const auto secondDump = capture / "second.bin"; - auto command = "\"" + tool.string() + "\" \"" + capture.string() + "\" --strict --dump " + std::to_string(at(layout.firstTarget)) + ":" + std::to_string(DataBytes) + ":\"" + firstDump.string() + - "\" --dump " + std::to_string(at(late.firstTarget)) + ":" + std::to_string(DataBytes) + ":\"" + secondDump.string() + "\""; + const auto firstDump = capture / "first dump.bin"; + const auto secondDump = capture / "second dump.bin"; + const auto replay = [&](const std::string& options, const std::filesystem::path& output) { + std::filesystem::remove(firstDump); + std::filesystem::remove(secondDump); + auto command = "\"" + tool.string() + "\" \"" + capture.string() + "\" --strict " + options + " --dump " + std::to_string(at(layout.firstTarget)) + ":" + std::to_string(DataBytes) + ":\"" + + firstDump.string() + "\" --dump " + std::to_string(at(late.firstTarget)) + ":" + std::to_string(DataBytes) + ":\"" + secondDump.string() + "\" > \"" + output.string() + "\" 2> \"" + output.string() + ".log\""; #ifdef _WIN32 - command = "\"" + command + "\""; + command = "\"" + command + "\""; #endif - const auto code = std::system(command.c_str()); - check(code == 0, "agc_frame_replay exited with " + std::to_string(code)); - check(readFile(firstDump) == first, "the replay did not reproduce the first copy"); - check(readFile(secondDump) == second, "the replay did not reproduce the second copy"); + const auto code = std::system(command.c_str()); + check(code == 0, "agc_frame_replay " + options + " exited with " + std::to_string(code)); + check(readFile(firstDump) == first, "the replay " + options + " did not reproduce the first copy"); + check(readFile(secondDump) == second, "the replay " + options + " did not reproduce the second copy"); + const auto json = readFile(output); + return std::string(reinterpret_cast(json.data()), json.size()); + }; + replay("", capture / "replay output.json"); + if (pipelines) { + check(std::getenv("APS5_HEADLESS_PIPELINE_CACHE") != nullptr, "the pipeline cache switch was not inherited"); + const auto warm = capture / "warm output.json"; + replay("", warm); + const auto warmLog = readFile(warm.string() + ".log"); + check(std::string(reinterpret_cast(warmLog.data()), warmLog.size()).find("[pipeline-cache] loaded ") != std::string::npos, "the warm replay did not load the seeded pipeline cache"); + const auto cold = capture / "cold output.json"; + replay("--cold-pipelines", cold); + const auto coldLog = readFile(cold.string() + ".log"); + check(std::string(reinterpret_cast(coldLog.data()), coldLog.size()).find("[pipeline-cache] loaded ") == std::string::npos, "--cold-pipelines loaded a cache with APS5_HEADLESS_PIPELINE_CACHE inherited"); + return; + } + const auto repeated = replay("--repeat 3", capture / "repeat output.json"); + std::size_t passes = 0; + for (auto found = repeated.find("\"frames\":2,\"p50\""); found != std::string::npos; found = repeated.find("\"frames\":2,\"p50\"", found + 1)) ++passes; + check(passes == 3, "the repeated replay did not replay both frames in each of three passes: " + repeated); } void testConcurrentCopies(const std::filesystem::path& tool) { @@ -261,16 +285,57 @@ void testConcurrentCopies(const std::filesystem::path& tool) { } } +void testReservationError(const std::filesystem::path& tool) { + const char* directory = std::getenv("APS5_FRAME_CAPTURE"); + check(directory != nullptr && *directory != '\0', "APS5_FRAME_CAPTURE is not set"); + const std::filesystem::path capture(directory); + std::uintptr_t base = 0; + std::size_t size = 0; + GuestArena::GuestArenaRange_nid_postfix(&base, &size); + AgcDriver::FrameCaptureWriter writer(capture); + AgcDriver::FrameCall output; + output.kind = FrameCallKind::RegisterVideoOutput; + output.values = {Handle}; + writer.WriteCall(output); + AgcDriver::FrameCall reservation; + reservation.kind = FrameCallKind::ReserveEopFlip; + reservation.values = {Handle, static_cast(-1), 1, 0, AgcDriver::EopFlipTokenBase}; + writer.WriteCall(reservation); + AgcDriver::FrameCall start; + start.kind = FrameCallKind::FrameStart; + writer.WriteCall(start); + output.kind = FrameCallKind::UnregisterVideoOutput; + writer.WriteCall(output); + output.kind = FrameCallKind::RegisterVideoOutput; + writer.WriteCall(output); + reservation.values[3] = 1; + writer.WriteCall(reservation); + writer.Finish("{\"arena_base\":" + std::to_string(base) + "}"); + const auto result = capture / "reservation error.json"; + const auto log = capture / "reservation error.log"; + auto command = "\"" + tool.string() + "\" \"" + capture.string() + "\" --repeat 2 --strict > \"" + result.string() + "\" 2> \"" + log.string() + "\""; +#ifdef _WIN32 + command = "\"" + command + "\""; +#endif + check(std::system(command.c_str()) != 0, "the replay accepted an incompatible reservation on the second pass"); + const auto errors = readFile(log); + check(std::string(reinterpret_cast(errors.data()), errors.size()).find("EOP flip token is reserved for a different flip") != std::string::npos, "the repeated replay did not report the incompatible reservation"); + const auto report = readFile(result); + check(std::string(reinterpret_cast(report.data()), report.size()).find("\"p50\"") == std::string::npos, "a failed repeated reservation produced a successful replay report"); +} + } int main(int argc, char** argv) { - if (argc != 2 && !(argc == 3 && std::string(argv[2]) == "concurrent")) { - std::fprintf(stderr, "usage: agc_driver_frame_replay_tests [concurrent]\n"); + if (argc != 2 && !(argc == 3 && (std::string(argv[2]) == "concurrent" || std::string(argv[2]) == "pipelines" || std::string(argv[2]) == "reservation_error"))) { + std::fprintf(stderr, "usage: agc_driver_frame_replay_tests [concurrent|pipelines|reservation_error]\n"); return 2; } try { - if (argc == 3) testConcurrentCopies(argv[1]); - else testCaptureAndReplay(argv[1]); + if (!OpenVulkanTestDevice()) return VulkanTestSkipped; + if (argc == 3 && std::string(argv[2]) == "concurrent") testConcurrentCopies(argv[1]); + else if (argc == 3 && std::string(argv[2]) == "reservation_error") testReservationError(argv[1]); + else testCaptureAndReplay(argv[1], argc == 3); } catch (const std::exception& error) { std::fprintf(stderr, "FAILED: %s\n", error.what()); return 1; diff --git a/core/libs/prx/libSceAgcDriver/tests/FrameReplayRelaunch.cpp b/core/libs/prx/libSceAgcDriver/tests/FrameReplayRelaunch.cpp new file mode 100644 index 0000000000..e5b984dae3 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/FrameReplayRelaunch.cpp @@ -0,0 +1,98 @@ +#define main frameReplayToolMain +#include "../tools/FrameReplay.cpp" +#undef main +#include + +namespace { + +using AgcDriver::FrameCaptureWriter; + +constexpr int Skip = 77; +constexpr DWORD ChildLimit = 60000; + +void check(bool condition, const std::string& reason) { + if (!condition) throw std::runtime_error(reason); +} + +void append(const std::filesystem::path& log, const std::string& line) { + std::ofstream file(log, std::ios::app); + file << line << '\n'; + check(file.good(), "cannot write the relaunch log"); +} + +int child(const std::filesystem::path& directory, int exitCode) { + const int local = 0; + const auto address = reinterpret_cast(&local) & ~std::uintptr_t{0xfff}; + if (!GuestArena::GuestArenaHostRegionOverlaps_nid_postfix(address, 4096)) return Skip; + const char* const attempt = std::getenv("APS5_FRAME_REPLAY_ATTEMPT"); + append(directory / "log.txt", attempt == nullptr ? "none" : attempt); + const auto capture = directory / std::to_string(GetCurrentProcessId()); + FrameCaptureWriter writer(capture); + writer.WriteRange(address, 4096, FrameRangeKind::Watched, 3, AgcDriver::FrameRangeAccess::Readable | AgcDriver::FrameRangeAccess::Gpu); + writer.Finish("{}"); + const FrameCaptureReader reader(capture); + relaunchIfHostRegionBlocks(reader); + append(directory / "log.txt", "continued"); + return exitCode; +} + +int start(const std::filesystem::path& directory, const char* attempt, int exitCode) { + std::filesystem::remove_all(directory); + std::filesystem::create_directories(directory); + check(_putenv_s("APS5_FRAME_REPLAY_ATTEMPT", attempt == nullptr ? "" : attempt) == 0, "cannot set the relaunch count"); + std::wstring self(32768, L'\0'); + self.resize(GetModuleFileNameW(nullptr, self.data(), static_cast(self.size()))); + std::wstring commandLine = L"\"" + self + L"\" child \"" + directory.wstring() + L"\" " + std::to_wstring(exitCode); + STARTUPINFOW startup{}; + startup.cb = sizeof(startup); + PROCESS_INFORMATION process{}; + check(CreateProcessW(nullptr, commandLine.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &startup, &process) != 0, "cannot start the replay child"); + CloseHandle(process.hThread); + const bool exited = WaitForSingleObject(process.hProcess, ChildLimit) == WAIT_OBJECT_0; + if (!exited) TerminateProcess(process.hProcess, 1); + DWORD code = 1; + GetExitCodeProcess(process.hProcess, &code); + CloseHandle(process.hProcess); + check(exited, "the replay child did not exit"); + return static_cast(code); +} + +std::vector lines(const std::filesystem::path& path) { + std::vector result; + std::ifstream file(path); + for (std::string line; std::getline(file, line);) result.push_back(line); + return result; +} + +bool expectRun(const std::filesystem::path& directory, const char* attempt, int exitCode, int expected, const std::vector& log) { + const int code = start(directory, attempt, exitCode); + if (code == Skip) return false; + const std::string name = attempt == nullptr ? "no count" : attempt; + check(code == expected, name + ": exited with " + std::to_string(code)); + check(lines(directory / "log.txt") == log, name + ": wrong process sequence"); + return true; +} + +} + +int main(int argc, char** argv) { + try { + if (argc == 4 && std::string_view(argv[1]) == "child") return child(argv[2], std::atoi(argv[3])); + check(argc == 2, "usage: agc_driver_frame_replay_relaunch_tests "); + const std::filesystem::path directory = argv[1]; + const std::vector full{"none", "1", "2", "3", "4", "5", "6", "7", "8", "continued"}; + if (!expectRun(directory, nullptr, 0, 0, full)) { + std::puts("skipped: the stack is not an arena host region"); + return Skip; + } + check(expectRun(directory, nullptr, 42, 42, full), "the failing run skipped"); + check(expectRun(directory, "7", 0, 0, {"7", "8", "continued"}), "the last relaunch skipped"); + check(expectRun(directory, "8", 5, 5, {"8", "continued"}), "the exhausted count skipped"); + for (const char* const malformed : {"-1", "-2147483648", "9", "99999999999", "abc", "1x", " 1", "+1"}) check(expectRun(directory, malformed, 0, 2, {malformed}), "a malformed count skipped"); + std::puts("frame replay relaunch tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "FAILED: %s\n", error.what()); + return argc == 4 ? 2 : 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/FrameReplayState.cpp b/core/libs/prx/libSceAgcDriver/tests/FrameReplayState.cpp index b8e502c97b..d475caadcc 100644 --- a/core/libs/prx/libSceAgcDriver/tests/FrameReplayState.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/FrameReplayState.cpp @@ -2,9 +2,11 @@ #include "../tools/FrameReplay.cpp" #undef main #include "prx/libSceAgcDriver/Execution/include/FrameRecorder.hpp" +#include "prx/libSceAgcDriver/Execution/include/Presentation.hpp" #include "prx/libc/include/GuestWriteWatch.hpp" #include #include +#include namespace { @@ -183,6 +185,94 @@ void testEopTokens() { AgcDriverUnregisterVideoOutput_nid_postfix(7, output); } +class ReservationOutput final : public AgcDriver::IVideoOutput { +public: + explicit ReservationOutput(Frames& frames) : frames(frames) {} + std::shared_ptr Reserve(const AgcDriver::FlipInfo&) override { + ++reservations; + if (failure) std::rethrow_exception(failure); + return available ? std::make_shared(frames) : nullptr; + } + void Fail(std::exception_ptr error) noexcept override { frames.Failed(error); } + std::exception_ptr failure; + bool available = true; + std::size_t reservations = 0; + +private: + Frames& frames; +}; + +FrameCall reservationCall(std::uint32_t token) { + FrameCall call; + call.kind = FrameCallKind::ReserveEopFlip; + call.values = {7, static_cast(-1), 1, 99, token}; + return call; +} + +template +void expectReservationFailure(Replay& replay, const FrameCall& call) { + const auto kept = replay.keptReservations.load(); + bool failed = false; + try { replay.runWindow(std::span(&call, 1)); } + catch (const TException&) { failed = true; } + check(failed, "the replay accepted a failed EOP reservation"); + check(replay.keptReservations.load() == kept, "a failed EOP reservation was counted as kept"); +} + +void testRepeatedReservations(const std::filesystem::path& directory, bool driverFailure) { + FrameCaptureWriter writer(directory); + writer.Finish("{}"); + const FrameCaptureReader reader(directory); + Options options; + options.pace = false; + options.release = false; + Replay replay{options, reader}; + auto output = std::make_shared(replay.frames); + AgcDriverRegisterVideoOutput_nid_postfix(7, output); + AgcDriverRegisterVideoOutput_nid_postfix(8, output); + const auto call = reservationCall(AgcDriver::EopFlipTokenBase); + replay.execute(call); + expectReservationFailure(replay, call); + replay.pass = 1; + if (driverFailure) { + AgcDriverReportFailure_nid_postfix(std::make_exception_ptr(std::runtime_error("reservation driver failure"))); + expectReservationFailure(replay, call); + bool failed = false; + try { AgcDriverShutdown_nid_postfix(); } + catch (const std::runtime_error& error) { failed = std::string_view(error.what()) == "reservation driver failure"; } + check(failed, "the injected driver failure was not propagated at shutdown"); + return; + } + replay.runWindow(std::span(&call, 1)); + check(replay.keptReservations.load() == 1 && output->reservations == 1, "a compatible pending reservation was not kept without allocating again"); + for (std::size_t field = 0; field < 4; ++field) { + auto different = call; + ++different.values[field]; + expectReservationFailure(replay, different); + } + auto missing = call; + missing.values[0] = 9; + expectReservationFailure(replay, missing); + expectReservationFailure(replay, reservationCall(AgcDriver::EopFlipTokenBase - 1)); + expectReservationFailure(replay, reservationCall(AgcDriver::EopFlipTokenLimit + 1)); + const auto fresh = reservationCall(AgcDriver::EopFlipTokenBase + 1); + output->failure = std::make_exception_ptr(std::bad_alloc{}); + expectReservationFailure(replay, fresh); + output->failure = std::make_exception_ptr(std::runtime_error("reservation output failure")); + expectReservationFailure(replay, fresh); + output->failure = nullptr; + output->available = false; + expectReservationFailure(replay, fresh); + output->available = true; + AgcDriverUnregisterVideoOutput_nid_postfix(7, output); + auto replacement = std::make_shared(replay.frames); + AgcDriverRegisterVideoOutput_nid_postfix(7, replacement); + replay.runWindow(std::span(&call, 1)); + check(replay.keptReservations.load() == 1 && replacement->reservations == 1, "a closed output's token was kept after reopening"); + AgcDriverUnregisterVideoOutput_nid_postfix(7, replacement); + AgcDriverUnregisterVideoOutput_nid_postfix(8, output); +} + class WaitingOutput final : public AgcDriver::IVideoOutput { public: WaitingOutput(Frames& frames, const std::uint32_t* label) : frames(frames), label(label) {} @@ -320,6 +410,101 @@ void testShaderCode(const std::filesystem::path& directory, bool host, std::stri check(protectedHeader, "the read-only host shader was recorded with write access"); } check(registered && captured == expected, "the complete separate shader code was not captured before registration"); + CapturedMemory memory(reader); + std::map> groups; + for (const auto& chunk : reader.Chunks()) { + if (chunk.kind == FrameMemoryKind::Delta) groups[chunk.group].push_back(&chunk); + } + std::optional> listed; + for (const auto& call : reader.Calls()) { + if (call.kind == FrameCallKind::RegisterShader) { + listed = memory.Read(address(code), CodeBytes); + break; + } + if (call.kind != FrameCallKind::Memory) continue; + if (const auto found = groups.find(call.values.at(0)); found != groups.end()) memory.Apply(found->second); + } + check(listed && *listed == expected, "the offline shader listing read other code than the capture holds at the registration"); +} + +void writeInspectionCapture(const std::filesystem::path& directory) { + constexpr std::uint64_t Commands = 0x1000000001ull; + constexpr std::uint64_t Header = 0x2000000000ull; + constexpr std::uint64_t Code = 0x2000010000ull; + const std::array commands{ + 0x00010000u, 0xc0053c00u, 0xc0064900u, 0x80000000u, 0xffff1000u, + 0xc0053c00u, 0x13u, 0x12345678u, 0x9abcdef0u, 0x10203040u, 0xf0f0f0f0u, 4u, + 0xc0064900u, 1u, 2u, 3u, 4u, 5u, 6u, 7u, + 0xc0033700u, 0x500u, 0xdeadbeefu, 0x12u, 0x1337u, + 0x00030000u, 0xc0053c00u}; + std::array code{0, 1, 2, 3, 4, 5, 6, 7, 255, 128, 13, 10, 39, 36, 38, 0}; + Shader shader{}; + shader.code = reinterpret_cast(static_cast(Code)); + shader.shader_size = code.size(); + FrameCaptureWriter writer(directory); + writer.WriteRange(Commands, 6, FrameRangeKind::Host, ReadWrite, FrameRangeAccess::Readable); + writer.WriteRange(Commands + 6, sizeof(commands) - 6, FrameRangeKind::Host, ReadWrite, FrameRangeAccess::Readable); + writer.WriteRange(Header, sizeof(shader), FrameRangeKind::Host, ReadWrite, FrameRangeAccess::Readable); + writer.WriteRange(Code, code.size(), FrameRangeKind::Host, ReadWrite, FrameRangeAccess::Readable); + writer.WriteSnapshot(Header, std::as_bytes(std::span(&shader, 1))); + writer.WriteSnapshot(Code, std::as_bytes(std::span(code))); + writer.WriteDelta(1, 0, Commands, std::as_bytes(std::span(commands))); + const std::uint32_t reference = 0x55667788u; + writer.WriteDelta(2, 0, Commands + 9 * sizeof(std::uint32_t), std::as_bytes(std::span(&reference, 1))); + code[0] = 0x42; + writer.WriteDelta(3, 0, Code, std::as_bytes(std::span(code))); + FrameCall call; + const auto write = [&](FrameCallKind kind, std::uint64_t location, std::uint64_t size, std::vector values = {}) { + call.kind = kind; + ++call.ordinal; + call.address = location; + call.size = size; + call.values = std::move(values); + call.thread = 7; + call.queue = 0x42; + call.accepted = 12; + call.completed = 9; + writer.WriteCall(call); + }; + write(FrameCallKind::Submit, Commands, commands.size()); + write(FrameCallKind::Memory, 0, 0, {1}); + write(FrameCallKind::RegisterShader, Header, 0); + write(FrameCallKind::FrameStart, 0, 0); + write(FrameCallKind::Submit, Commands, commands.size()); + write(FrameCallKind::Memory, 0, 0, {2}); + write(FrameCallKind::Submit, Commands, commands.size()); + write(FrameCallKind::RegisterShader, Header, 0); + write(FrameCallKind::Memory, 0, 0, {3}); + write(FrameCallKind::RegisterShader, Header, 0); + write(FrameCallKind::Flip, 0, 0); + write(FrameCallKind::ReserveEopFlip, 0, 0); + write(FrameCallKind::Submit, 0x3000000000ull, 1); + write(FrameCallKind::Submit, Commands, std::numeric_limits::max()); + write(FrameCallKind::Submit, std::numeric_limits::max() - 1, 1); + write(FrameCallKind::Submit, Commands, 0); + writer.Finish("{}"); +} + +void testColdPipelines() { + const auto* inherited = std::getenv("APS5_HEADLESS_PIPELINE_CACHE"); + check(inherited != nullptr && std::string_view(inherited) == "1", "pipeline cache setting was not inherited"); + Options options; + options.coldPipelines = true; + configureEnvironment(options); + check(std::getenv("APS5_HEADLESS_PIPELINE_CACHE") == nullptr, "cold replay kept the inherited pipeline cache setting"); + check(std::getenv("APS5_FRAME_CAPTURE") == nullptr, "replay kept the capture setting"); + options.coldPipelines = false; + configureEnvironment(options); + const auto* warm = std::getenv("APS5_HEADLESS_PIPELINE_CACHE"); + check(warm != nullptr && std::string_view(warm) == "1", "warm replay did not enable the pipeline cache"); +#ifdef _WIN32 + _putenv_s("APS5_HEADLESS_PIPELINE_CACHE", "0"); +#else + setenv("APS5_HEADLESS_PIPELINE_CACHE", "0", 1); +#endif + options.coldPipelines = true; + configureEnvironment(options); + check(std::getenv("APS5_HEADLESS_PIPELINE_CACHE") == nullptr, "cold replay kept a present zero pipeline cache setting"); } } @@ -329,6 +514,10 @@ int main(int argc, char** argv) { check(argc == 3, "expected a test name and capture directory"); const std::string mode = argv[1]; const std::filesystem::path directory = argv[2]; + if (mode == "inspect_capture") { + writeInspectionCapture(directory); + return 0; + } if (mode == "order") testMemoryOrder(directory); else if (mode == "zero") testReusedMemory(directory); else if (mode.starts_with("shader_prologue_")) testShaderCode(directory, true, mode); @@ -336,10 +525,12 @@ int main(int argc, char** argv) { else if (mode == "host_neighbors") testHostNeighbors(directory); else if (mode == "unwatched") testUnwatched(); else if (mode == "tokens") testEopTokens(); + else if (mode == "reservations" || mode == "reservation_failure") testRepeatedReservations(directory, mode == "reservation_failure"); else if (mode == "blocking") testBlockingSubmission(directory); + else if (mode == "environment") testColdPipelines(); else if (mode == "shader" || mode == "shader_host") testShaderCode(directory, mode == "shader_host"); else throw std::runtime_error("unknown test"); - AgcDriverShutdown_nid_postfix(); + if (mode != "reservation_failure") AgcDriverShutdown_nid_postfix(); std::puts("frame replay state test passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/FrameReplayTool.cmake b/core/libs/prx/libSceAgcDriver/tests/FrameReplayTool.cmake new file mode 100644 index 0000000000..dcb2bf078b --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/FrameReplayTool.cmake @@ -0,0 +1,105 @@ +set(capture "${DIRECTORY}/capture 'quoted' & $scene %APS5_HEADLESS_PIPELINE_CACHE% ! (1)") +execute_process(COMMAND "${WRITER}" inspect_capture "${capture}" + RESULT_VARIABLE result OUTPUT_VARIABLE output ERROR_VARIABLE error) +if(NOT result STREQUAL "0") + message(FATAL_ERROR "capture writer failed: ${result}\n${output}\n${error}") +endif() +if(NOT EXISTS "${capture}/calls.bin") + message(FATAL_ERROR "capture path was not passed literally: ${capture}") +endif() + +function(runTool expected) + execute_process(COMMAND "${TOOL}" "${capture}" ${ARGN} + RESULT_VARIABLE result OUTPUT_VARIABLE output ERROR_VARIABLE error) + string(REPLACE "\\" "/" error "${error}") + if(NOT result STREQUAL "${expected}") + message(FATAL_ERROR "agc_frame_replay ${ARGN}: expected ${expected}, got ${result}\n${output}\n${error}") + endif() + set(output "${output}" PARENT_SCOPE) + set(error "${error}" PARENT_SCOPE) +endfunction() + +function(requireContains text needle) + string(FIND "${text}" "${needle}" found) + if(found EQUAL -1) + message(FATAL_ERROR "missing '${needle}' in:\n${text}") + endif() +endfunction() + +if(MODE STREQUAL "submits") + runTool(0 --submits 1) + requireContains("${output}" "#1 ordinal 5 submit thread 7 queue 0x42 serials 9/12 address 0x1000000001 dwords 27") + requireContains("${output}" "@5 WAIT_REG_MEM function 3 space 1 address 0x9abcdef012345678 reference 0x10203040 mask 0xf0f0f0f0") + requireContains("${output}" "@12 RELEASE_MEM 00000001 00000002 00000003 00000004 00000005 00000006 00000007") + requireContains("${output}" "@20 WRITE_DATA 00000500 deadbeef 00000012 00001337") + requireContains("${output}" "@25 (truncated packet: 5 dwords, 2 available)") + requireContains("${output}" "packets: RELEASE_MEM x1 WAIT_REG_MEM x1 WRITE_DATA x1") + if(output MATCHES "#2|ordinal 1 |reference 0x55667788|NOP|@1 WAIT_REG_MEM") + message(FATAL_ERROR "listing included a later submission, prologue or payload packet:\n${output}") + endif() + runTool(0 --submits 2) + requireContains("${output}" "#2 ordinal 7 submit") + requireContains("${output}" "reference 0x55667788") + runTool(0 --submits 4) + requireContains("${output}" "#3 ordinal 11 flip") + requireContains("${output}" "#4 ordinal 12 reserve-eop-flip") + if(output MATCHES "#5") + message(FATAL_ERROR "listing exceeded --submits 4:\n${output}") + endif() + runTool(0 --submits 8) + requireContains("${output}" "#8 ordinal 16 submit") + string(REGEX MATCHALL "command buffer not readable" unreadable "${output}") + list(LENGTH unreadable count) + if(NOT count EQUAL 3) + message(FATAL_ERROR "missing unrecorded and overflowing buffer diagnostics:\n${output}") + endif() + foreach(value IN ITEMS 0 -1 +1 1junk 18446744073709551616) + runTool(2 --submits "${value}") + requireContains("${error}" "usage: agc_frame_replay") + endforeach() + runTool(2 --submits) +elseif(MODE STREQUAL "shader_output") + runTool(0 --shaders) + requireContains("${output}" "\"ordinal\":3,\"in_window\":false") + requireContains("${output}" "\"ordinal\":8,\"in_window\":true") + requireContains("${output}" "\"hash\":\"541dd60be6dc3e20\",\"copy\":2") + requireContains("${output}" "\"hash\":\"b6df526defd638c2\",\"copy\":1") + requireContains("${output}" "{\"registrations\":3,\"in_window\":2,\"distinct_code\":2,\"unreadable\":0}") + set(binaries "${DIRECTORY}/shader code 'quoted' & $scene (2)") + runTool(0 --cold-pipelines --shader-code "${binaries}") + file(GLOB files "${binaries}/*.bin") + list(LENGTH files count) + if(NOT count EQUAL 2) + message(FATAL_ERROR "expected two distinct shader binaries, got ${count}") + endif() + file(READ "${binaries}/type0-541dd60be6dc3e20.bin" first HEX) + file(READ "${binaries}/type0-b6df526defd638c2.bin" second HEX) + if(NOT first STREQUAL "0001020304050607ff800d0a27242600" OR NOT second STREQUAL "4201020304050607ff800d0a27242600") + message(FATAL_ERROR "shader binaries differ from the bytes at registration: ${first}, ${second}") + endif() + set(blocked "${DIRECTORY}/blocked output 'quoted' & $scene (3)") + set(blockedFile "${blocked}/type0-541dd60be6dc3e20.bin") + file(MAKE_DIRECTORY "${blockedFile}") + runTool(2 --shader-code "${blocked}") + requireContains("${error}" "cannot open ${blockedFile}") + if(output MATCHES "\"registrations\"") + message(FATAL_ERROR "failed shader output reported success JSON:\n${output}") + endif() + if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") + set(full "${DIRECTORY}/full output") + file(MAKE_DIRECTORY "${full}") + set(fullFile "${full}/type0-541dd60be6dc3e20.bin") + file(REMOVE "${fullFile}") + file(CREATE_LINK /dev/full "${fullFile}" SYMBOLIC RESULT linked) + if(NOT linked STREQUAL "0") + message(FATAL_ERROR "cannot link /dev/full: ${linked}") + endif() + runTool(2 --shader-code "${full}") + requireContains("${error}" "${fullFile}") + if(NOT error MATCHES "cannot (write|flush|close)" OR output MATCHES "\"registrations\"") + message(FATAL_ERROR "late shader output failure was not reported:\n${output}\n${error}") + endif() + endif() +else() + message(FATAL_ERROR "unknown inspection mode: ${MODE}") +endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 8437c7284b..ca9e414a98 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -5541,6 +5541,7 @@ struct MockVulkan { std::vector writes; std::map samplerFlags; std::uint32_t samplerCreateCount = 0; + float samplerLodBias = 0.0f; std::vector copies; bool allowCopies = false; std::uint32_t boundSets = 0; @@ -5731,6 +5732,7 @@ VKAPI_ATTR VkResult VKAPI_CALL mockFreeDescriptorSets(VkDevice, VkDescriptorPool VKAPI_ATTR VkResult VKAPI_CALL mockCreateSampler(VkDevice, const VkSamplerCreateInfo* info, const VkAllocationCallbacks*, VkSampler* sampler) { *sampler = makeHandle(); mock.samplerFlags[*sampler] = info->flags; + mock.samplerLodBias = info->mipLodBias; ++mock.samplerCreateCount; ++mock.live; return VK_SUCCESS; @@ -7579,8 +7581,9 @@ void samplerTableDeviceLimitTests(std::optional selected = std::n namespace Abi = ShaderRecompiler::ImageTableAbi; constexpr auto count = 2u * Abi::SamplerEntryElements; const std::array valid{0x92u, (4u * 256u) << 12u, 0u, 0u}; - const std::array, 3> invalid{{ + const std::array, 4> invalid{{ {0x92u, valid[1], 256u, 0u}, + {0x92u, valid[1], 0x1000u, 0u}, {0x92u | (2u << 9u), valid[1], (2u << 20u) | (2u << 22u), 0u}, {0x92u | (1u << 29u), valid[1], 0u, 0u} }}; @@ -7634,6 +7637,32 @@ void samplerTableDeviceLimitTests(std::optional selected = std::n } } +void samplerLodBiasTests() { + mock = MockVulkan{}; + auto context = mockContext(); + context.samplerAnisotropy = true; + context.limits.maxSamplerAnisotropy = 2.0f; + context.limits.maxSamplerLodBias = 10.0f; + const auto hostBias = [&](std::uint32_t field) { + const std::array words{0x92u, (4u * 256u) << 12u, field, 0u}; + AgcDriver::Graphics::Sampler sampler(context, AgcDriver::Graphics::DecodeSamplerResource(words)); + return mock.samplerLodBias; + }; + Require(hostBias(256u) == 1.0f, "a positive LOD bias within the device limit changed"); + Require(hostBias(0x3f00u) == -1.0f, "a negative LOD bias within the device limit changed"); + Require(hostBias(0xa00u) == 10.0f, "a LOD bias at the device limit changed"); + Require(hostBias(0x1facu) == 0.0f, "a host sampler kept a positive LOD bias the shader applies"); + Require(hostBias(0x2000u) == 0.0f, "a host sampler kept a negative LOD bias the shader applies"); + bool rejected = false; + try { + hostBias(0xf00u); + } catch (const std::runtime_error&) { + rejected = true; + } + Require(rejected, "a LOD bias beyond the device limit that the shader does not apply was accepted"); + Require(mock.live == 0 && mock.samplerFlags.empty(), "the sampler LOD bias checks leaked Vulkan objects"); +} + void hostImportBudgetTests() { using AgcDriver::Graphics::HostImportBudget; constexpr std::uint64_t MiB = 1ull << 20u; @@ -13538,7 +13567,7 @@ int main(int argc, char** argv) { imageTableSnapshotAllocationTests(); return 0; } - const std::array samplerCases{"--sampler-table-lod-bias", "--sampler-table-anisotropy", "--sampler-table-reduction-compare"}; + const std::array samplerCases{"--sampler-table-lod-bias", "--sampler-table-lod-bias-beyond-threshold", "--sampler-table-anisotropy", "--sampler-table-reduction-compare"}; for (std::uint32_t index = 0; index < samplerCases.size(); ++index) { if (argc == 2 && std::string_view(argv[1]) == samplerCases[index]) { samplerTableDeviceLimitTests(index); @@ -13707,6 +13736,7 @@ int main(int argc, char** argv) { imageTableAllocationTests(); imageTableSnapshotAllocationTests(); samplerTableNonSeamlessCubeTests(); + samplerLodBiasTests(); samplerTableDeviceLimitTests(); tableUpdateAfterBindTests(); misalignedShaderDataTests(); diff --git a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp index adb2819f08..167a3e64d0 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp @@ -257,6 +257,8 @@ void testContextAndBases() { AgcDriver::Pm4::Validate(makePacket(0x16, {0, 0x49}), 0); expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x15, {1, 1, 1, 0x51}), 0); }, "dispatch modifiers"); expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x16, {0, 0x51}), 0); }, "indirect dispatch modifiers"); + expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x26, {0x1001, 1}), 0); }, "unsupported index base alignment or address bits"); + expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x26, {0x1000, 0x10000}), 0); }, "unsupported index base alignment or address bits"); execute(state, makePacket(0x13, {32})); execute(state, makePacket(0x26, {0x1000, 1})); execute(state, makePacket(0x2a, {1})); @@ -448,6 +450,8 @@ void testIndirectDraw() { const auto counted = AgcDriver::Pm4::ResolveDraw(makePacket(0x2c, {0, 0x280, 0x280, 0x280 | (1u << 30u), 5, low(&countValue), high(&countValue), 16, 2}), state); check(counted.indirect->countIndirect && counted.indirect->count == 5 && counted.indirect->countAddress == reinterpret_cast(&countValue) && AgcDriver::Pm4::ReadDrawCount(*counted.indirect) == 2, "indirect draw count mismatch"); expectFailure([&] { AgcDriver::Pm4::ReadDrawCount(*local.indirect); }, "count address"); + expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x2c, {0, 0x280, 0x280, 0x280 | (1u << 30u), 5, low(&countValue), 0x10000, 16, 2}), 0); }, "invalid indirect draw count address"); + expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x38, {0, 0x280, 0x280, 0x280 | (1u << 30u), 5, low(&countValue), 0x10000, 20, 0}), 0); }, "invalid indirect draw count address"); } bool sameDraw(const AgcDriver::Pm4::DrawParameters& left, const AgcDriver::Pm4::DrawParameters& right) { diff --git a/core/libs/prx/libSceAgcDriver/tests/Screenshot.cpp b/core/libs/prx/libSceAgcDriver/tests/Screenshot.cpp index 4adf0c824d..93956ced7e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Screenshot.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Screenshot.cpp @@ -1,6 +1,8 @@ #include "prx/libSceAgcDriver/Execution/include/Screenshot.hpp" #include "Decoder/Png.hpp" +#include #include +#include #include #include #include @@ -11,6 +13,7 @@ #include #include #include +#include #include namespace { @@ -54,6 +57,30 @@ void writesRgbPng(const std::filesystem::path& directory) { require(image->pixels == expected, "the screenshot holds the frame's colors as opaque RGB"); } +void concurrentWritesClaimUniquePaths() { + constexpr std::size_t writerCount = 32; + constexpr int attempts = 20; + const std::array bgra{std::byte{0x10}, std::byte{0x20}, std::byte{0x30}, std::byte{0x00}}; + for (int attempt = 0; attempt < attempts; ++attempt) { + std::atomic ready{0}; + std::atomic start{false}; + std::array paths; + std::array writers; + for (std::size_t index = 0; index < writerCount; ++index) { + writers[index] = std::thread([&, index] { + ready.fetch_add(1, std::memory_order_release); + while (!start.load(std::memory_order_acquire)) std::this_thread::yield(); + paths[index] = AgcDriver::Screenshot::Write("RACE00001", 1, 1, bgra); + }); + } + while (ready.load(std::memory_order_acquire) != writerCount) std::this_thread::yield(); + start.store(true, std::memory_order_release); + for (auto& writer : writers) writer.join(); + std::sort(paths.begin(), paths.end()); + require(std::adjacent_find(paths.begin(), paths.end()) == paths.end(), "concurrent screenshots claim unique paths"); + } +} + void rejectsInvalidFrames() { const std::array pixel{}; const auto rejects = [](auto write) { @@ -78,6 +105,7 @@ int main() try { setScreenshotDirectory(directory); requestsMerge(); writesRgbPng(directory); + concurrentWritesClaimUniquePaths(); rejectsInvalidFrames(); std::filesystem::remove_all(root); std::cout << "agc screenshot tests passed\n"; diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index 2bdb2d6f98..44b4ee7a06 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -2454,6 +2454,82 @@ void verifyAllocationChurnSnapshot() { } } +void verifyRegisteredViewsUnderUnrelatedChurn() { + using namespace ShaderRecompiler; + constexpr std::size_t RangeBytes = 65536u; + constexpr std::uint32_t Checks = 20000u; + alignas(4096) static std::array texels{}; + alignas(4096) static std::array unrelated{}; + { + GuestAllocations::Mutation mutation; + mutation.Add(texels.data(), texels.size(), true, false, true); + } + const auto view = [] { + const auto range = GuestAllocations::GuestAllocationsLookup_nid_postfix(reinterpret_cast(texels.data()), false); + require(range != nullptr, "registered view churn: the image range was not registered"); + RegisteredImageView captured; + captured.address = reinterpret_cast(texels.data()) + 256u; + captured.hasRange = true; + captured.rangeAddress = range->address; + captured.rangeBytes = range->bytes; + captured.allocationAddress = range->allocationAddress; + captured.allocationBytes = range->allocationBytes; + captured.backingSerial = range->backingSerial; + captured.readable = range->readable; + captured.writable = range->writable; + captured.gpu = range->gpu; + return captured; + }(); + std::array views{view, view, view}; + views[1].address = view.address + 4096u; + views[2].address = view.address + 8192u; + std::atomic stop{false}; + std::atomic mutations{0}; + std::thread churn([&] { + while (!stop.load(std::memory_order_acquire)) { + { + GuestAllocations::Mutation mutation; + mutation.Add(unrelated.data(), unrelated.size(), true, true, true); + } + { + GuestAllocations::Mutation mutation; + mutation.Remove(unrelated.data()); + } + mutations.fetch_add(2u, std::memory_order_relaxed); + } + }); + std::uint32_t rejected = 0u; + const auto generationBefore = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); + while (mutations.load(std::memory_order_relaxed) < 64u) std::this_thread::yield(); + for (std::uint32_t check = 0; check < Checks; ++check) { + if (!AgcDriver::DriverDetail::CaptureRegisteredViewsCurrent(views)) ++rejected; + } + stop.store(true, std::memory_order_release); + churn.join(); + const auto generationAfter = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); + require(generationAfter - generationBefore >= 64u, "registered view churn: the unrelated mappings did not move the allocation generation"); + const auto rejectedMessage = "registered view churn: " + std::to_string(rejected) + " of " + std::to_string(Checks) + " checks rejected views whose own range never changed while only an unrelated allocation moved"; + require(rejected == 0u, rejectedMessage.c_str()); + { + GuestAllocations::Mutation mutation; + mutation.Remove(texels.data()); + } + require(!AgcDriver::DriverDetail::CaptureRegisteredViewsCurrent(views), "registered view churn: views were accepted after their range was removed"); + { + GuestAllocations::Mutation mutation; + mutation.Add(texels.data(), texels.size(), true, false, true); + } + require(!AgcDriver::DriverDetail::CaptureRegisteredViewsCurrent(views), "registered view churn: views were accepted after their range was mapped again with a new backing"); + views[0].backingSerial = GuestAllocations::GuestAllocationsLookup_nid_postfix(reinterpret_cast(texels.data()), false)->backingSerial; + require(!AgcDriver::DriverDetail::CaptureRegisteredViewsCurrent(views), "registered view churn: views were accepted while one of them still names the old backing"); + views[1].backingSerial = views[2].backingSerial = views[0].backingSerial; + require(AgcDriver::DriverDetail::CaptureRegisteredViewsCurrent(views), "registered view churn: views matching the new backing were rejected"); + { + GuestAllocations::Mutation mutation; + mutation.Remove(texels.data()); + } +} + void verifySnapshotAllocationMoves() { using namespace ShaderRecompiler; constexpr std::size_t RangeBytes = 65536u; @@ -9949,6 +10025,42 @@ void verifyUnusedUnnormalizedSampler() { require(msaaArrayAllocation.bindings.size() == 1u && msaaArrayAllocation.bindings[0].imageShape == DescriptorImageShape::Image2DMultisampleArray, "multisampled array image resource did not receive Image2DMultisampleArray descriptor shape"); } +void verifyDsSwizzleFftMode() { + using namespace ShaderRecompiler; + static constexpr std::array capabilities{spv::CapabilityShader, spv::CapabilityInt64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess, spv::CapabilityGroupNonUniform, spv::CapabilityGroupNonUniformBallot, spv::CapabilityGroupNonUniformShuffle}; + static constexpr std::array extensions{"SPV_KHR_physical_storage_buffer", "SPV_KHR_8bit_storage"}; + static std::array output{}; + const auto address = static_cast(reinterpret_cast(output.data())); + const std::array userData{static_cast(address), static_cast((address >> 32u) & 0xffffu), 128u, 0x31016facu}; + const auto compiled = [&userData](std::uint32_t offset) { + const std::vector code{0xd8d40000u | offset, 0x01000000u, 0x34040082u, 0xbf8cc07fu, 0xe0701000u, 0x80000102u, 0xbf810000u}; + RecompileRequest request{}; + request.shader = {ShaderStage::Compute, 0x26000u, code, 0, {}}; + 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.target.vulkanVersion = 0x00401000u; + request.target.spirvVersion = 0x00010300u; + request.target.subgroupSize = 32u; + request.target.supportedCapabilities = capabilities; + request.target.supportedExtensions = extensions; + request.target.bdaAbiVersion = BdaAbi::Version; + request.layout.pushConstantSizeBytes = 128; + request.useCache = false; + return Recompile(request).spirv.Copy(); + }; + const auto reversesLanes = [](std::span spirv) { + for (std::size_t cursor = 5; cursor < spirv.size() && (spirv[cursor] >> 16u) != 0u; cursor += spirv[cursor] >> 16u) { + if ((spirv[cursor] & 0xffffu) == spv::OpBitReverse) return true; + } + return false; + }; + for (const auto offset : {0xe000u, 0xe010u, 0xe018u, 0xe001u}) require(reversesLanes(compiled(offset)), "ds_swizzle FFT mode: an offset with a mask below 0x1f did not reverse the lane bits"); + for (const auto offset : {0xe01fu, 0xc000u, 0xc021u, 0xdfffu}) require(!reversesLanes(compiled(offset)), "ds_swizzle FFT mode: mask 0x1f or a rotate offset reversed the lane bits"); + require(compiled(0xffe0u) == compiled(0xe000u) && compiled(0xf550u) == compiled(0xe010u), "ds_swizzle FFT mode: offset bits 12:5 changed the swizzle"); +} + void verifyWaveUniformValues() { using namespace ShaderRecompiler; IrProgram program; @@ -11581,6 +11693,11 @@ int main(int argc, char** argv) { std::cout << "Image table keys, maps, records and registration passed\n"; return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--registered-view-churn") { + verifyRegisteredViewsUnderUnrelatedChurn(); + std::cout << "Registered image views stay current while unrelated allocations change\n"; + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--nested-snapshot-retry") { verifyNestedSnapshotRetry(); std::cout << "Nested snapshot retry preserves parent and child coverage\n"; @@ -11709,6 +11826,7 @@ int main(int argc, char** argv) { verifyUnnormalizedSamplers(); verifyUnusedUnnormalizedSampler(); verifyNullVolumeUnnormalizedSampler(); + verifyDsSwizzleFftMode(); verifyWaveUniformValues(); verifyTwoLaneUniformValues(); verifyBdaReadFallbackFunctions(); diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_frame_replay_relaunch_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_frame_replay_relaunch_tests.cmake new file mode 100644 index 0000000000..a3bb15ba3d --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_frame_replay_relaunch_tests.cmake @@ -0,0 +1,12 @@ +if(BUILD_TESTING AND WIN32) + add_test_executable(agc_driver_frame_replay_relaunch_tests tests/FrameReplayRelaunch.cpp Execution/src/FrameCapture.cpp) + target_include_directories(agc_driver_frame_replay_relaunch_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) + target_link_libraries(agc_driver_frame_replay_relaunch_tests PRIVATE libSceAgcDriver libkernel libc libzstd_static) + target_link_options(agc_driver_frame_replay_relaunch_tests PRIVATE -Wl,--disable-high-entropy-va) + set_target_properties(agc_driver_frame_replay_relaunch_tests PROPERTIES CXX_EXTENSIONS OFF) + configure_windows_unwind(agc_driver_frame_replay_relaunch_tests) + add_test(NAME agc_driver_frame_replay_relaunch COMMAND agc_driver_frame_replay_relaunch_tests "${CMAKE_CURRENT_BINARY_DIR}/frame replay relaunch") + set_tests_properties(agc_driver_frame_replay_relaunch PROPERTIES + TIMEOUT 120 SKIP_RETURN_CODE 77 + ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") +endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_frame_replay_state_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_frame_replay_state_tests.cmake index fe8f427bcc..6413c0743e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_frame_replay_state_tests.cmake +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_frame_replay_state_tests.cmake @@ -1,10 +1,22 @@ if(BUILD_TESTING) add_test_executable(agc_driver_frame_replay_state_tests tests/FrameReplayState.cpp Execution/src/FrameCapture.cpp) - target_include_directories(agc_driver_frame_replay_state_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_include_directories(agc_driver_frame_replay_state_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) target_link_libraries(agc_driver_frame_replay_state_tests PRIVATE libSceAgcDriver libkernel libc libzstd_static) set_target_properties(agc_driver_frame_replay_state_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_driver_frame_replay_state_tests) - set(agcFrameReplayModes order tokens blocking) + add_dependencies(agc_driver_frame_replay_state_tests agc_frame_replay) + foreach(mode IN ITEMS submits shader_output) + add_test(NAME agc_driver_frame_replay_${mode} COMMAND ${CMAKE_COMMAND} + "-DTOOL=$" "-DWRITER=$" + "-DDIRECTORY=${CMAKE_CURRENT_BINARY_DIR}/frame-replay-${mode}" "-DMODE=${mode}" + -P "${CMAKE_CURRENT_SOURCE_DIR}/tests/FrameReplayTool.cmake") + set_tests_properties(agc_driver_frame_replay_${mode} PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_driver_frame_replay_${mode} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() + set(agcFrameReplayModes order tokens blocking reservations reservation_failure environment) if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux") list(APPEND agcFrameReplayModes zero shader shader_host shader_prologue_code shader_prologue_header shader_prologue_readonly) endif() @@ -26,6 +38,10 @@ if(BUILD_TESTING) set_tests_properties(agc_driver_frame_replay_${mode} PROPERTIES ENVIRONMENT "APS5_NO_WRITE_WATCH=1;APS5_FRAME_CAPTURE=${capture};APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=1;APS5_FRAME_CAPTURE_MIN_FREE_BYTES=0") endif() + if(mode STREQUAL "environment") + set_tests_properties(agc_driver_frame_replay_${mode} PROPERTIES ENVIRONMENT + "APS5_HEADLESS_PIPELINE_CACHE=1;APS5_FRAME_CAPTURE=${capture};APS5_FRAME_CAPTURE_MIN_FREE_BYTES=0") + endif() if(WIN32) set_tests_properties(agc_driver_frame_replay_${mode} PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_frame_replay_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_frame_replay_tests.cmake index 10989ffcdc..5c60816b40 100644 --- a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_frame_replay_tests.cmake +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_frame_replay_tests.cmake @@ -1,23 +1,44 @@ if(BUILD_TESTING) if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux") add_test_executable(agc_driver_frame_replay_tests tests/FrameReplay.cpp Execution/src/FrameCapture.cpp) - target_include_directories(agc_driver_frame_replay_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_include_directories(agc_driver_frame_replay_tests PRIVATE + ${LIBS_INCLUDE_DIR} + ${CMAKE_SOURCE_DIR}/core/shader/recompiler + ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR} + ${ANYPS5_SPIRV_HEADERS_INCLUDE_DIR} + ) target_link_libraries(agc_driver_frame_replay_tests PRIVATE libSceAgc libSceAgcDriver libkernel libc libzstd_static) set_target_properties(agc_driver_frame_replay_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_driver_frame_replay_tests) add_dependencies(agc_driver_frame_replay_tests agc_frame_replay) add_test(NAME agc_driver_frame_replay COMMAND agc_driver_frame_replay_tests $) set_tests_properties(agc_driver_frame_replay PROPERTIES - ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame-replay-capture;APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=2;APS5_FRAME_CAPTURE_MIN_FREE_BYTES=0" - TIMEOUT 120) + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame replay capture;APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=2;APS5_FRAME_CAPTURE_MIN_FREE_BYTES=0" + TIMEOUT 120 SKIP_RETURN_CODE 77) if(WIN32) set_tests_properties(agc_driver_frame_replay PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test(NAME agc_driver_frame_replay_cold_pipelines COMMAND agc_driver_frame_replay_tests $ pipelines) + set_tests_properties(agc_driver_frame_replay_cold_pipelines PROPERTIES + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=0;APS5_HEADLESS_PIPELINE_CACHE=1;ANYPS5_SHADER_CACHE_DIR=${CMAKE_CURRENT_BINARY_DIR}/frame replay pipeline cache;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame replay cold capture;APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=2;APS5_FRAME_CAPTURE_MIN_FREE_BYTES=0" + TIMEOUT 30 SKIP_RETURN_CODE 77) + if(WIN32) + set_tests_properties(agc_driver_frame_replay_cold_pipelines PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test(NAME agc_driver_frame_replay_reservation_error COMMAND agc_driver_frame_replay_tests $ reservation_error) + set_tests_properties(agc_driver_frame_replay_reservation_error PROPERTIES + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame replay reservation error;APS5_FRAME_CAPTURE_MIN_FREE_BYTES=0" + TIMEOUT 30 SKIP_RETURN_CODE 77) + if(WIN32) + set_tests_properties(agc_driver_frame_replay_reservation_error PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test(NAME agc_driver_frame_replay_concurrent COMMAND agc_driver_frame_replay_tests $ concurrent) set_tests_properties(agc_driver_frame_replay_concurrent PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame-replay-concurrent-capture;APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=8;APS5_FRAME_CAPTURE_MIN_FREE_BYTES=0" - TIMEOUT 120) + TIMEOUT 120 SKIP_RETURN_CODE 77) if(WIN32) set_tests_properties(agc_driver_frame_replay_concurrent PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_sdwa_sin_cos_f32_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_sdwa_sin_cos_f32_tests.cmake new file mode 100644 index 0000000000..e2ab08832f --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_sdwa_sin_cos_f32_tests.cmake @@ -0,0 +1,9 @@ +if(BUILD_TESTING) + add_test(NAME agc_driver_sdwa_sin_cos_f32_wave64 COMMAND agc_driver_sdwa_sin_cos_f32_tests wave64) + set_tests_properties(agc_driver_sdwa_sin_cos_f32_wave64 PROPERTIES + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + if(WIN32) + set_tests_properties(agc_driver_sdwa_sin_cos_f32_wave64 PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_shader_memory_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_shader_memory_tests.cmake index 4460a3c25a..1e66e009a0 100644 --- a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_shader_memory_tests.cmake +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_shader_memory_tests.cmake @@ -30,6 +30,11 @@ if(BUILD_TESTING) if(WIN32) set_tests_properties(agc_shader_descriptor_phis PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test(NAME agc_shader_registered_view_churn COMMAND agc_shader_memory_tests --registered-view-churn) + set_tests_properties(agc_shader_registered_view_churn PROPERTIES TIMEOUT 60) + if(WIN32) + set_tests_properties(agc_shader_registered_view_churn PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() add_test(NAME agc_shader_nested_snapshot_retry COMMAND agc_shader_memory_tests --nested-snapshot-retry) set_tests_properties(agc_shader_nested_snapshot_retry PROPERTIES TIMEOUT 20) if(WIN32) diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/buffer_atomic_csub_translation_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/buffer_atomic_csub_translation_tests.cmake new file mode 100644 index 0000000000..605c6b5870 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/buffer_atomic_csub_translation_tests.cmake @@ -0,0 +1,10 @@ +if(BUILD_TESTING) + add_test_executable(buffer_atomic_csub_translation_tests ${LIBS_INCLUDE_DIR}/tests/BufferAtomicCsubTranslation.cpp) + target_link_libraries(buffer_atomic_csub_translation_tests PRIVATE shader_recompiler_agc_driver libc) + configure_windows_unwind(buffer_atomic_csub_translation_tests) + add_test(NAME buffer_atomic_csub_translation COMMAND buffer_atomic_csub_translation_tests) + if(WIN32) + set_tests_properties(buffer_atomic_csub_translation PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicCsub.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicCsub.cpp index 0b150b2da8..647217c7c2 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicCsub.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicCsub.cpp @@ -168,16 +168,10 @@ void Check(const Workgroup& workgroup) { } } -void CheckNoReturnRejected(const AgcDriver::VulkanDevice& device) { +void CheckNoReturnAccepted(const AgcDriver::VulkanDevice& device) { const auto userData = UserData(); - const std::string reason = "buffer_atomic_csub without glc is not supported"; - std::string failure; - try { - static_cast(Recompile(device, NoReturnCode, Workgroups[0], userData)); - } catch (const std::exception& error) { - failure = error.what(); - } - Require(failure.find(reason) != std::string::npos, "buffer atomic csub: expected '" + reason + "', got '" + failure + "'"); + const auto recompiled = Recompile(device, NoReturnCode, Workgroups[0], userData); + Require(!recompiled.spirv.empty(), "buffer atomic csub: expected valid SPIR-V for instruction without glc"); } } @@ -190,7 +184,7 @@ int main() { Run(*device, workgroup); Check(workgroup); } - CheckNoReturnRejected(*device); + CheckNoReturnAccepted(*device); std::puts("buffer atomic csub tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DsSwizzle.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DsSwizzle.cpp index bfae3c11cd..7d1ded3cd2 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DsSwizzle.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DsSwizzle.cpp @@ -51,6 +51,34 @@ alignas(256) constexpr std::array Wave64Code{ 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, }; +alignas(256) constexpr std::array FftWave32Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e2c02ff, 0xdeaddead, 0x7e2e02ff, + 0xdeaddead, 0x7e3002ff, 0xdeaddead, 0x7e3202ff, 0xdeaddead, 0xd8d4e000, 0x0a000004, 0xd8d4e010, + 0x0b000004, 0xd8d4e01f, 0x0c000004, 0xd8d4e018, 0x0d000004, 0xd8d4e01c, 0x0e000004, 0xd8d4e01e, + 0x0f000004, 0xd8d4e001, 0x10000004, 0xd8d4e00f, 0x11000004, 0xd8d4e015, 0x12000004, 0xd8d4e008, + 0x13000004, 0xd8d4ffe0, 0x14000004, 0xd8d4f550, 0x15000004, 0xbefe03ff, 0x33cc0ff3, 0xd8d4e000, + 0x16000004, 0xd8d4e010, 0x17000004, 0xd8d4e018, 0x18000004, 0xd8d4e001, 0x19000004, 0xbf8cc07f, + 0xbefe03c1, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, + 0x80011603, 0xbf810000, +}; + +alignas(256) constexpr std::array FftWave64Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e2c02ff, 0xdeaddead, 0x7e2e02ff, + 0xdeaddead, 0x7e3002ff, 0xdeaddead, 0x7e3202ff, 0xdeaddead, 0xd8d4e000, 0x0a000004, 0xd8d4e010, + 0x0b000004, 0xd8d4e01f, 0x0c000004, 0xd8d4e018, 0x0d000004, 0xd8d4e01c, 0x0e000004, 0xd8d4e01e, + 0x0f000004, 0xd8d4e001, 0x10000004, 0xd8d4e00f, 0x11000004, 0xd8d4e015, 0x12000004, 0xd8d4e008, + 0x13000004, 0xd8d4ffe0, 0x14000004, 0xd8d4f550, 0x15000004, 0xbefe03ff, 0x33cc0ff3, 0xbeff03ff, + 0xc3a5960f, 0xd8d4e000, 0x16000004, 0xd8d4e010, 0x17000004, 0xd8d4e018, 0x18000004, 0xd8d4e001, + 0x19000004, 0xbf8cc07f, 0xbefe04c1, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, + 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + constexpr std::uint32_t Rows[64][4] = { {0x1e7ea419u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x51c9bc70u, 0x00000000u, 0x00000000u, 0x00000000u}, @@ -219,7 +247,109 @@ constexpr std::uint32_t Expected64[64][16] = { {0x89be9c1cu, 0xbef7e268u, 0x16f44881u, 0x1c593af5u, 0x8eb5140fu, 0x16f44881u, 0x16f44881u, 0x1c593af5u, 0x57241955u, 0x8eb5140fu, 0x8eb5140fu, 0xd0944996u, 0xbef7e268u, 0x00000000u, 0x00000000u, 0x00000000u} }; +constexpr std::uint32_t FftExpected32[32][16] = { + {0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u}, + {0xc88b2875u, 0x1a466884u, 0x51c9bc70u, 0x8306d03bu, 0x80a4df5au, 0x51c9bc70u, 0xe255accbu, 0x51c9bc70u, 0xf38b2ffcu, 0x1a466884u, 0xc88b2875u, 0x1a466884u, 0x00000000u, 0x1a466884u, 0x8306d03bu, 0xe255accbu}, + {0x1a466884u, 0x8306d03bu, 0x80a4df5au, 0x80a4df5au, 0x51c9bc70u, 0x80a4df5au, 0x8306d03bu, 0x80a4df5au, 0x51c9bc70u, 0x8306d03bu, 0x1a466884u, 0x8306d03bu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc0433cbdu, 0x99dd251du, 0xf38b2ffcu, 0xdc28ff90u, 0xf38b2ffcu, 0xf38b2ffcu, 0x9f199504u, 0xf38b2ffcu, 0xf38b2ffcu, 0x99dd251du, 0xc0433cbdu, 0x99dd251du, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8306d03bu, 0x80a4df5au, 0x8306d03bu, 0x51c9bc70u, 0x8306d03bu, 0x8306d03bu, 0x80a4df5au, 0x8306d03bu, 0x8306d03bu, 0x80a4df5au, 0x8306d03bu, 0x80a4df5au, 0x8306d03bu, 0x00000000u, 0x51c9bc70u, 0x00000000u}, + {0xbb049a79u, 0x39292d22u, 0xa5aec797u, 0xa5aec797u, 0xdc28ff90u, 0xa5aec797u, 0xe5121482u, 0xa5aec797u, 0xf3f49249u, 0x39292d22u, 0xbb049a79u, 0x39292d22u, 0x00000000u, 0x39292d22u, 0xa5aec797u, 0xe5121482u}, + {0x99dd251du, 0xdc28ff90u, 0xdc28ff90u, 0xf38b2ffcu, 0xa5aec797u, 0xdc28ff90u, 0xdc28ff90u, 0xdc28ff90u, 0xa5aec797u, 0xdc28ff90u, 0x99dd251du, 0xdc28ff90u, 0x00000000u, 0xdc28ff90u, 0x00000000u, 0xdc28ff90u}, + {0x3d6c51e3u, 0x8e7aa6e9u, 0xf3f49249u, 0xf3f49249u, 0xf3f49249u, 0xf3f49249u, 0x6bad6be2u, 0xf3f49249u, 0xf3f49249u, 0x8e7aa6e9u, 0x3d6c51e3u, 0x8e7aa6e9u, 0x3d6c51e3u, 0x00000000u, 0xf3f49249u, 0x00000000u}, + {0x80a4df5au, 0x51c9bc70u, 0x1a466884u, 0x1a466884u, 0x1a466884u, 0x1a466884u, 0x51c9bc70u, 0x1a466884u, 0x1e7ea419u, 0xe255accbu, 0x80a4df5au, 0x51c9bc70u, 0x00000000u, 0x51c9bc70u, 0x1a466884u, 0x51c9bc70u}, + {0x8c3d5f16u, 0xe255accbu, 0xe255accbu, 0x99dd251du, 0x39292d22u, 0xe255accbu, 0xe255accbu, 0xe255accbu, 0xf38b2ffcu, 0xe255accbu, 0x8c3d5f16u, 0xe255accbu, 0x8c3d5f16u, 0xe255accbu, 0x00000000u, 0xe255accbu}, + {0x39292d22u, 0xa5aec797u, 0x39292d22u, 0x39292d22u, 0xe255accbu, 0x39292d22u, 0xa5aec797u, 0x39292d22u, 0x51c9bc70u, 0x9f199504u, 0x39292d22u, 0xa5aec797u, 0x39292d22u, 0xa5aec797u, 0x39292d22u, 0xa5aec797u}, + {0x96263ae6u, 0x9f199504u, 0xe5121482u, 0x8e7aa6e9u, 0xe5121482u, 0xe5121482u, 0x9f199504u, 0xe5121482u, 0xf38b2ffcu, 0x9f199504u, 0x96263ae6u, 0x9f199504u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xdc28ff90u, 0xf38b2ffcu, 0x99dd251du, 0xe255accbu, 0x99dd251du, 0x99dd251du, 0xf38b2ffcu, 0x99dd251du, 0x8306d03bu, 0xe5121482u, 0xdc28ff90u, 0xf38b2ffcu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc4a334bfu, 0xe5121482u, 0x9f199504u, 0x9f199504u, 0x8e7aa6e9u, 0x9f199504u, 0xe5121482u, 0x9f199504u, 0xf3f49249u, 0xe5121482u, 0xc4a334bfu, 0xe5121482u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8e7aa6e9u, 0xf3f49249u, 0x8e7aa6e9u, 0xe5121482u, 0x9f199504u, 0x8e7aa6e9u, 0xf3f49249u, 0x8e7aa6e9u, 0xa5aec797u, 0x6bad6be2u, 0x8e7aa6e9u, 0xf3f49249u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9d3c7decu, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0xf3f49249u, 0x6bad6be2u, 0x9d3c7decu, 0x6bad6be2u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x51c9bc70u, 0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0x1e7ea419u, 0x1e7ea419u, 0xc88b2875u, 0x1e7ea419u, 0x51c9bc70u, 0xc88b2875u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9293de8fu, 0xc0433cbdu, 0x9293de8fu, 0xbb049a79u, 0x8c3d5f16u, 0x9293de8fu, 0xe255accbu, 0x51c9bc70u, 0xd7a7a3ccu, 0x1a466884u, 0x9293de8fu, 0xc0433cbdu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xe255accbu, 0xbb049a79u, 0x8c3d5f16u, 0x8c3d5f16u, 0x9293de8fu, 0x8c3d5f16u, 0x8306d03bu, 0x80a4df5au, 0x9293de8fu, 0x8306d03bu, 0xe255accbu, 0xbb049a79u, 0xe255accbu, 0x00000000u, 0x8c3d5f16u, 0x8306d03bu}, + {0xc5e818fau, 0x3d6c51e3u, 0xd7a7a3ccu, 0xc4a334bfu, 0xd7a7a3ccu, 0xd7a7a3ccu, 0x9f199504u, 0xf38b2ffcu, 0xd7a7a3ccu, 0x99dd251du, 0xc5e818fau, 0x3d6c51e3u, 0xc5e818fau, 0x3d6c51e3u, 0xc4a334bfu, 0x00000000u}, + {0xa5aec797u, 0x8c3d5f16u, 0xbb049a79u, 0x9293de8fu, 0xbb049a79u, 0xbb049a79u, 0x80a4df5au, 0x8306d03bu, 0xbb049a79u, 0x80a4df5au, 0xa5aec797u, 0x8c3d5f16u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc6cd75e9u, 0x96263ae6u, 0xc6cd75e9u, 0xc6cd75e9u, 0xc4a334bfu, 0xc6cd75e9u, 0xe5121482u, 0xa5aec797u, 0x7dabe929u, 0x39292d22u, 0xc6cd75e9u, 0x96263ae6u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9f199504u, 0xc4a334bfu, 0xc4a334bfu, 0xd7a7a3ccu, 0xc6cd75e9u, 0xc4a334bfu, 0xdc28ff90u, 0xdc28ff90u, 0xc6cd75e9u, 0xdc28ff90u, 0x9f199504u, 0xc4a334bfu, 0x00000000u, 0xc4a334bfu, 0xd7a7a3ccu, 0xdc28ff90u}, + {0x00a61f93u, 0x9d3c7decu, 0x7dabe929u, 0x7dabe929u, 0x7dabe929u, 0x7dabe929u, 0x6bad6be2u, 0xf3f49249u, 0x7dabe929u, 0x8e7aa6e9u, 0x00a61f93u, 0x9d3c7decu, 0x00a61f93u, 0x00000000u, 0x7dabe929u, 0x00000000u}, + {0xf38b2ffcu, 0x9293de8fu, 0xc0433cbdu, 0xc0433cbdu, 0xc0433cbdu, 0xc0433cbdu, 0x51c9bc70u, 0x1a466884u, 0xc88b2875u, 0xe255accbu, 0xf38b2ffcu, 0x9293de8fu, 0x00000000u, 0x00000000u, 0xc0433cbdu, 0x51c9bc70u}, + {0xd7a7a3ccu, 0xc5e818fau, 0xc5e818fau, 0x3d6c51e3u, 0x96263ae6u, 0xc5e818fau, 0xe255accbu, 0xe255accbu, 0xd7a7a3ccu, 0xe255accbu, 0xd7a7a3ccu, 0xc5e818fau, 0xd7a7a3ccu, 0xc5e818fau, 0x3d6c51e3u, 0xe255accbu}, + {0xe5121482u, 0xc6cd75e9u, 0x96263ae6u, 0x96263ae6u, 0xc5e818fau, 0x96263ae6u, 0xa5aec797u, 0x39292d22u, 0x9293de8fu, 0x9f199504u, 0xe5121482u, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x70eb9a0au, 0x00a61f93u, 0x70eb9a0au, 0x9d3c7decu, 0x70eb9a0au, 0x70eb9a0au, 0x9f199504u, 0xe5121482u, 0xd7a7a3ccu, 0x9f199504u, 0x70eb9a0au, 0x00a61f93u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xf3f49249u, 0xd7a7a3ccu, 0x3d6c51e3u, 0xc5e818fau, 0x3d6c51e3u, 0x3d6c51e3u, 0xf38b2ffcu, 0x99dd251du, 0xbb049a79u, 0xe5121482u, 0xf3f49249u, 0xd7a7a3ccu, 0xf3f49249u, 0xd7a7a3ccu, 0xc5e818fau, 0x00000000u}, + {0x7dabe929u, 0x70eb9a0au, 0x00a61f93u, 0x00a61f93u, 0x9d3c7decu, 0x00a61f93u, 0xe5121482u, 0x9f199504u, 0x7dabe929u, 0xe5121482u, 0x7dabe929u, 0x70eb9a0au, 0x7dabe929u, 0x00000000u, 0x00a61f93u, 0xe5121482u}, + {0x6bad6be2u, 0x7dabe929u, 0x9d3c7decu, 0x70eb9a0au, 0x00a61f93u, 0x9d3c7decu, 0xf3f49249u, 0x8e7aa6e9u, 0xc6cd75e9u, 0x6bad6be2u, 0x6bad6be2u, 0x7dabe929u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x14aa4e71u, 0x14aa4e71u, 0x14aa4e71u, 0x14aa4e71u, 0x14aa4e71u, 0x14aa4e71u, 0x6bad6be2u, 0x6bad6be2u, 0x7dabe929u, 0x6bad6be2u, 0x14aa4e71u, 0x14aa4e71u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu} +}; +constexpr std::uint32_t FftExpected64[64][16] = { + {0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u}, + {0xc88b2875u, 0x1a466884u, 0x51c9bc70u, 0x8306d03bu, 0x80a4df5au, 0x51c9bc70u, 0xe255accbu, 0x51c9bc70u, 0xf38b2ffcu, 0x1a466884u, 0xc88b2875u, 0x1a466884u, 0x00000000u, 0x1a466884u, 0x8306d03bu, 0xe255accbu}, + {0x1a466884u, 0x8306d03bu, 0x80a4df5au, 0x80a4df5au, 0x51c9bc70u, 0x80a4df5au, 0x8306d03bu, 0x80a4df5au, 0x51c9bc70u, 0x8306d03bu, 0x1a466884u, 0x8306d03bu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc0433cbdu, 0x99dd251du, 0xf38b2ffcu, 0xdc28ff90u, 0xf38b2ffcu, 0xf38b2ffcu, 0x9f199504u, 0xf38b2ffcu, 0xf38b2ffcu, 0x99dd251du, 0xc0433cbdu, 0x99dd251du, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8306d03bu, 0x80a4df5au, 0x8306d03bu, 0x51c9bc70u, 0x8306d03bu, 0x8306d03bu, 0x80a4df5au, 0x8306d03bu, 0x8306d03bu, 0x80a4df5au, 0x8306d03bu, 0x80a4df5au, 0x8306d03bu, 0x00000000u, 0x51c9bc70u, 0x00000000u}, + {0xbb049a79u, 0x39292d22u, 0xa5aec797u, 0xa5aec797u, 0xdc28ff90u, 0xa5aec797u, 0xe5121482u, 0xa5aec797u, 0xf3f49249u, 0x39292d22u, 0xbb049a79u, 0x39292d22u, 0x00000000u, 0x39292d22u, 0xa5aec797u, 0xe5121482u}, + {0x99dd251du, 0xdc28ff90u, 0xdc28ff90u, 0xf38b2ffcu, 0xa5aec797u, 0xdc28ff90u, 0xdc28ff90u, 0xdc28ff90u, 0xa5aec797u, 0xdc28ff90u, 0x99dd251du, 0xdc28ff90u, 0x00000000u, 0xdc28ff90u, 0x00000000u, 0xdc28ff90u}, + {0x3d6c51e3u, 0x8e7aa6e9u, 0xf3f49249u, 0xf3f49249u, 0xf3f49249u, 0xf3f49249u, 0x6bad6be2u, 0xf3f49249u, 0xf3f49249u, 0x8e7aa6e9u, 0x3d6c51e3u, 0x8e7aa6e9u, 0x3d6c51e3u, 0x00000000u, 0xf3f49249u, 0x00000000u}, + {0x80a4df5au, 0x51c9bc70u, 0x1a466884u, 0x1a466884u, 0x1a466884u, 0x1a466884u, 0x51c9bc70u, 0x1a466884u, 0x1e7ea419u, 0xe255accbu, 0x80a4df5au, 0x51c9bc70u, 0x00000000u, 0x51c9bc70u, 0x1a466884u, 0x51c9bc70u}, + {0x8c3d5f16u, 0xe255accbu, 0xe255accbu, 0x99dd251du, 0x39292d22u, 0xe255accbu, 0xe255accbu, 0xe255accbu, 0xf38b2ffcu, 0xe255accbu, 0x8c3d5f16u, 0xe255accbu, 0x8c3d5f16u, 0xe255accbu, 0x00000000u, 0xe255accbu}, + {0x39292d22u, 0xa5aec797u, 0x39292d22u, 0x39292d22u, 0xe255accbu, 0x39292d22u, 0xa5aec797u, 0x39292d22u, 0x51c9bc70u, 0x9f199504u, 0x39292d22u, 0xa5aec797u, 0x39292d22u, 0xa5aec797u, 0x39292d22u, 0xa5aec797u}, + {0x96263ae6u, 0x9f199504u, 0xe5121482u, 0x8e7aa6e9u, 0xe5121482u, 0xe5121482u, 0x9f199504u, 0xe5121482u, 0xf38b2ffcu, 0x9f199504u, 0x96263ae6u, 0x9f199504u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xdc28ff90u, 0xf38b2ffcu, 0x99dd251du, 0xe255accbu, 0x99dd251du, 0x99dd251du, 0xf38b2ffcu, 0x99dd251du, 0x8306d03bu, 0xe5121482u, 0xdc28ff90u, 0xf38b2ffcu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc4a334bfu, 0xe5121482u, 0x9f199504u, 0x9f199504u, 0x8e7aa6e9u, 0x9f199504u, 0xe5121482u, 0x9f199504u, 0xf3f49249u, 0xe5121482u, 0xc4a334bfu, 0xe5121482u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8e7aa6e9u, 0xf3f49249u, 0x8e7aa6e9u, 0xe5121482u, 0x9f199504u, 0x8e7aa6e9u, 0xf3f49249u, 0x8e7aa6e9u, 0xa5aec797u, 0x6bad6be2u, 0x8e7aa6e9u, 0xf3f49249u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9d3c7decu, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0xf3f49249u, 0x6bad6be2u, 0x9d3c7decu, 0x6bad6be2u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x51c9bc70u, 0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0x1e7ea419u, 0x1e7ea419u, 0xc88b2875u, 0x1e7ea419u, 0x51c9bc70u, 0xc88b2875u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9293de8fu, 0xc0433cbdu, 0x9293de8fu, 0xbb049a79u, 0x8c3d5f16u, 0x9293de8fu, 0xe255accbu, 0x51c9bc70u, 0xd7a7a3ccu, 0x1a466884u, 0x9293de8fu, 0xc0433cbdu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xe255accbu, 0xbb049a79u, 0x8c3d5f16u, 0x8c3d5f16u, 0x9293de8fu, 0x8c3d5f16u, 0x8306d03bu, 0x80a4df5au, 0x9293de8fu, 0x8306d03bu, 0xe255accbu, 0xbb049a79u, 0xe255accbu, 0x00000000u, 0x8c3d5f16u, 0x8306d03bu}, + {0xc5e818fau, 0x3d6c51e3u, 0xd7a7a3ccu, 0xc4a334bfu, 0xd7a7a3ccu, 0xd7a7a3ccu, 0x9f199504u, 0xf38b2ffcu, 0xd7a7a3ccu, 0x99dd251du, 0xc5e818fau, 0x3d6c51e3u, 0xc5e818fau, 0x3d6c51e3u, 0xc4a334bfu, 0x00000000u}, + {0xa5aec797u, 0x8c3d5f16u, 0xbb049a79u, 0x9293de8fu, 0xbb049a79u, 0xbb049a79u, 0x80a4df5au, 0x8306d03bu, 0xbb049a79u, 0x80a4df5au, 0xa5aec797u, 0x8c3d5f16u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc6cd75e9u, 0x96263ae6u, 0xc6cd75e9u, 0xc6cd75e9u, 0xc4a334bfu, 0xc6cd75e9u, 0xe5121482u, 0xa5aec797u, 0x7dabe929u, 0x39292d22u, 0xc6cd75e9u, 0x96263ae6u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9f199504u, 0xc4a334bfu, 0xc4a334bfu, 0xd7a7a3ccu, 0xc6cd75e9u, 0xc4a334bfu, 0xdc28ff90u, 0xdc28ff90u, 0xc6cd75e9u, 0xdc28ff90u, 0x9f199504u, 0xc4a334bfu, 0x00000000u, 0xc4a334bfu, 0xd7a7a3ccu, 0xdc28ff90u}, + {0x00a61f93u, 0x9d3c7decu, 0x7dabe929u, 0x7dabe929u, 0x7dabe929u, 0x7dabe929u, 0x6bad6be2u, 0xf3f49249u, 0x7dabe929u, 0x8e7aa6e9u, 0x00a61f93u, 0x9d3c7decu, 0x00a61f93u, 0x00000000u, 0x7dabe929u, 0x00000000u}, + {0xf38b2ffcu, 0x9293de8fu, 0xc0433cbdu, 0xc0433cbdu, 0xc0433cbdu, 0xc0433cbdu, 0x51c9bc70u, 0x1a466884u, 0xc88b2875u, 0xe255accbu, 0xf38b2ffcu, 0x9293de8fu, 0x00000000u, 0x00000000u, 0xc0433cbdu, 0x51c9bc70u}, + {0xd7a7a3ccu, 0xc5e818fau, 0xc5e818fau, 0x3d6c51e3u, 0x96263ae6u, 0xc5e818fau, 0xe255accbu, 0xe255accbu, 0xd7a7a3ccu, 0xe255accbu, 0xd7a7a3ccu, 0xc5e818fau, 0xd7a7a3ccu, 0xc5e818fau, 0x3d6c51e3u, 0xe255accbu}, + {0xe5121482u, 0xc6cd75e9u, 0x96263ae6u, 0x96263ae6u, 0xc5e818fau, 0x96263ae6u, 0xa5aec797u, 0x39292d22u, 0x9293de8fu, 0x9f199504u, 0xe5121482u, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x70eb9a0au, 0x00a61f93u, 0x70eb9a0au, 0x9d3c7decu, 0x70eb9a0au, 0x70eb9a0au, 0x9f199504u, 0xe5121482u, 0xd7a7a3ccu, 0x9f199504u, 0x70eb9a0au, 0x00a61f93u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xf3f49249u, 0xd7a7a3ccu, 0x3d6c51e3u, 0xc5e818fau, 0x3d6c51e3u, 0x3d6c51e3u, 0xf38b2ffcu, 0x99dd251du, 0xbb049a79u, 0xe5121482u, 0xf3f49249u, 0xd7a7a3ccu, 0xf3f49249u, 0xd7a7a3ccu, 0xc5e818fau, 0x00000000u}, + {0x7dabe929u, 0x70eb9a0au, 0x00a61f93u, 0x00a61f93u, 0x9d3c7decu, 0x00a61f93u, 0xe5121482u, 0x9f199504u, 0x7dabe929u, 0xe5121482u, 0x7dabe929u, 0x70eb9a0au, 0x7dabe929u, 0x00000000u, 0x00a61f93u, 0xe5121482u}, + {0x6bad6be2u, 0x7dabe929u, 0x9d3c7decu, 0x70eb9a0au, 0x00a61f93u, 0x9d3c7decu, 0xf3f49249u, 0x8e7aa6e9u, 0xc6cd75e9u, 0x6bad6be2u, 0x6bad6be2u, 0x7dabe929u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x14aa4e71u, 0x14aa4e71u, 0x14aa4e71u, 0x14aa4e71u, 0x14aa4e71u, 0x14aa4e71u, 0x6bad6be2u, 0x6bad6be2u, 0x7dabe929u, 0x6bad6be2u, 0x14aa4e71u, 0x14aa4e71u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u}, + {0xfadb8908u, 0xaf67d461u, 0x49889310u, 0x731c9452u, 0xd11745adu, 0x49889310u, 0x7d836e77u, 0x49889310u, 0x1919e93au, 0xaf67d461u, 0xfadb8908u, 0xaf67d461u, 0xfadb8908u, 0x00000000u, 0x00000000u, 0x7d836e77u}, + {0xaf67d461u, 0x731c9452u, 0xd11745adu, 0xd11745adu, 0x49889310u, 0xd11745adu, 0x731c9452u, 0xd11745adu, 0x49889310u, 0x731c9452u, 0xaf67d461u, 0x731c9452u, 0x00000000u, 0x00000000u, 0xd11745adu, 0x00000000u}, + {0x837a7935u, 0x35d14880u, 0x1919e93au, 0xd0944996u, 0x1919e93au, 0x1919e93au, 0x65b00a2du, 0x1919e93au, 0x1919e93au, 0x35d14880u, 0x837a7935u, 0x35d14880u, 0x837a7935u, 0x35d14880u, 0x00000000u, 0x00000000u}, + {0x731c9452u, 0xd11745adu, 0x731c9452u, 0x49889310u, 0x731c9452u, 0x731c9452u, 0xd11745adu, 0x731c9452u, 0x731c9452u, 0xd11745adu, 0x731c9452u, 0xd11745adu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xce1d62e0u, 0xadec6f11u, 0x02f0ee99u, 0x02f0ee99u, 0xd0944996u, 0x02f0ee99u, 0x5071950eu, 0x02f0ee99u, 0xe4163207u, 0xadec6f11u, 0xce1d62e0u, 0xadec6f11u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x35d14880u, 0xd0944996u, 0xd0944996u, 0x1919e93au, 0x02f0ee99u, 0xd0944996u, 0xd0944996u, 0xd0944996u, 0x02f0ee99u, 0xd0944996u, 0x35d14880u, 0xd0944996u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x57241955u, 0x406329bcu, 0xe4163207u, 0xe4163207u, 0xe4163207u, 0xe4163207u, 0x59001ac9u, 0xe4163207u, 0xe4163207u, 0x406329bcu, 0x57241955u, 0x406329bcu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xd11745adu, 0x49889310u, 0xaf67d461u, 0xaf67d461u, 0xaf67d461u, 0xaf67d461u, 0x49889310u, 0xaf67d461u, 0x1c593af5u, 0x7d836e77u, 0xd11745adu, 0x49889310u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xede7bd0cu, 0x7d836e77u, 0x7d836e77u, 0x35d14880u, 0xadec6f11u, 0x7d836e77u, 0x7d836e77u, 0x7d836e77u, 0x1919e93au, 0x7d836e77u, 0xede7bd0cu, 0x7d836e77u, 0xede7bd0cu, 0x7d836e77u, 0x35d14880u, 0x7d836e77u}, + {0xadec6f11u, 0x02f0ee99u, 0xadec6f11u, 0xadec6f11u, 0x7d836e77u, 0xadec6f11u, 0x02f0ee99u, 0xadec6f11u, 0x49889310u, 0x65b00a2du, 0xadec6f11u, 0x02f0ee99u, 0xadec6f11u, 0x00000000u, 0xadec6f11u, 0x00000000u}, + {0x1389b372u, 0x65b00a2du, 0x5071950eu, 0x406329bcu, 0x5071950eu, 0x5071950eu, 0x65b00a2du, 0x5071950eu, 0x1919e93au, 0x65b00a2du, 0x1389b372u, 0x65b00a2du, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xd0944996u, 0x1919e93au, 0x35d14880u, 0x7d836e77u, 0x35d14880u, 0x35d14880u, 0x1919e93au, 0x35d14880u, 0x731c9452u, 0x5071950eu, 0xd0944996u, 0x1919e93au, 0x00000000u, 0x1919e93au, 0x7d836e77u, 0x1919e93au}, + {0xffe976abu, 0x5071950eu, 0x65b00a2du, 0x65b00a2du, 0x406329bcu, 0x65b00a2du, 0x5071950eu, 0x65b00a2du, 0xe4163207u, 0x5071950eu, 0xffe976abu, 0x5071950eu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x406329bcu, 0xe4163207u, 0x406329bcu, 0x5071950eu, 0x65b00a2du, 0x406329bcu, 0xe4163207u, 0x406329bcu, 0x02f0ee99u, 0x59001ac9u, 0x406329bcu, 0xe4163207u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8eb5140fu, 0x59001ac9u, 0x59001ac9u, 0x59001ac9u, 0x59001ac9u, 0x59001ac9u, 0x59001ac9u, 0x59001ac9u, 0xe4163207u, 0x59001ac9u, 0x8eb5140fu, 0x59001ac9u, 0x8eb5140fu, 0x59001ac9u, 0x59001ac9u, 0x59001ac9u}, + {0x49889310u, 0xfadb8908u, 0xfadb8908u, 0xfadb8908u, 0xfadb8908u, 0xfadb8908u, 0x1c593af5u, 0x1c593af5u, 0xfadb8908u, 0x1c593af5u, 0x49889310u, 0xfadb8908u, 0x49889310u, 0xfadb8908u, 0xfadb8908u, 0x1c593af5u}, + {0xffb88309u, 0x837a7935u, 0xffb88309u, 0xce1d62e0u, 0xede7bd0cu, 0xffb88309u, 0x7d836e77u, 0x49889310u, 0x5b4c8012u, 0xaf67d461u, 0xffb88309u, 0x837a7935u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x7d836e77u, 0xce1d62e0u, 0xede7bd0cu, 0xede7bd0cu, 0xffb88309u, 0xede7bd0cu, 0x731c9452u, 0xd11745adu, 0xffb88309u, 0x731c9452u, 0x7d836e77u, 0xce1d62e0u, 0x7d836e77u, 0x00000000u, 0xede7bd0cu, 0x00000000u}, + {0xa341738cu, 0x57241955u, 0x5b4c8012u, 0xffe976abu, 0x5b4c8012u, 0x5b4c8012u, 0x65b00a2du, 0x1919e93au, 0x5b4c8012u, 0x35d14880u, 0xa341738cu, 0x57241955u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x02f0ee99u, 0xede7bd0cu, 0xce1d62e0u, 0xffb88309u, 0xce1d62e0u, 0xce1d62e0u, 0xd11745adu, 0x731c9452u, 0xce1d62e0u, 0xd11745adu, 0x02f0ee99u, 0xede7bd0cu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x60581ccau, 0x1389b372u, 0x60581ccau, 0x60581ccau, 0xffe976abu, 0x60581ccau, 0x5071950eu, 0x02f0ee99u, 0xbef7e268u, 0xadec6f11u, 0x60581ccau, 0x1389b372u, 0x60581ccau, 0x00000000u, 0x60581ccau, 0x00000000u}, + {0x65b00a2du, 0xffe976abu, 0xffe976abu, 0x5b4c8012u, 0x60581ccau, 0xffe976abu, 0xd0944996u, 0xd0944996u, 0x60581ccau, 0xd0944996u, 0x65b00a2du, 0xffe976abu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x16f44881u, 0x8eb5140fu, 0xbef7e268u, 0xbef7e268u, 0xbef7e268u, 0xbef7e268u, 0x59001ac9u, 0xe4163207u, 0xbef7e268u, 0x406329bcu, 0x16f44881u, 0x8eb5140fu, 0x00000000u, 0x8eb5140fu, 0xbef7e268u, 0x59001ac9u}, + {0x1919e93au, 0xffb88309u, 0x837a7935u, 0x837a7935u, 0x837a7935u, 0x837a7935u, 0x49889310u, 0xaf67d461u, 0xfadb8908u, 0x7d836e77u, 0x1919e93au, 0xffb88309u, 0x1919e93au, 0x00000000u, 0x837a7935u, 0x49889310u}, + {0x5b4c8012u, 0xa341738cu, 0xa341738cu, 0x57241955u, 0x1389b372u, 0xa341738cu, 0x7d836e77u, 0x7d836e77u, 0x5b4c8012u, 0x7d836e77u, 0x5b4c8012u, 0xa341738cu, 0x00000000u, 0xa341738cu, 0x00000000u, 0x7d836e77u}, + {0x5071950eu, 0x60581ccau, 0x1389b372u, 0x1389b372u, 0xa341738cu, 0x1389b372u, 0x02f0ee99u, 0xadec6f11u, 0xffb88309u, 0x65b00a2du, 0x5071950eu, 0x60581ccau, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xb91dddd9u, 0x16f44881u, 0xb91dddd9u, 0x8eb5140fu, 0xb91dddd9u, 0xb91dddd9u, 0x65b00a2du, 0x5071950eu, 0x5b4c8012u, 0x65b00a2du, 0xb91dddd9u, 0x16f44881u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xe4163207u, 0x5b4c8012u, 0x57241955u, 0xa341738cu, 0x57241955u, 0x57241955u, 0x1919e93au, 0x35d14880u, 0xce1d62e0u, 0x5071950eu, 0xe4163207u, 0x5b4c8012u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xbef7e268u, 0xb91dddd9u, 0x16f44881u, 0x16f44881u, 0x8eb5140fu, 0x16f44881u, 0x5071950eu, 0x65b00a2du, 0xbef7e268u, 0x5071950eu, 0xbef7e268u, 0xb91dddd9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x59001ac9u, 0xbef7e268u, 0x8eb5140fu, 0xb91dddd9u, 0x16f44881u, 0x8eb5140fu, 0xe4163207u, 0x406329bcu, 0x60581ccau, 0x59001ac9u, 0x59001ac9u, 0xbef7e268u, 0x59001ac9u, 0xbef7e268u, 0x00000000u, 0x00000000u}, + {0x89be9c1cu, 0x89be9c1cu, 0x89be9c1cu, 0x89be9c1cu, 0x89be9c1cu, 0x89be9c1cu, 0x59001ac9u, 0x59001ac9u, 0xbef7e268u, 0x59001ac9u, 0x89be9c1cu, 0x89be9c1cu, 0x89be9c1cu, 0x89be9c1cu, 0x89be9c1cu, 0x59001ac9u} +}; + constexpr const char* Names[Checked] = {"bitmask_identity", "bitmask_swap8", "bitmask_broadcast", "bitmask_reverse", "bitmask_mixed", "quad_perm", "quad_perm_high_bits", "rotate_left_1", "rotate_right_3", "rotate_left_31", "rotate_masked", "rotate_high_bits", "inactive_swap8", "inactive_quad_perm", "inactive_rotate", "inactive_broadcast"}; +constexpr const char* FftNames[Checked] = {"fft_00", "fft_10", "fft_1f", "fft_18", "fft_1c", "fft_1e", "fft_01", "fft_0f", "fft_15", "fft_08", "fft_00_high_bits", "fft_10_high_bits", "inactive_fft_00", "inactive_fft_10", "inactive_fft_18", "inactive_fft_01"}; std::array BufferDescriptor(const void* data, std::uint32_t bytes) { const auto address = reinterpret_cast(data); @@ -255,11 +385,11 @@ void Run(AgcDriver::VulkanDevice& device, std::span code, s } template -void Check(const std::uint32_t (&expected)[Lanes][Checked]) { +void Check(const char* const (&names)[Checked], const std::uint32_t (&expected)[Lanes][Checked]) { for (std::uint32_t tid = 0; tid < Lanes; ++tid) { for (std::uint32_t index = 0; index < Checked; ++index) { const auto actual = Output[tid * Results + index]; - Require(actual == expected[tid][index], "ds_swizzle wave" + std::to_string(Lanes) + ": lane " + std::to_string(tid) + " " + Names[index] + " is " + Hex(actual) + ", expected " + Hex(expected[tid][index])); + Require(actual == expected[tid][index], "ds_swizzle wave" + std::to_string(Lanes) + ": lane " + std::to_string(tid) + " " + names[index] + " is " + Hex(actual) + ", expected " + Hex(expected[tid][index])); } } } @@ -275,11 +405,17 @@ int main() { return VulkanTestSkipped; } Run(*device, Wave32Code, 32, device->ComputeTarget(32)); - Check(Expected32); + Check(Names, Expected32); + Run(*device, FftWave32Code, 32, device->ComputeTarget(32)); + Check(FftNames, FftExpected32); Run(*device, Wave64Code, 64, device->Target()); - Check(Expected64); + Check(Names, Expected64); + Run(*device, FftWave64Code, 64, device->Target()); + Check(FftNames, FftExpected64); Run(*device, Wave64Code, 64, device->ComputeTarget(32)); - Check(Expected64); + Check(Names, Expected64); + Run(*device, FftWave64Code, 64, device->ComputeTarget(32)); + Check(FftNames, FftExpected64); std::puts("ds_swizzle tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleLodBiasSum.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleLodBiasSum.cpp new file mode 100644 index 0000000000..f195572fd9 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleLodBiasSum.cpp @@ -0,0 +1,204 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Width = 64; +constexpr std::uint32_t Height = 16; +constexpr std::uint32_t Size = 64; +constexpr std::uint32_t Mips = 7; +constexpr std::uint32_t Format8888UNorm = 56; +constexpr std::uint32_t Type2D = 9; +constexpr float CoordinateScale = 0.0473f; +constexpr std::uint32_t LevelStep = 20; +alignas(256) std::array Pixels{}; +alignas(256) std::array Texels{}; + +alignas(256) constexpr std::array VertexCode{ + 0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000, +}; + +alignas(256) constexpr std::array BiasPixelCode{ + 0x10040010, 0x06060300, 0x10060611, 0xf0940f08, 0x00610401, 0xbf8c3f70, 0xf800180f, 0x07060504, 0xbf810000, +}; + +constexpr std::array, 3> Triangle{{{-1.0f, -1.0f, 0.5f, 1.0f}, {3.0f, -1.0f, 0.5f, 1.0f}, {-1.0f, 3.0f, 0.5f, 1.0f}}}; + +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}; +} + +std::array TextureDescriptor() { + const auto address = static_cast(reinterpret_cast(Texels.data())); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (Format8888UNorm << 20u) | (((Size - 1u) & 3u) << 30u), + ((Size - 1u) >> 2u) | ((Size - 1u) << 14u), + 0xfacu | ((Mips - 1u) << 16u) | (Type2D << 28u), + 0u, + (Mips - 1u) << 4u, + 0u, + 0u, + }; +} + +std::array SamplerDescriptor() { + return {0u, 0xfffu << 12u, 1u << 26u, 0u}; +} + +void FillTexture() { + const auto mips = AgcDriver::Graphics::ComputeMipLayout(AgcDriver::Graphics::TextureTileMode::kLinear, Format8888UNorm, Size, Size, Mips); + Require(AgcDriver::Graphics::ComputeSurfaceSize(mips, 1) <= Texels.size(), "image sample LOD bias sum: the mip chain does not fit the texel storage"); + std::fill(Texels.begin(), Texels.end(), 0xeeu); + for (std::uint32_t level = 0; level < Mips; ++level) { + const auto& mip = mips[level]; + for (std::uint32_t y = 0; y < mip.height; ++y) { + for (std::uint32_t x = 0; x < mip.width; ++x) { + auto* texel = &Texels[mip.tiledOffset + y * mip.pitchBytes + x * 4u]; + texel[0] = static_cast(level * LevelStep); + texel[1] = 0u; + texel[2] = 0u; + texel[3] = 255u; + } + } + } +} + +void Draw(AgcDriver::VulkanDevice& device, std::array texture, std::array sampler, float bias) { + const auto& target = device.Target(); + alignas(256) std::array code{}; + std::copy_n(BiasPixelCode.begin(), 3u, code.begin()); + code[3] = 0x7e020212u; + std::copy(BiasPixelCode.begin() + 3, BiasPixelCode.end(), code.begin() + 4); + const auto pixelCode = std::span(code).first(10u); + Pixels.fill(std::byte{0}); + + std::vector vertexUserData(4, 0u); + const auto vertexBuffer = BufferDescriptor(Triangle.data(), 16u, static_cast(Triangle.size())); + std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin()); + const std::array vertexMemory{{{reinterpret_cast(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}}; + ShaderRecompiler::RecompileRequest vertex{ + {ShaderStage::Vertex, reinterpret_cast(VertexCode.data()), VertexCode, 0, {}}, + {64, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory}, + target, + {0, 0, 0, 64} + }; + vertex.useCache = false; + const auto vertexResult = ShaderRecompiler::Recompile(vertex); + const auto vertexPush = static_cast(vertexResult.pushConstants.size()); + + ShaderRecompiler::ShaderPixelStageInfo pixel{}; + pixel.wave32 = false; + pixel.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionX) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionY); + pixel.posX = true; + pixel.posY = true; + pixel.targetOutputMode[0] = 9; + pixel.targetExportMapping.fill(0xe4u); + std::vector pixelUserData(24, 0u); + std::copy(texture.begin(), texture.end(), pixelUserData.begin() + 4); + std::copy(sampler.begin(), sampler.end(), pixelUserData.begin() + 12); + pixelUserData[16] = std::bit_cast(CoordinateScale); + pixelUserData[17] = std::bit_cast(CoordinateScale); + pixelUserData[18] = std::bit_cast(bias); + const std::array pixelMemory{{{reinterpret_cast(pixelCode.data()), std::as_bytes(std::span(pixelCode))}}}; + ShaderRecompiler::RecompileRequest fragment{ + {ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, + {64, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, + target, + PixelPushLayout(vertexPush, target) + }; + const auto pixelResult = ShaderRecompiler::Recompile(fragment); + Require(!target.registrationModules || pixelResult.registrationModule, "image sample LOD bias sum did not exercise its requested registration module"); + const std::array shaders{{ + {ShaderStage::Vertex, &vertexResult, 0}, + {ShaderStage::Fragment, &pixelResult, PixelPushOffset(vertexPush, target)} + }}; + + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, 64u, 64u, std::nullopt, std::nullopt}; + state.color = {reinterpret_cast(Pixels.data()), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, Pixels.size(), 0xe4u}; + state.colors = {state.color}; + state.hasColorTarget = true; + state.renderExtent = {Width, Height}; + 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 = {}; + const AgcDriver::Pm4::DrawParameters draw{0, static_cast(Triangle.size()), 0, 1, 0, false}; + device.Draw(state, draw, shaders); + device.WaitIdle(); +} + +struct Case { + std::uint32_t field; + float shaderBias; + std::uint32_t level; +}; + +void Check(const Case& sample, bool llvmpipe) { + const std::string label = "image sample LOD bias sum: S# field " + std::to_string(sample.field) + " with shader bias " + std::to_string(sample.shaderBias); + std::uint32_t matching = 0; + for (std::uint32_t pixel = 0; pixel < Width * Height; ++pixel) { + const auto red = std::to_integer(Pixels[pixel * 4u]); + if (red == sample.level * LevelStep) { + ++matching; + continue; + } + Require(llvmpipe, label + " read " + std::to_string(red) + ", expected level " + std::to_string(sample.level)); + } + Require(matching * 4u >= Width * Height * 3u, label + " matched level " + std::to_string(sample.level) + " on only " + std::to_string(matching) + " of " + std::to_string(Width * Height) + " pixels"); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + FillTexture(); + const bool llvmpipe = device->DeviceName().find("llvmpipe") != std::string::npos; + constexpr std::array cases{{ + {0u, 0.0f, 2u}, + {0x100u, 0.0f, 3u}, + {0u, 2.0f, 4u}, + {0x1000u, -14.0f, 4u}, + {0x1facu, -29.0f, 5u}, + {0x3000u, 14.0f, 0u}, + {0x2000u, 32.0f, 2u}, + {0x1facu, 0.0f, 6u}, + {0x2000u, 0.0f, 0u}, + }}; + for (const auto& sample : cases) { + auto sampler = SamplerDescriptor(); + sampler[2] |= sample.field; + Draw(*device, TextureDescriptor(), sampler, sample.shaderBias); + Check(sample, llvmpipe); + } + std::puts("image sample LOD bias sum 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/SampleMultisampleTarget.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SampleMultisampleTarget.cpp index 87107f2054..de2e3a8c71 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/SampleMultisampleTarget.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SampleMultisampleTarget.cpp @@ -929,7 +929,7 @@ void CheckBdaSpan(AgcDriver::VulkanDevice& device, const Block& block, std::size failure = error.what(); } const auto deferred = log.Finish(); - if (failure.empty()) failure = deferred; + if (failure.empty() && deferred.find("BDA access failed:") != std::string::npos) failure = deferred; if (refused) { Require(failure.find("BDA access failed:") != std::string::npos, "BDA access to a resident native multisample range was not refused: " + failure); Require(std::equal(before.begin(), before.end(), block.data), "a refused BDA access changed guest memory"); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/SdwaSinCosF32.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaSinCosF32.cpp new file mode 100644 index 0000000000..d751313eed --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaSinCosF32.cpp @@ -0,0 +1,279 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "RdnaDecoder/RdnaInstructionDecoder.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t Inputs = 16; +constexpr std::uint32_t ResultsPerCase = 19; +constexpr std::uint32_t Results = Inputs * ResultsPerCase; +constexpr std::uint32_t Garbage = 0xabcd1234u; + +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +struct TestCase { + std::uint32_t opcode; + std::uint32_t input; + std::uint32_t expectedFull; + bool checkPinned; + const char* name; +}; + +constexpr TestCase TestCases[Inputs] = { + {0x35u, 0x00000000u, 0x00000000u, true, "v_sin_f32 0.0"}, + {0x35u, 0x3e800000u, 0x3f800000u, true, "v_sin_f32 0.25"}, + {0x35u, 0xbe800000u, 0xbf800000u, true, "v_sin_f32 -0.25"}, + {0x35u, 0x3f400000u, 0xbf800000u, true, "v_sin_f32 0.75"}, + {0x35u, 0x3fa00000u, 0x3f800000u, true, "v_sin_f32 1.25"}, + {0x35u, 0xbf400000u, 0x3f800000u, true, "v_sin_f32 -0.75"}, + {0x35u, 0xbe000000u, 0u, false, "v_sin_f32 oracle -0.125"}, + {0x35u, 0x3d000000u, 0u, false, "v_sin_f32 oracle 0.03125"}, + {0x36u, 0x00000000u, 0x3f800000u, true, "v_cos_f32 0.0"}, + {0x36u, 0x3f000000u, 0xbf800000u, true, "v_cos_f32 0.5"}, + {0x36u, 0xbf000000u, 0xbf800000u, true, "v_cos_f32 -0.5"}, + {0x36u, 0x3f800000u, 0x3f800000u, true, "v_cos_f32 1.0"}, + {0x36u, 0x3e800000u, 0x00000000u, true, "v_cos_f32 0.25"}, + {0x36u, 0xbe800000u, 0x00000000u, true, "v_cos_f32 -0.25"}, + {0x36u, 0xbe000000u, 0u, false, "v_cos_f32 oracle -0.125"}, + {0x36u, 0x3e600000u, 0u, false, "v_cos_f32 oracle 0.21875"}, +}; + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +std::uint32_t FieldWidth(std::uint32_t selector) { + return selector < 4u ? 8u : selector < 6u ? 16u : 32u; +} + +std::uint32_t FieldOffset(std::uint32_t selector) { + return selector < 4u ? selector * 8u : selector == 5u ? 16u : 0u; +} + +std::uint32_t InsertResult(std::uint32_t bits, std::uint32_t destination, std::uint32_t unused) { + const auto width = FieldWidth(destination); + const auto offset = FieldOffset(destination); + const auto mask = (1u << width) - 1u; + auto field = bits & mask; + if (unused == 1u && (field & (1u << (width - 1u)))) field |= ~mask; + return (field << offset) | (unused == 2u ? Garbage & ~(mask << offset) : 0u); +} + +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) | (4u << 16u), count, 0x11016facu}; +} + +void TestInvalidSdwaSelectors() { + constexpr std::uint32_t SinSdwa = 0x7e0000f9u | (5u << 17u) | (0x35u << 9u); + constexpr std::uint32_t CosSdwa = 0x7e0000f9u | (5u << 17u) | (0x36u << 9u); + + for (const auto opWord : {SinSdwa, CosSdwa}) { + { + const std::array words{opWord, 4u | (0u << 8u) | (2u << 11u) | (6u << 16u)}; + const auto instruction = ShaderRecompiler::DecodeRdnaInstruction(0u, words, 0u); + Require(instruction.family == ShaderRecompiler::RdnaInstructionFamily::VOP1, "instruction must be VOP1"); + Require(instruction.destination.sdwaSel == 0u, "destination sdwaSel must be 0"); + Require(instruction.destination.sdwaDstUnused == 2u, "destination sdwaDstUnused must be 2"); + Require(instruction.destination.explicitSdwaDst, "destination explicitSdwaDst must be true"); + Require(instruction.source0.sdwaSel == 6u, "source0 sdwaSel must be 6"); + Require(!instruction.source0.negate, "source0 negate must be false"); + Require(!instruction.source0.absolute, "source0 absolute must be false"); + Require(!instruction.destination.clamp, "destination clamp must be false"); + Require(instruction.destination.omod == 0u, "destination omod must be 0"); + } + { + const std::array words{opWord, 4u | (4u << 8u) | (1u << 11u) | (1u << 13u) | (1u << 14u) | (6u << 16u) | (1u << 20u) | (1u << 21u)}; + const auto instruction = ShaderRecompiler::DecodeRdnaInstruction(0u, words, 0u); + Require(instruction.destination.sdwaSel == 4u, "destination sdwaSel must be 4"); + Require(instruction.destination.sdwaDstUnused == 1u, "destination sdwaDstUnused must be 1"); + Require(instruction.destination.clamp, "destination clamp must be true"); + Require(instruction.destination.omod == 1u, "destination omod must be 1"); + Require(instruction.source0.negate, "source0 negate must be true"); + Require(instruction.source0.absolute, "source0 absolute must be true"); + } + { + const std::array words{opWord, 4u | (7u << 8u) | (6u << 16u)}; + bool threw = false; + try { + ShaderRecompiler::DecodeRdnaInstruction(0u, words, 0u); + } catch (const std::invalid_argument&) { + threw = true; + } + Require(threw, "SDWA destination selector 7 must throw std::invalid_argument"); + } + { + const std::array words{opWord, 4u | (0u << 8u) | (7u << 16u)}; + bool threw = false; + try { + ShaderRecompiler::DecodeRdnaInstruction(0u, words, 0u); + } catch (const std::invalid_argument&) { + threw = true; + } + Require(threw, "SDWA source selector 7 must throw std::invalid_argument"); + } + { + const std::array words{opWord, 4u | (0u << 8u) | (3u << 11u) | (6u << 16u)}; + bool threw = false; + try { + ShaderRecompiler::DecodeRdnaInstruction(0u, words, 0u); + } catch (const std::invalid_argument&) { + threw = true; + } + Require(threw, "SDWA destination unused 3 on byte destination must throw std::invalid_argument"); + } + { + const std::array words{opWord, 4u | (4u << 8u) | (3u << 11u) | (6u << 16u)}; + bool threw = false; + try { + ShaderRecompiler::DecodeRdnaInstruction(0u, words, 0u); + } catch (const std::invalid_argument&) { + threw = true; + } + Require(threw, "SDWA destination unused 3 on word destination must throw std::invalid_argument"); + } + { + const std::array words{opWord, 4u | (6u << 8u) | (3u << 11u) | (6u << 16u)}; + bool threw = false; + try { + ShaderRecompiler::DecodeRdnaInstruction(0u, words, 0u); + } catch (const std::invalid_argument&) { + threw = true; + } + Require(threw, "SDWA destination unused 3 on dword destination must throw std::invalid_argument"); + } + } +} + +void RunExecutionTests(AgcDriver::VulkanDevice& device, std::uint32_t waveSize) { + for (std::uint32_t lane = 0u; lane < Threads; ++lane) { + for (std::uint32_t c = 0u; c < Inputs; ++c) { + Input[lane * Inputs + c] = TestCases[c].input; + } + } + Output.fill(0xdeadbeefu); + + std::vector words{ + 0x160200ffu, Inputs, 0x160400ffu, Results, + }; + + std::uint32_t resultIndex = 0u; + for (std::uint32_t c = 0u; c < Inputs; ++c) { + words.insert(words.end(), {0xe0302000u | (c * 4u), 0x80000401u, 0xbf8c3f70u}); + words.insert(words.end(), {0x7e0002ffu | (5u << 17u), Garbage}); + words.push_back(0x7e000000u | (5u << 17u) | (TestCases[c].opcode << 9u) | 260u); + words.insert(words.end(), {0xe0702000u | (resultIndex * 4u), 0x80010502u}); + ++resultIndex; + + for (std::uint32_t destination = 0u; destination < 6u; ++destination) { + for (std::uint32_t unused = 0u; unused < 3u; ++unused, ++resultIndex) { + words.insert(words.end(), {0x7e0002ffu | (5u << 17u), Garbage}); + const auto word0 = 0x7e0000f9u | (5u << 17u) | (TestCases[c].opcode << 9u); + const auto word1 = 4u | (destination << 8u) | (unused << 11u) | (6u << 16u); + words.insert(words.end(), {word0, word1}); + words.insert(words.end(), {0xe0702000u | (resultIndex * 4u), 0x80010502u}); + } + } + } + words.push_back(0xbf810000u); + + std::vector userData(8u); + const auto inputDesc = BufferDescriptor(Input.data(), static_cast(Input.size())); + const auto outputDesc = BufferDescriptor(Output.data(), static_cast(Output.size())); + std::copy(inputDesc.begin(), inputDesc.end(), userData.begin()); + std::copy(outputDesc.begin(), outputDesc.end(), userData.begin() + 4); + + const std::span code(words); + 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{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); + + std::uint32_t checkIndex = 0u; + for (std::uint32_t c = 0u; c < Inputs; ++c) { + const auto fullResultIndex = c * ResultsPerCase; + for (std::uint32_t lane = 0u; lane < Threads; ++lane) { + const auto actualFull = Output[lane * Results + checkIndex]; + if (TestCases[c].checkPinned) { + Require(actualFull == TestCases[c].expectedFull, + std::string(TestCases[c].name) + " full result lane " + std::to_string(lane) + + " is " + Hex(actualFull) + ", expected " + Hex(TestCases[c].expectedFull)); + } else if (TestCases[c].opcode == 0x35u) { + Require((actualFull >> 16u) == 0xbf35u || (actualFull >> 16u) == 0x3f35u || (actualFull >> 16u) == 0x3e47u, + std::string(TestCases[c].name) + " full result lane " + std::to_string(lane) + + " is " + Hex(actualFull) + ", expected high word 0xbf35, 0x3f35 or 0x3e47"); + Require((actualFull & 0xffu) != 0u, std::string(TestCases[c].name) + " must produce non-zero low byte"); + Require((actualFull & 0x80u) != 0u, std::string(TestCases[c].name) + " must have bit 7 set for SEXT"); + if ((actualFull >> 16u) == 0x3e47u) { + Require((actualFull & 0x8000u) != 0u, std::string(TestCases[c].name) + " must have bit 15 set for word SEXT"); + } + } else { + Require((actualFull >> 16u) == 0x3f35u || (actualFull >> 16u) == 0x3e47u, + std::string(TestCases[c].name) + " full result lane " + std::to_string(lane) + + " is " + Hex(actualFull) + ", expected high word 0x3f35 or 0x3e47"); + Require((actualFull & 0xffu) != 0u, std::string(TestCases[c].name) + " must produce non-zero low byte"); + Require((actualFull & 0x80u) != 0u, std::string(TestCases[c].name) + " must have bit 7 set for SEXT"); + if ((actualFull >> 16u) == 0x3e47u) { + Require((actualFull & 0x8000u) != 0u, std::string(TestCases[c].name) + " must have bit 15 set for word SEXT"); + } + } + } + ++checkIndex; + for (std::uint32_t destination = 0u; destination < 6u; ++destination) { + for (std::uint32_t unused = 0u; unused < 3u; ++unused, ++checkIndex) { + for (std::uint32_t lane = 0u; lane < Threads; ++lane) { + const auto base = TestCases[c].checkPinned ? TestCases[c].expectedFull : Output[lane * Results + fullResultIndex]; + const auto expected = InsertResult(base, destination, unused); + const auto actual = Output[lane * Results + checkIndex]; + Require(actual == expected, + std::string(TestCases[c].name) + " lane " + std::to_string(lane) + + " wave" + std::to_string(waveSize) + " destination " + + std::to_string(destination) + " unused " + std::to_string(unused) + + " is " + Hex(actual) + ", expected " + Hex(expected)); + } + } + } + } +} + +} + +int main(int argc, char** argv) { + try { + TestInvalidSdwaSelectors(); + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Require(argc == 1 || (argc == 2 && std::string(argv[1]) == "wave64"), "expected no argument or wave64"); + const auto waveSize = argc == 2 ? 64u : 32u; + RunExecutionTests(*device, waveSize); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/VulkanTestDevice.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/VulkanTestDevice.cpp index c3107a5676..405d4269d2 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/VulkanTestDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/VulkanTestDevice.cpp @@ -1,4 +1,10 @@ #include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif #include #include #include @@ -21,6 +27,20 @@ void Require(const bool condition, const std::string& message) { if (!condition) throw std::runtime_error(message); } +#ifdef _WIN32 +bool HighIntegrity() { + HANDLE token = nullptr; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) return false; + alignas(TOKEN_MANDATORY_LABEL) unsigned char label[sizeof(TOKEN_MANDATORY_LABEL) + SECURITY_MAX_SID_SIZE]; + DWORD size = 0; + const bool queried = GetTokenInformation(token, TokenIntegrityLevel, label, sizeof(label), &size) != FALSE; + CloseHandle(token); + if (!queried) return false; + const PSID sid = reinterpret_cast(label)->Label.Sid; + return *GetSidSubAuthority(sid, *GetSidSubAuthorityCount(sid) - 1u) >= SECURITY_MANDATORY_HIGH_RID; +} +#endif + Outcome Open(std::string& message) { message.clear(); try { @@ -61,6 +81,12 @@ int NoDriverTests() { SetVariable("ANYPS5_GPU", ""); std::string message; const auto optional = Open(message); +#ifdef _WIN32 + if (optional == Outcome::Device && HighIntegrity()) { + std::cout << "skipped, the Vulkan loader ignores VK_DRIVER_FILES in an elevated process and opened " << message << '\n'; + return VulkanTestSkipped; + } +#endif Require(optional == Outcome::Skipped, "without a Vulkan driver the optional device was not skipped: " + message); SetVariable("ANYPS5_REQUIRE_VULKAN", "1"); diff --git a/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp b/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp index 0ddce118db..c3ed90e77f 100644 --- a/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp +++ b/core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp @@ -3,6 +3,7 @@ #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/FrameCapture.hpp" #include "prx/libSceAgcDriver/Execution/include/FrameDumpIo.hpp" +#include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" #include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp" #include "prx/libSceAgcDriver/Submit/include/Acb.hpp" #include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" @@ -13,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -22,7 +24,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -68,8 +72,15 @@ struct Options { std::chrono::milliseconds memoryPaceTimeout{10000}; std::chrono::milliseconds releaseAfter{3}; std::chrono::milliseconds drainTimeout{10000}; + std::chrono::milliseconds busyDrainTimeout{600000}; bool release = true; bool synthesize = true; + std::size_t repeat = 1; + bool shaders = false; + std::filesystem::path shaderCode; + std::size_t submits = 0; + bool traceSerials = false; + bool coldPipelines = false; }; class Frames { @@ -246,6 +257,21 @@ void mapHostRanges(const FrameCaptureReader& reader, Skipped& skipped) { #endif } +void restoreSnapshot(const FrameCaptureReader& reader, const Skipped& skipped) { + std::vector snapshot; + for (const auto& chunk : reader.Chunks()) { + if (chunk.kind == FrameMemoryKind::Snapshot && !skipped.Overlaps(chunk)) snapshot.push_back(&chunk); + } + const auto workers = std::max(1, std::thread::hardware_concurrency()); + for (std::size_t first = 0; first < snapshot.size(); first += workers) { + std::vector> pending; + for (auto index = first; index < std::min(snapshot.size(), first + workers); ++index) { + pending.push_back(std::async(std::launch::async, [&reader, chunk = snapshot[index]] { restore(reader, *chunk); })); + } + for (auto& future : pending) future.get(); + } +} + Skipped restoreMemory(const FrameCaptureReader& reader) { std::uintptr_t base = 0; std::size_t size = 0; @@ -280,21 +306,18 @@ Skipped restoreMemory(const FrameCaptureReader& reader) { mutation.Add(pointer, bytes, (chunk.access & FrameRangeAccess::Readable) != 0, (chunk.access & FrameRangeAccess::Writable) != 0, gpu); } } - std::vector snapshot; - for (const auto& chunk : reader.Chunks()) { - if (chunk.kind == FrameMemoryKind::Snapshot && !skipped.Overlaps(chunk)) snapshot.push_back(&chunk); - } - const auto workers = std::max(1, std::thread::hardware_concurrency()); - for (std::size_t first = 0; first < snapshot.size(); first += workers) { - std::vector> pending; - for (auto index = first; index < std::min(snapshot.size(), first + workers); ++index) { - pending.push_back(std::async(std::launch::async, [&reader, chunk = snapshot[index]] { restore(reader, *chunk); })); - } - for (auto& future : pending) future.get(); - } + restoreSnapshot(reader, skipped); return skipped; } +void resetMemory(const FrameCaptureReader& reader, const Skipped& skipped) { + AgcDriverWaitIdle_nid_postfix(); + for (const auto& chunk : reader.Chunks()) { + if (chunk.kind == FrameMemoryKind::Range && !skipped.Overlaps(chunk)) std::memset(reinterpret_cast(static_cast(chunk.address)), 0, static_cast(chunk.bytes)); + } + restoreSnapshot(reader, skipped); +} + std::pair progress() { std::uint64_t accepted = 0; std::uint64_t completed = 0; @@ -305,7 +328,7 @@ std::pair progress() { struct Replay { const Options& options; const FrameCaptureReader& reader; - Frames frames; + Frames& frames = *new Frames; std::map> outputs; std::mutex memoryMutex; std::map> groups; @@ -321,9 +344,11 @@ struct Replay { std::atomic earlyReleases{0}; std::atomic lateReleases{0}; std::atomic syntheticReleases{0}; + std::atomic keptReservations{0}; std::atomic sequenced{0}; std::uint64_t unfinished = 0; std::size_t threads = 0; + std::size_t pass = 0; bool recording = false; Clock::time_point startedAt{}; Clock::time_point endedAt{}; @@ -334,9 +359,24 @@ struct Replay { void report() const { const auto [accepted, completed] = progress(); - std::fprintf(stderr, "[frame-replay] %llu submits, %zu frames ready, %.1f s since the first frame, pacing waited %.1f s (%llu timeouts), serials %llu/%llu\n", + std::fprintf(stderr, "[frame-replay] %llu submits, %zu frames ready, %.1f s since the first frame, pacing waited %.1f s (%llu timeouts), serials %llu/%llu, blocked on%s\n", static_cast(submits.load()), frames.Times().size(), std::chrono::duration(Clock::now() - startedAt).count(), paceWaitedSeconds(), - static_cast(paceTimeouts.load()), static_cast(completed), static_cast(accepted)); + static_cast(paceTimeouts.load()), static_cast(completed), static_cast(accepted), describeBlocked().c_str()); + } + + static std::string describeBlocked() { + std::array awaited{}; + const auto count = AgcDriverAwaitedWaits_nid_postfix(awaited.data(), awaited.size()); + std::string text; + for (std::size_t slot = 0; slot < count; ++slot) { + const auto& wait = awaited[slot]; + if (wait.address == 0) continue; + char entry[128]; + std::snprintf(entry, sizeof(entry), " 0x%llx (function %u, reference 0x%llx, mask 0x%llx, now 0x%llx)", static_cast(wait.address), static_cast(wait.function), + static_cast(wait.reference), static_cast(wait.mask), static_cast(load(wait.address, wait.bytes == 8 ? 8 : 4))); + text += entry; + } + return text.empty() ? " nothing" : text; } static bool paced(FrameCallKind kind) { @@ -408,21 +448,31 @@ struct Replay { return; case FrameCallKind::Submit: { const Packet packet{reinterpret_cast(static_cast(call.address)), static_cast(call.size), static_cast(call.flags), {}}; - if (call.queue == 0) sceAgcDriverSubmitDcb(&packet); - else sceAgcDriverSubmitAcb(call.queue, &packet); + traced(call, [&] { + if (call.queue == 0) sceAgcDriverSubmitDcb(&packet); + else sceAgcDriverSubmitAcb(call.queue, &packet); + }); if (++submits % 25 == 0) report(); return; } case FrameCallKind::Flip: - AgcDriverSubmitFlip_nid_postfix(flipInfo(call)); + traced(call, [&] { AgcDriverSubmitFlip_nid_postfix(flipInfo(call)); }); return; case FrameCallKind::ReserveEopFlip: { const auto token = static_cast(call.values.at(4)); - if (AgcDriverReserveEopFlipAt_nid_postfix(flipInfo(call), token) != token) throw std::runtime_error("frame replay: EOP flip token " + std::to_string(token) + " was not reserved"); + std::uint32_t reserved = 0; + try { + reserved = AgcDriverReserveEopFlipAt_nid_postfix(flipInfo(call), token); + } catch (const AgcDriver::EopFlipAlreadyReserved&) { + if (pass == 0) throw; + ++keptReservations; + return; + } + if (reserved != token) throw std::runtime_error("frame replay: EOP flip token " + std::to_string(token) + " was not reserved"); return; } case FrameCallKind::Suspend: - AgcDriverSuspendPoint_nid_postfix(); + traced(call, [] { AgcDriverSuspendPoint_nid_postfix(); }); return; case FrameCallKind::Memory: { std::lock_guard lock(memoryMutex); @@ -533,6 +583,8 @@ struct Replay { std::size_t started = 0; std::exception_ptr failure; std::atomic finished{false}; + sequenced.store(0); + stalled.store(false); std::thread breaker([&] { if (!options.release) return; auto lastCompleted = progress().second; @@ -588,22 +640,38 @@ struct Replay { void drain() { auto lastCompleted = progress().second; auto lastProgress = Clock::now(); + auto lastReport = lastProgress; while (true) { const auto [accepted, completed] = progress(); unfinished = accepted - completed; if (unfinished == 0) return; + const auto now = Clock::now(); if (completed != lastCompleted) { lastCompleted = completed; - lastProgress = Clock::now(); - } else if (Clock::now() - lastProgress >= options.drainTimeout) { - std::fprintf(stderr, "[frame-replay] %llu submissions did not complete after the last captured call (serials %llu/%llu)\n", static_cast(unfinished), - static_cast(completed), static_cast(accepted)); + lastProgress = now; + } else if (now - lastReport >= options.drainTimeout) { + lastReport = now; + std::fprintf(stderr, "[frame-replay] draining: %llu submissions pending for %.0f s (serials %llu/%llu), blocked on%s\n", static_cast(unfinished), + std::chrono::duration(now - lastProgress).count(), static_cast(completed), static_cast(accepted), describeBlocked().c_str()); + } + if (now - lastProgress >= options.busyDrainTimeout) { + std::fprintf(stderr, "[frame-replay] %llu submissions did not complete after the last captured call (serials %llu/%llu), blocked on%s\n", static_cast(unfinished), + static_cast(completed), static_cast(accepted), describeBlocked().c_str()); return; } std::this_thread::sleep_for(std::chrono::milliseconds(5)); } } + template + void traced(const FrameCall& call, TFunction&& function) { + if (options.traceSerials) { + std::fprintf(stderr, "[frame-replay] call %llu (%s, thread %llu, queue 0x%x, %llu dwords at 0x%llx)\n", static_cast(call.ordinal), std::string(AgcDriver::FrameCallName(call.kind)).c_str(), + static_cast(call.thread), call.queue, static_cast(call.size), static_cast(call.address)); + } + function(); + } + template void registration(TFunction&& function) { try { @@ -626,7 +694,7 @@ struct Replay { int usage() { std::fprintf(stderr, "usage: agc_frame_replay [--no-pace] [--no-release] [--release-after-ms ] [--strict]\n" - " [--dump
::]...\n" + " [--repeat ] [--cold-pipelines] [--dump
::]...\n" " replays the driver calls and guest memory that APS5_FRAME_CAPTURE= recorded, through this\n" " build's AGC driver with no title running, and prints one JSON line with the frame times\n" " --no-pace issues calls without waiting for the in-flight depth the title had\n" @@ -634,6 +702,18 @@ int usage() { " --release-after-ms milliseconds without a completion before awaited dwords are released (3)\n" " --no-synthesize releases a wait only with a value the capture recorded, never with its reference value\n" " --strict fails on a pacing timeout or a failed registration inside the captured frames\n" + " --repeat replays the frames n times in this process, restoring the snapshot before each pass;\n" + " with n > 1 the frame times come from passes 2 to n, after the first warmed the caches\n" + " --cold-pipelines starts with an empty Vulkan pipeline cache instead of the title's pipeline cache file\n" + " --shaders replays nothing and maps no guest memory; lists each shader registration with its code\n" + " address, size and code hash as the snapshot and the captured writes before it hold them,\n" + " and how many registrations share that code\n" + " --shader-code as --shaders, and writes each distinct code as /type-.bin\n" + " --trace-serials prints each submission, flip and suspend point with its call ordinal and command\n" + " address as it is issued; with APS5_TRACE_GPU the driver prints the serial it accepts\n" + " for each address\n" + " --submits replays nothing; lists the first n submissions and flips of the captured frames with\n" + " their queue and serials, the packets of each and the fields of its waits and releases\n" " --dump writes guest memory to a file after the replay (hexadecimal or decimal)\n" " --frames writes each recorded and replayed display buffer as reference-.bmp and replay-.bmp\n"); return 2; @@ -714,9 +794,290 @@ FrameCheck compareFrames(const FrameCaptureReader& reader, const Options& option return check; } +#ifdef _WIN32 +constexpr int RelaunchAttempts = 8; + +int relaunchAttempt() { + const char* const text = std::getenv("APS5_FRAME_REPLAY_ATTEMPT"); + if (text == nullptr) return 0; + const std::string_view value = text; + int attempt = 0; + const auto [end, error] = std::from_chars(value.data(), value.data() + value.size(), attempt); + if (error != std::errc{} || end != value.data() + value.size() || attempt < 0 || attempt > RelaunchAttempts) + throw std::runtime_error("frame replay: APS5_FRAME_REPLAY_ATTEMPT must be a whole number from 0 to " + std::to_string(RelaunchAttempts) + ", not '" + std::string(value) + "'"); + return attempt; +} + +void relaunchIfHostRegionBlocks(const FrameCaptureReader& reader) { + const int attempt = relaunchAttempt() + 1; + const auto& chunks = reader.Chunks(); + const auto blocked = std::find_if(chunks.begin(), chunks.end(), [](const FrameMemoryChunk& chunk) { + return chunk.kind == FrameMemoryKind::Range && chunk.rangeKind != FrameRangeKind::Host && (chunk.access & FrameRangeAccess::Gpu) != 0 && + GuestArena::GuestArenaHostRegionOverlaps_nid_postfix(static_cast(chunk.address), static_cast(chunk.bytes)); + }); + if (blocked == chunks.end()) return; + if (attempt > RelaunchAttempts) return; + std::fprintf(stderr, "[frame-replay] a host region sits in the captured GPU range 0x%llx+0x%llx; relaunching (%d of %d)\n", static_cast(blocked->address), + static_cast(blocked->bytes), attempt, RelaunchAttempts); + std::fflush(stdout); + std::fflush(stderr); + if (_putenv_s("APS5_FRAME_REPLAY_ATTEMPT", std::to_string(attempt).c_str()) != 0) throw std::runtime_error("frame replay: cannot pass the relaunch count to the next process"); + std::wstring commandLine = GetCommandLineW(); + STARTUPINFOW startup{}; + startup.cb = sizeof(startup); + PROCESS_INFORMATION process{}; + if (!CreateProcessW(nullptr, commandLine.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &startup, &process)) throw std::runtime_error("frame replay: cannot relaunch, error " + std::to_string(GetLastError())); + CloseHandle(process.hThread); + WaitForSingleObject(process.hProcess, INFINITE); + DWORD code = 2; + GetExitCodeProcess(process.hProcess, &code); + std::_Exit(static_cast(code)); +} +#endif +std::uint64_t contentHash(const std::byte* bytes, std::size_t size) { + std::uint64_t hash = 0xcbf29ce484222325ull; + for (std::size_t index = 0; index < size; ++index) { + hash ^= std::to_integer(bytes[index]); + hash *= 0x100000001b3ull; + } + return hash; +} + +class CapturedMemory { +public: + explicit CapturedMemory(const FrameCaptureReader& reader) : reader(reader) { + for (const auto& chunk : reader.Chunks()) { + if (chunk.kind == FrameMemoryKind::Snapshot) snapshots.push_back(&chunk); + else if (chunk.kind == FrameMemoryKind::Range) ranges.push_back(&chunk); + } + const auto byAddress = [](const FrameMemoryChunk* a, const FrameMemoryChunk* b) { return a->address < b->address; }; + std::sort(snapshots.begin(), snapshots.end(), byAddress); + std::sort(ranges.begin(), ranges.end(), byAddress); + } + + std::optional> Read(std::uint64_t address, std::uint64_t bytes) { + if (bytes > std::numeric_limits::max() || bytes > std::numeric_limits::max() - address) return std::nullopt; + std::vector result(static_cast(bytes)); + for (auto at = address; at < address + bytes;) { + const auto* range = covering(ranges, at); + if (range == nullptr) return std::nullopt; + const auto* snapshot = covering(snapshots, at); + auto end = std::min(address + bytes, range->address + range->bytes); + if (snapshot != nullptr) { + end = std::min(end, snapshot->address + snapshot->bytes); + const auto& data = load(*snapshot); + std::memcpy(result.data() + (at - address), data.data() + (at - snapshot->address), static_cast(end - at)); + } else { + const auto next = std::upper_bound(snapshots.begin(), snapshots.end(), at, [](std::uint64_t value, const FrameMemoryChunk* chunk) { return value < chunk->address; }); + if (next != snapshots.end()) end = std::min(end, (*next)->address); + } + at = end; + } + for (const auto* delta : deltas) { + const auto begin = std::max(address, delta->address); + const auto end = std::min(address + bytes, delta->address + delta->bytes); + if (begin >= end) continue; + const auto data = reader.Read(*delta); + std::memcpy(result.data() + (begin - address), data.data() + (begin - delta->address), static_cast(end - begin)); + } + return result; + } + + void Apply(std::span chunks) { + deltas.insert(deltas.end(), chunks.begin(), chunks.end()); + } + +private: + static const FrameMemoryChunk* covering(const std::vector& chunks, std::uint64_t address) { + const auto next = std::upper_bound(chunks.begin(), chunks.end(), address, [](std::uint64_t value, const FrameMemoryChunk* chunk) { return value < chunk->address; }); + if (next == chunks.begin()) return nullptr; + const auto* chunk = *std::prev(next); + return address < chunk->address + chunk->bytes ? chunk : nullptr; + } + + const std::vector& load(const FrameMemoryChunk& chunk) { + if (cached != &chunk) { + data = reader.Read(chunk); + cached = &chunk; + } + return data; + } + + const FrameCaptureReader& reader; + std::vector snapshots; + std::vector ranges; + std::vector deltas; + const FrameMemoryChunk* cached = nullptr; + std::vector data; +}; + +int listShaders(const Options& options) { + const FrameCaptureReader reader(options.directory); + CapturedMemory memory(reader); + if (!options.shaderCode.empty()) std::filesystem::create_directories(options.shaderCode); + std::map> groups; + for (const auto& chunk : reader.Chunks()) { + if (chunk.kind == FrameMemoryKind::Delta) groups[chunk.group].push_back(&chunk); + } + const auto& calls = reader.Calls(); + const auto frameStart = std::find_if(calls.begin(), calls.end(), [](const FrameCall& call) { return call.kind == FrameCallKind::FrameStart; }); + std::map copies; + std::size_t listed = 0, inWindow = 0, unreadable = 0; + for (auto call = calls.begin(); call != calls.end(); ++call) { + if (call->kind == FrameCallKind::Memory) { + if (const auto found = groups.find(call->values.at(0)); found != groups.end()) memory.Apply(found->second); + continue; + } + if (call->kind != FrameCallKind::RegisterShader) continue; + const auto header = memory.Read(call->address, sizeof(Shader)); + if (!header) { + ++unreadable; + continue; + } + Shader fields{}; + std::memcpy(&fields, header->data(), sizeof(Shader)); + const auto code = static_cast(reinterpret_cast(const_cast(fields.code))); + const auto bytes = fields.shader_size != 0 ? memory.Read(code, fields.shader_size) : std::nullopt; + if (!bytes) { + ++unreadable; + continue; + } + const auto hash = contentHash(bytes->data(), bytes->size()); + const auto copy = ++copies[hash]; + if (copy == 1 && !options.shaderCode.empty()) { + char name[64]; + std::snprintf(name, sizeof(name), "type%u-%016llx.bin", static_cast(fields.type), static_cast(hash)); + const auto path = options.shaderCode / name; + std::ofstream file(path, std::ios::binary | std::ios::trunc); + if (!file.is_open()) throw std::runtime_error("frame replay: cannot open " + path.string()); + file.write(reinterpret_cast(bytes->data()), static_cast(bytes->size())); + if (!file) throw std::runtime_error("frame replay: cannot write " + path.string()); + file.flush(); + if (!file) throw std::runtime_error("frame replay: cannot flush " + path.string()); + file.close(); + if (!file) throw std::runtime_error("frame replay: cannot close " + path.string()); + } + const bool window = frameStart != calls.end() && call > frameStart; + ++listed; + inWindow += window ? 1 : 0; + std::printf("{\"ordinal\":%llu,\"in_window\":%s,\"header\":%llu,\"code\":%llu,\"bytes\":%u,\"type\":%u,\"hash\":\"%016llx\",\"copy\":%zu}\n", static_cast(call->ordinal), window ? "true" : "false", + static_cast(call->address), static_cast(code), fields.shader_size, static_cast(fields.type), static_cast(hash), copy); + } + std::printf("{\"registrations\":%zu,\"in_window\":%zu,\"distinct_code\":%zu,\"unreadable\":%zu}\n", listed, inWindow, copies.size(), unreadable); + std::fflush(stdout); + return 0; +} + +std::string_view packetName(std::uint32_t opcode) { + switch (opcode) { + case 0x10: return "NOP"; + case 0x15: return "DISPATCH_DIRECT"; + case 0x16: return "DISPATCH_INDIRECT"; + case 0x27: return "DRAW_INDEX_2"; + case 0x2d: return "DRAW_INDEX_AUTO"; + case 0x35: return "DRAW_INDEX_OFFSET_2"; + case 0x37: return "WRITE_DATA"; + case 0x3c: return "WAIT_REG_MEM"; + case 0x3f: return "INDIRECT_BUFFER"; + case 0x46: return "EVENT_WRITE"; + case 0x49: return "RELEASE_MEM"; + case 0x50: return "DMA_DATA"; + case 0x58: return "ACQUIRE_MEM"; + case 0x69: return "SET_CONTEXT_REG"; + case 0x76: return "SET_SH_REG"; + case 0x79: return "SET_UCONFIG_REG"; + default: return {}; + } +} + +void describeSubmission(const FrameCall& call, CapturedMemory& memory) { + const auto bytes = call.size <= std::numeric_limits::max() / sizeof(std::uint32_t) ? memory.Read(call.address, call.size * sizeof(std::uint32_t)) : std::nullopt; + if (!bytes) { + std::printf(" (command buffer not readable)\n"); + return; + } + std::vector words(static_cast(call.size)); + if (!bytes->empty()) std::memcpy(words.data(), bytes->data(), bytes->size()); + std::map counts; + for (std::size_t at = 0; at < call.size;) { + const auto header = words[at]; + const auto type = header >> 30u; + const auto length = AgcDriver::Pm4::PacketWords(header); + if (length > words.size() - at) { + std::printf(" @%zu (truncated packet: %zu dwords, %zu available)\n", at, length, words.size() - at); + break; + } + if (type == 3u && !AgcDriver::Pm4::FillerPacket(header)) { + const auto opcode = (header >> 8u) & 0xffu; + const auto name = packetName(opcode); + char label[24]; + std::snprintf(label, sizeof(label), "0x%02x", opcode); + const std::string key = name.empty() ? std::string(label) : std::string(name); + const auto word = [&](std::size_t index) { return index < length ? words[at + index] : 0u; }; + if (opcode == 0x3c) { + std::printf(" @%zu WAIT_REG_MEM function %u space %u address 0x%llx reference 0x%x mask 0x%x\n", at, word(1) & 7u, (word(1) >> 4u) & 3u, + static_cast(word(2)) | (static_cast(word(3)) << 32u), word(4), word(5)); + } else if (opcode == 0x49 || opcode == 0x37 || opcode == 0x46) { + std::printf(" @%zu %s", at, key.c_str()); + for (std::size_t index = 1; index < length && index < 8; ++index) std::printf(" %08x", word(index)); + std::printf("\n"); + } + ++counts[key]; + } + at += length; + } + std::printf(" packets:"); + for (const auto& [name, count] : counts) std::printf(" %s x%zu", name.c_str(), count); + std::printf("\n"); +} + +int listSubmits(const Options& options) { + const FrameCaptureReader reader(options.directory); + CapturedMemory memory(reader); + std::map> groups; + for (const auto& chunk : reader.Chunks()) { + if (chunk.kind == FrameMemoryKind::Delta) groups[chunk.group].push_back(&chunk); + } + const auto& calls = reader.Calls(); + const auto frameStart = std::find_if(calls.begin(), calls.end(), [](const FrameCall& call) { return call.kind == FrameCallKind::FrameStart; }); + std::size_t listed = 0; + for (auto call = calls.begin(); call != calls.end() && listed < options.submits; ++call) { + if (call->kind == FrameCallKind::Memory) { + if (const auto found = groups.find(call->values.at(0)); found != groups.end()) memory.Apply(found->second); + continue; + } + if (call < frameStart) continue; + if (call->kind != FrameCallKind::Submit && call->kind != FrameCallKind::Flip && call->kind != FrameCallKind::ReserveEopFlip) continue; + ++listed; + std::printf("#%zu ordinal %llu %s thread %llu queue 0x%x serials %llu/%llu address 0x%llx dwords %llu\n", listed, static_cast(call->ordinal), std::string(AgcDriver::FrameCallName(call->kind)).c_str(), + static_cast(call->thread), call->queue, static_cast(call->completed), static_cast(call->accepted), + static_cast(call->address), static_cast(call->size)); + if (call->kind == FrameCallKind::Submit) describeSubmission(*call, memory); + } + std::fflush(stdout); + return 0; +} + +struct Pass { + std::size_t firstFrame = 0; + std::size_t frames = 0; + double seconds = 0.0; + std::uint64_t paceTimeouts = 0; + std::uint64_t synthesized = 0; +}; + +double percentile(std::vector values, double fraction) { + if (values.empty()) return 0.0; + std::sort(values.begin(), values.end()); + return values[std::min(values.size() - 1, static_cast(fraction * static_cast(values.size())))]; +} + int run(const Options& options) { const auto loadStart = Clock::now(); const FrameCaptureReader reader(options.directory); +#ifdef _WIN32 + relaunchIfHostRegionBlocks(reader); +#endif if (reader.Truncated()) std::fprintf(stderr, "[frame-replay] the capture is truncated; replaying the complete records\n"); auto skipped = restoreMemory(reader); const auto loaded = Clock::now(); @@ -728,12 +1089,48 @@ int run(const Options& options) { const auto& calls = reader.Calls(); const auto frameStart = std::find_if(calls.begin(), calls.end(), [](const FrameCall& call) { return call.kind == FrameCallKind::FrameStart; }); for (auto call = calls.begin(); call != frameStart; ++call) replay.execute(*call); - replay.recording = true; - replay.startedAt = Clock::now(); - if (frameStart != calls.end()) replay.runWindow(std::span(calls).subspan(static_cast(frameStart - calls.begin()) + 1)); - if (frameStart == calls.end()) AgcDriverWaitIdle_nid_postfix(); - replay.endedAt = Clock::now(); - if (const auto failure = replay.frames.Failure()) std::rethrow_exception(failure); + const auto journalStart = Clock::now(); + AgcDriverWaitForPipelineJournal_nid_postfix(); + const auto journalSeconds = std::chrono::duration(Clock::now() - journalStart).count(); + std::set initialOutputs; + for (const auto& [handle, output] : replay.outputs) initialOutputs.insert(handle); + std::vector passes; + for (std::size_t pass = 0; pass < options.repeat && (pass == 0 || replay.unfinished == 0); ++pass) { + if (pass > 0) { + resetMemory(reader, replay.skipped); + replay.pass = pass; + replay.recording = false; + for (auto output = replay.outputs.begin(); output != replay.outputs.end();) { + if (initialOutputs.contains(output->first)) { + ++output; + continue; + } + AgcDriverUnregisterVideoOutput_nid_postfix(output->first, output->second); + output = replay.outputs.erase(output); + } + for (const auto handle : initialOutputs) { + if (replay.outputs.contains(handle)) continue; + auto output = std::make_shared(replay.frames); + AgcDriverRegisterVideoOutput_nid_postfix(handle, output); + replay.outputs.emplace(handle, std::move(output)); + } + for (auto call = calls.begin(); call != frameStart; ++call) { + if (call->kind == FrameCallKind::ReserveEopFlip) replay.execute(*call); + } + } + Pass measured{replay.frames.Times().size(), 0, 0.0, replay.paceTimeouts.load(), replay.syntheticReleases.load()}; + replay.recording = true; + replay.startedAt = Clock::now(); + if (frameStart != calls.end()) replay.runWindow(std::span(calls).subspan(static_cast(frameStart - calls.begin()) + 1)); + else AgcDriverWaitIdle_nid_postfix(); + replay.endedAt = Clock::now(); + if (const auto failure = replay.frames.Failure()) std::rethrow_exception(failure); + measured.frames = replay.frames.Times().size() - measured.firstFrame; + measured.seconds = std::chrono::duration(replay.endedAt - replay.startedAt).count(); + measured.paceTimeouts = replay.paceTimeouts.load() - measured.paceTimeouts; + measured.synthesized = replay.syntheticReleases.load() - measured.synthesized; + passes.push_back(measured); + } FrameCheck check; if (replay.unfinished == 0) { for (const auto& dump : options.dumps) writeDump(dump); @@ -742,24 +1139,50 @@ int run(const Options& options) { std::fprintf(stderr, "[frame-replay] no dumps written: the GPU work did not finish\n"); } const auto ready = replay.frames.Times(); + const std::size_t firstMeasured = passes.size() > 1 ? 1 : 0; std::vector frameMs; - for (std::size_t index = 1; index < ready.size(); ++index) frameMs.push_back(std::chrono::duration(ready[index] - ready[index - 1]).count()); std::string frameList; - for (const auto value : frameMs) frameList += (frameList.empty() ? "" : ",") + std::to_string(static_cast(value)); - std::sort(frameMs.begin(), frameMs.end()); - const auto percentile = [&](double fraction) { return frameMs.empty() ? 0.0 : frameMs[std::min(frameMs.size() - 1, static_cast(fraction * static_cast(frameMs.size())))]; }; - const auto seconds = std::chrono::duration(replay.endedAt - replay.startedAt).count(); - std::printf("{\"frames\":%zu,\"replay_s\":%.3f,\"fps\":%.3f,\"frame_ms\":{\"p50\":%.3f,\"p90\":%.3f,\"max\":%.3f,\"each\":[%s]},\"submits\":%llu,\"delta_mib\":%.1f,\"pace_timeouts\":%llu,\"pace_wait_s\":%.3f,\"stale_registrations\":%llu,\"failed_registrations\":%llu,\"skipped_mib\":%.1f,\"threads\":%zu,\"released_waits\":%llu,\"of_them_from_earlier_writes\":%llu,\"of_them_synthesized\":%llu,\"unfinished\":%llu,\"frames_compared\":%zu,\"pixels_identical\":%.6f,\"max_channel_diff\":%d,\"load_s\":%.3f}\n", - ready.size(), seconds, seconds > 0 ? static_cast(ready.size()) / seconds : 0.0, percentile(0.5), percentile(0.9), frameMs.empty() ? 0.0 : frameMs.back(), frameList.c_str(), + std::string passList; + double seconds = 0.0; + std::size_t frames = 0; + for (std::size_t index = 0; index < passes.size(); ++index) { + const auto& measured = passes[index]; + std::vector passMs; + for (auto frame = measured.firstFrame + 1; frame < measured.firstFrame + measured.frames; ++frame) passMs.push_back(std::chrono::duration(ready[frame] - ready[frame - 1]).count()); + char entry[192]; + std::snprintf(entry, sizeof(entry), "%s{\"replay_s\":%.3f,\"frames\":%zu,\"p50\":%.3f,\"pace_timeouts\":%llu,\"synthesized\":%llu}", passList.empty() ? "" : ",", measured.seconds, measured.frames, + percentile(passMs, 0.5), static_cast(measured.paceTimeouts), static_cast(measured.synthesized)); + passList += entry; + if (index < firstMeasured) continue; + for (const auto value : passMs) frameList += (frameList.empty() ? "" : ",") + std::to_string(static_cast(value)); + frameMs.insert(frameMs.end(), passMs.begin(), passMs.end()); + seconds += measured.seconds; + frames += measured.frames; + } + const auto slowest = frameMs.empty() ? 0.0 : *std::max_element(frameMs.begin(), frameMs.end()); + std::printf("{\"frames\":%zu,\"replay_s\":%.3f,\"fps\":%.3f,\"frame_ms\":{\"p50\":%.3f,\"p90\":%.3f,\"max\":%.3f,\"each\":[%s]},\"passes\":[%s],\"kept_reservations\":%llu,\"submits\":%llu,\"delta_mib\":%.1f,\"pace_timeouts\":%llu,\"pace_wait_s\":%.3f,\"stale_registrations\":%llu,\"failed_registrations\":%llu,\"skipped_mib\":%.1f,\"threads\":%zu,\"released_waits\":%llu,\"of_them_from_earlier_writes\":%llu,\"of_them_synthesized\":%llu,\"unfinished\":%llu,\"frames_compared\":%zu,\"pixels_identical\":%.6f,\"max_channel_diff\":%d,\"load_s\":%.3f,\"journal_s\":%.3f}\n", + frames, seconds, seconds > 0 ? static_cast(frames) / seconds : 0.0, percentile(frameMs, 0.5), percentile(frameMs, 0.9), slowest, frameList.c_str(), passList.c_str(), + static_cast(replay.keptReservations.load()), static_cast(replay.submits.load()), static_cast(replay.deltaBytes.load()) / (1024.0 * 1024.0), static_cast(replay.paceTimeouts.load()), replay.paceWaitedSeconds(), static_cast(replay.staleRegistrations.load()), static_cast(replay.failedRegistrations.load()), static_cast(replay.skipped.bytes) / (1024.0 * 1024.0), replay.threads, static_cast(replay.earlyReleases.load()), static_cast(replay.lateReleases.load()), static_cast(replay.syntheticReleases.load()), - static_cast(replay.unfinished), check.compared, check.identical, check.maxDifference, std::chrono::duration(loaded - loadStart).count()); + static_cast(replay.unfinished), check.compared, check.identical, check.maxDifference, std::chrono::duration(loaded - loadStart).count(), journalSeconds); std::fflush(stdout); return replay.unfinished == 0 ? 0 : 3; } +void configureEnvironment(const Options& options) { +#ifdef _WIN32 + _putenv_s("APS5_FRAME_CAPTURE", ""); + _putenv_s("APS5_HEADLESS_PIPELINE_CACHE", options.coldPipelines ? "" : "1"); +#else + unsetenv("APS5_FRAME_CAPTURE"); + if (options.coldPipelines) unsetenv("APS5_HEADLESS_PIPELINE_CACHE"); + else setenv("APS5_HEADLESS_PIPELINE_CACHE", "1", 1); +#endif +} + } int main(int argc, char** argv) { @@ -771,27 +1194,41 @@ int main(int argc, char** argv) { else if (argument == "--no-synthesize") options.synthesize = false; else if (argument == "--release-after-ms" && index + 1 < argc) options.releaseAfter = std::chrono::milliseconds(std::strtoll(argv[++index], nullptr, 10)); else if (argument == "--strict") options.strict = true; + else if (argument == "--repeat" && index + 1 < argc) options.repeat = std::max(1, std::strtoull(argv[++index], nullptr, 10)); + else if (argument == "--shaders") options.shaders = true; + else if (argument == "--shader-code" && index + 1 < argc) { + options.shaders = true; + options.shaderCode = argv[++index]; + } + else if (argument == "--submits" && index + 1 < argc) { + const std::string_view value = argv[++index]; + const auto [end, error] = std::from_chars(value.data(), value.data() + value.size(), options.submits); + if (error != std::errc{} || end != value.data() + value.size() || options.submits == 0) return usage(); + } else if (argument == "--trace-serials") options.traceSerials = true; + else if (argument == "--cold-pipelines") options.coldPipelines = true; else if (argument == "--dump" && index + 1 < argc) options.dumps.push_back(parseDump(argv[++index])); else if (argument == "--frames" && index + 1 < argc) options.frames = argv[++index]; else if (!argument.starts_with("-") && options.directory.empty()) options.directory = argument; else return usage(); } if (options.directory.empty()) return usage(); -#ifdef _WIN32 - _putenv_s("APS5_FRAME_CAPTURE", ""); -#else - unsetenv("APS5_FRAME_CAPTURE"); -#endif + configureEnvironment(options); int code = 2; + const bool listing = options.shaders || options.submits != 0; try { - code = run(options); - if (code == 0) { - AgcDriverShutdown_nid_postfix(); - return 0; - } + code = options.shaders ? listShaders(options) : options.submits != 0 ? listSubmits(options) : run(options); } catch (const std::exception& error) { std::fprintf(stderr, "agc_frame_replay: %s\n", error.what()); } + if (!listing) { + try { + AgcDriverShutdown_nid_postfix(); + } catch (const std::exception& error) { + std::fprintf(stderr, "agc_frame_replay: %s\n", error.what()); + if (code == 0) code = 2; + } + if (code == 0) return 0; + } std::fflush(stdout); std::fflush(stderr); std::_Exit(code); diff --git a/core/libs/prx/libSceAmpr/Export.cpp b/core/libs/prx/libSceAmpr/Export.cpp index 08c7fb26cb..1e07eb8ebb 100644 --- a/core/libs/prx/libSceAmpr/Export.cpp +++ b/core/libs/prx/libSceAmpr/Export.cpp @@ -991,8 +991,8 @@ double APS5_VABI sceAmprSinh(double x, double y) { } APS5_EXPORT("-vXEQdRADLI", sceAmprStrtokS); -char* APS5_VABI sceAmprStrtokS(char* str, const char* delim, char** context) { - return SonyStrtokS(str, delim, context); +char* APS5_VABI sceAmprStrtokS(char* str, std::size_t* remaining, const char* delimiters, char** context) { + return SonyStrtokS(str, remaining, delimiters, context); } APS5_EXPORT("2UFh+YKfuzk", sceAmprCtimeS); diff --git a/core/libs/prx/libSceAmpr/include/Api.hpp b/core/libs/prx/libSceAmpr/include/Api.hpp index 78ea95adf3..fd35f1af50 100644 --- a/core/libs/prx/libSceAmpr/include/Api.hpp +++ b/core/libs/prx/libSceAmpr/include/Api.hpp @@ -145,7 +145,7 @@ void APS5_VABI _Lock_shared_ptr_spin_lock_nid_postfix(void); SonyLconv* APS5_VABI sceAmprLocaleconv(void); unsigned __int128 APS5_VABI sceAmprUdivti3(unsigned __int128 dividend, unsigned __int128 divisor); double APS5_VABI sceAmprSinh(double x, double y); -char* APS5_VABI sceAmprStrtokS(char* str, const char* delim, char** context); +char* APS5_VABI sceAmprStrtokS(char* str, std::size_t* remaining, const char* delimiters, char** context); int APS5_VABI sceAmprCtimeS(char* buffer, std::size_t size, const std::int64_t* time); int APS5_VABI sceAmprWcscpyS(std::uint16_t* dest, std::size_t size, const std::uint16_t* src); void* APS5_VABI sceAmprSetConstraintHandlerS(void* handler); diff --git a/core/libs/prx/libSceAppContent/Export.cpp b/core/libs/prx/libSceAppContent/Export.cpp index 7f4947a6c1..d73037f562 100644 --- a/core/libs/prx/libSceAppContent/Export.cpp +++ b/core/libs/prx/libSceAppContent/Export.cpp @@ -22,6 +22,7 @@ static constexpr int SCE_APP_CONTENT_ERROR_BUSY = static_cast(0x80D90003); static constexpr int SCE_APP_CONTENT_ERROR_NOT_MOUNTED = static_cast(0x80D90004); static constexpr int SCE_APP_CONTENT_ERROR_NOT_FOUND = static_cast(0x80D90005); static constexpr int SCE_APP_CONTENT_ERROR_DRM_NO_ENTITLEMENT = static_cast(0x80D90007); +static constexpr int SCE_APP_CONTENT_ERROR_GET_STORE_COUNTRY = static_cast(0x80D91000); static constexpr uint32_t APPPARAM_ID_SKU_FLAG = 0; static constexpr int32_t SKU_FLAG_FULL = 3; @@ -62,6 +63,7 @@ bool IsTemporaryMountPoint(const AppContentMountPoint* mountPoint) { return std::strncmp(mountPoint->data, TEMPORARY_MOUNT_POINT, sizeof(mountPoint->data)) == 0; } +std::atomic download1QuotaBytes{0}; std::atomic download2QuotaBytes{0}; bool IsDownloadMountPoint(const AppContentMountPoint* mountPoint) { @@ -173,10 +175,10 @@ int APS5_VABI sceAppContentDownloadDataGetAvailableSpaceKb(const AppContentMount const std::uint64_t quotaMiB = GetAppDownloadDataSizeMiB_nid_postfix(); if (quotaMiB > std::numeric_limits::max() / 1024u) throw std::overflow_error("AppContent download quota exceeds KiB range"); quotaKb = quotaMiB * 1024u; + } else if (std::strncmp(mountPoint->data, "/download1", sizeof(mountPoint->data)) == 0) { + quotaKb = download1QuotaBytes.load() / 1024u; } else if (std::strncmp(mountPoint->data, "/download2", sizeof(mountPoint->data)) == 0) { quotaKb = download2QuotaBytes.load() / 1024u; - } else { - NotImplemented_nid_no_patch(__func__); } if (quotaKb == 0) { *availableSpaceKb = 0; @@ -322,8 +324,13 @@ int APS5_VABI sceAppContentDownload0Shrink(void) { return 0; } -int APS5_VABI sceAppContentDownload1Expand(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceAppContentDownload1Expand(size_t size) { + if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED; + if (size == 0) return SCE_APP_CONTENT_ERROR_PARAMETER; + const auto directory = ResolvePath_nid_no_patch("/download1"); + if (std::filesystem::create_directories(directory)) RecordWrittenPath_nid_no_patch(directory); + size_t current = download1QuotaBytes.load(); + while (size > current && !download1QuotaBytes.compare_exchange_weak(current, size)) {} return 0; } @@ -367,9 +374,12 @@ int APS5_VABI sceAppContentGetAddcontInfoListByIroTag(uint32_t iroTag, AppConten return 0; } -int APS5_VABI sceAppContentGetDownloadedStoreCountry(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAppContentGetDownloadedStoreCountry(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, void* storeCountry) { + if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED; + (void)serviceLabel; + if (!entitlementLabel || !storeCountry) return SCE_APP_CONTENT_ERROR_PARAMETER; + if (!validEntitlementLabel(*entitlementLabel, 0x01500000)) return SCE_APP_CONTENT_ERROR_PARAMETER; + return SCE_APP_CONTENT_ERROR_GET_STORE_COUNTRY; } int APS5_VABI sceAppContentGetEntitlementKey(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, AppContentEntitlementKey* key) { diff --git a/core/libs/prx/libSceAppContent/include/Api.hpp b/core/libs/prx/libSceAppContent/include/Api.hpp index e6c6f7021a..e588bb7f3e 100644 --- a/core/libs/prx/libSceAppContent/include/Api.hpp +++ b/core/libs/prx/libSceAppContent/include/Api.hpp @@ -36,14 +36,14 @@ int APS5_VABI sceAppContentCheckBundleLicenseOnDisc(uint32_t serviceLabel, const int APS5_VABI sceAppContentAppParamGetString(void); int APS5_VABI sceAppContentDownload0Expand(void); int APS5_VABI sceAppContentDownload0Shrink(void); -int APS5_VABI sceAppContentDownload1Expand(void); +int APS5_VABI sceAppContentDownload1Expand(size_t size); int APS5_VABI sceAppContentDownload1Shrink(void); int APS5_VABI sceAppContentDownload2Expand(size_t size); int APS5_VABI sceAppContentDownload2Shrink(void); int APS5_VABI sceAppContentUnknown_SWVxsi_ZBlw(void); int APS5_VABI sceAppContentGetAddcontInfoByEntitlementId(const char* entitlementId, AppContentAddcontInfo* info); int APS5_VABI sceAppContentGetAddcontInfoListByIroTag(uint32_t iroTag, AppContentAddcontInfo* list, uint32_t listNum, uint32_t* hitNum); -int APS5_VABI sceAppContentGetDownloadedStoreCountry(void); +int APS5_VABI sceAppContentGetDownloadedStoreCountry(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, void* storeCountry); int APS5_VABI sceAppContentGetEntitlementKey(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, AppContentEntitlementKey* key); int APS5_VABI sceAppContentGetGameTrialsFlag(uint32_t* gameTrialsFlag); int APS5_VABI sceAppContentGetPftFlag(uint32_t* pftFlag); diff --git a/core/libs/prx/libSceAudioIn/Export.cpp b/core/libs/prx/libSceAudioIn/Export.cpp index 9e9721f6eb..6a902bac4a 100644 --- a/core/libs/prx/libSceAudioIn/Export.cpp +++ b/core/libs/prx/libSceAudioIn/Export.cpp @@ -2,6 +2,7 @@ #include "prx/libSceAudioIn/AudioInCapture.hpp" #include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/PreciseWait.hpp" @@ -199,7 +200,12 @@ int32_t APS5_VABI sceAudioInIsPadEmbeddedMic(int32_t handle) { int32_t APS5_VABI sceAudioInGetGain(int32_t handle, float* gain) { if (!gain) return AUDIO_IN_ERROR_INVALID_POINTER; - *gain = 1.0f; + constexpr float defaultGain = 1.0f; + const int savedErrno = errno; + if (!GuestCopyOut_nid_no_patch(gain, &defaultGain, sizeof(defaultGain))) { + errno = savedErrno; + return AUDIO_IN_ERROR_INVALID_POINTER; + } std::lock_guard lock(g_mutex); return find(handle) ? 0 : AUDIO_IN_ERROR_INVALID_HANDLE; } diff --git a/core/libs/prx/libSceAudioOut/CMakeLists.txt b/core/libs/prx/libSceAudioOut/CMakeLists.txt index 48000b0f1d..3aeb8b1274 100644 --- a/core/libs/prx/libSceAudioOut/CMakeLists.txt +++ b/core/libs/prx/libSceAudioOut/CMakeLists.txt @@ -101,7 +101,7 @@ if(BUILD_TESTING) target_include_directories(audio_out_buffer_size_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR}) target_link_libraries(audio_out_buffer_size_tests PRIVATE libc libkernel libScePad) configure_windows_unwind(audio_out_buffer_size_tests) - foreach(bufferSizeMode IN ITEMS pass_through wrap mix threshold virtual_overflow invalid_format) + foreach(bufferSizeMode IN ITEMS pass_through wrap mix threshold virtual_overflow invalid_format invalid_sample_rate) set(bufferSizeTest "audio_out_buffer_size_${bufferSizeMode}") add_test(NAME ${bufferSizeTest} COMMAND audio_out_buffer_size_tests ${bufferSizeMode}) set_tests_properties(${bufferSizeTest} PROPERTIES TIMEOUT 5) diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutBufferSize.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutBufferSize.cpp index f0bc493e73..1ad484415d 100644 --- a/core/libs/prx/libSceAudioOut/tests/AudioOutBufferSize.cpp +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutBufferSize.cpp @@ -85,6 +85,12 @@ void RunInvalidFormat() { Require(sceAudioOutOpen(0, 0, 0, 512, 48000, 0xFF) == expectedError, "format 0xFF did not return SCE_AUDIO_OUT_ERROR_INVALID_FORMAT"); Require(opened == 0, "an invalid format opened an audio device"); } + +void RunInvalidSampleRate() { + constexpr int expectedError = -2144993272; + Require(sceAudioOutOpen(0, 0, 0, 512, 0, 0) == expectedError, "sample rate 0 did not return SCE_AUDIO_OUT_ERROR_INVALID_PARAM"); + Require(opened == 0, "a zero sample rate opened an audio device"); +} } int InitializeSdlAudio_nid_no_patch() { return 0; } @@ -125,6 +131,7 @@ int main(int argc, char** argv) { else if (mode == "threshold") RunOverflow(0x40000000u, 0, sizeof(std::int16_t)); else if (mode == "virtual_overflow") RunVirtualOverflow(); else if (mode == "invalid_format") RunInvalidFormat(); + else if (mode == "invalid_sample_rate") RunInvalidSampleRate(); else Require(false, "invalid buffer size mode"); std::puts("audio out buffer size tests passed"); return 0; diff --git a/core/libs/prx/libSceAvSetting/Export.cpp b/core/libs/prx/libSceAvSetting/Export.cpp index 7c8e48f6de..f775d99c8c 100644 --- a/core/libs/prx/libSceAvSetting/Export.cpp +++ b/core/libs/prx/libSceAvSetting/Export.cpp @@ -609,14 +609,18 @@ int APS5_VABI sceAvSettingSet2dVrMode() { return 0; } -int APS5_VABI sceAvSettingSetAudioOutModeAny_() { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI sceAvSettingSetAudioOutModeAny_(void* out, std::int32_t value) { + if (out == nullptr) return; + if (value < 0) NotImplemented_nid_no_patch(__func__); + std::memset(out, 0xff, static_cast(value)); + *static_cast(out) = value; } -int APS5_VABI sceAvSettingSetAudioOutModeInvalid_() { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI sceAvSettingSetAudioOutModeInvalid_(void* out, std::int32_t value) { + if (out == nullptr) return; + if (value < 0) NotImplemented_nid_no_patch(__func__); + std::memset(out, 0, static_cast(value)); + *static_cast(out) = value; } int APS5_VABI sceAvSettingSetAvOutputMode() { @@ -648,19 +652,22 @@ int APS5_VABI sceAvSettingSetHdmiGamutMetadata_() { return 0; } -int APS5_VABI sceAvSettingSetVideoOutModeAny_() { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI sceAvSettingSetVideoOutModeAny_(void* out, std::int32_t value) { + if (out == nullptr) return; + if (value < 0) NotImplemented_nid_no_patch(__func__); + std::memset(out, 0xff, static_cast(value)); + *static_cast(out) = value; } -int APS5_VABI sceAvSettingSetVideoOutModeInvalid_() { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI sceAvSettingSetVideoOutModeInvalid_(void* out, std::int32_t value) { + if (out == nullptr) return; + if (value < 0) NotImplemented_nid_no_patch(__func__); + std::memset(out, 0, static_cast(value)); + *static_cast(out) = value; } -int APS5_VABI sceAvSettingSetVideoOutSource() { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI sceAvSettingSetVideoOutSource(std::int32_t* out, std::int32_t source) { + if (out != nullptr) *out = source; } int APS5_VABI sceAvSettingSetVideoOutputColorEffect() { diff --git a/core/libs/prx/libSceAvSetting/include/Api.hpp b/core/libs/prx/libSceAvSetting/include/Api.hpp index 8c830608ed..37a6915ee2 100644 --- a/core/libs/prx/libSceAvSetting/include/Api.hpp +++ b/core/libs/prx/libSceAvSetting/include/Api.hpp @@ -118,17 +118,17 @@ int APS5_VABI sceAvSettingNotifyVideoOutMode(void); int APS5_VABI sceAvSettingRemoveCallbacks(void); int APS5_VABI sceAvSettingRemoveProcessOutputMode(void); int APS5_VABI sceAvSettingSet2dVrMode(void); -int APS5_VABI sceAvSettingSetAudioOutModeAny_(void); -int APS5_VABI sceAvSettingSetAudioOutModeInvalid_(void); +void APS5_VABI sceAvSettingSetAudioOutModeAny_(void* out, std::int32_t value); +void APS5_VABI sceAvSettingSetAudioOutModeInvalid_(void* out, std::int32_t value); int APS5_VABI sceAvSettingSetAvOutputMode(void); int APS5_VABI sceAvSettingSetDispclk(void); int APS5_VABI sceAvSettingSetDisplayBlackLevel(void); int APS5_VABI sceAvSettingSetHdcpMode(void); int APS5_VABI sceAvSettingSetHdcpStatus(void); int APS5_VABI sceAvSettingSetHdmiGamutMetadata_(void); -int APS5_VABI sceAvSettingSetVideoOutModeAny_(void); -int APS5_VABI sceAvSettingSetVideoOutModeInvalid_(void); -int APS5_VABI sceAvSettingSetVideoOutSource(void); +void APS5_VABI sceAvSettingSetVideoOutModeAny_(void* out, std::int32_t value); +void APS5_VABI sceAvSettingSetVideoOutModeInvalid_(void* out, std::int32_t value); +void APS5_VABI sceAvSettingSetVideoOutSource(std::int32_t* out, std::int32_t source); int APS5_VABI sceAvSettingSetVideoOutputColorEffect(void); int APS5_VABI sceAvSettingSimulateProcessOutputModeArbitration_(void); int APS5_VABI sceAvSettingTerm(void); diff --git a/core/libs/prx/libSceBemp2sys/Export.cpp b/core/libs/prx/libSceBemp2sys/Export.cpp index 072a28cede..6efe86a036 100644 --- a/core/libs/prx/libSceBemp2sys/Export.cpp +++ b/core/libs/prx/libSceBemp2sys/Export.cpp @@ -6,27 +6,37 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +void* LogFunction = nullptr; +int LogLevel = 0; + +struct MemoryManager { + void* allocate = nullptr; + void* deallocate = nullptr; + void* reallocate = nullptr; +}; +MemoryManager memoryManager; + +} + extern "C" { APS5_DUMMY_FUN int APS5_VABI bemp2sys_logprintf_getlevel_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return LogLevel; } -int APS5_VABI bemp2sys_logprintf_setfunc_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI bemp2sys_logprintf_setfunc_nid_postfix(void* function) { + LogFunction = function; } -int APS5_VABI bemp2sys_logprintf_setlevel_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI bemp2sys_logprintf_setlevel_nid_postfix(int level) { + if (static_cast(level) <= 3u) LogLevel = level; } -int APS5_VABI bemp2sys_memmanager_set_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI bemp2sys_memmanager_set_nid_postfix(void* allocate, void* deallocate, void* reallocate) { + memoryManager = {allocate, deallocate, reallocate}; } int APS5_VABI bemp2sys_packetizer_create_nid_postfix(void) { diff --git a/core/libs/prx/libSceBemp2sys/include/Api.hpp b/core/libs/prx/libSceBemp2sys/include/Api.hpp index 82e55243e5..db3b28669c 100644 --- a/core/libs/prx/libSceBemp2sys/include/Api.hpp +++ b/core/libs/prx/libSceBemp2sys/include/Api.hpp @@ -5,9 +5,9 @@ extern "C" { int APS5_VABI bemp2sys_logprintf_getlevel_nid_postfix(void); -int APS5_VABI bemp2sys_logprintf_setfunc_nid_postfix(void); -int APS5_VABI bemp2sys_logprintf_setlevel_nid_postfix(void); -int APS5_VABI bemp2sys_memmanager_set_nid_postfix(void); +void APS5_VABI bemp2sys_logprintf_setfunc_nid_postfix(void* function); +void APS5_VABI bemp2sys_logprintf_setlevel_nid_postfix(int level); +void APS5_VABI bemp2sys_memmanager_set_nid_postfix(void* allocate, void* deallocate, void* reallocate); int APS5_VABI bemp2sys_packetizer_create_nid_postfix(void); int APS5_VABI bemp2sys_packetizer_destroy_nid_postfix(void); int APS5_VABI bemp2sys_packetizer_init_nid_postfix(void); diff --git a/core/libs/prx/libSceCdlgG2P/Export.cpp b/core/libs/prx/libSceCdlgG2P/Export.cpp index a6f7113420..dba3faa623 100644 --- a/core/libs/prx/libSceCdlgG2P/Export.cpp +++ b/core/libs/prx/libSceCdlgG2P/Export.cpp @@ -2,6 +2,7 @@ #include "prx/libSceUserService/UserService.hpp" #include "SceTypes.hpp" #include +#include #include #include #include @@ -60,7 +61,11 @@ int APS5_VABI sceG2PDialogGetResult(CommonDialogResultPrefix* result) { const int status = g_status.load(); if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; if (status != COMMON_DIALOG_STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED; - result->result = COMMON_DIALOG_RESULT_USER_CANCELED; + const std::int32_t cancelResult = COMMON_DIALOG_RESULT_USER_CANCELED; + const int savedErrno = errno; + const bool copied = GuestCopyOut_nid_no_patch(&result->result, &cancelResult, sizeof(cancelResult)); + errno = savedErrno; + if (!copied) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; return 0; } diff --git a/core/libs/prx/libSceCesCs/CMakeLists.txt b/core/libs/prx/libSceCesCs/CMakeLists.txt index 72c9c58804..6c52e2e66f 100644 --- a/core/libs/prx/libSceCesCs/CMakeLists.txt +++ b/core/libs/prx/libSceCesCs/CMakeLists.txt @@ -42,8 +42,10 @@ if(BUILD_TESTING) endif() if(BUILD_TESTING) - foreach(variant IN ITEMS unicode unicode_module japanese japanese_module wide wide_module chinese chinese_module mbc mbc_module jis2004 jis2004_module iso646 iso646_module iso2022 iso2022_module setters setters_module) - if(variant MATCHES "^jis2004") + foreach(variant IN ITEMS unicode unicode_module japanese japanese_module wide wide_module chinese chinese_module mbc mbc_module jis2004 jis2004_module iso646 iso646_module iso2022 iso2022_module setters setters_module jiscs jiscs_module) + if(variant MATCHES "^jiscs") + add_test_executable(guest_ces_${variant}_tests ${LIBS_INCLUDE_DIR}/tests/GuestCesJiscs.cpp) + elseif(variant MATCHES "^jis2004") add_test_executable(guest_ces_${variant}_tests ${LIBS_INCLUDE_DIR}/tests/GuestCesJis2004.cpp) elseif(variant MATCHES "^setters") add_test_executable(guest_ces_${variant}_tests ${LIBS_INCLUDE_DIR}/tests/GuestCesProfileSetters.cpp) @@ -72,7 +74,7 @@ if(BUILD_TESTING) add_test(NAME guest_ces_${variant} COMMAND guest_ces_${variant}_tests) endforeach() if(WIN32) - set_tests_properties(guest_ces_unicode guest_ces_unicode_module guest_ces_japanese guest_ces_japanese_module guest_ces_wide guest_ces_wide_module guest_ces_chinese guest_ces_chinese_module guest_ces_mbc guest_ces_mbc_module guest_ces_jis2004 guest_ces_jis2004_module guest_ces_iso646 guest_ces_iso646_module guest_ces_iso2022 guest_ces_iso2022_module guest_ces_setters guest_ces_setters_module PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(guest_ces_unicode guest_ces_unicode_module guest_ces_japanese guest_ces_japanese_module guest_ces_wide guest_ces_wide_module guest_ces_chinese guest_ces_chinese_module guest_ces_mbc guest_ces_mbc_module guest_ces_jis2004 guest_ces_jis2004_module guest_ces_iso646 guest_ces_iso646_module guest_ces_iso2022 guest_ces_iso2022_module guest_ces_setters guest_ces_setters_module guest_ces_jiscs guest_ces_jiscs_module PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() endif() diff --git a/core/libs/prx/libSceCesCs/Export.cpp b/core/libs/prx/libSceCesCs/Export.cpp index 29ab54b5d7..588e381254 100644 --- a/core/libs/prx/libSceCesCs/Export.cpp +++ b/core/libs/prx/libSceCesCs/Export.cpp @@ -3,11 +3,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -1777,6 +1779,81 @@ int utf32ToSbc(const uint32_t* utf32, const uint8_t* profile, uint8_t* sbc) { return scalarToSbc(loadUnit(utf32), profile, sbc); } +std::string hexCode(uint32_t value) { + char digits[8]; + const auto end = std::to_chars(digits, digits + sizeof digits, value, 16).ptr; + return "0x" + std::string(digits, end); +} + +constexpr uint32_t JISCS_PLANE1 = 0x03010000; +constexpr uint32_t JISCS_X0213_PLANE2 = 0x03020000; +constexpr uint32_t JISCS_X0212 = 0x04000000; + +bool isJiscsProfile(const void* profile) { + return isSjis(profile) || isEucJp(profile) || isJis2004(profile); +} + +bool isJisGraphic(uint32_t byte) { + return byte >= 0x21 && byte <= 0x7E; +} + +bool isMeasuredJiscs(uint32_t jiscs) { + return jiscs <= 0xFF || (jiscs >> 16 <= 2 && isJisGraphic(jiscs >> 8 & 0xFF) && isJisGraphic(jiscs & 0xFF)); +} + +JisStrict jisX0208Strictness(const void* profile) { + return isSjisX0208(profile) ? JisStrict::Sjis : isEucJpX0208(profile) ? JisStrict::Euc : JisStrict::None; +} + +uint32_t jiscsCell(uint32_t base, uint32_t cell) { + return base | (cell / 94 + 1) << 8 | (cell % 94 + 1); +} + +int jiscsToUnicode(const void* profile, uint32_t jiscs, uint32_t& code) { + if (jiscs <= 0xFF) { + const auto byte = static_cast(jiscs); + if (byte <= 0x9F) code = isSjisX0208(profile) ? sjisX0208Single(byte) : byte; + else if (byte >= 0xA1 && byte <= 0xDF && hasSs2(profile)) code = 0xFF61 + (byte - 0xA1); + else return CES_ERROR_UNASSIGNED_CODE; + return 0; + } + if (jiscs >> 16 != 0) return CES_ERROR_ILLEGAL_CODE; + const uint32_t index = jisIndex(static_cast(jiscs)); + code = isJis2004(profile) ? JisX0213::PLANE1[index] : jisX0208ToUnicode(jisX0208Strictness(profile), index); + return code != 0 ? 0 : CES_ERROR_UNASSIGNED_CODE; +} + +int unicodeToJiscs(const void* profile, uint32_t code, uint32_t& jiscs) { + const bool strictSjis = isSjisX0208(profile); + if (strictSjis && (code == 0x00A5 || code == 0x203E)) { + jiscs = code == 0x00A5 ? 0x5C : 0x7E; + return 0; + } + if (code <= 0x9F && !(strictSjis && (code == 0x5C || code == 0x7E))) { + jiscs = code; + return 0; + } + if (code >= 0xFF61 && code <= 0xFF9F) { + if (!hasSs2(profile)) return CES_ERROR_UNASSIGNED_CODE; + jiscs = 0xA1 + (code - 0xFF61); + return 0; + } + if (isJis2004(profile)) { + const auto& reverse = jis2004Reverse(); + const auto found = std::lower_bound(reverse.begin(), reverse.end(), code, [](const Jis2004Cell& entry, uint32_t value) { return entry.code < value; }); + if (found == reverse.end() || found->code != code || (found->plane == JIS_X0212_PLANE && isSjis2004(profile))) return CES_ERROR_UNASSIGNED_CODE; + jiscs = jiscsCell(found->plane == JIS_X0212_PLANE ? JISCS_X0212 : found->plane == 2 ? JISCS_X0213_PLANE2 : JISCS_PLANE1, found->cell); + return 0; + } + const JisStrict strict = jisX0208Strictness(profile); + if (strict == JisStrict::None) code = jisAlias(code); + const auto& reverse = jisReverse(strict); + const auto found = std::lower_bound(reverse.begin(), reverse.end(), static_cast(code), [](const JisCell& entry, uint16_t value) { return entry.code < value; }); + if (found == reverse.end() || found->code != code || (found->supplementary && !hasSs3(profile))) return CES_ERROR_UNASSIGNED_CODE; + jiscs = jiscsCell(found->supplementary ? JISCS_X0212 : JISCS_PLANE1, found->cell); + return 0; +} + } extern "C" { @@ -2916,6 +2993,34 @@ void* APS5_VABI sceCesUcsProfileInitSJis2004X0213(void* sheet) { return sheet; } +int APS5_VABI sceCesJiscsToUcs(const void* profile, uint32_t jiscs, uint32_t* ucs) { + if (!isMeasuredJiscs(jiscs)) throw std::runtime_error("sceCesJiscsToUcs: code " + hexCode(jiscs) + " is neither a single byte nor a row and cell 0x21-0x7E of planes 0-2; its form is not implemented"); + if (ucs) *ucs = 0; + if (!profile) return CES_ERROR_INVALID_PROFILE; + if (!isJiscsProfile(profile)) throw std::runtime_error("sceCesJiscsToUcs: a profile other than the eight JIS profiles is not implemented"); + uint32_t code = 0; + const int result = jiscsToUnicode(profile, jiscs, code); + if (result != 0) return result; + if (!ucs) return CES_ERROR_INVALID_DST_BUFFER; + *ucs = code; + return 0; +} + +int APS5_VABI sceCesUcsToJiscs(uint32_t ucs, const void* profile, uint32_t* jiscs) { + if (jiscs) *jiscs = 0; + if (!isScalar(ucs)) return CES_ERROR_ILLEGAL_CODE; + if (ucs > 0xFFFF && ucs >> 16 != 2) throw std::runtime_error("sceCesUcsToJiscs: code point " + hexCode(ucs) + " outside the BMP and plane 2 is not implemented"); + if (!profile) return CES_ERROR_INVALID_PROFILE; + if (!isJiscsProfile(profile)) throw std::runtime_error("sceCesUcsToJiscs: a profile other than the eight JIS profiles is not implemented"); + if (ucs > 0xFFFF && !isJis2004(profile)) throw std::runtime_error("sceCesUcsToJiscs: plane 2 with a JIS X 0208 profile is not implemented"); + uint32_t code = 0; + const int result = unicodeToJiscs(profile, ucs, code); + if (result != 0) return result; + if (!jiscs) return CES_ERROR_INVALID_DST_BUFFER; + *jiscs = code; + return 0; +} + void* APS5_VABI sceCesUcsProfileInitIso2022JpRfc1468(void* sheet) { if (!sheet) return nullptr; std::memcpy(sheet, ISO2022_RFC1468_TAG, sizeof(ISO2022_RFC1468_TAG)); diff --git a/core/libs/prx/libSceCesCs/PendingExports.cpp b/core/libs/prx/libSceCesCs/PendingExports.cpp index 511cc0e7ce..70757c7730 100644 --- a/core/libs/prx/libSceCesCs/PendingExports.cpp +++ b/core/libs/prx/libSceCesCs/PendingExports.cpp @@ -22,11 +22,6 @@ int APS5_VABI sceCesIso2022JpUcsProfileSetSbcs(void) { return 0; } -int APS5_VABI sceCesJiscsToUcs(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesRefersUcsProfileCp1253(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -42,9 +37,4 @@ int APS5_VABI sceCesRefersUcsProfileCp1257(void) { return 0; } -int APS5_VABI sceCesUcsToJiscs(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - } diff --git a/core/libs/prx/libSceCesCs/include/Api.hpp b/core/libs/prx/libSceCesCs/include/Api.hpp index b65377b410..31324c9a0a 100644 --- a/core/libs/prx/libSceCesCs/include/Api.hpp +++ b/core/libs/prx/libSceCesCs/include/Api.hpp @@ -228,7 +228,7 @@ int APS5_VABI sceCesIso2022StrToUtf8Str(void* context, const uint8_t* iso, uint3 int APS5_VABI sceCesIso2022UcsContextInit(void* context, const void* profile); int APS5_VABI sceCesIso2022UcsContextInitCopy(void* context, const void* source); int APS5_VABI sceCesJisGetLevel(uint8_t plane, uint8_t row, uint8_t cell, uint32_t* level); -int APS5_VABI sceCesJiscsToUcs(void); +int APS5_VABI sceCesJiscsToUcs(const void* profile, uint32_t jiscs, uint32_t* ucs); int APS5_VABI sceCesMbcsUcsSetMbcsReplacementCharCode(void* context, uint32_t code); int APS5_VABI sceCesMbcsUcsSetMbcsReplacementCharUCode(void* context, uint32_t ucode); const uint8_t* APS5_VABI sceCesRefersUcsProfileAscii(void); @@ -263,7 +263,7 @@ void* APS5_VABI sceCesUcsProfileInitIso2022JpCp50221(void* sheet); void* APS5_VABI sceCesUcsProfileInitIso2022JpCp50222(void* sheet); void* APS5_VABI sceCesUcsProfileInitIso2022JpRfc1468(void* sheet); void* APS5_VABI sceCesUcsProfileInitSJis2004X0213(void* sheet); -int APS5_VABI sceCesUcsToJiscs(void); +int APS5_VABI sceCesUcsToJiscs(uint32_t ucs, const void* profile, uint32_t* jiscs); int APS5_VABI sceCesUhcUcsProfileSetSbcs(void* p, const void* sbcs); int APS5_VABI sceCesUtf16StrGetIso2022Len(void* context, const uint16_t* utf16, uint32_t utf16max, uint32_t* utf16Len, uint32_t* isoLen); int APS5_VABI sceCesUtf16StrToIso2022Str(void* context, const uint16_t* utf16, uint32_t utf16max, uint32_t* utf16Len, uint8_t* iso, uint32_t isoMax, uint32_t* isoLen); diff --git a/core/libs/prx/libSceCompanionHttpd/Export.cpp b/core/libs/prx/libSceCompanionHttpd/Export.cpp index d999be4d16..5401068823 100644 --- a/core/libs/prx/libSceCompanionHttpd/Export.cpp +++ b/core/libs/prx/libSceCompanionHttpd/Export.cpp @@ -1,4 +1,5 @@ #include "prx/libSceCompanionHttpd/include/Api.hpp" +#include #include #include #include @@ -37,7 +38,11 @@ extern "C" { int APS5_VABI sceCompanionHttpdOptParamInitialize(CompanionHttpdOptParam* param) { if (param == nullptr) return CompanionHttpdErrorInvalidParam; - std::memset(param, 0, sizeof(*param)); + CompanionHttpdOptParam zero{}; + const int savedErrno = errno; + const bool copied = GuestCopyOut_nid_no_patch(param, &zero, sizeof(zero)); + errno = savedErrno; + if (!copied) return CompanionHttpdErrorInvalidParam; return 0; } diff --git a/core/libs/prx/libSceContentSearch/Export.cpp b/core/libs/prx/libSceContentSearch/Export.cpp index 13c17b161a..9e0c985a9f 100644 --- a/core/libs/prx/libSceContentSearch/Export.cpp +++ b/core/libs/prx/libSceContentSearch/Export.cpp @@ -190,16 +190,26 @@ int APS5_VABI sceContentBinderStart() { int APS5_VABI sceContentBinderTerminate() { return static_cast(0x809D000F); } -int APS5_VABI sceContentSearchGetApplicationLastUpdateId() { +int APS5_VABI sceContentSearchGetApplicationLastUpdateId(std::int64_t* lastUpdateId) { + if (!g_initialized.load()) return ErrorNotInitialized; + if (lastUpdateId == nullptr) return ErrorInvalidArgument; + *lastUpdateId = LibraryUpdateId; + return 0; +} +int APS5_VABI sceContentSearchGetMyApplicationIndex(std::int32_t* index) { + if (!g_initialized.load()) return ErrorNotInitialized; + if (index == nullptr) return ErrorInvalidArgument; + *index = 0; NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceContentSearchGetMyApplicationIndex() { - NotImplemented_nid_no_patch(__func__); - return 0; -} -int APS5_VABI sceContentSearchGetNumOfApplication() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceContentSearchGetNumOfApplication(const ContentSearchContentColumnSet* columnSet, std::uint32_t columnSetLength, const ContentSearchContentOrderByCondition* orderByConditions, std::uint32_t orderByConditionsLength, std::int64_t* numOfApplication, std::int64_t* lastUpdateId) { + if (!g_initialized.load()) return ErrorNotInitialized; + if ((columnSet == nullptr) != (columnSetLength == 0) || (orderByConditions == nullptr) != (orderByConditionsLength == 0) || numOfApplication == nullptr || lastUpdateId == nullptr) return ErrorInvalidArgument; + ValidateColumnSets(__func__, columnSet, columnSetLength); + ValidateOrderBy(__func__, orderByConditions, orderByConditionsLength); + *numOfApplication = 0; + *lastUpdateId = LibraryUpdateId; return 0; } int APS5_VABI sceContentSearchSearchApplication() { diff --git a/core/libs/prx/libSceContentSearch/include/Api.hpp b/core/libs/prx/libSceContentSearch/include/Api.hpp index b54e8efb17..c648e0c87f 100644 --- a/core/libs/prx/libSceContentSearch/include/Api.hpp +++ b/core/libs/prx/libSceContentSearch/include/Api.hpp @@ -23,9 +23,9 @@ int APS5_VABI sceContentBinderReset(); int APS5_VABI sceContentBinderSearchDifference(); int APS5_VABI sceContentBinderStart(); int APS5_VABI sceContentBinderTerminate(); -int APS5_VABI sceContentSearchGetApplicationLastUpdateId(); -int APS5_VABI sceContentSearchGetMyApplicationIndex(); -int APS5_VABI sceContentSearchGetNumOfApplication(); +int APS5_VABI sceContentSearchGetApplicationLastUpdateId(std::int64_t* lastUpdateId); +int APS5_VABI sceContentSearchGetMyApplicationIndex(std::int32_t* index); +int APS5_VABI sceContentSearchGetNumOfApplication(const ContentSearchContentColumnSet* columnSet, std::uint32_t columnSetLength, const ContentSearchContentOrderByCondition* orderByConditions, std::uint32_t orderByConditionsLength, std::int64_t* numOfApplication, std::int64_t* lastUpdateId); int APS5_VABI sceContentSearchSearchApplication(); } diff --git a/core/libs/prx/libSceEditMp4/Export.cpp b/core/libs/prx/libSceEditMp4/Export.cpp index 423d5a76e0..552fc7870b 100644 --- a/core/libs/prx/libSceEditMp4/Export.cpp +++ b/core/libs/prx/libSceEditMp4/Export.cpp @@ -1,6 +1,8 @@ #include "prx/libSceEditMp4/include/Api.hpp" #include #include +#include +#include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" @@ -29,12 +31,14 @@ int APS5_VABI sceEditMp4Initialize() { void APS5_VABI sceEditMp4InitializeParam(void* param) { if (param == nullptr) return; - auto* bytes = static_cast(param); + std::array initial{}; const std::uint64_t at10 = 0x3f; const std::uint32_t at18 = 700; - std::memset(bytes, 0, 0x10); - std::memcpy(bytes + 0x10, &at10, sizeof(at10)); - std::memcpy(bytes + 0x18, &at18, sizeof(at18)); + std::memcpy(initial.data() + 0x10, &at10, sizeof(at10)); + std::memcpy(initial.data() + 0x18, &at18, sizeof(at18)); + const int savedErrno = errno; + GuestCopyOut_nid_no_patch(param, initial.data(), initial.size()); + errno = savedErrno; } std::uint64_t APS5_VABI sceEditMp4QueryMemSize() { diff --git a/core/libs/prx/libSceFiber/Export.cpp b/core/libs/prx/libSceFiber/Export.cpp index de28742c01..b70e32786d 100644 --- a/core/libs/prx/libSceFiber/Export.cpp +++ b/core/libs/prx/libSceFiber/Export.cpp @@ -624,7 +624,13 @@ int32_t APS5_VABI sceFiberRename(FiberObject* object, const char* name) { int32_t APS5_VABI sceFiberOptParamInitialize(FiberOptParam* opt_param) { if (!opt_param) return SCE_FIBER_ERROR_NULL; - std::memset(opt_param, 0, FIBER_OPT_PARAM_SIZE); + const std::uint8_t zero[FIBER_OPT_PARAM_SIZE] = {}; + const auto preserved = errno; + if (!GuestCopyOut_nid_no_patch(opt_param, zero, sizeof(zero))) { + errno = preserved; + return SCE_FIBER_ERROR_INVALID; + } + errno = preserved; return SCE_OK; } diff --git a/core/libs/prx/libSceFreeType/CMakeLists.txt b/core/libs/prx/libSceFreeType/CMakeLists.txt index 4e545ffaa2..7b7ffa46fe 100644 --- a/core/libs/prx/libSceFreeType/CMakeLists.txt +++ b/core/libs/prx/libSceFreeType/CMakeLists.txt @@ -4,7 +4,7 @@ add_library(libSceFreeType SHARED EXCLUDE_FROM_ALL src/Outline.cpp src/Charmap.cpp src/FaceInfo.cpp - src/Stroker.cpp src/OutlineStorage.cpp src/FaceCompat.cpp src/TrackKerning.cpp + src/Stroker.cpp src/OutlineStorage.cpp src/FaceCompat.cpp src/TrackKerning.cpp src/LcdFilter.cpp src/ModuleManagement.cpp src/OutlineTransform.cpp src/GuestObjects.cpp src/Names.cpp src/Trigonometry.cpp @@ -185,6 +185,61 @@ if(BUILD_TESTING) set_tests_properties(guest_freetype_track_kerning PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_freetype_slot_synthesis_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeSlotSynthesis.cpp) + target_include_directories(guest_freetype_slot_synthesis_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_slot_synthesis_tests PRIVATE libSceFreeType freetype libc) + configure_windows_unwind(guest_freetype_slot_synthesis_tests) + add_test(NAME guest_freetype_slot_synthesis COMMAND guest_freetype_slot_synthesis_tests) + if(WIN32) + set_tests_properties(guest_freetype_slot_synthesis PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_freetype_bitmap_embolden_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeBitmapEmbolden.cpp) + target_include_directories(guest_freetype_bitmap_embolden_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_bitmap_embolden_tests PRIVATE libSceFreeType freetype libc) + configure_windows_unwind(guest_freetype_bitmap_embolden_tests) + add_test(NAME guest_freetype_bitmap_embolden COMMAND guest_freetype_bitmap_embolden_tests) + if(WIN32) + set_tests_properties(guest_freetype_bitmap_embolden PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_freetype_slot_own_bitmap_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeSlotOwnBitmap.cpp) + target_include_directories(guest_freetype_slot_own_bitmap_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_slot_own_bitmap_tests PRIVATE libSceFreeType freetype libc) + configure_windows_unwind(guest_freetype_slot_own_bitmap_tests) + add_test(NAME guest_freetype_slot_own_bitmap COMMAND guest_freetype_slot_own_bitmap_tests) + if(WIN32) + set_tests_properties(guest_freetype_slot_own_bitmap PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + + add_test_executable(guest_freetype_lcd_filter_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeLcdFilter.cpp) + target_include_directories(guest_freetype_lcd_filter_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_lcd_filter_tests PRIVATE libSceFreeType freetype libc) + configure_windows_unwind(guest_freetype_lcd_filter_tests) + add_test(NAME guest_freetype_lcd_filter COMMAND guest_freetype_lcd_filter_tests) + if(WIN32) + set_tests_properties(guest_freetype_lcd_filter PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_freetype_module_management_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeModuleManagement.cpp) + target_include_directories(guest_freetype_module_management_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_module_management_tests PRIVATE libSceFreeType freetype libc) + configure_windows_unwind(guest_freetype_module_management_tests) + add_test(NAME guest_freetype_module_management COMMAND guest_freetype_module_management_tests) + if(WIN32) + set_tests_properties(guest_freetype_module_management PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_freetype_outline_transform_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeOutlineTransform.cpp) + target_include_directories(guest_freetype_outline_transform_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_outline_transform_tests PRIVATE libSceFreeType freetype libc) + configure_windows_unwind(guest_freetype_outline_transform_tests) + add_test(NAME guest_freetype_outline_transform COMMAND guest_freetype_outline_transform_tests) + if(WIN32) + set_tests_properties(guest_freetype_outline_transform PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_freetype_charmap_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeCharmap.cpp) target_include_directories(guest_freetype_charmap_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_freetype_charmap_tests PRIVATE libSceFreeType freetype libc) diff --git a/core/libs/prx/libSceFreeType/Export.cpp b/core/libs/prx/libSceFreeType/Export.cpp index 218a1a9a5c..5148aa911e 100644 --- a/core/libs/prx/libSceFreeType/Export.cpp +++ b/core/libs/prx/libSceFreeType/Export.cpp @@ -1,6 +1,7 @@ #include "prx/libSceFreeType/include/Api.hpp" #include "prx/libSceFreeType/include/GuestObjects.hpp" #include FT_OUTLINE_H +#include FT_SYNTHESIS_H #include "prx/libc/include/FilesystemError.hpp" #include "prx/libc/include/General.hpp" @@ -312,6 +313,20 @@ int APS5_VABI FT_Render_Glyph_nid_postfix(GuestGlyphSlotRec* slot, std::int32_t return error; } +void APS5_VABI FT_GlyphSlot_Oblique_nid_postfix(GuestGlyphSlotRec* slot) { + GuestFace* owner = FaceOf(slot); + if (owner) WriteBackOutline(*owner); + FT_GlyphSlot_Oblique(owner ? owner->host->glyph : nullptr); + if (owner) SyncSlot(*owner); +} + +void APS5_VABI FT_GlyphSlot_Embolden_nid_postfix(GuestGlyphSlotRec* slot) { + GuestFace* owner = FaceOf(slot); + if (owner) WriteBackOutline(*owner); + FT_GlyphSlot_Embolden(owner ? owner->host->glyph : nullptr); + if (owner) SyncSlot(*owner); +} + int APS5_VABI FT_Select_Charmap_nid_postfix(GuestFaceRec* face, std::int32_t encoding) { GuestFace* owner = FaceOf(face); const FT_Error error = FT_Select_Charmap(owner ? owner->host : nullptr, static_cast(encoding)); diff --git a/core/libs/prx/libSceFreeType/include/Api.hpp b/core/libs/prx/libSceFreeType/include/Api.hpp index 976f34b735..bb33801035 100644 --- a/core/libs/prx/libSceFreeType/include/Api.hpp +++ b/core/libs/prx/libSceFreeType/include/Api.hpp @@ -106,6 +106,9 @@ int APS5_VABI FT_Stroker_GetCounts_nid_postfix(void* stroker, std::uint32_t* poi int APS5_VABI FT_Stroker_CubicTo_nid_postfix(void* stroker, const FreeType::GuestVector* control1, const FreeType::GuestVector* control2, const FreeType::GuestVector* to); void APS5_VABI FT_Stroker_Done_nid_postfix(void* stroker); int APS5_VABI FT_Stroker_GetBorderCounts_nid_postfix(void* stroker, std::int32_t border, std::uint32_t* points, std::uint32_t* contours); +int APS5_VABI FT_Stroker_ParseOutline_nid_postfix(void* stroker, const FreeType::GuestOutline* outline, std::uint8_t opened); +void APS5_VABI FT_Stroker_ExportBorder_nid_postfix(void* stroker, std::int32_t border, FreeType::GuestOutline* outline); +void APS5_VABI FT_Stroker_Export_nid_postfix(void* stroker, FreeType::GuestOutline* outline); int APS5_VABI FT_Stroker_EndSubPath_nid_postfix(void* stroker); int APS5_VABI FT_Stroker_LineTo_nid_postfix(void* stroker, const FreeType::GuestVector* to); int APS5_VABI FT_Stroker_New_nid_postfix(FreeType::GuestLibrary* library, void** stroker); @@ -119,9 +122,28 @@ const char* APS5_VABI FT_Get_Font_Format_nid_postfix(FreeType::GuestFaceRec* fac std::uint8_t APS5_VABI FT_Face_CheckTrueTypePatents_nid_postfix(FreeType::GuestFaceRec* face); std::uint8_t APS5_VABI FT_Face_SetUnpatentedHinting_nid_postfix(FreeType::GuestFaceRec* face, std::uint8_t value); int APS5_VABI FT_Get_Track_Kerning_nid_postfix(FreeType::GuestFaceRec* face, std::int64_t pointSize, std::int32_t degree, std::int64_t* kerning); +void APS5_VABI FT_GlyphSlot_Oblique_nid_postfix(FreeType::GuestGlyphSlotRec* slot); +void APS5_VABI FT_GlyphSlot_Embolden_nid_postfix(FreeType::GuestGlyphSlotRec* slot); +int APS5_VABI FT_GlyphSlot_Own_Bitmap_nid_postfix(FreeType::GuestGlyphSlotRec* slot); +int APS5_VABI FT_Bitmap_Embolden_nid_postfix(FreeType::GuestLibrary* library, FreeType::GuestBitmap* bitmap, std::int64_t xStrength, std::int64_t yStrength); +int APS5_VABI FT_Library_SetLcdFilterWeights_nid_postfix(FreeType::GuestLibrary* library, unsigned char* weights); +int APS5_VABI FT_Library_SetLcdFilter_nid_postfix(FreeType::GuestLibrary* library, std::int32_t filter); +void APS5_VABI FT_Outline_Transform_nid_postfix(const FreeType::GuestOutline* outline, const FreeType::GuestMatrix* matrix); +int APS5_VABI FT_Outline_EmboldenXY_nid_postfix(FreeType::GuestOutline* outline, std::int64_t xStrength, std::int64_t yStrength); +int APS5_VABI FT_Outline_Embolden_nid_postfix(FreeType::GuestOutline* outline, std::int64_t strength); int APS5_VABI FT_Outline_New_nid_postfix(FreeType::GuestLibrary* library, std::uint32_t numPoints, std::int32_t numContours, FreeType::GuestOutline* outline); int APS5_VABI FT_Outline_Done_nid_postfix(FreeType::GuestLibrary* library, FreeType::GuestOutline* outline); int APS5_VABI sceFreeTypeOtOutlineDone_nid_no_patch(); int APS5_VABI sceFreeTypeOtOutlineNew_nid_no_patch(); +int APS5_VABI sceFreeTypeOtGlyphSlotEmbolden_nid_no_patch(); +int APS5_VABI sceFreeTypeOtGlyphSlotOblique_nid_no_patch(); +int APS5_VABI sceFreeTypeOtGlyphSlotOwnBitmap_nid_no_patch(); +int APS5_VABI sceFreeTypeOtLibrarySetLcdFilter_nid_no_patch(); +int APS5_VABI sceFreeTypeOtLibrarySetLcdFilterWeights_nid_no_patch(); +void* APS5_VABI FT_Get_Module_nid_postfix(FreeType::GuestLibrary* library, const char* moduleName); +void* APS5_VABI FT_Get_Renderer_nid_postfix(FreeType::GuestLibrary* library, std::uint32_t format); +int APS5_VABI FT_Remove_Module_nid_postfix(FreeType::GuestLibrary* library, void* module); +void APS5_VABI FT_Add_Default_Modules_nid_postfix(FreeType::GuestLibrary* library); +int APS5_VABI sceFreeTypeOtRemoveModule_nid_no_patch(); } diff --git a/core/libs/prx/libSceFreeType/src/Bitmap.cpp b/core/libs/prx/libSceFreeType/src/Bitmap.cpp index c0392d1e97..7b9cb5b3ef 100644 --- a/core/libs/prx/libSceFreeType/src/Bitmap.cpp +++ b/core/libs/prx/libSceFreeType/src/Bitmap.cpp @@ -1,9 +1,11 @@ +#include "prx/libSceFreeType/include/Api.hpp" #include "prx/libSceFreeType/include/GuestObjects.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include FT_BITMAP_H #include +#include #include #include #include @@ -92,6 +94,31 @@ int APS5_VABI FT_Bitmap_Convert_nid_postfix(GuestLibrary* library, const GuestBi return error; } +int APS5_VABI FT_Bitmap_Embolden_nid_postfix(GuestLibrary* library, GuestBitmap* bitmap, std::int64_t xStrength, std::int64_t yStrength) { + if (!library) return FT_Err_Invalid_Library_Handle; + if (!bitmap) return FT_Err_Invalid_Argument; + for (const std::int64_t strength : {xStrength, yStrength}) { + if (strength < std::numeric_limits::min() || strength > std::numeric_limits::max()) { + throw std::runtime_error("FT_Bitmap_Embolden: a strength outside the host FT_Pos range is not implemented"); + } + if (strength > std::numeric_limits::max() - 32) return FT_Err_Invalid_Argument; + constexpr std::int64_t minimumRounded = static_cast(std::numeric_limits::min()) * 64 - 32; + constexpr std::int64_t maximumRounded = static_cast(std::numeric_limits::max()) * 64 + 31; + if (strength < minimumRounded || strength > maximumRounded) return FT_Err_Invalid_Argument; + } + FT_Bitmap native = NativeBitmap(*bitmap); + const FT_Error error = FT_Bitmap_Embolden(library->host, &native, static_cast(xStrength), static_cast(yStrength)); + SyncBitmap(*bitmap, native); + return error; +} + +int APS5_VABI FT_GlyphSlot_Own_Bitmap_nid_postfix(GuestGlyphSlotRec* slot) { + GuestFace* owner = FaceOf(slot); + const FT_Error error = FT_GlyphSlot_Own_Bitmap(owner ? owner->host->glyph : nullptr); + if (owner) SyncSlot(*owner); + return error; +} + int APS5_VABI FT_Bitmap_Done_nid_postfix(GuestLibrary* library, GuestBitmap* bitmap) { if (!library) return FT_Err_Invalid_Library_Handle; if (!bitmap) return FT_Err_Invalid_Argument; diff --git a/core/libs/prx/libSceFreeType/src/LcdFilter.cpp b/core/libs/prx/libSceFreeType/src/LcdFilter.cpp new file mode 100644 index 0000000000..ee993833c5 --- /dev/null +++ b/core/libs/prx/libSceFreeType/src/LcdFilter.cpp @@ -0,0 +1,28 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include FT_LCD_FILTER_H + +#include +#include + +using namespace FreeType; + +extern "C" { + +int APS5_VABI FT_Library_SetLcdFilter_nid_postfix(GuestLibrary* library, std::int32_t filter) { + return FT_Library_SetLcdFilter(library ? library->host : nullptr, static_cast(filter)); +} + +int APS5_VABI FT_Library_SetLcdFilterWeights_nid_postfix(GuestLibrary* library, unsigned char* weights) { + FT_Library host = library ? library->host : nullptr; + if (!weights) return FT_Library_SetLcdFilterWeights(host, nullptr); + GuestArena::GuestArenaPrepareHostRead_nid_no_patch(weights, FT_LCD_FILTER_FIVE_TAPS); + std::array copy; + std::memcpy(copy.data(), weights, copy.size()); + return FT_Library_SetLcdFilterWeights(host, copy.data()); +} + +} diff --git a/core/libs/prx/libSceFreeType/src/ModuleManagement.cpp b/core/libs/prx/libSceFreeType/src/ModuleManagement.cpp new file mode 100644 index 0000000000..43c5ffc047 --- /dev/null +++ b/core/libs/prx/libSceFreeType/src/ModuleManagement.cpp @@ -0,0 +1,30 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include FT_MODULE_H +#include FT_RENDER_H + +#include + +using namespace FreeType; + +extern "C" { + +void* APS5_VABI FT_Get_Module_nid_postfix(GuestLibrary* library, const char* moduleName) { + return FT_Get_Module(library ? library->host : nullptr, moduleName); +} + +void* APS5_VABI FT_Get_Renderer_nid_postfix(GuestLibrary* library, std::uint32_t format) { + return FT_Get_Renderer(library ? library->host : nullptr, static_cast(format)); +} + +int APS5_VABI FT_Remove_Module_nid_postfix(GuestLibrary* library, void* module) { + return FT_Remove_Module(library ? library->host : nullptr, static_cast(module)); +} + +void APS5_VABI FT_Add_Default_Modules_nid_postfix(GuestLibrary* library) { + FT_Add_Default_Modules(library ? library->host : nullptr); +} + +} diff --git a/core/libs/prx/libSceFreeType/src/OutlineTransform.cpp b/core/libs/prx/libSceFreeType/src/OutlineTransform.cpp new file mode 100644 index 0000000000..c18260918d --- /dev/null +++ b/core/libs/prx/libSceFreeType/src/OutlineTransform.cpp @@ -0,0 +1,71 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include FT_OUTLINE_H + +#include +#include +#include +#include +#include +#include + +using namespace FreeType; + +namespace { + +FT_Pos HostCoordinate(std::int64_t value, const char* function) { + if (value < std::numeric_limits::min() || value > std::numeric_limits::max()) { + throw std::runtime_error(std::string(function) + ": a value outside the host FT_Pos range is not implemented"); + } + return static_cast(value); +} + +struct NativeOutline { + std::vector points; + FT_Outline outline{}; + + NativeOutline(const GuestOutline& guest, const char* function) { + points.reserve(guest.n_points); + for (std::size_t index = 0; index < guest.n_points; ++index) { + points.push_back({HostCoordinate(guest.points[index].x, function), HostCoordinate(guest.points[index].y, function)}); + } + outline = {guest.n_contours, guest.n_points, points.data(), guest.tags, guest.contours, guest.flags}; + } + + void Publish(GuestOutline& guest) const { + for (std::size_t index = 0; index < points.size(); ++index) guest.points[index] = {points[index].x, points[index].y}; + } +}; + +} + +extern "C" { + +int APS5_VABI FT_Outline_Embolden_nid_postfix(GuestOutline* outline, std::int64_t strength) { + if (!outline) return FT_Outline_Embolden(nullptr, 0); + NativeOutline native(*outline, __func__); + const FT_Error error = FT_Outline_Embolden(&native.outline, HostCoordinate(strength, __func__)); + native.Publish(*outline); + return error; +} + +int APS5_VABI FT_Outline_EmboldenXY_nid_postfix(GuestOutline* outline, std::int64_t xStrength, std::int64_t yStrength) { + if (!outline) return FT_Outline_EmboldenXY(nullptr, 0, 0); + NativeOutline native(*outline, __func__); + const FT_Error error = FT_Outline_EmboldenXY(&native.outline, HostCoordinate(xStrength, __func__), HostCoordinate(yStrength, __func__)); + native.Publish(*outline); + return error; +} + +void APS5_VABI FT_Outline_Transform_nid_postfix(const GuestOutline* outline, const GuestMatrix* matrix) { + if (!outline || !matrix || !outline->points) return; + NativeOutline native(*outline, __func__); + const FT_Matrix host{HostCoordinate(matrix->xx, __func__), HostCoordinate(matrix->xy, __func__), HostCoordinate(matrix->yx, __func__), + HostCoordinate(matrix->yy, __func__)}; + FT_Outline_Transform(&native.outline, &host); + native.Publish(*const_cast(outline)); +} + +} diff --git a/core/libs/prx/libSceFreeType/src/Stroker.cpp b/core/libs/prx/libSceFreeType/src/Stroker.cpp index ca2fe64191..a63f830968 100644 --- a/core/libs/prx/libSceFreeType/src/Stroker.cpp +++ b/core/libs/prx/libSceFreeType/src/Stroker.cpp @@ -1,19 +1,27 @@ #include "prx/libSceFreeType/include/Api.hpp" #include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/General.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include FT_STROKER_H +#include #include #include #include #include +#include using namespace FreeType; namespace { +constexpr bool SharedPoints = sizeof(FT_Pos) == sizeof(std::int64_t); + +static_assert(!SharedPoints || (sizeof(FT_Vector) == sizeof(GuestVector) && offsetof(FT_Vector, x) == offsetof(GuestVector, x) && + offsetof(FT_Vector, y) == offsetof(GuestVector, y))); + FT_Pos HostCoordinate(std::int64_t value, const char* function) { if (value < std::numeric_limits::min() || value > std::numeric_limits::max()) { throw std::runtime_error(std::string(function) + ": a value outside the host FT_Pos range is not implemented"); @@ -38,6 +46,57 @@ void WriteCount(std::uint32_t* target, FT_UInt value) { *target = value; } +FT_Outline NativeOutline(const GuestOutline& outline, FT_Vector* points) { + return {outline.n_contours, outline.n_points, points, outline.tags, outline.contours, outline.flags}; +} + +bool FitsInt32(std::int64_t value) { + return value >= std::numeric_limits::min() && value <= std::numeric_limits::max(); +} + +void CheckOutlineArrays(const GuestOutline& outline, const char* function) { + if (outline.n_contours == 0) return; + if (!outline.points || !outline.tags || !outline.contours) NotImplemented_nid_no_patch(function); + for (std::size_t contour = 0; contour < outline.n_contours; ++contour) { + if (outline.contours[contour] >= outline.n_points) NotImplemented_nid_no_patch(function); + } +} + +bool StrokedPointsFitInt32(const GuestOutline& outline) { + std::int32_t last = -1; + for (std::size_t contour = 0; contour < outline.n_contours; ++contour) { + const std::int32_t first = last + 1; + last = outline.contours[contour]; + if (last <= first) continue; + for (std::int32_t index = first; index <= last; ++index) { + if (!FitsInt32(outline.points[index].x) || !FitsInt32(outline.points[index].y)) return false; + } + } + return true; +} + +std::vector NativePoints(const GuestOutline& outline, const char* function) { + std::vector points; + if (outline.n_contours == 0) return points; + points.reserve(outline.n_points); + for (std::size_t index = 0; index < outline.n_points; ++index) { + points.push_back({HostCoordinate(outline.points[index].x, function), HostCoordinate(outline.points[index].y, function)}); + } + return points; +} + +void ExportBorder(void* stroker, std::int32_t border, GuestOutline* outline, const char* function) { + if (!stroker || !outline || (border != FT_STROKER_BORDER_LEFT && border != FT_STROKER_BORDER_RIGHT)) return; + if constexpr (SharedPoints) { + FT_Outline native = NativeOutline(*outline, reinterpret_cast(outline->points)); + FT_Stroker_ExportBorder(static_cast(stroker), static_cast(border), &native); + outline->n_contours = native.n_contours; + outline->n_points = native.n_points; + } else { + NotImplemented_nid_no_patch(function); + } +} + } extern "C" { @@ -108,4 +167,29 @@ int APS5_VABI FT_Stroker_CubicTo_nid_postfix(void* stroker, const GuestVector* c return FT_Stroker_CubicTo(static_cast(stroker), first.pointer, second.pointer, end.pointer); } +int APS5_VABI FT_Stroker_ParseOutline_nid_postfix(void* stroker, const GuestOutline* outline, std::uint8_t opened) { + if (!outline) return FT_Stroker_ParseOutline(static_cast(stroker), nullptr, opened); + if (stroker) CheckOutlineArrays(*outline, __func__); + std::vector converted; + FT_Vector* points = nullptr; + if constexpr (SharedPoints) { + if (stroker && !StrokedPointsFitInt32(*outline)) NotImplemented_nid_no_patch(__func__); + points = reinterpret_cast(outline->points); + } else if (stroker) { + converted = NativePoints(*outline, __func__); + points = converted.data(); + } + FT_Outline native = NativeOutline(*outline, points); + return FT_Stroker_ParseOutline(static_cast(stroker), &native, opened); +} + +void APS5_VABI FT_Stroker_ExportBorder_nid_postfix(void* stroker, std::int32_t border, GuestOutline* outline) { + ExportBorder(stroker, border, outline, __func__); +} + +void APS5_VABI FT_Stroker_Export_nid_postfix(void* stroker, GuestOutline* outline) { + ExportBorder(stroker, FT_STROKER_BORDER_LEFT, outline, __func__); + ExportBorder(stroker, FT_STROKER_BORDER_RIGHT, outline, __func__); +} + } diff --git a/core/libs/prx/libSceFreeTypeOt/CMakeLists.txt b/core/libs/prx/libSceFreeTypeOt/CMakeLists.txt index 6b94a30eec..9502ac13d0 100644 --- a/core/libs/prx/libSceFreeTypeOt/CMakeLists.txt +++ b/core/libs/prx/libSceFreeTypeOt/CMakeLists.txt @@ -9,6 +9,20 @@ endif() configure_windows_unwind(libSceFreeTypeOt) if(BUILD_TESTING) + add_test_executable(guest_freetype_ot_constants_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeOtConstants.cpp) + target_include_directories(guest_freetype_ot_constants_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_ot_constants_tests PRIVATE libSceFreeTypeOt) + if(WIN32) + target_link_libraries(guest_freetype_ot_constants_tests PRIVATE libc) + endif() + configure_windows_unwind(guest_freetype_ot_constants_tests) + add_test(NAME guest_freetype_ot_constants COMMAND guest_freetype_ot_constants_tests) + set_tests_properties(guest_freetype_ot_constants PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_freetype_ot_constants PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_freetype_corner_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeCorner.cpp) target_include_directories(guest_freetype_corner_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_freetype_corner_tests PRIVATE libSceFreeTypeOt) diff --git a/core/libs/prx/libSceFreeTypeOt/Export.cpp b/core/libs/prx/libSceFreeTypeOt/Export.cpp index f34b074db6..4abb0b91f0 100644 --- a/core/libs/prx/libSceFreeTypeOt/Export.cpp +++ b/core/libs/prx/libSceFreeTypeOt/Export.cpp @@ -1,10 +1,9 @@ -#include "prx/libSceFreeType/include/Api.hpp" +#include "prx/libSceFreeTypeOt/libSceFreeTypeOt.hpp" #include #include #include #include #include "SceTypes.hpp" -#include "prx/libSceFreeType/include/Api.hpp" #include "prx/libSceFreeTypeOt/include/Calc.hpp" #include "prx/libc/include/General.hpp" @@ -109,20 +108,20 @@ int APS5_VABI sceFreeTypeOtUnknown_ZhtAXOoLNyo_nid_no_patch() { } APS5_EXPORT("ZhtAXOoLNyo", sceFreeTypeOtUnknown_ZhtAXOoLNyo_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_73vg_nRH68g_nid_no_patch() { +int APS5_VABI sceFreeTypeOtGlyphSlotEmbolden_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("73vg+nRH68g", sceFreeTypeOtUnknown_73vg_nRH68g_nid_no_patch); +APS5_EXPORT("73vg+nRH68g", sceFreeTypeOtGlyphSlotEmbolden_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_wBKAYO7yEXw_nid_no_patch() { +int APS5_VABI sceFreeTypeOtGlyphSlotOblique_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("wBKAYO7yEXw", sceFreeTypeOtUnknown_wBKAYO7yEXw_nid_no_patch); +APS5_EXPORT("wBKAYO7yEXw", sceFreeTypeOtGlyphSlotOblique_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_nSIux4OQyJM_nid_no_patch() { +int APS5_VABI sceFreeTypeOtGlyphSlotOwnBitmap_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("nSIux4OQyJM", sceFreeTypeOtUnknown_nSIux4OQyJM_nid_no_patch); +APS5_EXPORT("nSIux4OQyJM", sceFreeTypeOtGlyphSlotOwnBitmap_nid_no_patch); int APS5_VABI sceFreeTypeOtGlyphCopy_nid_no_patch() { NotImplemented_nid_no_patch(__func__); @@ -159,15 +158,15 @@ int APS5_VABI sceFreeTypeOtHasPSGlyphNames_nid_no_patch() { } APS5_EXPORT("x8y0qzLiYOU", sceFreeTypeOtHasPSGlyphNames_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_VW4GD_TaIUc_nid_no_patch() { +int APS5_VABI sceFreeTypeOtLibrarySetLcdFilter_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("VW4GD-TaIUc", sceFreeTypeOtUnknown_VW4GD_TaIUc_nid_no_patch); +APS5_EXPORT("VW4GD-TaIUc", sceFreeTypeOtLibrarySetLcdFilter_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_GiuNQjoj4fs_nid_no_patch() { +int APS5_VABI sceFreeTypeOtLibrarySetLcdFilterWeights_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("GiuNQjoj4fs", sceFreeTypeOtUnknown_GiuNQjoj4fs_nid_no_patch); +APS5_EXPORT("GiuNQjoj4fs", sceFreeTypeOtLibrarySetLcdFilterWeights_nid_no_patch); int APS5_VABI sceFreeTypeOtLibraryVersion_nid_no_patch() { NotImplemented_nid_no_patch(__func__); @@ -229,15 +228,15 @@ int APS5_VABI sceFreeTypeOtUnknown_umIqupy8UU0_nid_no_patch() { } APS5_EXPORT("umIqupy8UU0", sceFreeTypeOtUnknown_umIqupy8UU0_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_l6Cf9rb9fZQ_nid_no_patch() { +int APS5_VABI sceFreeTypeOtOutlineEmbolden_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("l6Cf9rb9fZQ", sceFreeTypeOtUnknown_l6Cf9rb9fZQ_nid_no_patch); +APS5_EXPORT("l6Cf9rb9fZQ", sceFreeTypeOtOutlineEmbolden_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_BgUMBGvhATU_nid_no_patch() { +int APS5_VABI sceFreeTypeOtOutlineEmboldenXY_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("BgUMBGvhATU", sceFreeTypeOtUnknown_BgUMBGvhATU_nid_no_patch); +APS5_EXPORT("BgUMBGvhATU", sceFreeTypeOtOutlineEmboldenXY_nid_no_patch); int APS5_VABI sceFreeTypeOtOutlineGetBBox_nid_no_patch() { NotImplemented_nid_no_patch(__func__); @@ -264,10 +263,10 @@ int APS5_VABI sceFreeTypeOtUnknown_9km02mPqPJE_nid_no_patch() { } APS5_EXPORT("9km02mPqPJE", sceFreeTypeOtUnknown_9km02mPqPJE_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_0Lv6K1d47jg_nid_no_patch() { +int APS5_VABI sceFreeTypeOtOutlineTransform_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("0Lv6K1d47jg", sceFreeTypeOtUnknown_0Lv6K1d47jg_nid_no_patch); +APS5_EXPORT("0Lv6K1d47jg", sceFreeTypeOtOutlineTransform_nid_no_patch); int APS5_VABI sceFreeTypeOtPropertyGet_nid_no_patch() { NotImplemented_nid_no_patch(__func__); @@ -304,10 +303,10 @@ int APS5_VABI sceFreeTypeOtUnknown_qAPXM5DN62Q_nid_no_patch() { } APS5_EXPORT("qAPXM5DN62Q", sceFreeTypeOtUnknown_qAPXM5DN62Q_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_5ipBcLYVc2k_nid_no_patch() { +int APS5_VABI sceFreeTypeOtRemoveModule_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("5ipBcLYVc2k", sceFreeTypeOtUnknown_5ipBcLYVc2k_nid_no_patch); +APS5_EXPORT("5ipBcLYVc2k", sceFreeTypeOtRemoveModule_nid_no_patch); int APS5_VABI sceFreeTypeOtUnknown_8A0cAU4yBVY_nid_no_patch() { NotImplemented_nid_no_patch(__func__); @@ -394,15 +393,15 @@ int APS5_VABI sceFreeTypeOtStrokerEndSubPath_nid_no_patch() { } APS5_EXPORT("pBqh-Uh-gns", sceFreeTypeOtStrokerEndSubPath_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_2rcvKZfm5rI_nid_no_patch() { +int APS5_VABI sceFreeTypeOtStrokerExport_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("2rcvKZfm5rI", sceFreeTypeOtUnknown_2rcvKZfm5rI_nid_no_patch); +APS5_EXPORT("2rcvKZfm5rI", sceFreeTypeOtStrokerExport_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_7cIS2cFG3PE_nid_no_patch() { +int APS5_VABI sceFreeTypeOtStrokerExportBorder_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("7cIS2cFG3PE", sceFreeTypeOtUnknown_7cIS2cFG3PE_nid_no_patch); +APS5_EXPORT("7cIS2cFG3PE", sceFreeTypeOtStrokerExportBorder_nid_no_patch); int APS5_VABI sceFreeTypeOtStrokerGetBorderCounts_nid_no_patch() { NotImplemented_nid_no_patch(__func__); @@ -424,10 +423,10 @@ int APS5_VABI sceFreeTypeOtStrokerNew_nid_no_patch() { } APS5_EXPORT("eqGCaeizvWM", sceFreeTypeOtStrokerNew_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_RvFx7dv9ThU_nid_no_patch() { +int APS5_VABI sceFreeTypeOtStrokerParseOutline_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("RvFx7dv9ThU", sceFreeTypeOtUnknown_RvFx7dv9ThU_nid_no_patch); +APS5_EXPORT("RvFx7dv9ThU", sceFreeTypeOtStrokerParseOutline_nid_no_patch); int APS5_VABI sceFreeTypeOtStrokerRewind_nid_no_patch() { NotImplemented_nid_no_patch(__func__); @@ -439,15 +438,16 @@ int APS5_VABI sceFreeTypeOtStrokerSet_nid_no_patch() { } APS5_EXPORT("B3uU5aDZn94", sceFreeTypeOtStrokerSet_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_08_mhD_AAds_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI FT_Trace_Get_Count_nid_no_patch() { + return 0; } -APS5_EXPORT("08-mhD-AAds", sceFreeTypeOtUnknown_08_mhD_AAds_nid_no_patch); +APS5_EXPORT("08-mhD-AAds", FT_Trace_Get_Count_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_64XMq6sDxUo_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); +const char* APS5_VABI FT_Trace_Get_Name_nid_no_patch(int idx) { + static_cast(idx); + return nullptr; } -APS5_EXPORT("64XMq6sDxUo", sceFreeTypeOtUnknown_64XMq6sDxUo_nid_no_patch); +APS5_EXPORT("64XMq6sDxUo", FT_Trace_Get_Name_nid_no_patch); int APS5_VABI sceFreeTypeOtUnknown_4xG87W9dTSg_nid_no_patch() { NotImplemented_nid_no_patch(__func__); diff --git a/core/libs/prx/libSceFreeTypeOt/libSceFreeTypeOt.hpp b/core/libs/prx/libSceFreeTypeOt/libSceFreeTypeOt.hpp index 8518083a79..9b00c6da50 100644 --- a/core/libs/prx/libSceFreeTypeOt/libSceFreeTypeOt.hpp +++ b/core/libs/prx/libSceFreeTypeOt/libSceFreeTypeOt.hpp @@ -5,3 +5,8 @@ #include "prx/libSceFreeTypeOt/include/Calc.hpp" #include "prx/libSceFreeTypeOt/include/Memory.hpp" #include "prx/libSceFreeTypeOt/include/Raccess.hpp" + +extern "C" { +int APS5_VABI FT_Trace_Get_Count_nid_no_patch(); +const char* APS5_VABI FT_Trace_Get_Name_nid_no_patch(int idx); +} diff --git a/core/libs/prx/libSceHmd/Export.cpp b/core/libs/prx/libSceHmd/Export.cpp index 8d8bddd1cb..1c67d0c65d 100644 --- a/core/libs/prx/libSceHmd/Export.cpp +++ b/core/libs/prx/libSceHmd/Export.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -113,9 +114,14 @@ std::int32_t APS5_VABI sceHmdClose(std::int32_t handle) { std::int32_t APS5_VABI sceHmdGetDeviceInformation(Hmd::DeviceInformation* info) { if (!info) return Hmd::ParameterNull; if (!initialized.load()) return Hmd::NotInitialized; - *info = {}; - info->status = Hmd::DeviceStatus::NotDetected; - info->userId = -1; + Hmd::DeviceInformation result{}; + result.status = Hmd::DeviceStatus::NotDetected; + result.userId = -1; + const int savedErrno = errno; + if (!GuestCopyOut_nid_no_patch(info, &result, sizeof(result))) { + errno = savedErrno; + return Hmd::ParameterNull; + } return 0; } @@ -128,7 +134,12 @@ std::int32_t APS5_VABI sceHmdGetDeviceInformationByHandle(std::int32_t handle, std::int32_t APS5_VABI sceHmdInternalGetDeviceStatus(Hmd::DeviceStatus* status) { if (!status) return Hmd::ParameterNull; - *status = Hmd::DeviceStatus::NotDetected; + constexpr Hmd::DeviceStatus deviceStatus = Hmd::DeviceStatus::NotDetected; + const int savedErrno = errno; + if (!GuestCopyOut_nid_no_patch(status, &deviceStatus, sizeof(deviceStatus))) { + errno = savedErrno; + return Hmd::ParameterNull; + } return 0; } diff --git a/core/libs/prx/libSceHttp2/CMakeLists.txt b/core/libs/prx/libSceHttp2/CMakeLists.txt index 86b947eec0..dad538b79e 100644 --- a/core/libs/prx/libSceHttp2/CMakeLists.txt +++ b/core/libs/prx/libSceHttp2/CMakeLists.txt @@ -77,9 +77,10 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() add_test(NAME guest_http2_http1 COMMAND guest_http2_transport_tests --http1) - set_tests_properties(guest_http2_http1 PROPERTIES TIMEOUT 30) + add_test(NAME guest_http2_http1_cookies COMMAND guest_http2_transport_tests --http1-cookies) + set_tests_properties(guest_http2_http1 guest_http2_http1_cookies PROPERTIES TIMEOUT 30) if(WIN32) - set_tests_properties(guest_http2_http1 PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(guest_http2_http1 guest_http2_http1_cookies PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() add_test(NAME guest_http2_ssl_callbacks COMMAND guest_http2_transport_tests --ssl-callbacks) @@ -109,3 +110,12 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +if(BUILD_TESTING) + add_test(NAME guest_http2_auth_status COMMAND guest_http2_transport_tests --auth-status) + set_tests_properties(guest_http2_auth_status PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_http2_auth_status PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceHttp2/Cookies.cpp b/core/libs/prx/libSceHttp2/Cookies.cpp index 9e767f997a..1b8a4443b6 100644 --- a/core/libs/prx/libSceHttp2/Cookies.cpp +++ b/core/libs/prx/libSceHttp2/Cookies.cpp @@ -321,10 +321,49 @@ void AppendCookie(std::string& header, const Cookie& cookie) { } CookieBox* FindBox(int cookieBoxId) { - if (!Http2CookieBoxExists(cookieBoxId)) return nullptr; - return &g_cookieBoxes[cookieBoxId]; + const auto box = g_cookieBoxes.find(cookieBoxId); + return box != g_cookieBoxes.end() ? &box->second : nullptr; } +std::vector Matches(CookieBox& box, const Url& url) { + RemoveExpired(box, Now()); + std::vector matches; + for (const Cookie& stored : box.cookies) { + if (DomainMatches(url.host, stored.domain) && PathMatches(url.path, stored.path) && (!stored.secure || url.secure)) matches.push_back(&stored); + } + std::stable_sort(matches.begin(), matches.end(), [](const Cookie* left, const Cookie* right) { return left->path.size() > right->path.size(); }); + for (auto run = matches.begin(); run != matches.end();) { + const auto end = std::find_if(run, matches.end(), [&](const Cookie* match) { return match->path.size() != (*run)->path.size(); }); + std::rotate(run, run + 1, end); + run = end; + } + return matches; +} + +} + +std::optional Http2CookieHeader(int cookieBoxId, std::string_view url) { + std::lock_guard lock(g_cookiesMutex); + CookieBox* box = FindBox(cookieBoxId); + Url parsed{}; + if (!box || ParseUrl(url, parsed) != 0) return std::nullopt; + const auto matches = Matches(*box, parsed); + if (matches.empty()) return std::nullopt; + std::string header; + for (const Cookie* match : matches) AppendCookie(header, *match); + return header; +} + +void Http2StoreResponseCookie(int cookieBoxId, std::string_view url, std::string_view cookie) { + std::lock_guard lock(g_cookiesMutex); + CookieBox* box = FindBox(cookieBoxId); + Url parsed{}; + if (box && ParseUrl(url, parsed) == 0) static_cast(StoreCookie(*box, parsed, cookie, Now())); +} + +void Http2AddCookieBox(int cookieBoxId) { + std::lock_guard lock(g_cookiesMutex); + g_cookieBoxes.try_emplace(cookieBoxId); } void Http2ForgetCookies(int cookieBoxId) { @@ -358,19 +397,9 @@ int APS5_VABI sceHttp2GetCookie(int cookieBoxId, const char* url, char* cookie, Url parsed{}; if (const int result = ParseUrl(url, parsed); result == ERROR_REJECTED) return ERROR_NOT_FOUND; else if (result != 0) return result; - RemoveExpired(*box, Now()); - std::vector matches; - for (const Cookie& stored : box->cookies) { - if (DomainMatches(parsed.host, stored.domain) && PathMatches(parsed.path, stored.path) && (!stored.secure || parsed.secure)) matches.push_back(&stored); - } + const auto matches = Matches(*box, parsed); if (matches.empty()) return ERROR_NOT_FOUND; if (!cookie && !required) return ERROR_INVALID_VALUE; - std::stable_sort(matches.begin(), matches.end(), [](const Cookie* left, const Cookie* right) { return left->path.size() > right->path.size(); }); - for (auto run = matches.begin(); run != matches.end();) { - const auto end = std::find_if(run, matches.end(), [&](const Cookie* match) { return match->path.size() != (*run)->path.size(); }); - std::rotate(run, run + 1, end); - run = end; - } std::string header; for (const Cookie* match : matches) AppendCookie(header, *match); const std::size_t size = header.size() + HEADER_SLACK; diff --git a/core/libs/prx/libSceHttp2/Cookies.hpp b/core/libs/prx/libSceHttp2/Cookies.hpp index 44a8587c3c..4983d98f01 100644 --- a/core/libs/prx/libSceHttp2/Cookies.hpp +++ b/core/libs/prx/libSceHttp2/Cookies.hpp @@ -1,5 +1,11 @@ #pragma once -bool Http2CookieBoxExists(int cookieBoxId); +#include +#include +#include + +void Http2AddCookieBox(int cookieBoxId); void Http2ForgetCookies(int cookieBoxId); void Http2FlushCookies(int cookieBoxId); +std::optional Http2CookieHeader(int cookieBoxId, std::string_view url); +void Http2StoreResponseCookie(int cookieBoxId, std::string_view url, std::string_view cookie); diff --git a/core/libs/prx/libSceHttp2/Export.cpp b/core/libs/prx/libSceHttp2/Export.cpp index 2f038c8e3d..01e551efe6 100644 --- a/core/libs/prx/libSceHttp2/Export.cpp +++ b/core/libs/prx/libSceHttp2/Export.cpp @@ -66,11 +66,6 @@ static void ReleaseOwnedHandles(int owner) { } } -bool Http2CookieBoxExists(int cookieBoxId) { - std::lock_guard lock(g_handlesMutex); - return FindHandle(cookieBoxId, Http2Kind::CookieBox) != nullptr; -} - static bool HasSettings(const Http2Handle& handle) { return handle.kind == Http2Kind::Template || handle.kind == Http2Kind::Request; } @@ -362,9 +357,15 @@ int APS5_VABI sceHttp2CreateCookieBox(std::size_t poolSize) { void* address = nullptr; if (const int result = sceKernelMapNamedFlexibleMemory(&address, mappedSize, 3, 0, "SceHttp2CookieBox"); result != 0) return result; std::shared_ptr memory(address, [mappedSize](void* mapped) { sceKernelMunmap(reinterpret_cast(mapped), mappedSize); }); - std::lock_guard lock(g_handlesMutex); const int id = g_nextHandle.fetch_add(1, std::memory_order_relaxed); - g_handles.emplace(id, Http2Handle{Http2Kind::CookieBox, 0, poolSize, std::move(memory)}); + Http2AddCookieBox(id); + try { + std::lock_guard lock(g_handlesMutex); + g_handles.emplace(id, Http2Handle{Http2Kind::CookieBox, 0, poolSize, std::move(memory)}); + } catch (...) { + Http2ForgetCookies(id); + throw; + } return id; } @@ -379,7 +380,11 @@ int APS5_VABI sceHttp2DeleteCookieBox(int cookie_box_id) { } int APS5_VABI sceHttp2SetCookieBox(int id, int cookie_box_id) { - return Update(id, __func__, [&](auto&) { if (cookie_box_id != 0) NotImplemented_nid_no_patch("sceHttp2SetCookieBox"); }); + return Update(id, __func__, [&](auto& settings) { + if (cookie_box_id != 0 && !FindHandle(cookie_box_id, Http2Kind::CookieBox)) return ERROR_INVALID_ID; + settings.cookieBox = cookie_box_id; + return 0; + }, true); } int APS5_VABI sceHttp2SetRequestNoContentLength(int id) { @@ -457,9 +462,14 @@ int APS5_VABI sceHttp2GetAutoRedirect(int id, int* enable) { }); } -int APS5_VABI sceHttp2GetCookieBox(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceHttp2GetCookieBox(int id, int* cookieBoxId) { + if (!cookieBoxId) return ERROR_NULL_POINTER; + *cookieBoxId = 0; + return Read(id, [&](const auto& settings) { + if (settings.cookieBox == 0) return ERROR_NOT_FOUND; + *cookieBoxId = settings.cookieBox; + return 0; + }); } int APS5_VABI sceHttp2RedirectCacheFlush(int lib_http2_ctx_id) { diff --git a/core/libs/prx/libSceHttp2/Transport.cpp b/core/libs/prx/libSceHttp2/Transport.cpp index 3eb7cc779b..1bbc39ed7f 100644 --- a/core/libs/prx/libSceHttp2/Transport.cpp +++ b/core/libs/prx/libSceHttp2/Transport.cpp @@ -8,6 +8,7 @@ #endif #include "Transport.hpp" +#include "Cookies.hpp" #include "prx/libSceHttp/include/Http.hpp" #include "zlib.h" @@ -320,6 +321,8 @@ struct Request::State { std::string contentEncoding; bool inflatedGzip = false; int blockStatus = 0; + bool hasAuthChallenge = false; + bool hasProxyAuthChallenge = false; std::string location; bool hasLocation = false; int networkResult = 0; @@ -360,6 +363,8 @@ struct Request::State { ~State() { Close(); } + std::string Address() const { return std::string(url.secure ? "https://" : "http://") + url.authority + url.path; } + void Close() { callbackLock = nullptr; if (session) nghttp2_session_del(std::exchange(session, nullptr)); @@ -650,7 +655,7 @@ struct Request::State { Require(sceHttpParseStatusLine(rawHeaders.data(), rawHeaders.size(), &major, &minor, &code, &reason, &reasonSize) >= 0 && major == 1 && minor >= 0 && minor <= 1, "sceHttp2: malformed or unsupported HTTP1 status line"); status = code; - hasLength = chunked = hasLocation = false; + hasLength = chunked = hasLocation = hasAuthChallenge = hasProxyAuthChallenge = false; contentEncoding.clear(); contentLength = 0; location.clear(); @@ -674,6 +679,12 @@ struct Request::State { Require(!hasLocation, "sceHttp2: duplicate HTTP1 Location"); hasLocation = true; location = value; + } else if (name == "set-cookie" && settings.cookieBox != 0) { + Http2StoreResponseCookie(settings.cookieBox, Address(), value); + } else if (name == "www-authenticate") { + hasAuthChallenge = true; + } else if (name == "proxy-authenticate") { + hasProxyAuthChallenge = true; } } if (status >= 100 && status < 200) { @@ -681,7 +692,8 @@ struct Request::State { return; } Require(status >= 200 && status <= 599 && !(hasLength && chunked), "sceHttp2: invalid or ambiguous HTTP1 framing"); - Require(!settings.auth || (status != 401 && status != 407), "sceHttp2: authentication is not implemented"); + const bool authenticationChallenge = (status == 401 && hasAuthChallenge) || (status == 407 && hasProxyAuthChallenge); + Require(!settings.auth || !authenticationChallenge, "sceHttp2: authentication is not implemented"); remaining = contentLength; ready = true; ended = method == "HEAD" || status == 204 || status == 304 || (hasLength && remaining == 0); @@ -697,7 +709,23 @@ struct Request::State { header += "Host: " + authority + "\r\nConnection: close\r\n"; if (!settings.userAgent.empty() && std::none_of(settings.headers.begin(), settings.headers.end(), [](const auto& field) { return field.first == "user-agent"; })) header += "User-Agent: " + settings.userAgent + "\r\n"; - for (const auto& [name, value] : settings.headers) if (name != "host") header += name + ": " + value + "\r\n"; + std::string cookies; + bool hasCookie = false; + for (const auto& [name, value] : settings.headers) { + if (name == "cookie") { + if (!cookies.empty() && !value.empty()) cookies += "; "; + cookies += value; + hasCookie = true; + } else if (name != "host") header += name + ": " + value + "\r\n"; + } + if (settings.cookieBox != 0) { + if (const auto stored = Http2CookieHeader(settings.cookieBox, Address())) { + if (!cookies.empty()) cookies += "; "; + cookies += *stored; + hasCookie = true; + } + } + if (hasCookie) header += "cookie: " + cookies + "\r\n"; const bool chunkUpload = settings.noContentLength && size; if (chunkUpload) header += "Transfer-Encoding: chunked\r\n"; else if (!settings.noContentLength) header += "Content-Length: " + std::to_string(size) + "\r\n"; @@ -784,6 +812,7 @@ struct Request::State { if (frame->hd.stream_id == state.stream) { Require(!state.ready, "sceHttp2: response trailers are not implemented"); state.blockStatus = 0; + state.hasAuthChallenge = state.hasProxyAuthChallenge = false; state.hasLength = false; state.contentLength = 0; state.location.clear(); @@ -803,6 +832,8 @@ struct Request::State { const std::string_view key(reinterpret_cast(name), nameSize); const std::string_view text(reinterpret_cast(value), valueSize); if (key == ":status") state.blockStatus = static_cast(Number(text)); + if (key == "www-authenticate") state.hasAuthChallenge = true; + if (key == "proxy-authenticate") state.hasProxyAuthChallenge = true; if (key == "location") { Require(!state.hasLocation, "sceHttp2: duplicate redirect location; console error mapping is unknown"); state.location = text; @@ -812,6 +843,7 @@ struct Request::State { state.contentLength = Number(text); state.hasLength = true; } + if (key == "set-cookie" && state.settings.cookieBox != 0) Http2StoreResponseCookie(state.settings.cookieBox, state.Address(), text); if (key == "content-encoding") state.contentEncoding = Lower(std::string(text)); }); } @@ -825,7 +857,8 @@ struct Request::State { if (frame->hd.stream_id != state.stream) return; if (frame->hd.type == NGHTTP2_HEADERS && state.blockStatus >= 200) { state.status = state.blockStatus; - Require(!state.settings.auth || (state.status != 401 && state.status != 407), "sceHttp2: authentication is not implemented"); + const bool authenticationChallenge = (state.status == 401 && state.hasAuthChallenge) || (state.status == 407 && state.hasProxyAuthChallenge); + Require(!state.settings.auth || !authenticationChallenge, "sceHttp2: authentication is not implemented"); state.ready = true; } if ((frame->hd.type == NGHTTP2_HEADERS || frame->hd.type == NGHTTP2_DATA) && @@ -894,6 +927,9 @@ struct Request::State { if (!settings.userAgent.empty() && std::none_of(settings.headers.begin(), settings.headers.end(), [](const auto& header) { return header.first == "user-agent"; })) fields.emplace_back("user-agent", settings.userAgent); for (const auto& header : settings.headers) if (header.first != "host") fields.push_back(header); + if (settings.cookieBox != 0) { + if (auto cookie = Http2CookieHeader(settings.cookieBox, Address())) fields.emplace_back("cookie", std::move(*cookie)); + } if (!settings.noContentLength) fields.emplace_back("content-length", std::to_string(settings.contentLength)); std::vector headers; for (auto& [name, value] : fields) headers.push_back({reinterpret_cast(name.data()), diff --git a/core/libs/prx/libSceHttp2/Transport.hpp b/core/libs/prx/libSceHttp2/Transport.hpp index 75462da340..151794c281 100644 --- a/core/libs/prx/libSceHttp2/Transport.hpp +++ b/core/libs/prx/libSceHttp2/Transport.hpp @@ -42,6 +42,7 @@ struct Settings { void* authInfoArgument = nullptr; void* preSendCallback = nullptr; void* preSendArgument = nullptr; + int cookieBox = 0; }; struct Capacity { diff --git a/core/libs/prx/libSceHttp2/include/Http2.hpp b/core/libs/prx/libSceHttp2/include/Http2.hpp index 500984a649..551a179707 100644 --- a/core/libs/prx/libSceHttp2/include/Http2.hpp +++ b/core/libs/prx/libSceHttp2/include/Http2.hpp @@ -82,7 +82,7 @@ int APS5_VABI sceHttp2CookieImport(void); int APS5_VABI sceHttp2GetAuthEnabled(int id, int* isEnable); int APS5_VABI sceHttp2GetAutoRedirect(int id, int* enable); int APS5_VABI sceHttp2GetCookie(int cookieBoxId, const char* url, char* cookie, std::size_t* required, std::size_t prepared); -int APS5_VABI sceHttp2GetCookieBox(void); +int APS5_VABI sceHttp2GetCookieBox(int id, int* cookieBoxId); int APS5_VABI sceHttp2GetCookieStats(int cookieBoxId, Http2CookieStats* stats); int APS5_VABI sceHttp2RedirectCacheFlush(int libHttp2CtxId); int APS5_VABI sceHttp2RemoveRequestHeader(int id, const char* name); diff --git a/core/libs/prx/libSceIcu/CMakeLists.txt b/core/libs/prx/libSceIcu/CMakeLists.txt index ef5d9da1b7..53aab7ebfb 100644 --- a/core/libs/prx/libSceIcu/CMakeLists.txt +++ b/core/libs/prx/libSceIcu/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceIcu SHARED EXCLUDE_FROM_ALL Comparisons.cpp ConstrainedFieldPosition.cpp Enumeration.cpp ErrorName.cpp Export.cpp Invariant.cpp Iterator.cpp Numeric.cpp Set.cpp Tokenize.cpp Unescape.cpp Version.cpp) +add_library(libSceIcu SHARED EXCLUDE_FROM_ALL Comparisons.cpp ConstrainedFieldPosition.cpp Enumeration.cpp ErrorName.cpp Export.cpp Invariant.cpp Iterator.cpp Numeric.cpp Set.cpp Tokenize.cpp Unescape.cpp Version.cpp Utf32.cpp JavaUtf8.cpp Compare.cpp Search.cpp Character.cpp Configuration.cpp) target_include_directories(libSceIcu PRIVATE ${LIBS_INCLUDE_DIR}) target_sources(libSceIcu PRIVATE RecoveredInventory.cpp) configure_windows_unwind(libSceIcu) @@ -9,6 +9,33 @@ target_sources(libSceIcu PRIVATE CompareIter.cpp) target_sources(libSceIcu PRIVATE Space.cpp) target_sources(libSceIcu PRIVATE Utf8.cpp) target_sources(libSceIcu PRIVATE Resource.cpp) +target_sources(libSceIcu PRIVATE Locale.cpp) + +if(BUILD_TESTING) + add_test_executable(guest_icu_locale_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Locale.cpp) + target_include_directories(guest_icu_locale_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_icu_locale_tests PRIVATE libSceIcu) + configure_windows_unwind(guest_icu_locale_tests) + add_test(NAME guest_icu_locale COMMAND guest_icu_locale_tests) + set_tests_properties(guest_icu_locale PROPERTIES TIMEOUT 10) + if(WIN32) + set_tests_properties(guest_icu_locale PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_icu_version_extents_tests ${LIBS_INCLUDE_DIR}/tests/GuestIcuVersionExtents.cpp) + target_include_directories(guest_icu_version_extents_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_icu_version_extents_tests PRIVATE libSceIcu libc) + configure_windows_unwind(guest_icu_version_extents_tests) + add_test(NAME guest_icu_version_extents COMMAND guest_icu_version_extents_tests) + set_tests_properties(guest_icu_version_extents PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_icu_version_extents PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() if(BUILD_TESTING) add_test_executable(guest_icu_utf8_tests ${LIBS_INCLUDE_DIR}/tests/GuestIcuUtf8.cpp) @@ -175,3 +202,81 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +if(BUILD_TESTING) + add_test_executable(guest_icu_utf32_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Utf32.cpp) + target_include_directories(guest_icu_utf32_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_icu_utf32_tests PRIVATE libSceIcu) + configure_windows_unwind(guest_icu_utf32_tests) + add_test(NAME guest_icu_utf32 COMMAND guest_icu_utf32_tests) + set_tests_properties(guest_icu_utf32 PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_icu_utf32 PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_icu_java_utf8_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/JavaUtf8.cpp) + target_include_directories(guest_icu_java_utf8_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_icu_java_utf8_tests PRIVATE libSceIcu) + configure_windows_unwind(guest_icu_java_utf8_tests) + add_test(NAME guest_icu_java_utf8 COMMAND guest_icu_java_utf8_tests) + set_tests_properties(guest_icu_java_utf8 PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_icu_java_utf8 PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_icu_compare_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Compare.cpp) + target_include_directories(guest_icu_compare_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_icu_compare_tests PRIVATE libSceIcu) + configure_windows_unwind(guest_icu_compare_tests) + add_test(NAME guest_icu_compare COMMAND guest_icu_compare_tests) + set_tests_properties(guest_icu_compare PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_icu_compare PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_icu_search_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Search.cpp) + target_include_directories(guest_icu_search_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_icu_search_tests PRIVATE libSceIcu) + configure_windows_unwind(guest_icu_search_tests) + add_test(NAME guest_icu_search COMMAND guest_icu_search_tests) + set_tests_properties(guest_icu_search PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_icu_search PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_icu_character_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Character.cpp) + target_include_directories(guest_icu_character_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_icu_character_tests PRIVATE libSceIcu) + configure_windows_unwind(guest_icu_character_tests) + add_test(NAME guest_icu_character COMMAND guest_icu_character_tests) + set_tests_properties(guest_icu_character PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_icu_character PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_icu_configuration_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Configuration.cpp) + target_include_directories(guest_icu_configuration_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_icu_configuration_tests PRIVATE libSceIcu) + configure_windows_unwind(guest_icu_configuration_tests) + add_test(NAME guest_icu_configuration COMMAND guest_icu_configuration_tests) + set_tests_properties(guest_icu_configuration PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_icu_configuration PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceIcu/Character.cpp b/core/libs/prx/libSceIcu/Character.cpp new file mode 100644 index 0000000000..42cf9f7187 --- /dev/null +++ b/core/libs/prx/libSceIcu/Character.cpp @@ -0,0 +1,27 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include "prx/libSceIcu/Status.hpp" +#include + +extern "C" { + +std::int8_t APS5_VABI u_isISOControl_nid_postfix(std::int32_t character) { + return static_cast(character) <= 0x9f && (character <= 0x1f || character >= 0x7f); +} + +std::int32_t APS5_VABI u_forDigit_nid_postfix(std::int32_t digit, std::int8_t radix) { + if (radix < 2 || radix > 36 || digit < 0 || digit >= radix) return 0; + return digit < 10 ? '0' + digit : 'a' - 10 + digit; +} + + +std::int32_t APS5_VABI u_getISOComment_nid_postfix(std::int32_t, char* destination, std::int32_t capacity, std::int32_t* status) { + if (!status || *status > 0) return 0; + if (capacity < 0 || (capacity > 0 && !destination)) { + *status = Icu::IllegalArgument; + return 0; + } + Icu::Terminate(destination, capacity, 0, status); + return 0; +} + +} diff --git a/core/libs/prx/libSceIcu/Character.hpp b/core/libs/prx/libSceIcu/Character.hpp new file mode 100644 index 0000000000..4a4c7f5ad9 --- /dev/null +++ b/core/libs/prx/libSceIcu/Character.hpp @@ -0,0 +1,9 @@ +#pragma once +#include "prx/libc/include/general/VabiMacros.hpp" +#include + +extern "C" { +std::int8_t APS5_VABI u_isISOControl_nid_postfix(std::int32_t); +std::int32_t APS5_VABI u_forDigit_nid_postfix(std::int32_t, std::int8_t); +std::int32_t APS5_VABI u_getISOComment_nid_postfix(std::int32_t, char*, std::int32_t, std::int32_t*); +} diff --git a/core/libs/prx/libSceIcu/Compare.cpp b/core/libs/prx/libSceIcu/Compare.cpp new file mode 100644 index 0000000000..64181e30ee --- /dev/null +++ b/core/libs/prx/libSceIcu/Compare.cpp @@ -0,0 +1,58 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include "prx/libSceIcu/Status.hpp" +#include + +namespace { + +bool Lead(std::int32_t unit) { return unit >= 0xd800 && unit <= 0xdbff; } +bool Trail(std::int32_t unit) { return unit >= 0xdc00 && unit <= 0xdfff; } + +std::int64_t Measure(const char16_t* text) { + std::int64_t length = 0; + while (text[length] != 0) ++length; + return length; +} + +std::int32_t CodePointOrder(const char16_t* text, std::int64_t index, std::int64_t length) { + std::int32_t unit = text[index]; + const bool pair = (Lead(unit) && (length < 0 || index + 1 < length) && Trail(text[index + 1])) || + (Trail(unit) && index > 0 && Lead(text[index - 1])); + return pair ? unit : unit - 0x2800; +} + +} + +extern "C" { + +std::int32_t APS5_VABI u_strCompare_nid_postfix(const char16_t* first, std::int32_t firstLength, const char16_t* second, + std::int32_t secondLength, std::int8_t codePointOrder) { + if (!first || firstLength < -1 || !second || secondLength < -1) return 0; + const bool terminated = firstLength < 0 && secondLength < 0; + std::int64_t limit1 = firstLength; + std::int64_t limit2 = secondLength; + std::int32_t equalResult = 0; + std::int64_t index = 0; + if (terminated) { + if (first == second) return 0; + for (; first[index] == second[index]; ++index) { + if (first[index] == 0) return 0; + } + } else { + if (limit1 < 0) limit1 = Measure(first); + if (limit2 < 0) limit2 = Measure(second); + const auto common = limit1 < limit2 ? limit1 : limit2; + equalResult = limit1 < limit2 ? -1 : limit1 > limit2 ? 1 : 0; + if (first == second) return equalResult; + for (; index < common && first[index] == second[index]; ++index) {} + if (index == common) return equalResult; + } + std::int32_t unit1 = first[index]; + std::int32_t unit2 = second[index]; + if (unit1 >= 0xd800 && unit2 >= 0xd800 && codePointOrder) { + unit1 = CodePointOrder(first, index, terminated ? -1 : limit1); + unit2 = CodePointOrder(second, index, terminated ? -1 : limit2); + } + return unit1 - unit2; +} + +} diff --git a/core/libs/prx/libSceIcu/Compare.hpp b/core/libs/prx/libSceIcu/Compare.hpp new file mode 100644 index 0000000000..683682388f --- /dev/null +++ b/core/libs/prx/libSceIcu/Compare.hpp @@ -0,0 +1,7 @@ +#pragma once +#include "prx/libc/include/general/VabiMacros.hpp" +#include + +extern "C" { +std::int32_t APS5_VABI u_strCompare_nid_postfix(const char16_t*, std::int32_t, const char16_t*, std::int32_t, std::int8_t); +} diff --git a/core/libs/prx/libSceIcu/Configuration.cpp b/core/libs/prx/libSceIcu/Configuration.cpp new file mode 100644 index 0000000000..ef325fdb53 --- /dev/null +++ b/core/libs/prx/libSceIcu/Configuration.cpp @@ -0,0 +1,88 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include "prx/libSceIcu/Status.hpp" +#include +#include +#include +#include + +namespace { + +struct MemoryFunctions { + const void* context = nullptr; + Icu::MemoryAllocate allocate = nullptr; + Icu::MemoryReallocate reallocate = nullptr; + Icu::MemoryFree release = nullptr; +}; + +std::mutex MemoryLock; +MemoryFunctions Memory; +std::mutex DirectoryLock; +std::string DataDirectory; +std::string TimeZoneDirectory; + +} + +extern "C" { + +void APS5_VABI u_setMutexFunctions_nid_postfix(const void*, const void*, const void*, const void*, const void*, std::int32_t* status) { + if (!Icu::Failure(status)) *status = Icu::UnsupportedError; +} + +void APS5_VABI u_setAtomicIncDecFunctions_nid_postfix(const void*, const void*, const void*, std::int32_t* status) { + if (!Icu::Failure(status)) *status = Icu::UnsupportedError; +} + + +void APS5_VABI u_setMemoryFunctions_nid_postfix(const void* context, Icu::MemoryAllocate allocate, Icu::MemoryReallocate reallocate, + Icu::MemoryFree release, std::int32_t* status) { + if (Icu::Failure(status)) return; + if (!allocate || !reallocate || !release) { + *status = Icu::IllegalArgument; + return; + } + const std::lock_guard lock(MemoryLock); + Memory = {context, allocate, reallocate, release}; +} + + +const char* APS5_VABI u_getDataDirectory_nid_postfix() { + const std::lock_guard lock(DirectoryLock); + return DataDirectory.c_str(); +} + +void APS5_VABI u_setDataDirectory_nid_postfix(const char* directory) { + std::string copy = directory ? directory : ""; + const std::lock_guard lock(DirectoryLock); + DataDirectory = std::move(copy); +} + + +const char* APS5_VABI u_getTimeZoneFilesDirectory_nid_postfix(std::int32_t* status) { + if (Icu::Failure(status)) return ""; + const std::lock_guard lock(DirectoryLock); + return TimeZoneDirectory.c_str(); +} + +void APS5_VABI u_setTimeZoneFilesDirectory_nid_postfix(const char* path, std::int32_t* status) { + if (Icu::Failure(status)) return; + const std::lock_guard lock(DirectoryLock); + TimeZoneDirectory.clear(); + if (!path) { + *status = Icu::IllegalArgument; + return; + } + TimeZoneDirectory = path; +} + + +void APS5_VABI u_cleanup_nid_postfix() { + { + const std::lock_guard lock(MemoryLock); + Memory = {}; + } + const std::lock_guard lock(DirectoryLock); + DataDirectory.clear(); + TimeZoneDirectory.clear(); +} + +} diff --git a/core/libs/prx/libSceIcu/Configuration.hpp b/core/libs/prx/libSceIcu/Configuration.hpp new file mode 100644 index 0000000000..e99344e378 --- /dev/null +++ b/core/libs/prx/libSceIcu/Configuration.hpp @@ -0,0 +1,21 @@ +#pragma once +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +namespace Icu { +using MemoryAllocate = void* (APS5_VABI*)(const void*, std::size_t); +using MemoryReallocate = void* (APS5_VABI*)(const void*, void*, std::size_t); +using MemoryFree = void (APS5_VABI*)(const void*, void*); +} // namespace Icu + +extern "C" { +void APS5_VABI u_setMutexFunctions_nid_postfix(const void*, const void*, const void*, const void*, const void*, std::int32_t*); +void APS5_VABI u_setAtomicIncDecFunctions_nid_postfix(const void*, const void*, const void*, std::int32_t*); +void APS5_VABI u_setMemoryFunctions_nid_postfix(const void*, Icu::MemoryAllocate, Icu::MemoryReallocate, Icu::MemoryFree, std::int32_t*); +const char* APS5_VABI u_getDataDirectory_nid_postfix(); +void APS5_VABI u_setDataDirectory_nid_postfix(const char*); +const char* APS5_VABI u_getTimeZoneFilesDirectory_nid_postfix(std::int32_t*); +void APS5_VABI u_setTimeZoneFilesDirectory_nid_postfix(const char*, std::int32_t*); +void APS5_VABI u_cleanup_nid_postfix(); +} diff --git a/core/libs/prx/libSceIcu/Export.cpp b/core/libs/prx/libSceIcu/Export.cpp index 2d705d1b23..8c28bed13e 100644 --- a/core/libs/prx/libSceIcu/Export.cpp +++ b/core/libs/prx/libSceIcu/Export.cpp @@ -42,10 +42,6 @@ int APS5_VABI UCNV_TO_U_CALLBACK_SUBSTITUTE_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI locale_getKeywordsStart_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI res_countArrayItems_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -118,11 +114,6 @@ int APS5_VABI u_charType_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI sceIcuUnknown_1SsridDRBYQ_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("1SsridDRBYQ", sceIcuUnknown_1SsridDRBYQ_nid_no_patch); - int APS5_VABI u_digit_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -143,11 +134,6 @@ int APS5_VABI u_foldCase_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI sceIcuUnknown_hI8JVrcGWhc_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("hI8JVrcGWhc", sceIcuUnknown_hI8JVrcGWhc_nid_no_patch); - int APS5_VABI u_getBidiPairedBracket_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -160,11 +146,6 @@ int APS5_VABI u_getCombiningClass_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI sceIcuUnknown_UmNS0P0_ovs_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("UmNS0P0-ovs", sceIcuUnknown_UmNS0P0_ovs_nid_no_patch); - int APS5_VABI u_getDefaultConverter_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -173,11 +154,6 @@ int APS5_VABI u_getFC_NFKC_Closure_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI sceIcuUnknown__kd2qcTMvHE_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("+kd2qcTMvHE", sceIcuUnknown__kd2qcTMvHE_nid_no_patch); - int APS5_VABI u_getIntPropertyMap_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -214,11 +190,6 @@ int APS5_VABI u_getPropertyValueName_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI sceIcuUnknown_5__ZFSRQ0_k_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("5+-ZFSRQ0+k", sceIcuUnknown_5__ZFSRQ0_k_nid_no_patch); - int APS5_VABI u_hasBinaryProperty_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -235,11 +206,6 @@ int APS5_VABI u_isIDStart_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI sceIcuUnknown_dzbKmf3Ycog_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("dzbKmf3Ycog", sceIcuUnknown_dzbKmf3Ycog_nid_no_patch); - int APS5_VABI u_isJavaIDPart_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -324,78 +290,18 @@ int APS5_VABI u_releaseDefaultConverter_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI sceIcuUnknown_yVobQd4t_iA_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("yVobQd4t+iA", sceIcuUnknown_yVobQd4t_iA_nid_no_patch); - -int APS5_VABI sceIcuUnknown_FNfP9dKTd5Q_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("FNfP9dKTd5Q", sceIcuUnknown_FNfP9dKTd5Q_nid_no_patch); - -int APS5_VABI sceIcuUnknown_TU6Yl8_mT3c_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("TU6Yl8+mT3c", sceIcuUnknown_TU6Yl8_mT3c_nid_no_patch); - -int APS5_VABI sceIcuUnknown_Wm0y_9IRE8I_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("Wm0y-9IRE8I", sceIcuUnknown_Wm0y_9IRE8I_nid_no_patch); - -int APS5_VABI sceIcuUnknown_xAo5lvA_W3o_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("xAo5lvA+W3o", sceIcuUnknown_xAo5lvA_W3o_nid_no_patch); - int APS5_VABI u_strCaseCompare_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI sceIcuUnknown_pgvyZZ_d1ck_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("pgvyZZ+d1ck", sceIcuUnknown_pgvyZZ_d1ck_nid_no_patch); - int APS5_VABI u_strFoldCase_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI sceIcuUnknown_OAFz3KoG_gA_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("OAFz3KoG-gA", sceIcuUnknown_OAFz3KoG_gA_nid_no_patch); - -int APS5_VABI sceIcuUnknown_6_NsFXHpWXc_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("6+NsFXHpWXc", sceIcuUnknown_6_NsFXHpWXc_nid_no_patch); - -int APS5_VABI sceIcuUnknown_q3mX36AQeC4_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("q3mX36AQeC4", sceIcuUnknown_q3mX36AQeC4_nid_no_patch); - -int APS5_VABI sceIcuUnknown_RIcNJWDYKhM_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("RIcNJWDYKhM", sceIcuUnknown_RIcNJWDYKhM_nid_no_patch); - int APS5_VABI u_strToLower_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI sceIcuUnknown_ZxnyBy8reV4_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("ZxnyBy8reV4", sceIcuUnknown_ZxnyBy8reV4_nid_no_patch); - -int APS5_VABI sceIcuUnknown_X7gbPHVoNEw_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("X7gbPHVoNEw", sceIcuUnknown_X7gbPHVoNEw_nid_no_patch); - int APS5_VABI u_strToUpper_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -408,16 +314,6 @@ int APS5_VABI u_strncasecmp_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI sceIcuUnknown_BsjbIVU27jQ_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("BsjbIVU27jQ", sceIcuUnknown_BsjbIVU27jQ_nid_no_patch); - -int APS5_VABI sceIcuUnknown_wezXBwKdjAY_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("wezXBwKdjAY", sceIcuUnknown_wezXBwKdjAY_nid_no_patch); - int APS5_VABI u_tolower() { NotImplemented_nid_no_patch(__func__); diff --git a/core/libs/prx/libSceIcu/Identified.hpp b/core/libs/prx/libSceIcu/Identified.hpp index 0be2f37e55..a5929da370 100644 --- a/core/libs/prx/libSceIcu/Identified.hpp +++ b/core/libs/prx/libSceIcu/Identified.hpp @@ -7,7 +7,7 @@ int APS5_VABI UCNV_FROM_U_CALLBACK_ESCAPE_nid_postfix(); int APS5_VABI UCNV_FROM_U_CALLBACK_SUBSTITUTE_nid_postfix(); int APS5_VABI UCNV_TO_U_CALLBACK_ESCAPE_nid_postfix(); int APS5_VABI UCNV_TO_U_CALLBACK_SUBSTITUTE_nid_postfix(); -int APS5_VABI locale_getKeywordsStart_nid_postfix(); +const char* APS5_VABI locale_getKeywordsStart_nid_postfix(const char* localeID); int APS5_VABI res_countArrayItems_nid_postfix(); int APS5_VABI res_getAlias_nid_postfix(); int APS5_VABI res_getArrayItem_nid_postfix(); diff --git a/core/libs/prx/libSceIcu/JavaUtf8.cpp b/core/libs/prx/libSceIcu/JavaUtf8.cpp new file mode 100644 index 0000000000..d3b04e7a92 --- /dev/null +++ b/core/libs/prx/libSceIcu/JavaUtf8.cpp @@ -0,0 +1,145 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include "prx/libSceIcu/Status.hpp" +#include +#include + +namespace { + +bool Continuation(std::uint8_t byte) { + return byte >= 0x80 && byte <= 0xbf; +} + +bool ThreeByteSecond(std::uint8_t lead, std::uint8_t byte) { + if (lead == 0xe0) return byte >= 0xa0 && byte <= 0xbf; + if (lead == 0xed) return byte >= 0x80 && byte <= 0x9f; + return Continuation(byte); +} + +bool FourByteSecond(std::uint8_t lead, std::uint8_t byte) { + if (lead == 0xf0) return byte >= 0x90 && byte <= 0xbf; + if (lead == 0xf4) return byte >= 0x80 && byte <= 0x8f; + return lead >= 0xf1 && lead <= 0xf3 && Continuation(byte); +} + +std::int64_t Skipped(const std::uint8_t* source, std::int64_t lead, std::int64_t length) { + const auto first = source[lead]; + const auto at = [&](std::int64_t offset) { return lead + offset < length ? source[lead + offset] : 0; }; + if (first >= 0xf0 && first <= 0xf4) { + if (lead + 1 >= length || !FourByteSecond(first, source[lead + 1])) return 1; + if (!Continuation(at(2))) return 2; + if (!Continuation(at(3))) return 3; + return 4; + } + if (first >= 0xe0 && first <= 0xef) { + if (lead + 1 >= length || !ThreeByteSecond(first, source[lead + 1])) return 1; + return 2; + } + return 1; +} + +int Encoded(char16_t unit) { + return unit != 0 && unit <= 0x7f ? 1 : unit <= 0x7ff ? 2 : 3; +} + +} + +extern "C" { + +char16_t* APS5_VABI u_strFromJavaModifiedUTF8WithSub_nid_postfix(char16_t* destination, std::int32_t capacity, std::int32_t* destinationLength, + const char* text, std::int32_t sourceLength, std::int32_t substitution, + std::int32_t* substitutions, std::int32_t* status) { + if (Icu::Failure(status)) return nullptr; + if ((!text && sourceLength != 0) || sourceLength < -1 || (!destination && capacity != 0) || capacity < 0 || + substitution > 0x10ffff || Icu::Surrogate(substitution)) { + *status = Icu::IllegalArgument; + return nullptr; + } + if (substitutions) *substitutions = 0; + const auto* source = reinterpret_cast(text); + const std::int64_t length = sourceLength < 0 ? static_cast(std::strlen(text)) : sourceLength; + std::int64_t written = 0; + std::int64_t extra = 0; + std::int32_t replaced = 0; + for (std::int64_t index = 0; index < length;) { + const auto lead = source[index]; + const bool writing = written < capacity; + char16_t unit = lead; + std::int64_t used = 1; + bool valid = true; + if (lead >= 0xc0 && lead <= 0xdf && index + 1 < length && Continuation(source[index + 1])) { + unit = static_cast(((lead & 0x1f) << 6) | (source[index + 1] & 0x3f)); + used = 2; + } else if (lead >= 0xe0 && lead <= 0xef && index + 2 < length && Continuation(source[index + 1]) && Continuation(source[index + 2])) { + unit = static_cast(((lead & 0x0f) << 12) | ((source[index + 1] & 0x3f) << 6) | (source[index + 2] & 0x3f)); + used = 3; + } else if (lead >= 0x80) { + valid = false; + } + if (valid) { + index += used; + if (writing) destination[written++] = unit; + else ++extra; + continue; + } + if (substitution < 0) { + *status = Icu::InvalidCharacter; + return nullptr; + } + index += Skipped(source, index, length); + ++replaced; + if (!writing) { + ++extra; + } else if (substitution <= 0xffff) { + destination[written++] = static_cast(substitution); + } else { + destination[written++] = static_cast(0xd7c0 + (substitution >> 10)); + if (written < capacity) destination[written++] = static_cast(0xdc00 + (substitution & 0x3ff)); + else ++extra; + } + } + const auto required = Icu::Checked(written + extra); + if (substitutions) *substitutions = replaced; + if (destinationLength) *destinationLength = required; + Icu::Terminate(destination, capacity, required, status); + return destination; +} + + +char* APS5_VABI u_strToJavaModifiedUTF8_nid_postfix(char* destination, std::int32_t capacity, std::int32_t* destinationLength, + const char16_t* source, std::int32_t sourceLength, std::int32_t* status) { + if (Icu::Failure(status)) return nullptr; + if ((!source && sourceLength != 0) || sourceLength < -1 || (!destination && capacity != 0) || capacity < 0) { + *status = Icu::IllegalArgument; + return nullptr; + } + std::int64_t length = sourceLength; + if (length < 0) for (length = 0; source[length] != 0; ++length) {} + std::int64_t written = 0; + std::int64_t extra = 0; + bool full = false; + for (std::int64_t index = 0; index < length; ++index) { + const auto unit = source[index]; + const auto size = Encoded(unit); + if (!full && capacity - written >= size) { + if (size == 1) { + destination[written++] = static_cast(unit); + } else if (size == 2) { + destination[written++] = static_cast(0xc0 | (unit >> 6)); + destination[written++] = static_cast(0x80 | (unit & 0x3f)); + } else { + destination[written++] = static_cast(0xe0 | (unit >> 12)); + destination[written++] = static_cast(0x80 | ((unit >> 6) & 0x3f)); + destination[written++] = static_cast(0x80 | (unit & 0x3f)); + } + } else { + full = true; + extra += size; + } + } + const auto required = Icu::Checked(written + extra); + if (destinationLength) *destinationLength = required; + Icu::Terminate(destination, capacity, required, status); + return destination; +} + +} diff --git a/core/libs/prx/libSceIcu/JavaUtf8.hpp b/core/libs/prx/libSceIcu/JavaUtf8.hpp new file mode 100644 index 0000000000..3b4043a6fb --- /dev/null +++ b/core/libs/prx/libSceIcu/JavaUtf8.hpp @@ -0,0 +1,8 @@ +#pragma once +#include "prx/libc/include/general/VabiMacros.hpp" +#include + +extern "C" { +char16_t* APS5_VABI u_strFromJavaModifiedUTF8WithSub_nid_postfix(char16_t*, std::int32_t, std::int32_t*, const char*, std::int32_t, std::int32_t, std::int32_t*, std::int32_t*); +char* APS5_VABI u_strToJavaModifiedUTF8_nid_postfix(char*, std::int32_t, std::int32_t*, const char16_t*, std::int32_t, std::int32_t*); +} diff --git a/core/libs/prx/libSceIcu/Locale.cpp b/core/libs/prx/libSceIcu/Locale.cpp new file mode 100644 index 0000000000..bc951aa45a --- /dev/null +++ b/core/libs/prx/libSceIcu/Locale.cpp @@ -0,0 +1,8 @@ +#include "prx/libSceIcu/Identified.hpp" +#include "prx/libc/include/General.hpp" +#include + +extern "C" const char* APS5_VABI locale_getKeywordsStart_nid_postfix(const char* localeID) { + if (!localeID) APS5_INVALID_ARG_EX; + return std::strchr(localeID, '@'); +} diff --git a/core/libs/prx/libSceIcu/Search.cpp b/core/libs/prx/libSceIcu/Search.cpp new file mode 100644 index 0000000000..71f2d084b0 --- /dev/null +++ b/core/libs/prx/libSceIcu/Search.cpp @@ -0,0 +1,57 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include +#include + +namespace { + +constexpr std::size_t Unbounded = static_cast(-1); + +bool Lead(char16_t unit) { return unit >= 0xd800 && unit <= 0xdbff; } +bool Trail(char16_t unit) { return unit >= 0xdc00 && unit <= 0xdfff; } + +std::size_t Measure(const char16_t* text) { + std::size_t length = 0; + while (text[length] != 0) ++length; + return length; +} + +bool Matches(const char16_t* text, std::size_t textLength, std::size_t index, const char16_t* sub, std::size_t subLength) { + for (std::size_t offset = 0; offset < subLength; ++offset) { + if (text[index + offset] != sub[offset]) return false; + } + if (Trail(text[index]) && index > 0 && Lead(text[index - 1])) return false; + const auto end = index + subLength; + return !(Lead(text[end - 1]) && end < textLength && Trail(text[end])); +} + +} + +extern "C" { + +char16_t* APS5_VABI u_strstr_nid_postfix(const char16_t* text, const char16_t* sub) { + if (!sub) return const_cast(text); + if (!text) return nullptr; + if (*sub == 0) return const_cast(text); + const auto subLength = Measure(sub); + for (std::size_t index = 0; text[index] != 0; ++index) { + std::size_t offset = 0; + while (offset < subLength && text[index + offset] == sub[offset]) ++offset; + if (offset == subLength && Matches(text, Unbounded, index, sub, subLength)) return const_cast(text + index); + if (offset < subLength && text[index + offset] == 0) return nullptr; + } + return nullptr; +} + +char16_t* APS5_VABI u_strrstr_nid_postfix(const char16_t* text, const char16_t* sub) { + if (!sub) return const_cast(text); + if (!text) return nullptr; + const auto subLength = Measure(sub); + if (subLength == 0) return const_cast(text); + const auto length = Measure(text); + for (std::size_t index = length < subLength ? 0 : length - subLength + 1; index-- > 0;) { + if (Matches(text, length, index, sub, subLength)) return const_cast(text + index); + } + return nullptr; +} + +} diff --git a/core/libs/prx/libSceIcu/Search.hpp b/core/libs/prx/libSceIcu/Search.hpp new file mode 100644 index 0000000000..dbe9e9ae67 --- /dev/null +++ b/core/libs/prx/libSceIcu/Search.hpp @@ -0,0 +1,8 @@ +#pragma once +#include "prx/libc/include/general/VabiMacros.hpp" +#include + +extern "C" { +char16_t* APS5_VABI u_strstr_nid_postfix(const char16_t*, const char16_t*); +char16_t* APS5_VABI u_strrstr_nid_postfix(const char16_t*, const char16_t*); +} diff --git a/core/libs/prx/libSceIcu/Status.hpp b/core/libs/prx/libSceIcu/Status.hpp new file mode 100644 index 0000000000..b0beb517e1 --- /dev/null +++ b/core/libs/prx/libSceIcu/Status.hpp @@ -0,0 +1,42 @@ +#pragma once +#include "prx/libc/include/General.hpp" +#include + +namespace Icu { + +constexpr std::int32_t IllegalArgument = 1; +constexpr std::int32_t InvalidCharacter = 10; +constexpr std::int32_t BufferOverflow = 15; +constexpr std::int32_t UnsupportedError = 16; +constexpr std::int32_t NotTerminated = -124; + +inline void RequireStatus(const std::int32_t* status) { + if (!status) NotImplemented_nid_no_patch("libSceIcu: null UErrorCode"); +} + +inline bool Failure(const std::int32_t* status) { + RequireStatus(status); + return *status > 0; +} + +template +void Terminate(T* destination, std::int32_t capacity, std::int32_t length, std::int32_t* status) { + if (*status > 0 || length < 0) return; + if (length < capacity) { + destination[length] = 0; + if (*status == NotTerminated) *status = 0; + } else { + *status = length == capacity ? NotTerminated : BufferOverflow; + } +} + +inline bool Surrogate(std::int32_t value) { + return (static_cast(value) & 0xfffff800u) == 0xd800u; +} + +inline std::int32_t Checked(std::int64_t length) { + if (length > INT32_MAX) NotImplemented_nid_no_patch("libSceIcu: length exceeds INT32_MAX"); + return static_cast(length); +} + +} diff --git a/core/libs/prx/libSceIcu/Utf32.cpp b/core/libs/prx/libSceIcu/Utf32.cpp new file mode 100644 index 0000000000..d0daa9cd7e --- /dev/null +++ b/core/libs/prx/libSceIcu/Utf32.cpp @@ -0,0 +1,117 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include "prx/libSceIcu/Status.hpp" +#include + +namespace { + +constexpr std::int32_t NoSubstitution = -1; + +bool Basic(std::int32_t value) { + return (value >= 0 && value < 0xd800) || (value >= 0xe000 && value <= 0xffff); +} + +bool ValidArguments(const void* source, std::int32_t sourceLength, const void* destination, std::int32_t capacity, std::int32_t substitution) { + return !((!source && sourceLength != 0) || sourceLength < -1 || capacity < 0 || (!destination && capacity > 0) || + substitution > 0x10ffff || Icu::Surrogate(substitution)); +} + +} + +extern "C" { + +char16_t* APS5_VABI u_strFromUTF32WithSub_nid_postfix(char16_t* destination, std::int32_t capacity, std::int32_t* destinationLength, + const std::int32_t* source, std::int32_t sourceLength, std::int32_t substitution, + std::int32_t* substitutions, std::int32_t* status) { + if (Icu::Failure(status)) return nullptr; + if (!ValidArguments(source, sourceLength, destination, capacity, substitution)) { + *status = Icu::IllegalArgument; + return nullptr; + } + if (substitutions) *substitutions = 0; + std::int64_t length = sourceLength; + if (length < 0) for (length = 0; source[length] != 0; ++length) {} + std::int64_t written = 0; + std::int64_t overflow = 0; + std::int32_t replaced = 0; + const auto put = [&](char16_t unit) { + if (written < capacity) destination[written++] = unit; + else ++overflow; + }; + for (std::int64_t index = 0; index < length; ++index) { + auto value = source[index]; + for (;;) { + if (Basic(value)) { + put(static_cast(value)); + } else if (value >= 0x10000 && value <= 0x10ffff) { + if (written + 2 <= capacity) { + destination[written++] = static_cast(0xd7c0 + (value >> 10)); + destination[written++] = static_cast(0xdc00 + (value & 0x3ff)); + } else { + overflow += 2; + } + } else if ((value = substitution) < 0) { + *status = Icu::InvalidCharacter; + return nullptr; + } else { + ++replaced; + continue; + } + break; + } + } + const auto required = Icu::Checked(written + overflow); + if (destinationLength) *destinationLength = required; + if (substitutions) *substitutions = replaced; + Icu::Terminate(destination, capacity, required, status); + return destination; +} + +char16_t* APS5_VABI u_strFromUTF32_nid_postfix(char16_t* destination, std::int32_t capacity, std::int32_t* destinationLength, + const std::int32_t* source, std::int32_t sourceLength, std::int32_t* status) { + return u_strFromUTF32WithSub_nid_postfix(destination, capacity, destinationLength, source, sourceLength, NoSubstitution, nullptr, status); +} + + +std::int32_t* APS5_VABI u_strToUTF32WithSub_nid_postfix(std::int32_t* destination, std::int32_t capacity, std::int32_t* destinationLength, + const char16_t* source, std::int32_t sourceLength, std::int32_t substitution, + std::int32_t* substitutions, std::int32_t* status) { + if (Icu::Failure(status)) return nullptr; + if (!ValidArguments(source, sourceLength, destination, capacity, substitution)) { + *status = Icu::IllegalArgument; + return nullptr; + } + if (substitutions) *substitutions = 0; + std::int64_t length = sourceLength; + if (length < 0) for (length = 0; source[length] != 0; ++length) {} + std::int64_t written = 0; + std::int64_t overflow = 0; + std::int32_t replaced = 0; + for (std::int64_t index = 0; index < length; ++index) { + std::int32_t value = source[index]; + if (Icu::Surrogate(value)) { + if (value < 0xdc00 && index + 1 < length && source[index + 1] >= 0xdc00 && source[index + 1] <= 0xdfff) { + value = 0x10000 + ((value - 0xd800) << 10) + (source[index + 1] - 0xdc00); + ++index; + } else if ((value = substitution) < 0) { + *status = Icu::InvalidCharacter; + return nullptr; + } else { + ++replaced; + } + } + if (written < capacity) destination[written++] = value; + else ++overflow; + } + const auto required = Icu::Checked(written + overflow); + if (destinationLength) *destinationLength = required; + if (substitutions) *substitutions = replaced; + Icu::Terminate(destination, capacity, required, status); + return destination; +} + +std::int32_t* APS5_VABI u_strToUTF32_nid_postfix(std::int32_t* destination, std::int32_t capacity, std::int32_t* destinationLength, + const char16_t* source, std::int32_t sourceLength, std::int32_t* status) { + return u_strToUTF32WithSub_nid_postfix(destination, capacity, destinationLength, source, sourceLength, NoSubstitution, nullptr, status); +} + +} diff --git a/core/libs/prx/libSceIcu/Utf32.hpp b/core/libs/prx/libSceIcu/Utf32.hpp new file mode 100644 index 0000000000..7b0b06ad94 --- /dev/null +++ b/core/libs/prx/libSceIcu/Utf32.hpp @@ -0,0 +1,10 @@ +#pragma once +#include "prx/libc/include/general/VabiMacros.hpp" +#include + +extern "C" { +char16_t* APS5_VABI u_strFromUTF32_nid_postfix(char16_t*, std::int32_t, std::int32_t*, const std::int32_t*, std::int32_t, std::int32_t*); +char16_t* APS5_VABI u_strFromUTF32WithSub_nid_postfix(char16_t*, std::int32_t, std::int32_t*, const std::int32_t*, std::int32_t, std::int32_t, std::int32_t*, std::int32_t*); +std::int32_t* APS5_VABI u_strToUTF32_nid_postfix(std::int32_t*, std::int32_t, std::int32_t*, const char16_t*, std::int32_t, std::int32_t*); +std::int32_t* APS5_VABI u_strToUTF32WithSub_nid_postfix(std::int32_t*, std::int32_t, std::int32_t*, const char16_t*, std::int32_t, std::int32_t, std::int32_t*, std::int32_t*); +} diff --git a/core/libs/prx/libSceIcu/libSceIcu.hpp b/core/libs/prx/libSceIcu/libSceIcu.hpp index 4bef312695..7d905d4d29 100644 --- a/core/libs/prx/libSceIcu/libSceIcu.hpp +++ b/core/libs/prx/libSceIcu/libSceIcu.hpp @@ -20,3 +20,9 @@ #include "prx/libSceIcu/include/Api.hpp" #include "Api.hpp" +#include "Utf32.hpp" +#include "JavaUtf8.hpp" +#include "Compare.hpp" +#include "Search.hpp" +#include "Character.hpp" +#include "Configuration.hpp" diff --git a/core/libs/prx/libSceIcu/tests/Character.cpp b/core/libs/prx/libSceIcu/tests/Character.cpp new file mode 100644 index 0000000000..10a7997e2a --- /dev/null +++ b/core/libs/prx/libSceIcu/tests/Character.cpp @@ -0,0 +1,91 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "ICU character check failed at line %u\n", location.line()); + std::abort(); +} + +void IsoControl() { + for (std::int32_t value = -0x1000; value <= 0x110010; ++value) { + const bool expected = (value >= 0 && value <= 0x1f) || (value >= 0x7f && value <= 0x9f); + Require((u_isISOControl_nid_postfix(value) != 0) == expected); + } + Require(u_isISOControl_nid_postfix(0x20) == 0 && u_isISOControl_nid_postfix(0x7e) == 0 && u_isISOControl_nid_postfix(0xa0) == 0); + Require(u_isISOControl_nid_postfix(0x85) != 0 && u_isISOControl_nid_postfix(0x7f) != 0 && u_isISOControl_nid_postfix(0) != 0); + Require(u_isISOControl_nid_postfix(std::numeric_limits::min()) == 0); + Require(u_isISOControl_nid_postfix(std::numeric_limits::max()) == 0); + Require(u_isISOControl_nid_postfix(-1) == 0); + Require(u_isISOControl_nid_postfix(0x2028) == 0); +} + +void ForDigit() { + for (int radix = -128; radix <= 127; ++radix) { + for (std::int32_t digit = -3; digit <= 40; ++digit) { + std::int32_t expected = 0; + if (radix >= 2 && radix <= 36 && digit >= 0 && digit < radix) expected = digit < 10 ? '0' + digit : 'a' + (digit - 10); + Require(u_forDigit_nid_postfix(digit, static_cast(radix)) == expected); + } + } + Require(u_forDigit_nid_postfix(0, 10) == '0' && u_forDigit_nid_postfix(9, 10) == '9'); + Require(u_forDigit_nid_postfix(10, 10) == 0); + Require(u_forDigit_nid_postfix(10, 16) == 'a' && u_forDigit_nid_postfix(15, 16) == 'f'); + Require(u_forDigit_nid_postfix(35, 36) == 'z' && u_forDigit_nid_postfix(36, 36) == 0); + Require(u_forDigit_nid_postfix(1, 2) == '1' && u_forDigit_nid_postfix(2, 2) == 0); + Require(u_forDigit_nid_postfix(0, 1) == 0 && u_forDigit_nid_postfix(0, 37) == 0 && u_forDigit_nid_postfix(0, 0) == 0); + Require(u_forDigit_nid_postfix(-1, 10) == 0); + Require(u_forDigit_nid_postfix(std::numeric_limits::min(), 36) == 0); + Require(u_forDigit_nid_postfix(std::numeric_limits::max(), 36) == 0); +} + +void IsoComment() { + constexpr std::int32_t Illegal = 1; + constexpr std::int32_t NotTerminated = -124; + for (std::int32_t character : {0, 0x41, 0xa0, 0x10ffff, -1}) { + char buffer[4] = {'x', 'y', 'z', 'w'}; + std::int32_t status = 0; + Require(u_getISOComment_nid_postfix(character, buffer, 4, &status) == 0); + Require(status == 0 && buffer[0] == 0 && buffer[1] == 'y' && buffer[3] == 'w'); + } + char buffer[2] = {'x', 'y'}; + std::int32_t status = 0; + Require(u_getISOComment_nid_postfix(0x41, buffer, 1, &status) == 0 && status == 0 && buffer[0] == 0 && buffer[1] == 'y'); + buffer[0] = 'x'; + status = 0; + Require(u_getISOComment_nid_postfix(0x41, buffer, 0, &status) == 0 && status == NotTerminated && buffer[0] == 'x'); + status = 0; + Require(u_getISOComment_nid_postfix(0x41, nullptr, 0, &status) == 0 && status == NotTerminated); + status = NotTerminated; + Require(u_getISOComment_nid_postfix(0x41, buffer, 2, &status) == 0 && status == 0 && buffer[0] == 0); + status = -5; + buffer[0] = 'x'; + Require(u_getISOComment_nid_postfix(0x41, buffer, 2, &status) == 0 && status == -5 && buffer[0] == 0); + status = 0; + buffer[0] = 'x'; + Require(u_getISOComment_nid_postfix(0x41, buffer, -1, &status) == 0 && status == Illegal && buffer[0] == 'x'); + status = 0; + Require(u_getISOComment_nid_postfix(0x41, nullptr, 1, &status) == 0 && status == Illegal); + for (std::int32_t incoming : {Illegal, 7, 15}) { + status = incoming; + buffer[0] = 'x'; + Require(u_getISOComment_nid_postfix(0x41, buffer, 2, &status) == 0 && status == incoming && buffer[0] == 'x'); + } + Require(u_getISOComment_nid_postfix(0x41, buffer, 2, nullptr) == 0 && buffer[0] == 'x'); +} + +} + +int main() { + IsoControl(); + ForDigit(); + IsoComment(); + std::puts("ICU character tests passed"); +} diff --git a/core/libs/prx/libSceIcu/tests/Compare.cpp b/core/libs/prx/libSceIcu/tests/Compare.cpp new file mode 100644 index 0000000000..8847e86c11 --- /dev/null +++ b/core/libs/prx/libSceIcu/tests/Compare.cpp @@ -0,0 +1,113 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "ICU string comparison check failed at line %u\n", location.line()); + std::abort(); +} + +std::int32_t Compare(const std::u16string& first, std::int32_t firstLength, const std::u16string& second, std::int32_t secondLength, bool codePointOrder) { + return u_strCompare_nid_postfix(first.c_str(), firstLength, second.c_str(), secondLength, codePointOrder); +} + +std::int32_t Terminated(const std::u16string& first, const std::u16string& second, bool codePointOrder) { + return Compare(first, -1, second, -1, codePointOrder); +} + +std::int32_t Counted(const std::u16string& first, const std::u16string& second, bool codePointOrder) { + return Compare(first, static_cast(first.size()), second, static_cast(second.size()), codePointOrder); +} + +void Plain() { + for (bool order : {false, true}) { + Require(Terminated(u"abc", u"abc", order) == 0); + Require(Counted(u"abc", u"abc", order) == 0); + Require(Terminated(u"abc", u"abd", order) == -1); + Require(Terminated(u"abd", u"abc", order) == 1); + Require(Terminated(u"ab", u"abc", order) == -u'c'); + Require(Terminated(u"abc", u"ab", order) == u'c'); + Require(Terminated(u"", u"", order) == 0); + Require(Terminated(u"a", u"", order) == u'a'); + Require(Counted(u"ab", u"abc", order) == -1); + Require(Counted(u"abc", u"ab", order) == 1); + Require(Counted(u"", u"a", order) == -1); + Require(Counted(u"x", u"abc", order) == u'x' - u'a'); + Require(Compare(u"ab", -1, u"abc", 3, order) == -1); + Require(Compare(u"abc", 3, u"ab", -1, order) == 1); + Require(Compare(u"abc", 2, u"abd", -1, order) == -1); + Require(Compare(u"abc", 2, u"ab", -1, order) == 0); + Require(Compare(u"abcd", 3, u"abce", 3, order) == 0); + Require(Compare(u"abcd", 4, u"abce", 3, order) == 1); + const std::u16string first({0x41, 0, 0x42}); + const std::u16string second({0x41, 0, 0x43}); + Require(Compare(first, 3, second, 3, order) == -1); + Require(Compare(first, 1, second, 1, order) == 0); + Require(Terminated(first, second, order) == 0); + Require(u_strCompare_nid_postfix(first.c_str(), -1, first.c_str(), -1, order) == 0); + Require(u_strCompare_nid_postfix(first.c_str(), 1, first.c_str(), 3, order) == -1); + Require(u_strCompare_nid_postfix(first.c_str(), 3, first.c_str(), 1, order) == 1); + Require(u_strCompare_nid_postfix(first.c_str(), 3, first.c_str(), 3, order) == 0); + } +} + +void Surrogates() { + const std::u16string bmp({0xe000}); + const std::u16string supplementary({0xd800, 0xdc00}); + for (bool terminated : {false, true}) { + const auto compare = [&](const std::u16string& first, const std::u16string& second, bool order) { + return terminated ? Terminated(first, second, order) : Counted(first, second, order); + }; + Require(compare(bmp, supplementary, false) == 0xe000 - 0xd800); + Require(compare(bmp, supplementary, true) == 0xe000 - 0x2800 - 0xd800); + Require(compare(supplementary, bmp, true) == 0xd800 - (0xe000 - 0x2800)); + + const std::u16string loneLead({0xd900}); + Require(compare(loneLead, supplementary, false) == 0xd900 - 0xd800); + Require(compare(loneLead, supplementary, true) == 0xd900 - 0x2800 - 0xd800); + + const std::u16string loneTrail({0xdc00}); + Require(compare(loneTrail, supplementary, false) == 0xdc00 - 0xd800); + Require(compare(loneTrail, supplementary, true) == 0xdc00 - 0x2800 - 0xd800); + + Require(compare(std::u16string({0xd800, 0xdc00}), std::u16string({0xd800, 0xdc01}), true) == -1); + Require(compare(std::u16string({0xd800, 0xdc00}), std::u16string({0xd801, 0xdc00}), true) == -1); + Require(compare(std::u16string({0xd800, 0x41}), std::u16string({0xd800, 0xdc00}), true) == 0x41 - 0xdc00); + Require(compare(std::u16string({0xdc00, 0xdc00}), std::u16string({0xdc00, 0xe000}), true) == 0xdc00 - 0x2800 - (0xe000 - 0x2800)); + Require(compare(std::u16string({0xd800, 0xdc00}), std::u16string({0xdbff, 0xdfff}), true) == 0xd800 - 0xdbff); + Require(compare(std::u16string({0x41, 0xd7ff}), std::u16string({0x41, 0xe000}), true) == 0xd7ff - 0xe000); + Require(compare(std::u16string({0xe000}), std::u16string({0xffff}), true) == 0xe000 - 0xffff); + Require(compare(std::u16string({0xd800}), std::u16string({0xdfff}), true) == 0xd800 - 0xdfff); + Require(compare(std::u16string({0xdfff}), std::u16string({0xd800}), false) == 0xdfff - 0xd800); + } + Require(Counted(std::u16string({0xd800, 0xdc00}), std::u16string({0xd800}), true) == 1); + Require(Counted(std::u16string({0xd800}), std::u16string({0xd800, 0xdc00}), true) == -1); + Require(Compare(std::u16string({0xd900, 0xdc00}), 1, std::u16string({0xd800, 0xdc00}), 2, true) == 0xd900 - 0x2800 - 0xd800); + Require(Compare(std::u16string({0xd900, 0xdc00}), 2, std::u16string({0xd800, 0xdc00}), 2, true) == 0xd900 - 0xd800); +} + +void Arguments() { + const std::u16string text(u"a"); + Require(u_strCompare_nid_postfix(nullptr, 1, text.c_str(), 1, 0) == 0); + Require(u_strCompare_nid_postfix(text.c_str(), 1, nullptr, 1, 0) == 0); + Require(u_strCompare_nid_postfix(nullptr, 0, nullptr, 0, 0) == 0); + Require(u_strCompare_nid_postfix(text.c_str(), -2, text.c_str(), 1, 0) == 0); + Require(u_strCompare_nid_postfix(text.c_str(), 1, text.c_str(), std::numeric_limits::min(), 0) == 0); + Require(u_strCompare_nid_postfix(text.c_str(), -2, u"b", 1, 1) == 0); +} + +} + +int main() { + Plain(); + Surrogates(); + Arguments(); + std::puts("ICU string comparison tests passed"); +} diff --git a/core/libs/prx/libSceIcu/tests/Configuration.cpp b/core/libs/prx/libSceIcu/tests/Configuration.cpp new file mode 100644 index 0000000000..32549a95da --- /dev/null +++ b/core/libs/prx/libSceIcu/tests/Configuration.cpp @@ -0,0 +1,246 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr std::int32_t Unsupported = 16; + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "ICU configuration check failed at line %u\n", location.line()); + std::abort(); +} + +template +void RequireThrows(TAction action) { + try { action(); } catch (const std::exception&) { return; } + Require(false); +} + +void Locking() { + int marker = 0; + for (std::int32_t incoming : {0, -128, -124, -1}) { + auto status = incoming; + u_setMutexFunctions_nid_postfix(&marker, nullptr, nullptr, nullptr, nullptr, &status); + Require(status == Unsupported); + status = incoming; + u_setMutexFunctions_nid_postfix(&marker, &marker, &marker, &marker, &marker, &status); + Require(status == Unsupported); + status = incoming; + u_setAtomicIncDecFunctions_nid_postfix(&marker, nullptr, nullptr, &status); + Require(status == Unsupported); + status = incoming; + u_setAtomicIncDecFunctions_nid_postfix(nullptr, &marker, &marker, &status); + Require(status == Unsupported); + } + for (std::int32_t incoming : {1, 7, 10, 15, 16, 27}) { + auto status = incoming; + u_setMutexFunctions_nid_postfix(&marker, &marker, &marker, &marker, &marker, &status); + Require(status == incoming); + u_setAtomicIncDecFunctions_nid_postfix(&marker, &marker, &marker, &status); + Require(status == incoming); + } + RequireThrows([&] { u_setMutexFunctions_nid_postfix(&marker, nullptr, nullptr, nullptr, nullptr, nullptr); }); + RequireThrows([&] { u_setAtomicIncDecFunctions_nid_postfix(&marker, nullptr, nullptr, nullptr); }); +} + +void* APS5_VABI Allocate_nid_no_patch(const void*, std::size_t) { return nullptr; } +void* APS5_VABI Reallocate_nid_no_patch(const void*, void*, std::size_t) { return nullptr; } +void APS5_VABI Release_nid_no_patch(const void*, void*) {} + +void Memory() { + int marker = 0; + for (std::int32_t incoming : {0, -124, -1}) { + auto status = incoming; + u_setMemoryFunctions_nid_postfix(&marker, Allocate_nid_no_patch, Reallocate_nid_no_patch, Release_nid_no_patch, &status); + Require(status == incoming); + status = incoming; + u_setMemoryFunctions_nid_postfix(nullptr, Allocate_nid_no_patch, Reallocate_nid_no_patch, Release_nid_no_patch, &status); + Require(status == incoming); + } + for (int missing = 0; missing < 8; ++missing) { + std::int32_t status = 0; + u_setMemoryFunctions_nid_postfix(&marker, missing & 1 ? nullptr : Allocate_nid_no_patch, missing & 2 ? nullptr : Reallocate_nid_no_patch, + missing & 4 ? nullptr : Release_nid_no_patch, &status); + Require(status == (missing ? 1 : 0)); + } + std::int32_t status = -124; + u_setMemoryFunctions_nid_postfix(&marker, nullptr, nullptr, nullptr, &status); + Require(status == 1); + for (std::int32_t incoming : {1, 7, 16}) { + status = incoming; + u_setMemoryFunctions_nid_postfix(&marker, nullptr, nullptr, nullptr, &status); + Require(status == incoming); + } + RequireThrows([&] { u_setMemoryFunctions_nid_postfix(&marker, Allocate_nid_no_patch, Reallocate_nid_no_patch, Release_nid_no_patch, nullptr); }); + + std::vector threads; + std::vector results(8, 99); + for (std::size_t index = 0; index < results.size(); ++index) { + threads.emplace_back([&, index] { + std::int32_t local = 0; + for (int round = 0; round < 1000; ++round) { + local = 0; + u_setMemoryFunctions_nid_postfix(&marker, Allocate_nid_no_patch, Reallocate_nid_no_patch, Release_nid_no_patch, &local); + if (local != 0) break; + } + results[index] = local; + }); + } + for (auto& thread : threads) thread.join(); + for (auto value : results) Require(value == 0); +} + +void DataDirectory() { + Require(std::string(u_getDataDirectory_nid_postfix()) == ""); + u_setDataDirectory_nid_postfix("/app0/icu"); + Require(std::string(u_getDataDirectory_nid_postfix()) == "/app0/icu"); + u_setDataDirectory_nid_postfix(nullptr); + Require(std::string(u_getDataDirectory_nid_postfix()) == ""); + u_setDataDirectory_nid_postfix("/data"); + u_setDataDirectory_nid_postfix(""); + Require(std::string(u_getDataDirectory_nid_postfix()) == ""); + + char buffer[] = "/first/path"; + u_setDataDirectory_nid_postfix(buffer); + buffer[1] = 'X'; + Require(std::string(u_getDataDirectory_nid_postfix()) == "/first/path"); + Require(u_getDataDirectory_nid_postfix() == u_getDataDirectory_nid_postfix()); + u_setDataDirectory_nid_postfix("C:\\icu\\data/mixed"); + Require(std::string(u_getDataDirectory_nid_postfix()) == "C:\\icu\\data/mixed"); + u_setDataDirectory_nid_postfix(std::string(5000, 'd').c_str()); + Require(std::string(u_getDataDirectory_nid_postfix()) == std::string(5000, 'd')); + + std::vector threads; + for (int index = 0; index < 8; ++index) { + threads.emplace_back([index] { + const std::string value = "/thread/" + std::to_string(index); + for (int round = 0; round < 500; ++round) u_setDataDirectory_nid_postfix(value.c_str()); + }); + } + for (auto& thread : threads) thread.join(); + const std::string final = u_getDataDirectory_nid_postfix(); + Require(final.size() == 9 && final.starts_with("/thread/") && final[8] >= '0' && final[8] < '8'); + u_setDataDirectory_nid_postfix(nullptr); +} + +void TimeZoneDirectory() { + std::int32_t status = 0; + Require(std::string(u_getTimeZoneFilesDirectory_nid_postfix(&status)) == "" && status == 0); + u_setTimeZoneFilesDirectory_nid_postfix("/app0/tz", &status); + Require(status == 0 && std::string(u_getTimeZoneFilesDirectory_nid_postfix(&status)) == "/app0/tz" && status == 0); + + status = 7; + Require(std::string(u_getTimeZoneFilesDirectory_nid_postfix(&status)) == "" && status == 7); + u_setTimeZoneFilesDirectory_nid_postfix("/other", &status); + Require(status == 7); + status = 0; + Require(std::string(u_getTimeZoneFilesDirectory_nid_postfix(&status)) == "/app0/tz"); + + status = -124; + Require(std::string(u_getTimeZoneFilesDirectory_nid_postfix(&status)) == "/app0/tz" && status == -124); + u_setTimeZoneFilesDirectory_nid_postfix("/warn", &status); + Require(status == -124); + status = 0; + Require(std::string(u_getTimeZoneFilesDirectory_nid_postfix(&status)) == "/warn"); + + u_setTimeZoneFilesDirectory_nid_postfix(nullptr, &status); + Require(status == 1); + status = 0; + Require(std::string(u_getTimeZoneFilesDirectory_nid_postfix(&status)) == ""); + + u_setTimeZoneFilesDirectory_nid_postfix("/first", &status); + u_setTimeZoneFilesDirectory_nid_postfix("", &status); + Require(status == 0 && std::string(u_getTimeZoneFilesDirectory_nid_postfix(&status)) == ""); + + char buffer[] = "/copy/me"; + u_setTimeZoneFilesDirectory_nid_postfix(buffer, &status); + buffer[1] = 'X'; + Require(std::string(u_getTimeZoneFilesDirectory_nid_postfix(&status)) == "/copy/me"); + u_setTimeZoneFilesDirectory_nid_postfix("C:\\tz/mixed", &status); + Require(std::string(u_getTimeZoneFilesDirectory_nid_postfix(&status)) == "C:\\tz/mixed"); + Require(std::string(u_getDataDirectory_nid_postfix()) == ""); + u_setDataDirectory_nid_postfix("/data"); + Require(std::string(u_getTimeZoneFilesDirectory_nid_postfix(&status)) == "C:\\tz/mixed"); + u_setDataDirectory_nid_postfix(nullptr); + + std::vector threads; + for (int index = 0; index < 8; ++index) { + threads.emplace_back([index] { + const std::string value = "/zone/" + std::to_string(index); + std::int32_t local = 0; + for (int round = 0; round < 500; ++round) u_setTimeZoneFilesDirectory_nid_postfix(value.c_str(), &local); + Require(local == 0); + }); + } + for (auto& thread : threads) thread.join(); + const std::string final = u_getTimeZoneFilesDirectory_nid_postfix(&status); + Require(final.size() == 7 && final.starts_with("/zone/") && final[6] >= '0' && final[6] < '8'); + status = 0; + u_setTimeZoneFilesDirectory_nid_postfix(nullptr, &status); + + RequireThrows([&] { u_getTimeZoneFilesDirectory_nid_postfix(nullptr); }); + RequireThrows([&] { u_setTimeZoneFilesDirectory_nid_postfix("/x", nullptr); }); +} + +void Cleanup() { + std::int32_t status = 0; + u_cleanup_nid_postfix(); + Require(std::string(u_getDataDirectory_nid_postfix()) == ""); + Require(std::string(u_getTimeZoneFilesDirectory_nid_postfix(&status)) == "" && status == 0); + + u_setDataDirectory_nid_postfix("/app0/icu"); + u_setTimeZoneFilesDirectory_nid_postfix("/app0/tz", &status); + int marker = 0; + u_setMemoryFunctions_nid_postfix(&marker, Allocate_nid_no_patch, Reallocate_nid_no_patch, Release_nid_no_patch, &status); + Require(status == 0 && std::string(u_getDataDirectory_nid_postfix()) == "/app0/icu"); + u_cleanup_nid_postfix(); + Require(std::string(u_getDataDirectory_nid_postfix()) == ""); + Require(std::string(u_getTimeZoneFilesDirectory_nid_postfix(&status)) == "" && status == 0); + u_cleanup_nid_postfix(); + u_cleanup_nid_postfix(); + Require(std::string(u_getDataDirectory_nid_postfix()) == ""); + + u_setDataDirectory_nid_postfix("/again"); + u_setTimeZoneFilesDirectory_nid_postfix("/again/tz", &status); + u_setMemoryFunctions_nid_postfix(nullptr, Allocate_nid_no_patch, Reallocate_nid_no_patch, Release_nid_no_patch, &status); + Require(status == 0); + Require(std::string(u_getDataDirectory_nid_postfix()) == "/again"); + Require(std::string(u_getTimeZoneFilesDirectory_nid_postfix(&status)) == "/again/tz"); + u_cleanup_nid_postfix(); + + std::vector threads; + for (int index = 0; index < 4; ++index) { + threads.emplace_back([index] { + std::int32_t local = 0; + for (int round = 0; round < 300; ++round) { + if (index % 2) u_cleanup_nid_postfix(); + else u_setDataDirectory_nid_postfix("/busy"); + } + u_getTimeZoneFilesDirectory_nid_postfix(&local); + }); + } + for (auto& thread : threads) thread.join(); + u_cleanup_nid_postfix(); + Require(std::string(u_getDataDirectory_nid_postfix()) == ""); +} + +} + +int main() { + Locking(); + Memory(); + DataDirectory(); + TimeZoneDirectory(); + Cleanup(); + std::puts("ICU configuration tests passed"); +} diff --git a/core/libs/prx/libSceIcu/tests/JavaUtf8.cpp b/core/libs/prx/libSceIcu/tests/JavaUtf8.cpp new file mode 100644 index 0000000000..39fca834fd --- /dev/null +++ b/core/libs/prx/libSceIcu/tests/JavaUtf8.cpp @@ -0,0 +1,331 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr std::int32_t Illegal = 1; +constexpr std::int32_t Invalid = 10; +constexpr std::int32_t Overflow = 15; +constexpr std::int32_t NotTerminated = -124; +constexpr char16_t Fill = 0xa55a; + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "ICU Java modified UTF-8 check failed at line %u\n", location.line()); + std::abort(); +} + +template +void RequireThrows(TAction action) { + try { action(); } catch (const std::exception&) { return; } + Require(false); +} + +std::string Bytes(std::initializer_list values) { + std::string result; + for (auto value : values) result.push_back(static_cast(value)); + return result; +} + +struct FromResult { + std::u16string text; + std::int32_t length = -77; + std::int32_t substitutions = -77; + std::int32_t status = 0; + bool null = false; + char16_t after = 0; +}; + +FromResult From(const std::string& source, std::int32_t sourceLength, std::int32_t capacity, std::int32_t substitution, std::int32_t status = 0) { + std::array buffer; + buffer.fill(Fill); + FromResult result; + result.status = status; + char16_t* returned = u_strFromJavaModifiedUTF8WithSub_nid_postfix(buffer.data(), capacity, &result.length, source.c_str(), sourceLength, substitution, &result.substitutions, &result.status); + result.null = returned == nullptr; + if (returned) Require(returned == buffer.data()); + const auto size = capacity < 0 ? 0 : std::min(capacity, buffer.size()); + result.text.assign(buffer.data(), size); + result.after = size < buffer.size() ? buffer[size] : Fill; + return result; +} + +FromResult Whole(const std::string& source, std::int32_t substitution, std::int32_t capacity = 12) { + return From(source, static_cast(source.size()), capacity, substitution); +} + +void Check(const FromResult& result, std::u16string expected, std::int32_t substitutions) { + Require(!result.null && result.status == 0 && result.length == static_cast(expected.size()) && result.substitutions == substitutions); + Require(result.text.substr(0, expected.size()) == expected && result.text[expected.size()] == 0); +} + +void Valid() { + auto result = From("abc", -1, 8, -1); + Check(result, u"abc", 0); + Require(result.text[4] == Fill); + + Check(Whole(Bytes({0xc0, 0x80}), -1), std::u16string({0}), 0); + Check(Whole(Bytes({0x41, 0, 0x42}), -1), std::u16string({0x41, 0, 0x42}), 0); + Check(Whole(Bytes({0xe2, 0x82, 0xac, 0xc3, 0xa9, 0x7f, 0xdf, 0xbf, 0xef, 0xbf, 0xbf}), -1), + std::u16string({0x20ac, 0xe9, 0x7f, 0x7ff, 0xffff}), 0); + Check(Whole(Bytes({0xed, 0xa0, 0xbd, 0xed, 0xb8, 0x80}), -1), std::u16string({0xd83d, 0xde00}), 0); + Check(Whole(Bytes({0xed, 0xb8, 0x80, 0xed, 0xa0, 0xbd}), -1), std::u16string({0xde00, 0xd83d}), 0); + Check(Whole(Bytes({0xe0, 0x80, 0x80, 0xc1, 0xbf, 0xc0, 0x80}), -1), std::u16string({0, 0x7f, 0}), 0); + Check(From("", -1, 4, -1), u"", 0); + Check(From("", 0, 4, -1), u"", 0); +} + +void Capacity() { + const auto euro = Bytes({'a', 'b', 0xe2, 0x82, 0xac}); + auto result = Whole(euro, -1, 3); + Require(result.status == NotTerminated && result.length == 3 && result.after == Fill && result.text == std::u16string({'a', 'b', 0x20ac})); + result = Whole(euro, -1, 2); + Require(result.status == Overflow && result.length == 3 && result.text == u"ab"); + result = Whole(euro, -1, 0); + Require(result.status == Overflow && result.length == 3 && result.after == Fill); + result = From(euro, -1, 1, -1); + Require(result.status == Overflow && result.length == 3 && result.text == u"a"); + result = From("ab", -1, 2, -1); + Require(result.status == NotTerminated && result.length == 2 && result.after == Fill); + + std::int32_t length = -77; + std::int32_t status = 0; + Require(u_strFromJavaModifiedUTF8WithSub_nid_postfix(nullptr, 0, &length, euro.c_str(), 5, -1, nullptr, &status) == nullptr); + Require(status == Overflow && length == 3); + status = 0; + Require(u_strFromJavaModifiedUTF8WithSub_nid_postfix(nullptr, 0, nullptr, nullptr, 0, -1, nullptr, &status) == nullptr); + Require(status == NotTerminated); +} + +void Malformed() { + Check(Whole(Bytes({0xf0, 0x9f, 0x98, 0x80}), 0xfffd), std::u16string({0xfffd}), 1); + Check(Whole(Bytes({0xf0, 0x9f, 0x98, 0x41}), 0xfffd), std::u16string({0xfffd, 0x41}), 1); + Check(Whole(Bytes({0xf0, 0x9f, 0x41}), 0xfffd), std::u16string({0xfffd, 0x41}), 1); + Check(Whole(Bytes({0xf0, 0x80, 0x80, 0x80, 0x41}), 0xfffd), std::u16string({0xfffd, 0xfffd, 0xfffd, 0xfffd, 0x41}), 4); + Check(Whole(Bytes({0xf4, 0x90, 0x80, 0x80}), 0xfffd), std::u16string({0xfffd, 0xfffd, 0xfffd, 0xfffd}), 4); + Check(Whole(Bytes({0xf4, 0x8f, 0xbf, 0xbf}), 0xfffd), std::u16string({0xfffd}), 1); + Check(Whole(Bytes({0xf5, 0x80, 0x80, 0x80}), 0xfffd), std::u16string({0xfffd, 0xfffd, 0xfffd, 0xfffd}), 4); + Check(Whole(Bytes({0xff, 0x41}), 0xfffd), std::u16string({0xfffd, 0x41}), 1); + Check(Whole(Bytes({0x80, 0x41}), 0xfffd), std::u16string({0xfffd, 0x41}), 1); + Check(Whole(Bytes({0xe2, 0x82, 0x41}), 0xfffd), std::u16string({0xfffd, 0x41}), 1); + Check(Whole(Bytes({0xe2, 0x41}), 0xfffd), std::u16string({0xfffd, 0x41}), 1); + Check(Whole(Bytes({0xe0, 0x80, 0x41}), 0xfffd), std::u16string({0xfffd, 0xfffd, 0x41}), 2); + Check(Whole(Bytes({0xed, 0xa0}), 0xfffd), std::u16string({0xfffd, 0xfffd}), 2); + Check(Whole(Bytes({0xe2, 0x82}), 0xfffd), std::u16string({0xfffd}), 1); + Check(Whole(Bytes({0xe2}), 0xfffd), std::u16string({0xfffd}), 1); + Check(Whole(Bytes({0xc3}), 0xfffd), std::u16string({0xfffd}), 1); + Check(Whole(Bytes({0xc3, 0x41}), 0xfffd), std::u16string({0xfffd, 0x41}), 1); + Check(Whole(Bytes({0x41, 0xc3}), 0x3f), std::u16string({0x41, 0x3f}), 1); + Check(From(Bytes({0xe2, 0x82, 0xac, 0xe2, 0x82}), 4, 8, 0x3f), std::u16string({0x20ac, 0x3f}), 1); + Check(Whole(Bytes({0x80}), 0x1f600), std::u16string({0xd83d, 0xde00}), 1); + Check(Whole(Bytes({0xf0, 0x9f, 0x98, 0x80, 0x41}), 0), std::u16string({0, 0x41}), 1); + + auto result = Whole(Bytes({0x41, 0x80, 0x42}), -1); + Require(result.null && result.status == Invalid && result.length == -77 && result.substitutions == 0); + Require(result.text[0] == 0x41 && result.text[1] == Fill); + result = Whole(Bytes({0xf0, 0x9f, 0x98, 0x80}), -1); + Require(result.null && result.status == Invalid); + result = Whole(Bytes({0x41, 0x41, 0x80}), -1, 1); + Require(result.null && result.status == Invalid); + result = From(Bytes({0xc3}), -1, 8, -1); + Require(result.null && result.status == Invalid); +} + +void Preflight() { + auto result = Whole(Bytes({0x41, 0x80}), 0x1f600, 2); + Require(result.status == Overflow && result.length == 3 && result.substitutions == 1 && result.text == std::u16string({0x41, 0xd83d})); + + result = Whole(Bytes({0x41, 0x80, 0x80}), 0x1f600, 1); + Require(result.status == Overflow && result.length == 3 && result.substitutions == 2 && result.text == std::u16string({0x41})); + + result = Whole(Bytes({0x41, 0x80, 0x80, 0x80}), 0x1f600, 2); + Require(result.status == Overflow && result.length == 5 && result.substitutions == 3 && result.text == std::u16string({0x41, 0xd83d})); + + result = Whole(Bytes({0x41, 0x80, 0x80}), 0x3f, 1); + Require(result.status == Overflow && result.length == 3 && result.substitutions == 2); + + result = Whole(Bytes({0xe2, 0x82, 0xac, 0x61, 0xf0, 0x9f, 0x98, 0x80, 0xc0, 0x80}), 0x3f, 0); + Require(result.status == Overflow && result.length == 4 && result.substitutions == 1); + + result = Whole(Bytes({0xe2, 0x82, 0xac, 0x61}), 0x3f, 0); + Require(result.status == Overflow && result.length == 2 && result.substitutions == 0); + + result = Whole(Bytes({0xe2, 0x82}), 0x3f, 0); + Require(result.status == Overflow && result.length == 1 && result.substitutions == 1); +} + +void Arguments() { + const std::string text = "a"; + for (auto substitution : {0x110000, 0xd800, 0xdfff, 0x7fffffff}) { + const auto result = From(text, 1, 8, substitution); + Require(result.null && result.status == Illegal && result.length == -77 && result.substitutions == -77 && result.text[0] == Fill); + } + for (auto length : {-2, std::numeric_limits::min()}) { + const auto result = From(text, length, 8, -1); + Require(result.null && result.status == Illegal && result.length == -77); + } + Require(From(text, 1, -1, -1).status == Illegal); + std::int32_t status = 0; + std::int32_t length = -77; + std::array buffer{}; + Require(!u_strFromJavaModifiedUTF8WithSub_nid_postfix(buffer.data(), 4, &length, nullptr, 1, -1, nullptr, &status) && status == Illegal); + status = 0; + Require(!u_strFromJavaModifiedUTF8WithSub_nid_postfix(buffer.data(), 4, &length, nullptr, -1, -1, nullptr, &status) && status == Illegal); + status = 0; + Require(!u_strFromJavaModifiedUTF8WithSub_nid_postfix(nullptr, 1, &length, text.c_str(), 1, -1, nullptr, &status) && status == Illegal); + Require(length == -77); +} + +void Status() { + auto result = From("a", 1, 4, -1, Illegal); + Require(result.null && result.status == Illegal && result.text[0] == Fill && result.length == -77); + result = From("a", 1, 4, -1, NotTerminated); + Require(!result.null && result.status == 0 && result.text.substr(0, 2) == std::u16string({'a', 0})); + result = From("a", 1, 1, -1, NotTerminated); + Require(result.status == NotTerminated && result.length == 1); + result = From("ab", 2, 1, -1, NotTerminated); + Require(result.status == Overflow); + + std::array buffer{}; + std::int32_t length = 0; + RequireThrows([&] { u_strFromJavaModifiedUTF8WithSub_nid_postfix(buffer.data(), 4, &length, "a", 1, -1, nullptr, nullptr); }); +} + +constexpr char FillByte = 0x5a; + +struct ToResult { + std::string bytes; + std::int32_t length = -77; + std::int32_t status = 0; + bool null = false; + char after = 0; +}; + +ToResult To(const std::u16string& source, std::int32_t sourceLength, std::int32_t capacity, std::int32_t status = 0) { + std::array buffer; + buffer.fill(FillByte); + ToResult result; + result.status = status; + char* returned = u_strToJavaModifiedUTF8_nid_postfix(buffer.data(), capacity, &result.length, source.data(), sourceLength, &result.status); + result.null = returned == nullptr; + if (returned) Require(returned == buffer.data()); + const auto size = capacity < 0 ? 0 : std::min(capacity, buffer.size()); + result.bytes.assign(buffer.data(), size); + result.after = size < buffer.size() ? buffer[size] : FillByte; + return result; +} + +void CheckTo(const ToResult& result, const std::string& expected) { + Require(!result.null && result.status == 0 && result.length == static_cast(expected.size())); + Require(result.bytes.substr(0, expected.size()) == expected && result.bytes[expected.size()] == 0); +} + +void ToValid() { + CheckTo(To(u"abc", -1, 8), "abc"); + CheckTo(To(std::u16string({0}), 1, 8), Bytes({0xc0, 0x80})); + CheckTo(To(std::u16string({0x41, 0, 0x42}), 3, 8), Bytes({0x41, 0xc0, 0x80, 0x42})); + CheckTo(To(std::u16string({0x20ac, 0xe9, 0x7f, 0x80, 0x7ff, 0x800, 0xffff}), 7, 20), + Bytes({0xe2, 0x82, 0xac, 0xc3, 0xa9, 0x7f, 0xc2, 0x80, 0xdf, 0xbf, 0xe0, 0xa0, 0x80, 0xef, 0xbf, 0xbf})); + CheckTo(To(std::u16string({0xd83d, 0xde00}), -1, 8), Bytes({0xed, 0xa0, 0xbd, 0xed, 0xb8, 0x80})); + CheckTo(To(std::u16string({0xde00, 0xd83d}), 2, 8), Bytes({0xed, 0xb8, 0x80, 0xed, 0xa0, 0xbd})); + CheckTo(To(u"", -1, 4), ""); + CheckTo(To(u"", 0, 4), ""); +} + +void ToCapacity() { + const std::u16string text{0x61, 0x20ac, 0x62}; + auto result = To(text, 3, 5); + Require(result.status == NotTerminated && result.length == 5 && result.after == FillByte); + Require(result.bytes == Bytes({0x61, 0xe2, 0x82, 0xac, 0x62})); + result = To(text, 3, 4); + Require(result.status == Overflow && result.length == 5 && result.bytes == Bytes({0x61, 0xe2, 0x82, 0xac})); + result = To(text, 3, 3); + Require(result.status == Overflow && result.length == 5 && result.bytes == std::string("a") + FillByte + FillByte); + result = To(text, 3, 2); + Require(result.status == Overflow && result.length == 5 && result.bytes == std::string("a") + FillByte); + result = To(text, 3, 0); + Require(result.status == Overflow && result.length == 5 && result.after == FillByte); + result = To(std::u16string({0x61, 0x62, 0}), -1, 2); + Require(result.status == NotTerminated && result.length == 2 && result.bytes == "ab"); + result = To(std::u16string({0x61, 0, 0x62}), 3, 2); + Require(result.status == Overflow && result.length == 4 && result.bytes == std::string("a") + FillByte); + result = To(std::u16string({0xd83d, 0xde00}), 2, 5); + Require(result.status == Overflow && result.length == 6 && result.bytes == Bytes({0xed, 0xa0, 0xbd, FillByte, FillByte})); + + std::int32_t length = -77; + std::int32_t status = 0; + Require(u_strToJavaModifiedUTF8_nid_postfix(nullptr, 0, &length, text.data(), 3, &status) == nullptr); + Require(status == Overflow && length == 5); + status = 0; + Require(u_strToJavaModifiedUTF8_nid_postfix(nullptr, 0, nullptr, nullptr, 0, &status) == nullptr); + Require(status == NotTerminated); +} + +void ToArguments() { + const std::u16string text{0x41}; + for (auto length : {-2, std::numeric_limits::min()}) { + const auto result = To(text, length, 8); + Require(result.null && result.status == Illegal && result.length == -77 && result.bytes[0] == FillByte); + } + Require(To(text, 1, -1).status == Illegal); + std::int32_t status = 0; + std::int32_t length = -77; + std::array buffer{}; + Require(!u_strToJavaModifiedUTF8_nid_postfix(buffer.data(), 4, &length, nullptr, 1, &status) && status == Illegal); + status = 0; + Require(!u_strToJavaModifiedUTF8_nid_postfix(buffer.data(), 4, &length, nullptr, -1, &status) && status == Illegal); + status = 0; + Require(!u_strToJavaModifiedUTF8_nid_postfix(nullptr, 1, &length, text.data(), 1, &status) && status == Illegal); + Require(length == -77); + + auto result = To(text, 1, 4, Illegal); + Require(result.null && result.status == Illegal && result.bytes[0] == FillByte && result.length == -77); + result = To(text, 1, 4, NotTerminated); + Require(!result.null && result.status == 0 && result.bytes.substr(0, 2) == std::string("A\0", 2)); + result = To(text, 1, 1, NotTerminated); + Require(result.status == NotTerminated && result.length == 1); + result = To(std::u16string({0x41, 0x42}), 2, 1, NotTerminated); + Require(result.status == Overflow); + RequireThrows([&] { u_strToJavaModifiedUTF8_nid_postfix(buffer.data(), 4, &length, text.data(), 1, nullptr); }); +} + +void RoundTrip() { + for (std::uint32_t unit = 0; unit <= 0xffff; ++unit) { + const char16_t text[2] = {static_cast(unit), 0}; + std::array encoded; + std::int32_t encodedLength = 0; + std::int32_t status = 0; + Require(u_strToJavaModifiedUTF8_nid_postfix(encoded.data(), 8, &encodedLength, text, 1, &status) == encoded.data()); + Require(status == 0 && encodedLength == (unit != 0 && unit <= 0x7f ? 1 : unit <= 0x7ff ? 2 : 3)); + std::array decoded; + std::int32_t decodedLength = 0; + std::int32_t substitutions = -1; + Require(u_strFromJavaModifiedUTF8WithSub_nid_postfix(decoded.data(), 4, &decodedLength, encoded.data(), encodedLength, -1, &substitutions, &status) == decoded.data()); + Require(status == 0 && decodedLength == 1 && substitutions == 0 && decoded[0] == unit && decoded[1] == 0); + } +} + +} + +int main() { + Valid(); + Capacity(); + Malformed(); + Preflight(); + Arguments(); + Status(); + ToValid(); + ToCapacity(); + ToArguments(); + RoundTrip(); + std::puts("ICU Java modified UTF-8 tests passed"); +} diff --git a/core/libs/prx/libSceIcu/tests/Locale.cpp b/core/libs/prx/libSceIcu/tests/Locale.cpp new file mode 100644 index 0000000000..cc20c099d3 --- /dev/null +++ b/core/libs/prx/libSceIcu/tests/Locale.cpp @@ -0,0 +1,59 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include +#include +#include +#include +#include + +static_assert(std::is_same_v); + +namespace { + +void Check(const char* localeID, const char* expected) { + const char* actual = locale_getKeywordsStart_nid_postfix(localeID); + if (actual != expected) { + std::fprintf(stderr, "locale \"%s\": expected offset %td, got %td\n", localeID, expected - localeID, actual - localeID); + std::abort(); + } +} + +void CheckNullThrows() { + try { + locale_getKeywordsStart_nid_postfix(nullptr); + } catch (const std::invalid_argument&) { + return; + } + std::fputs("a null locale did not throw\n", stderr); + std::abort(); +} + +} + +int main() { + const char keywords[] = "de_DE@collation=phonebook;currency=EUR"; + Check(keywords, keywords + 5); + const char twoSigns[] = "en@a=b@c=d"; + Check(twoSigns, twoSigns + 2); + const char leading[] = "@calendar=buddhist"; + Check(leading, leading); + const char trailing[] = "sr_Latn_RS@"; + Check(trailing, trailing + 10); + const char highBytes[] = "\xff\x80@x=y"; + Check(highBytes, highBytes + 2); + const char pastTerminator[] = "fr\0@x=y"; + Check(pastTerminator, nullptr); + Check("", nullptr); + Check("en_US_POSIX", nullptr); + Check("ja_JP;calendar=japanese", nullptr); + + for (const std::size_t position : {0u, 1u, 4095u, 65535u}) { + std::string locale(65536, 'a'); + locale[position] = '@'; + Check(locale.c_str(), locale.c_str() + position); + } + const std::string noKeywords(65536, 'a'); + Check(noKeywords.c_str(), nullptr); + + CheckNullThrows(); + std::puts("ICU locale keyword tests passed"); +} diff --git a/core/libs/prx/libSceIcu/tests/Search.cpp b/core/libs/prx/libSceIcu/tests/Search.cpp new file mode 100644 index 0000000000..a0aa450854 --- /dev/null +++ b/core/libs/prx/libSceIcu/tests/Search.cpp @@ -0,0 +1,88 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "ICU substring search check failed at line %u\n", location.line()); + std::abort(); +} + +constexpr std::ptrdiff_t None = -1; + +std::ptrdiff_t First(const std::u16string& text, const std::u16string& sub) { + const auto* found = u_strstr_nid_postfix(text.c_str(), sub.c_str()); + return found ? found - text.c_str() : None; +} + +std::ptrdiff_t Last(const std::u16string& text, const std::u16string& sub) { + const auto* found = u_strrstr_nid_postfix(text.c_str(), sub.c_str()); + return found ? found - text.c_str() : None; +} + +void Check(const std::u16string& text, const std::u16string& sub, std::ptrdiff_t first, std::ptrdiff_t last, + std::source_location location = std::source_location::current()) { + if (First(text, sub) == first && Last(text, sub) == last) return; + std::fprintf(stderr, "ICU substring search check failed at line %u\n", location.line()); + std::abort(); +} + +void Plain() { + Check(u"abcabc", u"bc", 1, 4); + Check(u"abcabc", u"abc", 0, 3); + Check(u"abcabc", u"c", 2, 5); + Check(u"abcabc", u"abcabc", 0, 0); + Check(u"abcabc", u"abcabcd", None, None); + Check(u"abcabc", u"x", None, None); + Check(u"abcabc", u"cb", None, None); + Check(u"aaa", u"aa", 0, 1); + Check(u"aaa", u"a", 0, 2); + Check(u"ab", u"abc", None, None); + Check(u"", u"a", None, None); + Check(u"abc", u"", 0, 0); + Check(u"", u"", 0, 0); + Check(std::u16string({0x41, 0, 0x42}), u"B", None, None); + Check(std::u16string({0x41, 0x42, 0x43}), std::u16string({0x42, 0x43}), 1, 1); + + const char16_t text[] = u"xyz"; + Require(u_strstr_nid_postfix(text, nullptr) == text); + Require(u_strrstr_nid_postfix(text, nullptr) == text); + Require(u_strstr_nid_postfix(nullptr, nullptr) == nullptr); + Require(u_strrstr_nid_postfix(nullptr, nullptr) == nullptr); + Require(u_strstr_nid_postfix(nullptr, text) == nullptr); + Require(u_strrstr_nid_postfix(nullptr, text) == nullptr); +} + +void Surrogates() { + const std::u16string pair({0xd83d, 0xde00}); + Check(std::u16string({0x41, 0xd83d, 0xde00, 0x42}), pair, 1, 1); + Check(std::u16string({0xd83d, 0xde00, 0xd83d, 0xde00}), pair, 0, 2); + + Check(std::u16string({0xd800, 0xdc00}), std::u16string({0xdc00}), None, None); + Check(std::u16string({0xd800, 0xdc00}), std::u16string({0xd800}), None, None); + Check(std::u16string({0xd800, 0xdc00, 0xdc00}), std::u16string({0xdc00}), 2, 2); + Check(std::u16string({0xdc00, 0xdc00, 0xd800, 0xdc00}), std::u16string({0xdc00}), 0, 1); + Check(std::u16string({0xd800, 0xdc00, 0xd800}), std::u16string({0xd800}), 2, 2); + Check(std::u16string({0xd800, 0xd800, 0xdc00}), std::u16string({0xd800}), 0, 0); + Check(std::u16string({0x41, 0xd800, 0x42}), std::u16string({0x41, 0xd800}), 0, 0); + Check(std::u16string({0x41, 0xd800, 0xdc00}), std::u16string({0x41, 0xd800}), None, None); + Check(std::u16string({0xd800, 0xdc00, 0x41}), std::u16string({0xdc00, 0x41}), None, None); + Check(std::u16string({0xdc00, 0x41, 0xd800, 0xdc00, 0x41}), std::u16string({0xdc00, 0x41}), 0, 0); + Check(std::u16string({0xd800, 0xdc00, 0x41, 0xdc00, 0x41}), std::u16string({0xdc00, 0x41}), 3, 3); + Check(std::u16string({0xd800, 0xdc00, 0x41, 0xdc00, 0x41}), std::u16string({0xd800, 0xdc00, 0x41}), 0, 0); + Check(std::u16string({0xdc00, 0xd800}), std::u16string({0xdc00}), 0, 0); +} + +} + +int main() { + Plain(); + Surrogates(); + std::puts("ICU substring search tests passed"); +} diff --git a/core/libs/prx/libSceIcu/tests/Utf32.cpp b/core/libs/prx/libSceIcu/tests/Utf32.cpp new file mode 100644 index 0000000000..0629570d19 --- /dev/null +++ b/core/libs/prx/libSceIcu/tests/Utf32.cpp @@ -0,0 +1,345 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr std::int32_t Illegal = 1; +constexpr std::int32_t Invalid = 10; +constexpr std::int32_t Overflow = 15; +constexpr std::int32_t NotTerminated = -124; +constexpr char16_t Fill = 0xa55a; + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "ICU UTF-32 check failed at line %u\n", location.line()); + std::abort(); +} + +template +void RequireThrows(TAction action) { + try { action(); } catch (const std::exception&) { return; } + Require(false); +} + +struct FromResult { + std::u16string text; + std::int32_t length = -77; + std::int32_t substitutions = -77; + std::int32_t status = 0; + bool null = false; + char16_t after = 0; +}; + +FromResult From(const std::vector& source, std::int32_t sourceLength, std::int32_t capacity, + std::int32_t substitution, std::int32_t status = 0, bool withSub = true) { + std::array buffer; + buffer.fill(Fill); + FromResult result; + result.status = status; + char16_t* returned = withSub + ? u_strFromUTF32WithSub_nid_postfix(buffer.data(), capacity, &result.length, source.data(), sourceLength, substitution, &result.substitutions, &result.status) + : u_strFromUTF32_nid_postfix(buffer.data(), capacity, &result.length, source.data(), sourceLength, &result.status); + result.null = returned == nullptr; + if (returned) Require(returned == buffer.data()); + const auto size = capacity < 0 ? 0 : std::min(capacity, buffer.size()); + result.text.assign(buffer.data(), size); + result.after = size < buffer.size() ? buffer[size] : Fill; + return result; +} + +void Valid() { + const std::vector mixed{0x41, 0x1f600, 0xe000, 0xffff}; + auto result = From(mixed, 4, 8, -1); + Require(!result.null && result.status == 0 && result.length == 5 && result.substitutions == 0); + Require(result.text.substr(0, 6) == std::u16string({0x41, 0xd83d, 0xde00, 0xe000, 0xffff, 0})); + Require(result.text[6] == Fill); + + result = From({0x41, 0x1f600, 0, 0x42}, -1, 8, -1); + Require(result.status == 0 && result.length == 3 && result.text.substr(0, 4) == std::u16string({0x41, 0xd83d, 0xde00, 0})); + + result = From({0x10000, 0x10ffff, 0x7f, 0x80}, 4, 8, -1); + Require(result.length == 6 && result.text.substr(0, 7) == std::u16string({0xd800, 0xdc00, 0xdbff, 0xdfff, 0x7f, 0x80, 0})); + + result = From({}, 0, 4, -1); + Require(!result.null && result.status == 0 && result.length == 0 && result.text[0] == 0 && result.text[1] == Fill); + result = From({0}, -1, 1, -1); + Require(result.status == 0 && result.length == 0 && result.text[0] == 0); + + result = From(mixed, 4, 8, -1, 0, false); + Require(result.status == 0 && result.length == 5 && result.substitutions == -77); +} + +void Capacity() { + const std::vector mixed{0x41, 0x1f600, 0xe000, 0xffff}; + auto result = From(mixed, 4, 5, -1); + Require(result.status == NotTerminated && result.length == 5 && result.after == Fill); + Require(result.text == std::u16string({0x41, 0xd83d, 0xde00, 0xe000, 0xffff})); + + result = From(mixed, 4, 4, -1); + Require(result.status == Overflow && result.length == 5); + Require(result.text == std::u16string({0x41, 0xd83d, 0xde00, 0xe000})); + + result = From(mixed, 4, 2, -1); + Require(result.status == Overflow && result.length == 5 && result.text == std::u16string({0x41, 0xe000})); + + result = From(mixed, 4, 0, -1); + Require(result.status == Overflow && result.length == 5 && result.after == Fill); + + result = From({0x1f600}, 1, 1, -1); + Require(result.status == Overflow && result.length == 2 && result.text == std::u16string({Fill})); + + result = From({0x41, 0}, -1, 1, -1); + Require(result.status == NotTerminated && result.length == 1 && result.text == std::u16string({0x41})); + + std::int32_t length = -77; + std::int32_t status = 0; + const std::vector text{0x61, 0x1f600}; + Require(u_strFromUTF32_nid_postfix(nullptr, 0, &length, text.data(), 2, &status) == nullptr); + Require(status == Overflow && length == 3); + status = 0; + Require(u_strFromUTF32_nid_postfix(nullptr, 0, nullptr, nullptr, 0, &status) == nullptr); + Require(status == NotTerminated); +} + +void Substitution() { + auto result = From({0xd800, 0x41, 0x110000, -5, 0xdfff}, 5, 8, 0xfffd); + Require(result.status == 0 && result.length == 5 && result.substitutions == 4); + Require(result.text.substr(0, 6) == std::u16string({0xfffd, 0x41, 0xfffd, 0xfffd, 0xfffd, 0})); + + result = From({0xdfff, 0x41}, 2, 8, 0x1f600); + Require(result.status == 0 && result.length == 3 && result.substitutions == 1); + Require(result.text.substr(0, 4) == std::u16string({0xd83d, 0xde00, 0x41, 0})); + + result = From({0xd800}, 1, 1, 0x1f600); + Require(result.status == Overflow && result.length == 2 && result.substitutions == 1 && result.text == std::u16string({Fill})); + + result = From({0xd800, 0x0}, -1, 4, 0x3f); + Require(result.status == 0 && result.length == 1 && result.substitutions == 1 && result.text.substr(0, 2) == std::u16string({0x3f, 0})); + + result = From({0x41, 0xd800, 0x42}, 3, 8, -1); + Require(result.null && result.status == Invalid && result.length == -77 && result.substitutions == 0); + Require(result.text[0] == 0x41 && result.text[1] == Fill); + + result = From({0x41, 0xd800}, 2, 8, -1, 0, false); + Require(result.null && result.status == Invalid); + result = From({0x110000}, 1, 8, -1, 0, false); + Require(result.null && result.status == Invalid); + result = From({-1}, 1, 8, -1, 0, false); + Require(result.null && result.status == Invalid); +} + +void Arguments() { + const std::vector text{0x41}; + for (auto substitution : {0x110000, 0xd800, 0xdfff, 0x7fffffff}) { + const auto result = From(text, 1, 8, substitution); + Require(result.null && result.status == Illegal && result.length == -77 && result.substitutions == -77 && result.text[0] == Fill); + } + for (auto length : {-2, std::numeric_limits::min()}) { + const auto result = From(text, length, 8, -1); + Require(result.null && result.status == Illegal && result.length == -77); + } + Require(From(text, 1, -1, -1).status == Illegal); + std::int32_t status = 0; + std::int32_t length = -77; + std::array buffer{}; + Require(!u_strFromUTF32_nid_postfix(buffer.data(), 4, &length, nullptr, 1, &status) && status == Illegal); + status = 0; + Require(!u_strFromUTF32_nid_postfix(buffer.data(), 4, &length, nullptr, -1, &status) && status == Illegal); + status = 0; + Require(!u_strFromUTF32_nid_postfix(nullptr, 1, &length, text.data(), 1, &status) && status == Illegal); + Require(length == -77); +} + +void Status() { + auto result = From({0x41}, 1, 4, -1, Illegal); + Require(result.null && result.status == Illegal && result.text[0] == Fill && result.length == -77); + result = From({0x41}, 1, 4, -1, Invalid); + Require(result.null && result.status == Invalid); + + result = From({0x41}, 1, 4, -1, NotTerminated); + Require(!result.null && result.status == 0 && result.text.substr(0, 2) == std::u16string({0x41, 0})); + result = From({0x41}, 1, 1, -1, NotTerminated); + Require(result.status == NotTerminated && result.length == 1); + result = From({0x41, 0x42}, 2, 1, -1, NotTerminated); + Require(result.status == Overflow); + result = From({0x41}, 1, 4, -1, -5); + Require(result.status == -5 && result.text.substr(0, 2) == std::u16string({0x41, 0})); + + const std::vector text{0x41}; + std::array buffer{}; + std::int32_t length = 0; + RequireThrows([&] { u_strFromUTF32_nid_postfix(buffer.data(), 4, &length, text.data(), 1, nullptr); }); + RequireThrows([&] { u_strFromUTF32WithSub_nid_postfix(buffer.data(), 4, &length, text.data(), 1, -1, nullptr, nullptr); }); +} + +struct ToResult { + std::vector values; + std::int32_t length = -77; + std::int32_t substitutions = -77; + std::int32_t status = 0; + bool null = false; + std::int32_t after = 0; +}; + +constexpr std::int32_t Fill32 = 0x5a5aa5a5; + +ToResult To(const std::u16string& source, std::int32_t sourceLength, std::int32_t capacity, + std::int32_t substitution, std::int32_t status = 0, bool withSub = true) { + std::array buffer; + buffer.fill(Fill32); + ToResult result; + result.status = status; + std::int32_t* returned = withSub + ? u_strToUTF32WithSub_nid_postfix(buffer.data(), capacity, &result.length, source.data(), sourceLength, substitution, &result.substitutions, &result.status) + : u_strToUTF32_nid_postfix(buffer.data(), capacity, &result.length, source.data(), sourceLength, &result.status); + result.null = returned == nullptr; + if (returned) Require(returned == buffer.data()); + const auto size = capacity < 0 ? 0 : std::min(capacity, buffer.size()); + result.values.assign(buffer.begin(), buffer.begin() + size); + result.after = size < buffer.size() ? buffer[size] : Fill32; + return result; +} + +using Values = std::vector; + +void ToValid() { + const std::u16string mixed{0x41, 0xd83d, 0xde00, 0xe000}; + auto result = To(mixed, 4, 8, -1); + Require(!result.null && result.status == 0 && result.length == 3 && result.substitutions == 0); + Require(Values(result.values.begin(), result.values.begin() + 4) == Values({0x41, 0x1f600, 0xe000, 0})); + Require(result.values[4] == Fill32); + + result = To(std::u16string({0x41, 0xd83d, 0xde00, 0, 0x42}), -1, 8, -1); + Require(result.status == 0 && result.length == 2 && result.values[2] == 0 && result.values[3] == Fill32); + + result = To(std::u16string({0x41, 0, 0x42}), 3, 8, -1); + Require(result.status == 0 && result.length == 3 && result.values[1] == 0 && result.values[2] == 0x42 && result.values[3] == 0); + + result = To(std::u16string({0xd800, 0xdc00, 0xdbff, 0xdfff, 0xffff}), 5, 8, -1); + Require(result.length == 3 && Values(result.values.begin(), result.values.begin() + 4) == Values({0x10000, 0x10ffff, 0xffff, 0})); + + result = To({}, 0, 4, -1); + Require(!result.null && result.status == 0 && result.length == 0 && result.values[0] == 0 && result.values[1] == Fill32); + result = To(mixed, 4, 8, -1, 0, false); + Require(result.status == 0 && result.length == 3 && result.substitutions == -77); +} + +void ToCapacity() { + const std::u16string mixed{0x41, 0xd83d, 0xde00, 0xe000}; + auto result = To(mixed, 4, 3, -1); + Require(result.status == NotTerminated && result.length == 3 && result.after == Fill32); + Require(result.values == Values({0x41, 0x1f600, 0xe000})); + + result = To(mixed, 4, 2, -1); + Require(result.status == Overflow && result.length == 3 && result.values == Values({0x41, 0x1f600})); + + result = To(mixed, 4, 0, -1); + Require(result.status == Overflow && result.length == 3 && result.after == Fill32); + + result = To(std::u16string({0x41, 0}), -1, 1, -1); + Require(result.status == NotTerminated && result.length == 1 && result.values == Values({0x41})); + + std::int32_t length = -77; + std::int32_t status = 0; + Require(u_strToUTF32_nid_postfix(nullptr, 0, &length, mixed.data(), 4, &status) == nullptr); + Require(status == Overflow && length == 3); + status = 0; + Require(u_strToUTF32_nid_postfix(nullptr, 0, nullptr, nullptr, 0, &status) == nullptr); + Require(status == NotTerminated); +} + +void ToSubstitution() { + auto result = To(std::u16string({0x41, 0xd800}), 2, 8, 0xfffd); + Require(result.status == 0 && result.length == 2 && result.substitutions == 1); + Require(Values(result.values.begin(), result.values.begin() + 3) == Values({0x41, 0xfffd, 0})); + + result = To(std::u16string({0xd800, 0x41, 0xdc00}), 3, 8, 0xfffd); + Require(result.length == 3 && result.substitutions == 2 && Values(result.values.begin(), result.values.begin() + 4) == Values({0xfffd, 0x41, 0xfffd, 0})); + + result = To(std::u16string({0xd800, 0xd800, 0xdc00}), 3, 8, 0x3f); + Require(result.length == 2 && result.substitutions == 1 && Values(result.values.begin(), result.values.begin() + 3) == Values({0x3f, 0x10000, 0})); + + result = To(std::u16string({0xdc00, 0xd800}), 2, 8, 0x1f600); + Require(result.length == 2 && result.substitutions == 2 && Values(result.values.begin(), result.values.begin() + 3) == Values({0x1f600, 0x1f600, 0})); + + result = To(std::u16string({0xdc00, 0xdc00}), 2, 8, 0x3f); + Require(result.length == 2 && result.substitutions == 2 && Values(result.values.begin(), result.values.begin() + 3) == Values({0x3f, 0x3f, 0})); + + result = To(std::u16string({0xd800, 0}), -1, 8, 0x3f); + Require(result.length == 1 && result.substitutions == 1 && result.values[0] == 0x3f && result.values[1] == 0); + + result = To(std::u16string({0xd800, 0xdc00}), 1, 1, 0x3f); + Require(result.status == NotTerminated && result.length == 1 && result.substitutions == 1 && result.values == Values({0x3f})); + + result = To(std::u16string({0x41, 0xdc00, 0x42}), 3, 8, -1); + Require(result.null && result.status == Invalid && result.length == -77 && result.substitutions == 0); + Require(result.values[0] == 0x41 && result.values[1] == Fill32); + + result = To(std::u16string({0xd800}), 1, 8, -1, 0, false); + Require(result.null && result.status == Invalid); + result = To(std::u16string({0xd800}), -1, 8, -1, 0, false); + Require(result.null && result.status == Invalid); +} + +void ToArguments() { + const std::u16string text{0x41}; + for (auto substitution : {0x110000, 0xd800, 0xdfff, 0x7fffffff}) { + const auto result = To(text, 1, 8, substitution); + Require(result.null && result.status == Illegal && result.length == -77 && result.substitutions == -77 && result.values[0] == Fill32); + } + for (auto length : {-2, std::numeric_limits::min()}) { + const auto result = To(text, length, 8, -1); + Require(result.null && result.status == Illegal && result.length == -77); + } + Require(To(text, 1, -1, -1).status == Illegal); + std::int32_t status = 0; + std::int32_t length = -77; + std::array buffer{}; + Require(!u_strToUTF32_nid_postfix(buffer.data(), 4, &length, nullptr, 1, &status) && status == Illegal); + status = 0; + Require(!u_strToUTF32_nid_postfix(buffer.data(), 4, &length, nullptr, -1, &status) && status == Illegal); + status = 0; + Require(!u_strToUTF32_nid_postfix(nullptr, 1, &length, text.data(), 1, &status) && status == Illegal); + Require(length == -77); +} + +void ToStatus() { + auto result = To(u"A", 1, 4, -1, Illegal); + Require(result.null && result.status == Illegal && result.values[0] == Fill32 && result.length == -77); + result = To(u"A", 1, 4, -1, NotTerminated); + Require(!result.null && result.status == 0 && result.values[0] == 0x41 && result.values[1] == 0); + result = To(u"A", 1, 1, -1, NotTerminated); + Require(result.status == NotTerminated && result.length == 1); + result = To(u"AB", 2, 1, -1, NotTerminated); + Require(result.status == Overflow); + + std::array buffer{}; + std::int32_t length = 0; + RequireThrows([&] { u_strToUTF32_nid_postfix(buffer.data(), 4, &length, u"A", 1, nullptr); }); + RequireThrows([&] { u_strToUTF32WithSub_nid_postfix(buffer.data(), 4, &length, u"A", 1, -1, nullptr, nullptr); }); +} + +} + +int main() { + Valid(); + Capacity(); + Substitution(); + Arguments(); + Status(); + ToValid(); + ToCapacity(); + ToSubstitution(); + ToArguments(); + ToStatus(); + std::puts("ICU UTF-32 conversion tests passed"); +} diff --git a/core/libs/prx/libSceIpmi/CMakeLists.txt b/core/libs/prx/libSceIpmi/CMakeLists.txt index f549ae49f1..1d085ad4db 100644 --- a/core/libs/prx/libSceIpmi/CMakeLists.txt +++ b/core/libs/prx/libSceIpmi/CMakeLists.txt @@ -1,3 +1,29 @@ add_library(libSceIpmi SHARED EXCLUDE_FROM_ALL Export.cpp PendingExports.cpp) target_include_directories(libSceIpmi PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(libSceIpmi) + +if(BUILD_TESTING) + add_test_executable(guest_ipmi_noops_tests ${LIBS_INCLUDE_DIR}/tests/GuestIpmiNoOps.cpp) + target_include_directories(guest_ipmi_noops_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_ipmi_noops_tests PRIVATE libSceIpmi libc) + configure_windows_unwind(guest_ipmi_noops_tests) + add_test(NAME guest_ipmi_noops COMMAND guest_ipmi_noops_tests) + set_tests_properties(guest_ipmi_noops PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_ipmi_noops PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_ipmi_config_estimators_tests ${LIBS_INCLUDE_DIR}/tests/GuestIpmiConfigEstimators.cpp) + target_include_directories(guest_ipmi_config_estimators_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_ipmi_config_estimators_tests PRIVATE libSceIpmi libc) + configure_windows_unwind(guest_ipmi_config_estimators_tests) + add_test(NAME guest_ipmi_config_estimators COMMAND guest_ipmi_config_estimators_tests) + set_tests_properties(guest_ipmi_config_estimators PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_ipmi_config_estimators PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceIpmi/Export.cpp b/core/libs/prx/libSceIpmi/Export.cpp index 7d74d75265..10f820b7c4 100644 --- a/core/libs/prx/libSceIpmi/Export.cpp +++ b/core/libs/prx/libSceIpmi/Export.cpp @@ -46,8 +46,9 @@ int APS5_VABI _ZN4IPMI4impl10ClientImpl11getUserDataEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN4IPMI4impl10ClientImpl12tryGetResultEjPiPvPmm() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _ZN4IPMI4impl10ClientImpl12tryGetResultEjPiPvPmm(void* self, unsigned int, int*, void*, unsigned long*, unsigned long) { + (void)self; + return 0; } int APS5_VABI _ZN4IPMI4impl10ClientImpl12tryGetResultEjjPiPNS_10BufferInfoEj() { @@ -74,8 +75,9 @@ int APS5_VABI _ZN4IPMI4impl10ClientImpl17invokeAsyncMethodEjPKNS_8DataInfoEjPjPK NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN4IPMI4impl10ClientImpl17invokeAsyncMethodEjPKvmPjPKNS_6Client12EventNotifeeE() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _ZN4IPMI4impl10ClientImpl17invokeAsyncMethodEjPKvmPjPKNS_6Client12EventNotifeeE(void* self, unsigned int, const void*, unsigned long, unsigned int*, const void*) { + (void)self; + return 0; } int APS5_VABI _ZN4IPMI4impl10ClientImpl19terminateConnectionEv() { @@ -170,8 +172,9 @@ int APS5_VABI _ZN4IPMI4impl11SessionImpl11getUserDataEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN4IPMI4impl11SessionImpl11tryDispatchEPvm() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _ZN4IPMI4impl11SessionImpl11tryDispatchEPvm(void* self, void*, unsigned long) { + (void)self; + return 0; } int APS5_VABI _ZN4IPMI4impl11SessionImpl12getClientPidEv() { diff --git a/core/libs/prx/libSceIpmi/PendingExports.cpp b/core/libs/prx/libSceIpmi/PendingExports.cpp index 5970f64dd6..4a12a0d86f 100644 --- a/core/libs/prx/libSceIpmi/PendingExports.cpp +++ b/core/libs/prx/libSceIpmi/PendingExports.cpp @@ -14,45 +14,40 @@ int APS5_VABI _ZN4IPMI6Client6Config14MsgQueueConfig20estimateMsgQueueSizeEmj(vo NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZN4IPMI6Server12EventHandler21onAsyncMethodDispatchEPNS_7SessionEjjPvmS4_m(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN4IPMI6Server12EventHandler21onAsyncMethodDispatchEPNS_7SessionEjjPvmS4_m(void* self) { + (void)self; } int APS5_VABI _ZN4IPMI6Client6Config26SyncMethodInvocationConfigC1Ev(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZN4IPMI6Server12EventHandler20onSyncMethodDispatchEPNS_7SessionEjPvmmS4_m(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN4IPMI6Server12EventHandler20onSyncMethodDispatchEPNS_7SessionEjPvmmS4_m(void* self) { + (void)self; } -int APS5_VABI _ZN4IPMI7SessionD1Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN4IPMI7SessionD1Ev(void* self) { + (void)self; } int APS5_VABI _ZN4IPMI4impl11SessionImpl9tryGetMsgEjPvPmm(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZN4IPMI6Server12EventHandler20onSyncMethodDispatchEPNS_7SessionEjPKNS_8DataInfoEjPNS_10BufferInfoEj(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN4IPMI6Server12EventHandler20onSyncMethodDispatchEPNS_7SessionEjPKNS_8DataInfoEjPNS_10BufferInfoEj(void* self) { + (void)self; } int APS5_VABI _ZN4IPMI6Client6Config14MsgQueueConfigC1Ev(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZN4IPMI6ServerD2Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN4IPMI6ServerD2Ev(void* self) { + (void)self; } int APS5_VABI _ZN4IPMI6ServerD0Ev(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZN4IPMI6Client6Config24estimateClientMemorySizeEv(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::size_t APS5_VABI _ZN4IPMI6Client6Config24estimateClientMemorySizeEv(void* self) { + (void)self; + return 0x1000; } int APS5_VABI _ZN4IPMI6Server6createEPPS0_PKNS0_6ConfigEPvS6_(void) { NotImplemented_nid_no_patch(__func__); @@ -62,9 +57,8 @@ int APS5_VABI _ZN4IPMI6Client6ConfigC2Ev(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZN4IPMI6Server12EventHandler15onSessionKilledEPNS_7SessionE(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN4IPMI6Server12EventHandler15onSessionKilledEPNS_7SessionE(void* self) { + (void)self; } int APS5_VABI _ZN4IPMI4impl11SessionImplD1Ev(void) { NotImplemented_nid_no_patch(__func__); @@ -78,13 +72,11 @@ int APS5_VABI _ZN4IPMI6Client6Config27AsyncMethodInvocationConfigC2Ev(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZN4IPMI6Server12EventHandler21onAsyncMethodDispatchEPNS_7SessionEjjPKNS_8DataInfoEj(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN4IPMI6Server12EventHandler21onAsyncMethodDispatchEPNS_7SessionEjjPKNS_8DataInfoEj(void* self) { + (void)self; } -int APS5_VABI _ZN4IPMI6ClientD2Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN4IPMI6ClientD2Ev(void* self) { + (void)self; } int APS5_VABI _ZN4IPMI4impl11SessionImpl7sendMsgEjPKvmPj(void) { NotImplemented_nid_no_patch(__func__); @@ -94,9 +86,8 @@ int APS5_VABI _ZN4IPMI6ClientC2Ev(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZN4IPMI7SessionD2Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN4IPMI7SessionD2Ev(void* self) { + (void)self; } int APS5_VABI _ZNK4IPMI4impl11SessionImpl16isPeerPrivilegedEv(void) { NotImplemented_nid_no_patch(__func__); @@ -110,21 +101,22 @@ int APS5_VABI _ZN4IPMI4impl11SessionImplD2Ev(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZN4IPMI6ClientD1Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN4IPMI6ClientD1Ev(void* self) { + (void)self; } int APS5_VABI _ZN4IPMI6Server12EventHandlerD0Ev(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZN4IPMI7Session6Config25estimateSessionMemorySizeEv(void) { - NotImplemented_nid_no_patch(__func__); +std::size_t APS5_VABI _ZN4IPMI7Session6Config25estimateSessionMemorySizeEv(void* self) { + (void)self; return 0; } -int APS5_VABI _ZNK4IPMI6Server6Config29estimateTempWorkingMemorySizeEv(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::size_t APS5_VABI _ZNK4IPMI6Server6Config29estimateTempWorkingMemorySizeEv(const void* self) { + const auto* values = static_cast(self); + const std::uint64_t fromFirst = values[0] + 0x100; + const std::uint64_t fromSecond = values[1] + 0x100; + return fromFirst >= fromSecond ? fromFirst : fromSecond; } int APS5_VABI _ZN4IPMI4impl11SessionImplD0Ev(void) { NotImplemented_nid_no_patch(__func__); @@ -146,9 +138,8 @@ int APS5_VABI _ZN4IPMI4impl11SessionImpl27respondToAsyncMethodRequestEjjiPKNS_8D NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZN4IPMI6Server12EventHandlerD2Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN4IPMI6Server12EventHandlerD2Ev(void* self) { + (void)self; } int APS5_VABI _ZN4IPMI6ClientD0Ev(void) { NotImplemented_nid_no_patch(__func__); @@ -170,17 +161,15 @@ int APS5_VABI _ZN4IPMI7SessionD0Ev(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZN4IPMI6ServerD1Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN4IPMI6ServerD1Ev(void* self) { + (void)self; } int APS5_VABI _ZN4IPMI6Server6ConfigC1Ev(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZN4IPMI6Server12EventHandlerD1Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI _ZN4IPMI6Server12EventHandlerD1Ev(void* self) { + (void)self; } int APS5_VABI _ZN4IPMI4impl11SessionImpl9getServerEv(void) { NotImplemented_nid_no_patch(__func__); diff --git a/core/libs/prx/libSceIpmi/include/Api.hpp b/core/libs/prx/libSceIpmi/include/Api.hpp index ae6dfa84ed..55cbef0741 100644 --- a/core/libs/prx/libSceIpmi/include/Api.hpp +++ b/core/libs/prx/libSceIpmi/include/Api.hpp @@ -11,48 +11,48 @@ int APS5_VABI sceIpmiMgrRaiseExceptionToPid(void); int APS5_VABI sceIpmiMgrStartDump(void); int APS5_VABI _ZN4IPMI4impl11SessionImplC1Ev(void); int APS5_VABI _ZN4IPMI6Client6Config14MsgQueueConfig20estimateMsgQueueSizeEmj(void); -int APS5_VABI _ZN4IPMI6Server12EventHandler21onAsyncMethodDispatchEPNS_7SessionEjjPvmS4_m(void); +void APS5_VABI _ZN4IPMI6Server12EventHandler21onAsyncMethodDispatchEPNS_7SessionEjjPvmS4_m(void* self); int APS5_VABI _ZN4IPMI6Client6Config26SyncMethodInvocationConfigC1Ev(void); -int APS5_VABI _ZN4IPMI6Server12EventHandler20onSyncMethodDispatchEPNS_7SessionEjPvmmS4_m(void); -int APS5_VABI _ZN4IPMI7SessionD1Ev(void); +void APS5_VABI _ZN4IPMI6Server12EventHandler20onSyncMethodDispatchEPNS_7SessionEjPvmmS4_m(void* self); +void APS5_VABI _ZN4IPMI7SessionD1Ev(void* self); int APS5_VABI _ZN4IPMI4impl11SessionImpl9tryGetMsgEjPvPmm(void); -int APS5_VABI _ZN4IPMI6Server12EventHandler20onSyncMethodDispatchEPNS_7SessionEjPKNS_8DataInfoEjPNS_10BufferInfoEj(void); +void APS5_VABI _ZN4IPMI6Server12EventHandler20onSyncMethodDispatchEPNS_7SessionEjPKNS_8DataInfoEjPNS_10BufferInfoEj(void* self); int APS5_VABI _ZN4IPMI6Client6Config14MsgQueueConfigC1Ev(void); -int APS5_VABI _ZN4IPMI6ServerD2Ev(void); +void APS5_VABI _ZN4IPMI6ServerD2Ev(void* self); int APS5_VABI _ZN4IPMI6ServerD0Ev(void); -int APS5_VABI _ZN4IPMI6Client6Config24estimateClientMemorySizeEv(void); +std::size_t APS5_VABI _ZN4IPMI6Client6Config24estimateClientMemorySizeEv(void* self); int APS5_VABI _ZN4IPMI6Server6createEPPS0_PKNS0_6ConfigEPvS6_(void); int APS5_VABI _ZN4IPMI6Client6ConfigC2Ev(void); -int APS5_VABI _ZN4IPMI6Server12EventHandler15onSessionKilledEPNS_7SessionE(void); +void APS5_VABI _ZN4IPMI6Server12EventHandler15onSessionKilledEPNS_7SessionE(void* self); int APS5_VABI _ZN4IPMI4impl11SessionImplD1Ev(void); int APS5_VABI _ZN4IPMI4impl11SessionImpl27respondToAsyncMethodRequestEjiPKvm(void); int APS5_VABI _ZN4IPMI6Client6Config27AsyncMethodInvocationConfigC2Ev(void); -int APS5_VABI _ZN4IPMI6Server12EventHandler21onAsyncMethodDispatchEPNS_7SessionEjjPKNS_8DataInfoEj(void); -int APS5_VABI _ZN4IPMI6ClientD2Ev(void); +void APS5_VABI _ZN4IPMI6Server12EventHandler21onAsyncMethodDispatchEPNS_7SessionEjjPKNS_8DataInfoEj(void* self); +void APS5_VABI _ZN4IPMI6ClientD2Ev(void* self); int APS5_VABI _ZN4IPMI4impl11SessionImpl7sendMsgEjPKvmPj(void); int APS5_VABI _ZN4IPMI6ClientC2Ev(void); -int APS5_VABI _ZN4IPMI7SessionD2Ev(void); +void APS5_VABI _ZN4IPMI7SessionD2Ev(void* self); int APS5_VABI _ZNK4IPMI4impl11SessionImpl16isPeerPrivilegedEv(void); int APS5_VABI _ZN4IPMI7SessionC2Ev(void); int APS5_VABI _ZN4IPMI4impl11SessionImplD2Ev(void); -int APS5_VABI _ZN4IPMI6ClientD1Ev(void); +void APS5_VABI _ZN4IPMI6ClientD1Ev(void* self); int APS5_VABI _ZN4IPMI6Server12EventHandlerD0Ev(void); -int APS5_VABI _ZN4IPMI7Session6Config25estimateSessionMemorySizeEv(void); -int APS5_VABI _ZNK4IPMI6Server6Config29estimateTempWorkingMemorySizeEv(void); +std::size_t APS5_VABI _ZN4IPMI7Session6Config25estimateSessionMemorySizeEv(void* self); +std::size_t APS5_VABI _ZNK4IPMI6Server6Config29estimateTempWorkingMemorySizeEv(const void* self); int APS5_VABI _ZN4IPMI4impl11SessionImplD0Ev(void); int APS5_VABI _ZN4IPMI4impl11SessionImpl7destroyEv(void); int APS5_VABI _ZN4IPMI6Client6Config27AsyncMethodInvocationConfigC1Ev(void); int APS5_VABI _ZN4IPMI4impl11SessionImpl6getMsgEjPvPmmPj(void); int APS5_VABI _ZN4IPMI4impl11SessionImpl27respondToAsyncMethodRequestEjjiPKNS_8DataInfoEj(void); -int APS5_VABI _ZN4IPMI6Server12EventHandlerD2Ev(void); +void APS5_VABI _ZN4IPMI6Server12EventHandlerD2Ev(void* self); int APS5_VABI _ZN4IPMI6ClientD0Ev(void); int APS5_VABI _ZN4IPMI6Client6createEPPS0_PKNS0_6ConfigEPvS6_(void); int APS5_VABI _ZN4IPMI6ServerC2Ev(void); int APS5_VABI _ZN4IPMI4impl11SessionImplC2Ev(void); int APS5_VABI _ZN4IPMI7SessionD0Ev(void); -int APS5_VABI _ZN4IPMI6ServerD1Ev(void); +void APS5_VABI _ZN4IPMI6ServerD1Ev(void* self); int APS5_VABI _ZN4IPMI6Server6ConfigC1Ev(void); -int APS5_VABI _ZN4IPMI6Server12EventHandlerD1Ev(void); +void APS5_VABI _ZN4IPMI6Server12EventHandlerD1Ev(void* self); int APS5_VABI _ZN4IPMI4impl11SessionImpl9getServerEv(void); int APS5_VABI _ZN4IPMI6Client6ConfigC1Ev(void); int APS5_VABI _ZN4IPMI6Client6Config26SyncMethodInvocationConfigC2Ev(void); @@ -62,14 +62,14 @@ int APS5_VABI _ZN4IPMI6Server6ConfigC2Ev(void); int APS5_VABI _ZN4IPMI4impl10ClientImpl10disconnectEv(); int APS5_VABI _ZN4IPMI4impl10ClientImpl10initializeEPKNS_6Client6ConfigEPv(); int APS5_VABI _ZN4IPMI4impl10ClientImpl11getUserDataEv(); -int APS5_VABI _ZN4IPMI4impl10ClientImpl12tryGetResultEjPiPvPmm(); +int APS5_VABI _ZN4IPMI4impl10ClientImpl12tryGetResultEjPiPvPmm(void* self, unsigned int, int*, void*, unsigned long*, unsigned long); int APS5_VABI _ZN4IPMI4impl10ClientImpl12tryGetResultEjjPiPNS_10BufferInfoEj(); int APS5_VABI _ZN4IPMI4impl10ClientImpl13pollEventFlagEjmjPm(); int APS5_VABI _ZN4IPMI4impl10ClientImpl13waitEventFlagEjmjPmPj(); int APS5_VABI _ZN4IPMI4impl10ClientImpl16invokeSyncMethodEjPKNS_8DataInfoEjPiPNS_10BufferInfoEj(); int APS5_VABI _ZN4IPMI4impl10ClientImpl16invokeSyncMethodEjPKvmPiPvPmm(); int APS5_VABI _ZN4IPMI4impl10ClientImpl17invokeAsyncMethodEjPKNS_8DataInfoEjPjPKNS_6Client12EventNotifeeE(); -int APS5_VABI _ZN4IPMI4impl10ClientImpl17invokeAsyncMethodEjPKvmPjPKNS_6Client12EventNotifeeE(); +int APS5_VABI _ZN4IPMI4impl10ClientImpl17invokeAsyncMethodEjPKvmPjPKNS_6Client12EventNotifeeE(void* self, unsigned int, const void*, unsigned long, unsigned int*, const void*); int APS5_VABI _ZN4IPMI4impl10ClientImpl19terminateConnectionEv(); int APS5_VABI _ZN4IPMI4impl10ClientImpl6getMsgEjPvPmmPj(); int APS5_VABI _ZN4IPMI4impl10ClientImpl7connectEPKvmPi(); @@ -93,7 +93,7 @@ int APS5_VABI _ZN4IPMI4impl10ServerImplD1Ev(); int APS5_VABI _ZN4IPMI4impl10ServerImplD2Ev(); int APS5_VABI _ZN4IPMI4impl11SessionImpl10trySendMsgEjPKvm(); int APS5_VABI _ZN4IPMI4impl11SessionImpl11getUserDataEv(); -int APS5_VABI _ZN4IPMI4impl11SessionImpl11tryDispatchEPvm(); +int APS5_VABI _ZN4IPMI4impl11SessionImpl11tryDispatchEPvm(void* self, void*, unsigned long); int APS5_VABI _ZN4IPMI4impl11SessionImpl12getClientPidEv(); int APS5_VABI _ZN4IPMI4impl11SessionImpl12setEventFlagEjm(); int APS5_VABI _ZN4IPMI4impl11SessionImpl13emptyMsgQueueEj(); diff --git a/core/libs/prx/libSceJobManager/Export.cpp b/core/libs/prx/libSceJobManager/Export.cpp index abefb6f4f7..8457fd2cd6 100644 --- a/core/libs/prx/libSceJobManager/Export.cpp +++ b/core/libs/prx/libSceJobManager/Export.cpp @@ -829,13 +829,22 @@ void* APS5_VABI WorkerEntry(void* argument) { continue; } const bool spinning = manager.pendingInvocations != 0 && __rdtsc() - lastWork < manager.spinTicks; - if (spinning || manager.sleepingWorkers >= manager.workerCount - manager.persistentThreads) { + if (spinning) { const std::uint32_t epoch = manager.workEpoch.load(std::memory_order_acquire); guard.unlock(); for (std::uint32_t pause = 0; pause != SpinPauses && manager.workEpoch.load(std::memory_order_acquire) == epoch; ++pause) _mm_pause(); guard.lock(); continue; } + if (manager.sleepingWorkers >= manager.workerCount - manager.persistentThreads) { + const std::uint32_t epoch = manager.workEpoch.load(std::memory_order_acquire); + guard.unlock(); + while (manager.workEpoch.load(std::memory_order_acquire) == epoch) { + for (std::uint32_t pause = 0; pause != SpinPauses; ++pause) _mm_pause(); + } + guard.lock(); + continue; + } ++manager.sleepingWorkers; manager.work.wait(guard, [&manager] { return !manager.running || manager.wakeTokens != 0; }); if (manager.wakeTokens != 0) --manager.wakeTokens; diff --git a/core/libs/prx/libSceJxr/CMakeLists.txt b/core/libs/prx/libSceJxr/CMakeLists.txt index 2a1c524e2c..d966c59ede 100644 --- a/core/libs/prx/libSceJxr/CMakeLists.txt +++ b/core/libs/prx/libSceJxr/CMakeLists.txt @@ -1,9 +1,14 @@ -add_library(jxr_lookup_native STATIC src/NativeLookup.c) +add_library(jxr_lookup_native STATIC src/NativeLookup.c src/NativeQuant.c src/NativeBitIo.c) configure_jxr_native(jxr_lookup_native) target_include_directories(jxr_lookup_native PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(jxr_lookup_native PRIVATE jxr) -add_library(libSceJxr SHARED EXCLUDE_FROM_ALL Export.cpp src/Allocation.cpp src/PixelFormatLookup.cpp src/GuidExports.cpp) -target_link_libraries(libSceJxr PRIVATE jxr_lookup_native) +add_library(libSceJxr SHARED EXCLUDE_FROM_ALL Export.cpp src/Allocation.cpp src/GlueHelpers.cpp src/PixelFormatLookup.cpp src/GuidExports.cpp src/Quantizer.cpp src/BitIo.cpp) +add_library(jxr_glue_guest STATIC ${JXR_SOURCE_DIR}/jxrgluelib/JXRGlue.c src/NativeGlue.c) +configure_jxr_native(jxr_glue_guest) +set_source_files_properties(${JXR_SOURCE_DIR}/jxrgluelib/JXRGlue.c TARGET_DIRECTORY jxr_glue_guest + PROPERTIES COMPILE_OPTIONS "-include;${CMAKE_CURRENT_SOURCE_DIR}/src/GuestHeap.h") +target_link_libraries(jxr_glue_guest PRIVATE jxr) +target_link_libraries(libSceJxr PRIVATE jxr_glue_guest jxr_lookup_native) if(NOT APPLE) target_link_options(libSceJxr PRIVATE LINKER:--exclude-libs,ALL) endif() @@ -69,3 +74,48 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +if(BUILD_TESTING) + add_test_executable(guest_jxr_glue_tests ${LIBS_INCLUDE_DIR}/tests/GuestJxrGlue.cpp) + target_include_directories(guest_jxr_glue_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_jxr_glue_tests PRIVATE libSceJxr libc jxr) + configure_windows_unwind(guest_jxr_glue_tests) + add_test(NAME guest_jxr_glue COMMAND guest_jxr_glue_tests) + set_tests_properties(guest_jxr_glue PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_jxr_glue PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + +if(BUILD_TESTING) + add_library(jxr_quantizer_reference STATIC ${LIBS_INCLUDE_DIR}/tests/GuestJxrQuantizerReference.c) + configure_jxr_native(jxr_quantizer_reference) + target_link_libraries(jxr_quantizer_reference PRIVATE jxr) + add_test_executable(guest_jxr_quantizer_tests ${LIBS_INCLUDE_DIR}/tests/GuestJxrQuantizer.cpp) + target_include_directories(guest_jxr_quantizer_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_jxr_quantizer_tests PRIVATE libSceJxr libc jxr_quantizer_reference) + configure_windows_unwind(guest_jxr_quantizer_tests) + add_test(NAME guest_jxr_quantizer COMMAND guest_jxr_quantizer_tests) + set_tests_properties(guest_jxr_quantizer PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_jxr_quantizer PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + +if(BUILD_TESTING) + add_library(jxr_bitio_reference STATIC ${LIBS_INCLUDE_DIR}/tests/GuestJxrBitIoReference.c) + configure_jxr_native(jxr_bitio_reference) + target_link_libraries(jxr_bitio_reference PRIVATE jxr) + add_test_executable(guest_jxr_bitio_tests ${LIBS_INCLUDE_DIR}/tests/GuestJxrBitIo.cpp) + target_include_directories(guest_jxr_bitio_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_jxr_bitio_tests PRIVATE libSceJxr libc jxr_bitio_reference) + configure_windows_unwind(guest_jxr_bitio_tests) + add_test(NAME guest_jxr_bitio COMMAND guest_jxr_bitio_tests) + set_tests_properties(guest_jxr_bitio PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_jxr_bitio PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceJxr/Export.cpp b/core/libs/prx/libSceJxr/Export.cpp index 5c977f0d3b..47ff263e1d 100644 --- a/core/libs/prx/libSceJxr/Export.cpp +++ b/core/libs/prx/libSceJxr/Export.cpp @@ -1,3 +1,31 @@ +//*@@@+++@@@@****************************************************************** +// +// Copyright © Microsoft Corp. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// • Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// • Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +//*@@@---@@@@****************************************************************** + #include "prx/libc/include/Api.hpp" #include "prx/libSceJxr/include/Api.hpp" #include @@ -10,6 +38,15 @@ namespace { constexpr std::int64_t WmpErrSuccess = 0; +constexpr std::int64_t WmpErrOutOfMemory = -101; +constexpr std::int64_t WmpErrBufferOverflow = -103; +constexpr std::size_t MemoryBuffer = 0; +constexpr std::size_t MemorySize = 1; +constexpr std::size_t MemoryPosition = 2; + +std::int64_t APS5_VABI CloseWS_Memory_nid_no_patch(Jxr::Stream** stream) { + return WMPFree_nid_postfix(reinterpret_cast(stream)); +} } extern "C" { @@ -72,34 +109,36 @@ int APS5_VABI CreateWS_List_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI CreateWS_Memory_nid_postfix() { - NotImplemented_nid_no_patch(__func__); +std::int64_t APS5_VABI CreateWS_Memory_nid_postfix(Jxr::Stream** stream, void* buffer, std::size_t size) { + if (!stream) APS5_INVALID_ARG_EX; + const auto result = WMPAlloc_nid_postfix(reinterpret_cast(stream), sizeof(Jxr::Stream)); + if (result < 0) return result; + auto* created = *stream; + created->state[MemoryBuffer] = reinterpret_cast(buffer); + created->state[MemorySize] = size; + created->state[MemoryPosition] = 0; + created->close = CloseWS_Memory_nid_no_patch; + created->end = EOSWS_Memory_nid_postfix; + created->read = ReadWS_Memory_nid_postfix; + created->write = WriteWS_Memory_nid_postfix; + created->setPosition = SetPosWS_Memory_nid_postfix; + created->getPosition = GetPosWS_Memory_nid_postfix; + return WmpErrSuccess; } int APS5_VABI EOSWS_File_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI EOSWS_Memory_nid_postfix() { - NotImplemented_nid_no_patch(__func__); +std::int32_t APS5_VABI EOSWS_Memory_nid_postfix(Jxr::Stream* stream) { + if (!stream) APS5_INVALID_ARG_EX; + return stream->state[MemorySize] <= stream->state[MemoryPosition]; } int APS5_VABI FreeDescMetadata_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI GetImageDecodeIID_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI GetImageEncodeIID_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI GetPixelFormatFromHash_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI GetPosWS_File_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -108,20 +147,10 @@ int APS5_VABI GetPosWS_List_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI GetPosWS_Memory_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI IDPEmpty_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI InitZigzagScan_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI PKAllocAligned_nid_postfix() { - NotImplemented_nid_no_patch(__func__); +std::int64_t APS5_VABI GetPosWS_Memory_nid_postfix(Jxr::Stream* stream, std::size_t* position) { + if (!stream || !position) APS5_INVALID_ARG_EX; + *position = stream->state[MemoryPosition]; + return WmpErrSuccess; } int APS5_VABI PKCodecFactory_CreateCodec_nid_postfix() { @@ -196,10 +225,6 @@ int APS5_VABI PKFormatConverter_Release_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI PKFreeAligned_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI PKImageDecode_Copy_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -332,8 +357,18 @@ int APS5_VABI ReadWS_List_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI ReadWS_Memory_nid_postfix() { - NotImplemented_nid_no_patch(__func__); +std::int64_t APS5_VABI ReadWS_Memory_nid_postfix(Jxr::Stream* stream, void* buffer, std::size_t size) { + if (!stream) APS5_INVALID_ARG_EX; + auto& state = stream->state; + if (state[MemorySize] < state[MemoryPosition]) return WmpErrSuccess; + if (state[MemoryPosition] + size < state[MemoryPosition]) return WmpErrBufferOverflow; + if (state[MemorySize] < state[MemoryPosition] + size) size = state[MemorySize] - state[MemoryPosition]; + if (size != 0) { + if (!buffer || !state[MemoryBuffer]) APS5_INVALID_ARG_EX; + std::memcpy(buffer, reinterpret_cast(state[MemoryBuffer]) + state[MemoryPosition], size); + } + state[MemoryPosition] += size; + return WmpErrSuccess; } int APS5_VABI ResetCodingContext_nid_postfix() { @@ -348,16 +383,25 @@ int APS5_VABI SetPosWS_List_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI SetPosWS_Memory_nid_postfix() { - NotImplemented_nid_no_patch(__func__); +std::int64_t APS5_VABI SetPosWS_Memory_nid_postfix(Jxr::Stream* stream, std::size_t position) { + if (!stream) APS5_INVALID_ARG_EX; + stream->state[MemoryPosition] = position; + return WmpErrSuccess; } -int APS5_VABI WMPAlloc_nid_postfix() { - NotImplemented_nid_no_patch(__func__); +std::int64_t APS5_VABI WMPAlloc_nid_postfix(void** memory, std::size_t size) { + if (!memory) APS5_INVALID_ARG_EX; + *memory = calloc_nid_postfix(1, size); + return *memory ? WmpErrSuccess : WmpErrOutOfMemory; } -int APS5_VABI WMPFree_nid_postfix() { - NotImplemented_nid_no_patch(__func__); +std::int64_t APS5_VABI WMPFree_nid_postfix(void** memory) { + if (!memory) APS5_INVALID_ARG_EX; + if (*memory) { + free_nid_postfix(*memory); + *memory = nullptr; + } + return WmpErrSuccess; } int APS5_VABI WriteWS_File_nid_postfix() { @@ -368,8 +412,16 @@ int APS5_VABI WriteWS_List_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WriteWS_Memory_nid_postfix() { - NotImplemented_nid_no_patch(__func__); +std::int64_t APS5_VABI WriteWS_Memory_nid_postfix(Jxr::Stream* stream, const void* buffer, std::size_t size) { + if (!stream) APS5_INVALID_ARG_EX; + auto& state = stream->state; + if (state[MemoryPosition] + size < state[MemoryPosition] || state[MemorySize] < state[MemoryPosition] + size) return WmpErrBufferOverflow; + if (size != 0) { + if (!buffer || !state[MemoryBuffer]) APS5_INVALID_ARG_EX; + std::memcpy(reinterpret_cast(state[MemoryBuffer]) + state[MemoryPosition], buffer, size); + } + state[MemoryPosition] += size; + return WmpErrSuccess; } int APS5_VABI advanceMRPtr_nid_postfix() { @@ -424,30 +476,10 @@ int APS5_VABI detach_SB_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI dquantBits_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI fillToByte_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI flushBit16_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI flushToByte_SB_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI flushToByte_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI formatQuantizer_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI freePredInfo_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -460,34 +492,10 @@ int APS5_VABI freeTileInfo_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI getACPredMode_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI getBit16_S_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI getBit16_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI getBit16s_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI getBit32_SB_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI getBit32_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI getBool16_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI getByteRead_SB_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -496,18 +504,6 @@ int APS5_VABI getDCACPredMode_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI getPosRead_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI getSizeRead_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI getSizeWrite_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI getTilePos_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -520,10 +516,6 @@ int APS5_VABI initPostProc_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI peekBit16_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI postProcBlock_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -532,22 +524,6 @@ int APS5_VABI postProcMB_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI putBit16_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI putBit16z_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI putBit32_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI remapQP_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI setBitIOPointers_nid_postfix() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libSceJxr/include/Api.hpp b/core/libs/prx/libSceJxr/include/Api.hpp index da282b82ff..90cc3ece61 100644 --- a/core/libs/prx/libSceJxr/include/Api.hpp +++ b/core/libs/prx/libSceJxr/include/Api.hpp @@ -2,6 +2,7 @@ #include "SceTypes.hpp" #include +#include #include "prx/libSceJxr/GuestPixelInfo.h" #include "prx/libSceJxr/Stream.hpp" @@ -11,23 +12,44 @@ std::int64_t APS5_VABI PKFree_nid_postfix(void** memory); std::int64_t APS5_VABI PixelFormatLookup_nid_postfix(JxrPixelInfo* value, std::uint8_t mode); std::int64_t APS5_VABI readIS_nid_postfix(void* codec, Jxr::BitIo* io); std::int64_t APS5_VABI writeIS_nid_postfix(void* codec, Jxr::BitIo* io); +std::uint32_t APS5_VABI peekBit16_nid_postfix(Jxr::BitIo* io, std::uint32_t bits); +std::uint32_t APS5_VABI flushBit16_nid_postfix(Jxr::BitIo* io, std::uint32_t bits); +std::uint32_t APS5_VABI getBit16_nid_postfix(Jxr::BitIo* io, std::uint32_t bits); +std::uint32_t APS5_VABI getBool16_nid_postfix(Jxr::BitIo* io); +std::int32_t APS5_VABI getBit16s_nid_postfix(Jxr::BitIo* io, std::uint32_t bits); +std::uint32_t APS5_VABI getBit32_nid_postfix(Jxr::BitIo* io, std::uint32_t bits); +std::uint32_t APS5_VABI flushToByte_nid_postfix(Jxr::BitIo* io); +void APS5_VABI putBit16z_nid_postfix(Jxr::BitIo* io, std::uint32_t value, std::uint32_t bits); +void APS5_VABI putBit16_nid_postfix(Jxr::BitIo* io, std::uint32_t value, std::uint32_t bits); +void APS5_VABI putBit32_nid_postfix(Jxr::BitIo* io, std::uint32_t value, std::uint32_t bits); +void APS5_VABI fillToByte_nid_postfix(Jxr::BitIo* io); +std::uint32_t APS5_VABI getBit16_S_nid_postfix(void* codec, Jxr::BitIo* io, std::uint32_t bits); +std::uint32_t APS5_VABI getSizeRead_nid_postfix(Jxr::BitIo* io); +std::uint32_t APS5_VABI getSizeWrite_nid_postfix(Jxr::BitIo* io); +std::uint32_t APS5_VABI getPosRead_nid_postfix(Jxr::BitIo* io); +std::uint8_t APS5_VABI dquantBits_nid_postfix(std::uint8_t qp); +std::int64_t APS5_VABI GetImageDecodeIID_nid_postfix(const char* extension, const std::uint32_t** iid); +std::int64_t APS5_VABI GetImageEncodeIID_nid_postfix(const char* extension, const std::uint32_t** iid); +const void* APS5_VABI GetPixelFormatFromHash_nid_postfix(std::uint8_t hash); +std::int32_t APS5_VABI IDPEmpty_nid_postfix(void* codec); +std::int64_t APS5_VABI PKAllocAligned_nid_postfix(void** memory, std::size_t size, std::size_t alignment); +std::int64_t APS5_VABI PKFreeAligned_nid_postfix(void** memory); +void APS5_VABI remapQP_nid_postfix(void* quantizer, std::int32_t shift, std::int32_t scaled); +void APS5_VABI formatQuantizer_nid_postfix(void** quantizers, std::uint8_t mode, std::size_t channels, + std::size_t position, std::int32_t shiftedUv, std::int32_t scaled); +std::int32_t APS5_VABI getACPredMode_nid_postfix(void* macroblock, std::int32_t format); +void APS5_VABI InitZigzagScan_nid_postfix(void* context); int APS5_VABI AllocateCodingContextDec_nid_postfix(); int APS5_VABI CloseWS_List_nid_postfix(); int APS5_VABI CreateWS_List_nid_postfix(); -int APS5_VABI CreateWS_Memory_nid_postfix(); +std::int64_t APS5_VABI CreateWS_Memory_nid_postfix(Jxr::Stream** stream, void* buffer, std::size_t size); int APS5_VABI EOSWS_File_nid_postfix(); -int APS5_VABI EOSWS_Memory_nid_postfix(); +std::int32_t APS5_VABI EOSWS_Memory_nid_postfix(Jxr::Stream* stream); int APS5_VABI FreeDescMetadata_nid_postfix(); -int APS5_VABI GetImageDecodeIID_nid_postfix(); -int APS5_VABI GetImageEncodeIID_nid_postfix(); -int APS5_VABI GetPixelFormatFromHash_nid_postfix(); int APS5_VABI GetPosWS_File_nid_postfix(); int APS5_VABI GetPosWS_List_nid_postfix(); -int APS5_VABI GetPosWS_Memory_nid_postfix(); -int APS5_VABI IDPEmpty_nid_postfix(); -int APS5_VABI InitZigzagScan_nid_postfix(); -int APS5_VABI PKAllocAligned_nid_postfix(); +std::int64_t APS5_VABI GetPosWS_Memory_nid_postfix(Jxr::Stream* stream, std::size_t* position); std::int64_t APS5_VABI PKAlloc_nid_postfix(void** memory, std::size_t size); int APS5_VABI PKCodecFactory_CreateCodec_nid_postfix(); int APS5_VABI PKCodecFactory_CreateDecoderFromFile_nid_postfix(); @@ -47,7 +69,6 @@ int APS5_VABI PKFormatConverter_GetSourcePixelFormat_nid_postfix(); int APS5_VABI PKFormatConverter_InitializeConvert_nid_postfix(); int APS5_VABI PKFormatConverter_Initialize_nid_postfix(); int APS5_VABI PKFormatConverter_Release_nid_postfix(); -int APS5_VABI PKFreeAligned_nid_postfix(); int APS5_VABI PKImageDecode_Copy_nid_postfix(); int APS5_VABI PKImageDecode_Create_WMP_nid_postfix(); int APS5_VABI PKImageDecode_Create_nid_postfix(); @@ -81,16 +102,16 @@ int APS5_VABI PKImageEncode_WritePixels_nid_postfix(); int APS5_VABI PKImageEncode_WriteSource_nid_postfix(); int APS5_VABI ReadWS_File_nid_postfix(); int APS5_VABI ReadWS_List_nid_postfix(); -int APS5_VABI ReadWS_Memory_nid_postfix(); +std::int64_t APS5_VABI ReadWS_Memory_nid_postfix(Jxr::Stream* stream, void* buffer, std::size_t size); int APS5_VABI ResetCodingContext_nid_postfix(); int APS5_VABI SetPosWS_File_nid_postfix(); int APS5_VABI SetPosWS_List_nid_postfix(); -int APS5_VABI SetPosWS_Memory_nid_postfix(); -int APS5_VABI WMPAlloc_nid_postfix(); -int APS5_VABI WMPFree_nid_postfix(); +std::int64_t APS5_VABI SetPosWS_Memory_nid_postfix(Jxr::Stream* stream, std::size_t position); +std::int64_t APS5_VABI WMPAlloc_nid_postfix(void** memory, std::size_t size); +std::int64_t APS5_VABI WMPFree_nid_postfix(void** memory); int APS5_VABI WriteWS_File_nid_postfix(); int APS5_VABI WriteWS_List_nid_postfix(); -int APS5_VABI WriteWS_Memory_nid_postfix(); +std::int64_t APS5_VABI WriteWS_Memory_nid_postfix(Jxr::Stream* stream, const void* buffer, std::size_t size); int APS5_VABI advanceMRPtr_nid_postfix(); int APS5_VABI advanceOneMBRow_nid_postfix(); int APS5_VABI allocateBitIOInfo_nid_postfix(); @@ -104,37 +125,18 @@ int APS5_VABI checkImageBuffer_nid_postfix(); int APS5_VABI detachISRead_nid_postfix(); int APS5_VABI detachISWrite_nid_postfix(); int APS5_VABI detach_SB_nid_postfix(); -int APS5_VABI dquantBits_nid_postfix(); -int APS5_VABI fillToByte_nid_postfix(); -int APS5_VABI flushBit16_nid_postfix(); int APS5_VABI flushToByte_SB_nid_postfix(); -int APS5_VABI flushToByte_nid_postfix(); -int APS5_VABI formatQuantizer_nid_postfix(); int APS5_VABI freePredInfo_nid_postfix(); int APS5_VABI freeQuantizer_nid_postfix(); int APS5_VABI freeTileInfo_nid_postfix(); -int APS5_VABI getACPredMode_nid_postfix(); -int APS5_VABI getBit16_S_nid_postfix(); -int APS5_VABI getBit16_nid_postfix(); -int APS5_VABI getBit16s_nid_postfix(); int APS5_VABI getBit32_SB_nid_postfix(); -int APS5_VABI getBit32_nid_postfix(); -int APS5_VABI getBool16_nid_postfix(); int APS5_VABI getByteRead_SB_nid_postfix(); int APS5_VABI getDCACPredMode_nid_postfix(); -int APS5_VABI getPosRead_nid_postfix(); -int APS5_VABI getSizeRead_nid_postfix(); -int APS5_VABI getSizeWrite_nid_postfix(); int APS5_VABI getTilePos_nid_postfix(); int APS5_VABI initMRPtr_nid_postfix(); int APS5_VABI initPostProc_nid_postfix(); -int APS5_VABI peekBit16_nid_postfix(); int APS5_VABI postProcBlock_nid_postfix(); int APS5_VABI postProcMB_nid_postfix(); -int APS5_VABI putBit16_nid_postfix(); -int APS5_VABI putBit16z_nid_postfix(); -int APS5_VABI putBit32_nid_postfix(); -int APS5_VABI remapQP_nid_postfix(); int APS5_VABI setBitIOPointers_nid_postfix(); int APS5_VABI setUniformQuantizer_nid_postfix(); int APS5_VABI slideOneMBRow_nid_postfix(); diff --git a/core/libs/prx/libSceJxr/src/BitIo.cpp b/core/libs/prx/libSceJxr/src/BitIo.cpp new file mode 100644 index 0000000000..01abd23bc7 --- /dev/null +++ b/core/libs/prx/libSceJxr/src/BitIo.cpp @@ -0,0 +1,149 @@ +#include "prx/libSceJxr/libSceJxr.hpp" +#include +#include +#include + +extern "C" { +std::uint32_t JxrPeekBit16_nid_no_patch(void* io, std::uint32_t bits); +std::uint32_t JxrFlushBit16_nid_no_patch(void* io, std::uint32_t bits); +std::uint32_t JxrGetBit16_nid_no_patch(void* io, std::uint32_t bits); +std::uint32_t JxrGetBool16_nid_no_patch(void* io); +std::int32_t JxrGetBit16s_nid_no_patch(void* io, std::uint32_t bits); +std::uint32_t JxrGetBit32_nid_no_patch(void* io, std::uint32_t bits); +std::uint32_t JxrFlushToByte_nid_no_patch(void* io); +void JxrPutBit16z_nid_no_patch(void* io, std::uint32_t value, std::uint32_t bits); +void JxrFillToByte_nid_no_patch(void* io); +std::uint32_t JxrGetSizeRead_nid_no_patch(void* io); +std::uint32_t JxrGetSizeWrite_nid_no_patch(void* io); +std::uint32_t JxrGetPosRead_nid_no_patch(void* io); +std::uint8_t JxrDquantBits_nid_no_patch(std::uint8_t qp); +} + +namespace { + +void Require(bool condition, const char* function, const char* message) { + if (!condition) throw std::invalid_argument(std::string(function) + ": " + message); +} + +void* State(Jxr::BitIo* io, const char* function) { + Require(io != nullptr, function, "null bit state"); + return io; +} + +void* Stream(Jxr::BitIo* io, const char* function) { + State(io, function); + Require(io->current != nullptr, function, "null current pointer"); + return io; +} + +void* Read(Jxr::BitIo* io, std::uint32_t bits, std::uint32_t limit, const char* function) { + Stream(io, function); + Require(bits <= limit, function, "bit count over the jxrlib limit"); + Require((io->mask & 1) == 0, function, "odd read wrap mask"); + return io; +} + +void* Write(Jxr::BitIo* io, std::uint32_t bits, std::uint32_t limit, const char* function) { + Stream(io, function); + Require(bits <= limit, function, "bit count over the jxrlib limit"); + Require(io->bitsUsed < 16, function, "write position outside the 16-bit word"); + Require(reinterpret_cast(io->current) % alignof(std::uint16_t) == 0, function, + "unaligned write position"); + return io; +} + +std::uint32_t ZeroBitRead(Jxr::BitIo* io) { + JxrFlushBit16_nid_no_patch(io, 0); + return 0; +} + +void PutMasked(Jxr::BitIo* io, std::uint32_t value, std::uint32_t bits) { + if (bits == 0 && io->bitsUsed == 0) return; + JxrPutBit16z_nid_no_patch(io, value & (0xffffu >> (16 - bits)), bits); +} + +} // namespace + +extern "C" { + +std::uint32_t APS5_VABI peekBit16_nid_postfix(Jxr::BitIo* io, std::uint32_t bits) { + Read(io, bits, 16, __func__); + return bits == 0 ? 0 : JxrPeekBit16_nid_no_patch(io, bits); +} + +std::uint32_t APS5_VABI flushBit16_nid_postfix(Jxr::BitIo* io, std::uint32_t bits) { + return JxrFlushBit16_nid_no_patch(Read(io, bits, 16, __func__), bits); +} + +std::uint32_t APS5_VABI getBit16_nid_postfix(Jxr::BitIo* io, std::uint32_t bits) { + Read(io, bits, 16, __func__); + return bits == 0 ? ZeroBitRead(io) : JxrGetBit16_nid_no_patch(io, bits); +} + +std::uint32_t APS5_VABI getBool16_nid_postfix(Jxr::BitIo* io) { + return JxrGetBool16_nid_no_patch(Read(io, 1, 16, __func__)); +} + +std::int32_t APS5_VABI getBit16s_nid_postfix(Jxr::BitIo* io, std::uint32_t bits) { + return JxrGetBit16s_nid_no_patch(Read(io, bits, 15, __func__), bits); +} + +std::uint32_t APS5_VABI getBit32_nid_postfix(Jxr::BitIo* io, std::uint32_t bits) { + Read(io, bits, 32, __func__); + return bits == 0 ? ZeroBitRead(io) : JxrGetBit32_nid_no_patch(io, bits); +} + +std::uint32_t APS5_VABI flushToByte_nid_postfix(Jxr::BitIo* io) { + return JxrFlushToByte_nid_no_patch(Read(io, 0, 16, __func__)); +} + +void APS5_VABI putBit16z_nid_postfix(Jxr::BitIo* io, std::uint32_t value, std::uint32_t bits) { + Require(bits >= 32 || (value >> bits) == 0, __func__, "value wider than the bit count"); + Write(io, bits, 16, __func__); + if (bits == 0 && io->bitsUsed == 0) return; + JxrPutBit16z_nid_no_patch(io, value, bits); +} + +void APS5_VABI putBit16_nid_postfix(Jxr::BitIo* io, std::uint32_t value, std::uint32_t bits) { + Write(io, bits, 16, __func__); + PutMasked(io, value, bits); +} + +void APS5_VABI putBit32_nid_postfix(Jxr::BitIo* io, std::uint32_t value, std::uint32_t bits) { + Write(io, bits, 32, __func__); + if (bits > 16) { + PutMasked(io, value >> (bits - 16), 16); + bits -= 16; + } + PutMasked(io, value, bits); +} + +void APS5_VABI fillToByte_nid_postfix(Jxr::BitIo* io) { + Write(io, 0, 16, __func__); + if (io->bitsUsed == 0) return; + JxrFillToByte_nid_no_patch(io); +} + +std::uint32_t APS5_VABI getBit16_S_nid_postfix(void* codec, Jxr::BitIo* io, std::uint32_t bits) { + const auto value = getBit16_nid_postfix(io, bits); + readIS_nid_postfix(codec, io); + return value; +} + +std::uint32_t APS5_VABI getSizeRead_nid_postfix(Jxr::BitIo* io) { + return JxrGetSizeRead_nid_no_patch(State(io, __func__)); +} + +std::uint32_t APS5_VABI getSizeWrite_nid_postfix(Jxr::BitIo* io) { + return JxrGetSizeWrite_nid_no_patch(State(io, __func__)); +} + +std::uint32_t APS5_VABI getPosRead_nid_postfix(Jxr::BitIo* io) { + return JxrGetPosRead_nid_no_patch(State(io, __func__)); +} + +std::uint8_t APS5_VABI dquantBits_nid_postfix(std::uint8_t qp) { + return JxrDquantBits_nid_no_patch(qp); +} + +} // extern "C" diff --git a/core/libs/prx/libSceJxr/src/GlueHelpers.cpp b/core/libs/prx/libSceJxr/src/GlueHelpers.cpp new file mode 100644 index 0000000000..a328b6b262 --- /dev/null +++ b/core/libs/prx/libSceJxr/src/GlueHelpers.cpp @@ -0,0 +1,81 @@ +#include "prx/libSceJxr/libSceJxr.hpp" +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include + +extern "C" { +const void* JxrHashLookup_nid_no_patch(std::uint8_t hash); +std::int64_t JxrEncodeIid_nid_no_patch(const char* extension, std::uint32_t* value); +std::int64_t JxrDecodeIid_nid_no_patch(const char* extension, std::uint32_t* value); +std::int64_t JxrAllocAligned_nid_no_patch(void** memory, std::size_t size, std::size_t alignment); +std::int64_t JxrFreeAligned_nid_no_patch(void** memory); +std::int32_t JxrIdpEmpty_nid_no_patch(void* codec); + +void* JxrGuestCalloc_nid_no_patch(std::size_t count, std::size_t size) { + return calloc_nid_postfix(count, size); +} + +void JxrGuestFree_nid_no_patch(void* memory) { + free_nid_postfix(memory); +} +} + +namespace { + +constexpr std::uint32_t ImageUnsupported = 100; + +std::int64_t StoreIid(std::int64_t (*lookup)(const char*, std::uint32_t*), const char* extension, + const std::uint32_t** iid, const char* function) { + if (!extension) throw std::invalid_argument(std::string(function) + " requires an extension"); + std::uint32_t value = 0; + const auto result = lookup(extension, &value); + if (result < 0) return result; + if (!iid) throw std::invalid_argument(std::string(function) + " requires an output for a known extension"); + switch (value) { + case 101: *iid = &JxrGuest_IID_PKImageWmpEncode_nid_no_patch; break; + case 201: *iid = &JxrGuest_IID_PKImageWmpDecode_nid_no_patch; break; + case ImageUnsupported: *iid = &ImageUnsupported; break; + default: throw std::logic_error(std::string(function) + ": jxrlib returned an unknown IID " + std::to_string(value) + " for " + extension); + } + return result; +} + +} // namespace + +extern "C" { + +const void* APS5_VABI GetPixelFormatFromHash_nid_postfix(std::uint8_t hash) { + return JxrHashLookup_nid_no_patch(hash); +} + +std::int64_t APS5_VABI GetImageEncodeIID_nid_postfix(const char* extension, const std::uint32_t** iid) { + return StoreIid(JxrEncodeIid_nid_no_patch, extension, iid, "GetImageEncodeIID"); +} + +std::int64_t APS5_VABI GetImageDecodeIID_nid_postfix(const char* extension, const std::uint32_t** iid) { + return StoreIid(JxrDecodeIid_nid_no_patch, extension, iid, "GetImageDecodeIID"); +} + +std::int64_t APS5_VABI PKAllocAligned_nid_postfix(void** memory, std::size_t size, std::size_t alignment) { + if (!memory) throw std::invalid_argument("PKAllocAligned requires an output pointer"); + *memory = nullptr; + if (alignment < sizeof(void*)) + throw std::invalid_argument("PKAllocAligned: alignments below 8 can leave no room for the original pointer"); + constexpr auto maximum = std::numeric_limits::max(); + if (size > maximum - sizeof(void*) || alignment - 1 > maximum - sizeof(void*) - size) + throw std::length_error("PKAllocAligned: block size overflows size_t"); + return JxrAllocAligned_nid_no_patch(memory, size, alignment); +} + +std::int64_t APS5_VABI PKFreeAligned_nid_postfix(void** memory) { + return JxrFreeAligned_nid_no_patch(memory); +} + +std::int32_t APS5_VABI IDPEmpty_nid_postfix(void* codec) { + return JxrIdpEmpty_nid_no_patch(codec); +} + +} diff --git a/core/libs/prx/libSceJxr/src/GuestHeap.h b/core/libs/prx/libSceJxr/src/GuestHeap.h new file mode 100644 index 0000000000..777f12743a --- /dev/null +++ b/core/libs/prx/libSceJxr/src/GuestHeap.h @@ -0,0 +1,13 @@ +#ifndef CORE_LIBS_PRX_LIBSCEJXR_SRC_GUESTHEAP_H +#define CORE_LIBS_PRX_LIBSCEJXR_SRC_GUESTHEAP_H + +#include +#include + +void* JxrGuestCalloc_nid_no_patch(size_t count, size_t size); +void JxrGuestFree_nid_no_patch(void* memory); + +#define calloc JxrGuestCalloc_nid_no_patch +#define free JxrGuestFree_nid_no_patch + +#endif diff --git a/core/libs/prx/libSceJxr/src/NativeBitIo.c b/core/libs/prx/libSceJxr/src/NativeBitIo.c new file mode 100644 index 0000000000..f8e32f06d3 --- /dev/null +++ b/core/libs/prx/libSceJxr/src/NativeBitIo.c @@ -0,0 +1,18 @@ +#include "strcodec.h" +#include + +_Static_assert(sizeof(BitIOInfo) == 48, "BitIOInfo size"); + +uint32_t JxrPeekBit16_nid_no_patch(void* io, uint32_t bits) { return peekBit16((BitIOInfo*)io, bits); } +uint32_t JxrFlushBit16_nid_no_patch(void* io, uint32_t bits) { return flushBit16((BitIOInfo*)io, bits); } +uint32_t JxrGetBit16_nid_no_patch(void* io, uint32_t bits) { return getBit16((BitIOInfo*)io, bits); } +uint32_t JxrGetBool16_nid_no_patch(void* io) { return getBool16((BitIOInfo*)io); } +int32_t JxrGetBit16s_nid_no_patch(void* io, uint32_t bits) { return getBit16s((BitIOInfo*)io, bits); } +uint32_t JxrGetBit32_nid_no_patch(void* io, uint32_t bits) { return getBit32((BitIOInfo*)io, bits); } +uint32_t JxrFlushToByte_nid_no_patch(void* io) { return flushToByte((BitIOInfo*)io); } +void JxrPutBit16z_nid_no_patch(void* io, uint32_t value, uint32_t bits) { putBit16z((BitIOInfo*)io, value, bits); } +void JxrFillToByte_nid_no_patch(void* io) { fillToByte((BitIOInfo*)io); } +uint32_t JxrGetSizeRead_nid_no_patch(void* io) { return getSizeRead((BitIOInfo*)io); } +uint32_t JxrGetSizeWrite_nid_no_patch(void* io) { return getSizeWrite((BitIOInfo*)io); } +uint32_t JxrGetPosRead_nid_no_patch(void* io) { return getPosRead((BitIOInfo*)io); } +uint8_t JxrDquantBits_nid_no_patch(uint8_t qp) { return dquantBits(qp); } diff --git a/core/libs/prx/libSceJxr/src/NativeGlue.c b/core/libs/prx/libSceJxr/src/NativeGlue.c new file mode 100644 index 0000000000..7718a39ac6 --- /dev/null +++ b/core/libs/prx/libSceJxr/src/NativeGlue.c @@ -0,0 +1,36 @@ +#include "strcodec.h" +#include "JXRGlue.h" +#include + +const void* JxrHashLookup_nid_no_patch(U8 hash) { + return GetPixelFormatFromHash(hash); +} + +typedef ERR (*IidLookup)(const char*, const PKIID**); + +static int64_t StoreIid(IidLookup lookup, const char* extension, uint32_t* value) { + const PKIID* iid = NULL; + const ERR result = lookup(extension, &iid); + if (result >= 0 && iid) *value = *iid; + return result; +} + +int64_t JxrEncodeIid_nid_no_patch(const char* extension, uint32_t* value) { + return StoreIid(GetImageEncodeIID, extension, value); +} + +int64_t JxrDecodeIid_nid_no_patch(const char* extension, uint32_t* value) { + return StoreIid(GetImageDecodeIID, extension, value); +} + +int64_t JxrAllocAligned_nid_no_patch(void** memory, size_t size, size_t alignment) { + return PKAllocAligned(memory, size, alignment); +} + +int64_t JxrFreeAligned_nid_no_patch(void** memory) { + return PKFreeAligned(memory); +} + +int32_t JxrIdpEmpty_nid_no_patch(void* codec) { + return IDPEmpty((CWMImageStrCodec*)codec); +} diff --git a/core/libs/prx/libSceJxr/src/NativeQuant.c b/core/libs/prx/libSceJxr/src/NativeQuant.c new file mode 100644 index 0000000000..bd332dff86 --- /dev/null +++ b/core/libs/prx/libSceJxr/src/NativeQuant.c @@ -0,0 +1,23 @@ +#include "strcodec.h" +#include + +_Static_assert(sizeof(CWMIQuantizer) == 20, "CWMIQuantizer size"); +_Static_assert(sizeof(CWMIMBInfo) == 1160, "CWMIMBInfo size"); +_Static_assert(sizeof(CAdaptiveScan) == 8 && sizeof(CCodingContext) == 704, "CCodingContext size"); + +void JxrRemapQp_nid_no_patch(void* quantizer, int32_t shift, int32_t scaled) { + remapQP((CWMIQuantizer*)quantizer, shift, scaled); +} + +void JxrFormatQuantizer_nid_no_patch(void** quantizers, uint8_t mode, size_t channels, size_t position, + int32_t shiftedUv, int32_t scaled) { + formatQuantizer((CWMIQuantizer**)quantizers, mode, channels, position, shiftedUv, scaled); +} + +int32_t JxrGetAcPredMode_nid_no_patch(void* macroblock, int32_t format) { + return getACPredMode((CWMIMBInfo*)macroblock, (COLORFORMAT)format); +} + +void JxrInitZigzagScan_nid_no_patch(void* context) { + InitZigzagScan((CCodingContext*)context); +} diff --git a/core/libs/prx/libSceJxr/src/Quantizer.cpp b/core/libs/prx/libSceJxr/src/Quantizer.cpp new file mode 100644 index 0000000000..44ae36ac67 --- /dev/null +++ b/core/libs/prx/libSceJxr/src/Quantizer.cpp @@ -0,0 +1,59 @@ +#include "prx/libSceJxr/libSceJxr.hpp" +#include +#include +#include +#include + +extern "C" { +void JxrRemapQp_nid_no_patch(void* quantizer, std::int32_t shift, std::int32_t scaled); +void JxrFormatQuantizer_nid_no_patch(void** quantizers, std::uint8_t mode, std::size_t channels, std::size_t position, + std::int32_t shiftedUv, std::int32_t scaled); +std::int32_t JxrGetAcPredMode_nid_no_patch(void* macroblock, std::int32_t format); +void JxrInitZigzagScan_nid_no_patch(void* context); +} + +namespace { + +constexpr std::size_t MaxChannels = 16; + +void Require(bool condition, const char* function, const char* message) { + if (!condition) throw std::invalid_argument(std::string(function) + ": " + message); +} + +void RequireShift(const void* quantizer, std::int32_t shift, std::int32_t scaled, const char* function) { + Require(quantizer != nullptr, function, "null quantizer"); + const auto index = *static_cast(quantizer); + if (index == 0 || !scaled) return; + const std::int64_t exponent = static_cast(shift) + (index < 16 ? 0 : (index >> 4) - 1); + Require(exponent >= 0 && exponent <= 24, function, "shift leaves the QP outside 32 bits"); +} + +} // namespace + +extern "C" { + +void APS5_VABI remapQP_nid_postfix(void* quantizer, std::int32_t shift, std::int32_t scaled) { + RequireShift(quantizer, shift, scaled, __func__); + JxrRemapQp_nid_no_patch(quantizer, shift, scaled); +} + +void APS5_VABI formatQuantizer_nid_postfix(void** quantizers, std::uint8_t mode, std::size_t channels, + std::size_t position, std::int32_t shiftedUv, std::int32_t scaled) { + if (channels == 0) return; + Require(quantizers != nullptr, __func__, "null quantizer table"); + Require(channels <= MaxChannels, __func__, "more than 16 channels"); + for (std::size_t channel = 0; channel < channels; ++channel) + Require(quantizers[channel] != nullptr, __func__, "null channel quantizer"); + JxrFormatQuantizer_nid_no_patch(quantizers, mode, channels, position, shiftedUv, scaled); +} + +std::int32_t APS5_VABI getACPredMode_nid_postfix(void* macroblock, std::int32_t format) { + Require(macroblock != nullptr, __func__, "null macroblock"); + return JxrGetAcPredMode_nid_no_patch(macroblock, format); +} + +void APS5_VABI InitZigzagScan_nid_postfix(void* context) { + JxrInitZigzagScan_nid_no_patch(context); +} + +} // extern "C" diff --git a/core/libs/prx/libSceLibcInternal/CMakeLists.txt b/core/libs/prx/libSceLibcInternal/CMakeLists.txt index e222b977f1..41186537c1 100644 --- a/core/libs/prx/libSceLibcInternal/CMakeLists.txt +++ b/core/libs/prx/libSceLibcInternal/CMakeLists.txt @@ -23,6 +23,17 @@ target_sources(libSceLibcInternal PRIVATE InitOnce.cpp) target_sources(libSceLibcInternal PRIVATE Mutex.cpp) target_sources(libSceLibcInternal PRIVATE AlignedNew.cpp) if(BUILD_TESTING) + add_test_executable(guest_libc_internal_showmanyc_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibcInternalShowmanyc.cpp) + target_include_directories(guest_libc_internal_showmanyc_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libc_internal_showmanyc_tests PRIVATE libSceLibcInternal) + configure_windows_unwind(guest_libc_internal_showmanyc_tests) + add_test(NAME guest_libc_internal_showmanyc COMMAND guest_libc_internal_showmanyc_tests) + set_tests_properties(guest_libc_internal_showmanyc PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_libc_internal_showmanyc PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_libc_internal_aligned_new_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibcInternalAlignedNew.cpp) target_include_directories(guest_libc_internal_aligned_new_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_libc_internal_aligned_new_tests PRIVATE libSceLibcInternal libkernel libc) @@ -327,3 +338,30 @@ if(BUILD_TESTING AND (WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux")) -- "$") set_tests_properties(guest_sigvec_modules PROPERTIES TIMEOUT 20) endif() + +target_sources(libSceLibcInternal PRIVATE MultiPrecision.cpp) +if(BUILD_TESTING) + add_test_executable(guest_libc_internal_multi_precision_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibcInternalMultiPrecision.cpp) + target_include_directories(guest_libc_internal_multi_precision_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libc_internal_multi_precision_tests PRIVATE libSceLibcInternal libkernel libc) + configure_windows_unwind(guest_libc_internal_multi_precision_tests) + add_test(NAME guest_libc_internal_multi_precision COMMAND guest_libc_internal_multi_precision_tests) + set_tests_properties(guest_libc_internal_multi_precision PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_libc_internal_multi_precision PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_libc_internal_firmware_answers_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibcInternalFirmwareAnswers.cpp) + target_include_directories(guest_libc_internal_firmware_answers_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libc_internal_firmware_answers_tests PRIVATE libSceLibcInternal libkernel libc) + configure_windows_unwind(guest_libc_internal_firmware_answers_tests) + add_test(NAME guest_libc_internal_firmware_answers COMMAND guest_libc_internal_firmware_answers_tests) + set_tests_properties(guest_libc_internal_firmware_answers PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_libc_internal_firmware_answers PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceLibcInternal/Export.cpp b/core/libs/prx/libSceLibcInternal/Export.cpp index dff386de77..e33c9e736d 100644 --- a/core/libs/prx/libSceLibcInternal/Export.cpp +++ b/core/libs/prx/libSceLibcInternal/Export.cpp @@ -1136,9 +1136,16 @@ int APS5_VABI _Closreg_nid_postfix() { return 0; } -int APS5_VABI _Daysto_nid_postfix() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI _Daysto_nid_postfix(int year, int month) { + static constexpr short DaysBeforeMonth[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; + static constexpr short LeapDaysBeforeMonth[12] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}; + if (month < 0 || month >= 12) throw std::out_of_range("_Daysto: the month index is outside the twelve-entry tables"); + const auto bits = static_cast(year); + std::uint32_t leapDays = 0; + if (year > 0) leapDays = (bits - 1u) >> 2; + else if (year <= -4) leapDays = ((4u - bits) >> 2) + 1u; + const bool common = (bits & 3u) != 0 || year == 0; + return static_cast(leapDays + static_cast(common ? DaysBeforeMonth[month] : LeapDaysBeforeMonth[month])); } int APS5_VABI _Deletegloballocale_nid_postfix() { @@ -1248,9 +1255,15 @@ int APS5_VABI _FXp_addx_nid_postfix() { return 0; } -int APS5_VABI _FXp_getw_nid_postfix() { - NotImplemented_nid_no_patch(__func__); - return 0; +float APS5_VABI _FXp_getw_nid_postfix(const float* parts, int count) { + if (count == 0) return 0.0f; + if (count == 1 || parts[0] == 0.0f || parts[1] == 0.0f) return parts[0]; + if (count == 2 || parts[2] == 0.0f) return parts[0] + parts[1]; + const float head = parts[0] + parts[1]; + float tail = parts[2]; + if (count >= 4) tail += parts[3]; + if (head - parts[0] == parts[1]) return head + tail; + return parts[0] + (parts[1] + tail); } int APS5_VABI _FXp_invx_nid_postfix() { @@ -1440,8 +1453,7 @@ int APS5_VABI sceLibcInternalUnknown_wUa_oPQvFZ0_nid_no_patch() { APS5_EXPORT("wUa+oPQvFZ0", sceLibcInternalUnknown_wUa_oPQvFZ0_nid_no_patch); int APS5_VABI _Isdst_nid_postfix() { - NotImplemented_nid_no_patch(__func__); - return 0; + return -1; } int APS5_VABI _LAtan_nid_postfix() { diff --git a/core/libs/prx/libSceLibcInternal/InventoryExports.cpp b/core/libs/prx/libSceLibcInternal/InventoryExports.cpp index 3047b1c7a3..d4dd88e4da 100644 --- a/core/libs/prx/libSceLibcInternal/InventoryExports.cpp +++ b/core/libs/prx/libSceLibcInternal/InventoryExports.cpp @@ -229,8 +229,8 @@ int APS5_VABI libSceLibcInternalInventory_046_nid_no_patch() { } APS5_EXPORT("sXaxl1QGorg", libSceLibcInternalInventory_046_nid_no_patch); -int APS5_VABI libSceLibcInternalInventory_047_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); +std::streamsize APS5_VABI libSceLibcInternalInventory_047_nid_no_patch(std::basic_streambuf*) { + return 0; } APS5_EXPORT("IAEl1Ta7yVU", libSceLibcInternalInventory_047_nid_no_patch); @@ -244,8 +244,8 @@ int APS5_VABI libSceLibcInternalInventory_049_nid_no_patch() { } APS5_EXPORT("041c37QfoUc", libSceLibcInternalInventory_049_nid_no_patch); -int APS5_VABI libSceLibcInternalInventory_050_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); +std::streamsize APS5_VABI libSceLibcInternalInventory_050_nid_no_patch(std::basic_streambuf*) { + return 0; } APS5_EXPORT("olsoiZsezkk", libSceLibcInternalInventory_050_nid_no_patch); @@ -1224,26 +1224,6 @@ int APS5_VABI libSceLibcInternalInventory_246_nid_no_patch() { } APS5_EXPORT("aqyjhIx7jaY", libSceLibcInternalInventory_246_nid_no_patch); -int APS5_VABI libSceLibcInternalInventory_247_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("QfPuSqhub7o", libSceLibcInternalInventory_247_nid_no_patch); - -int APS5_VABI libSceLibcInternalInventory_248_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("lrQSsTRMMr4", libSceLibcInternalInventory_248_nid_no_patch); - -int APS5_VABI libSceLibcInternalInventory_249_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("Gav1Xt1Ce+c", libSceLibcInternalInventory_249_nid_no_patch); - -int APS5_VABI libSceLibcInternalInventory_250_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("Ozk+Z6QnlTY", libSceLibcInternalInventory_250_nid_no_patch); - int APS5_VABI libSceLibcInternalInventory_251_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libSceLibcInternal/MultiPrecision.cpp b/core/libs/prx/libSceLibcInternal/MultiPrecision.cpp new file mode 100644 index 0000000000..9c01797599 --- /dev/null +++ b/core/libs/prx/libSceLibcInternal/MultiPrecision.cpp @@ -0,0 +1,147 @@ +/* Derived from Microsoft STL stl/src/multprec.cpp (219514876ea86491de191ceaa88632616bbc0f19) */ +/* Copyright (c) Microsoft Corporation. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception */ +#include "prx/libSceLibcInternal/include/MultiPrecision.hpp" +#include "prx/libc/include/general/ExportMacros.hpp" +#include +#include +#include + +namespace { + +constexpr int ValueWords = 5; +constexpr int DigitBits = 32; +constexpr unsigned long long DigitMask = 0xFFFFFFFFull; +constexpr unsigned long long DigitBase = DigitMask + 1; + +void RequireValue(const unsigned long long* value, const char* function) { + if (!value) throw std::invalid_argument(std::string(function) + ": null value"); +} + +void AddDigits(unsigned long long* value, int valueWords, const unsigned long long* addend, int addendWords) { + unsigned long long carry = 0; + int index = 0; + for (; index < addendWords; ++index) { + value[index] += addend[index] + carry; + carry = value[index] >> DigitBits; + value[index] &= DigitMask; + } + for (; carry != 0 && index < valueWords; ++index) { + value[index] += carry; + carry = value[index] >> DigitBits; + value[index] &= DigitMask; + } +} + +void ScaleDigits(unsigned long long* value, int words, unsigned long long factor) { + unsigned long long carry = 0; + for (int index = 0; index < words; ++index) { + value[index] = value[index] * factor + carry; + carry = value[index] >> DigitBits; + value[index] &= DigitMask; + } +} + +void UnscaleDigits(unsigned long long* value, unsigned long long factor) { + unsigned long long remainder = 0; + for (int index = ValueWords - 1; index >= 0; --index) { + const unsigned long long current = (remainder << DigitBits) + value[index]; + value[index] = current / factor; + remainder = current % factor; + } +} + +unsigned long long TakeBorrow(unsigned long long& word) { + const unsigned long long high = word >> DigitBits; + word &= DigitMask; + return high == 0 ? 0 : DigitBase - high; +} + +void Reduce(unsigned long long* value, unsigned long long divisor) { + if (divisor == 0) throw std::invalid_argument("_MP_Rem: zero divisor"); + if (value[ValueWords - 1] != 0) throw std::invalid_argument("_MP_Rem: nonzero fifth word"); + int words = ValueWords - 1; + while (words > 0 && value[words - 1] == 0) --words; + if (words == 0) throw std::invalid_argument("_MP_Rem: zero value"); + unsigned long long digits[2] = {divisor & DigitMask, divisor >> DigitBits}; + const int divisorWords = digits[1] == 0 ? 1 : 2; + unsigned long long work[ValueWords]; + std::copy_n(value, ValueWords, work); + const unsigned long long scale = DigitBase / (digits[divisorWords - 1] + 1); + if (scale != 1) { + ScaleDigits(work, ValueWords, scale); + ScaleDigits(digits, divisorWords, scale); + } + const unsigned long long leading = digits[divisorWords - 1]; + for (int position = words - divisorWords; position >= 0; --position) { + const unsigned long long top = (work[position + divisorWords] << DigitBits) + work[position + divisorWords - 1]; + unsigned long long quotient = top / leading; + if (quotient == 0) continue; + unsigned long long remainder = top % leading; + for (;;) { + if (quotient < DigitBase) { + if (divisorWords == 1) throw std::invalid_argument("_MP_Rem: nonzero quotient digit for divisor " + std::to_string(divisor)); + if (quotient * digits[0] <= (remainder << DigitBits) + work[position]) break; + } + --quotient; + remainder += leading; + if (remainder >= DigitBase) break; + } + unsigned long long borrow = 0; + int index = position; + for (; index < position + divisorWords; ++index) { + work[index] -= quotient * digits[index - position] + borrow; + borrow = TakeBorrow(work[index]); + } + for (; borrow != 0 && index < ValueWords; ++index) { + work[index] -= borrow; + borrow = TakeBorrow(work[index]); + } + if (borrow != 0) AddDigits(work + position, divisorWords + 1, digits, divisorWords); + } + if (scale != 1) UnscaleDigits(work, scale); + std::copy_n(work, ValueWords, value); +} + +} + +extern "C" { + +unsigned long long APS5_VABI sceLibcInternalMultiPrecisionGet_nid_no_patch(unsigned long long* value) { + RequireValue(value, "_MP_Get"); + return (value[1] << DigitBits) + value[0]; +} +APS5_EXPORT("lrQSsTRMMr4", sceLibcInternalMultiPrecisionGet_nid_no_patch); + +void APS5_VABI sceLibcInternalMultiPrecisionAdd_nid_no_patch(unsigned long long* value, unsigned long long addend) { + RequireValue(value, "_MP_Add"); + const unsigned long long digits[2] = {addend & DigitMask, addend >> DigitBits}; + AddDigits(value, ValueWords, digits, 2); +} +APS5_EXPORT("QfPuSqhub7o", sceLibcInternalMultiPrecisionAdd_nid_no_patch); + +void APS5_VABI sceLibcInternalMultiPrecisionMultiply_nid_no_patch(unsigned long long* product, unsigned long long left, unsigned long long right) { + RequireValue(product, "_MP_Mul"); + const unsigned long long leftDigits[2] = {left & DigitMask, left >> DigitBits}; + const unsigned long long rightDigits[2] = {right & DigitMask, right >> DigitBits}; + for (int index = 0; index < ValueWords; ++index) product[index] = 0; + for (int column = 0; column < 2; ++column) { + unsigned long long carry = 0; + for (int row = 0; row < 2; ++row) { + unsigned long long& word = product[row + column]; + word = leftDigits[row] * rightDigits[column] + word + carry; + carry = word >> DigitBits; + word &= DigitMask; + } + product[column + 2] = carry; + } +} +APS5_EXPORT("Gav1Xt1Ce+c", sceLibcInternalMultiPrecisionMultiply_nid_no_patch); + +void APS5_VABI sceLibcInternalMultiPrecisionRemainder_nid_no_patch(unsigned long long* value, unsigned long long divisor) { + RequireValue(value, "_MP_Rem"); + Reduce(value, divisor); +} +APS5_EXPORT("Ozk+Z6QnlTY", sceLibcInternalMultiPrecisionRemainder_nid_no_patch); + +} diff --git a/core/libs/prx/libSceLibcInternal/include/InventoryExports.hpp b/core/libs/prx/libSceLibcInternal/include/InventoryExports.hpp index 83ea0a2f25..463daa0be3 100644 --- a/core/libs/prx/libSceLibcInternal/include/InventoryExports.hpp +++ b/core/libs/prx/libSceLibcInternal/include/InventoryExports.hpp @@ -1,6 +1,7 @@ #pragma once #include "SceTypes.hpp" +#include extern "C" { @@ -49,10 +50,10 @@ int APS5_VABI libSceLibcInternalInventory_042_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_043_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_045_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_046_nid_no_patch(); -int APS5_VABI libSceLibcInternalInventory_047_nid_no_patch(); +std::streamsize APS5_VABI libSceLibcInternalInventory_047_nid_no_patch(std::basic_streambuf*); int APS5_VABI libSceLibcInternalInventory_048_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_049_nid_no_patch(); -int APS5_VABI libSceLibcInternalInventory_050_nid_no_patch(); +std::streamsize APS5_VABI libSceLibcInternalInventory_050_nid_no_patch(std::basic_streambuf*); int APS5_VABI libSceLibcInternalInventory_052_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_053_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_054_nid_no_patch(); @@ -248,10 +249,6 @@ int APS5_VABI libSceLibcInternalInventory_243_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_244_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_245_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_246_nid_no_patch(); -int APS5_VABI libSceLibcInternalInventory_247_nid_no_patch(); -int APS5_VABI libSceLibcInternalInventory_248_nid_no_patch(); -int APS5_VABI libSceLibcInternalInventory_249_nid_no_patch(); -int APS5_VABI libSceLibcInternalInventory_250_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_251_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_252_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_253_nid_no_patch(); diff --git a/core/libs/prx/libSceLibcInternal/include/MathInternals.hpp b/core/libs/prx/libSceLibcInternal/include/MathInternals.hpp index d706b9c36f..508e2c90dd 100644 --- a/core/libs/prx/libSceLibcInternal/include/MathInternals.hpp +++ b/core/libs/prx/libSceLibcInternal/include/MathInternals.hpp @@ -16,7 +16,7 @@ int APS5_VABI _FTan_nid_postfix(); int APS5_VABI _FTgamma_nid_postfix(); int APS5_VABI _FXp_addh_nid_postfix(); int APS5_VABI _FXp_addx_nid_postfix(); -int APS5_VABI _FXp_getw_nid_postfix(); +float APS5_VABI _FXp_getw_nid_postfix(const float* parts, int count); int APS5_VABI _FXp_invx_nid_postfix(); int APS5_VABI _FXp_ldexpx_nid_postfix(); int APS5_VABI _FXp_movx_nid_postfix(); diff --git a/core/libs/prx/libSceLibcInternal/include/MultiPrecision.hpp b/core/libs/prx/libSceLibcInternal/include/MultiPrecision.hpp new file mode 100644 index 0000000000..5a68bd7e2d --- /dev/null +++ b/core/libs/prx/libSceLibcInternal/include/MultiPrecision.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +unsigned long long APS5_VABI sceLibcInternalMultiPrecisionGet_nid_no_patch(unsigned long long* value); +void APS5_VABI sceLibcInternalMultiPrecisionAdd_nid_no_patch(unsigned long long* value, unsigned long long addend); +void APS5_VABI sceLibcInternalMultiPrecisionMultiply_nid_no_patch(unsigned long long* product, unsigned long long left, unsigned long long right); +void APS5_VABI sceLibcInternalMultiPrecisionRemainder_nid_no_patch(unsigned long long* value, unsigned long long divisor); + +} diff --git a/core/libs/prx/libSceLibcInternal/include/RuntimeInternals.hpp b/core/libs/prx/libSceLibcInternal/include/RuntimeInternals.hpp index 4d3634a435..b3a225156c 100644 --- a/core/libs/prx/libSceLibcInternal/include/RuntimeInternals.hpp +++ b/core/libs/prx/libSceLibcInternal/include/RuntimeInternals.hpp @@ -11,7 +11,7 @@ int APS5_VABI _CStrxfrm_nid_postfix(); int APS5_VABI _CWcsxfrm_nid_postfix(); int APS5_VABI _Call_onceEx_nid_postfix(); int APS5_VABI _Closreg_nid_postfix(); -int APS5_VABI _Daysto_nid_postfix(); +int APS5_VABI _Daysto_nid_postfix(int year, int month); int APS5_VABI _Deletegloballocale_nid_postfix(); int APS5_VABI _Do_call_nid_postfix(); int APS5_VABI _Fac_tidy_nid_postfix(); diff --git a/core/libs/prx/libSceLibcInternal/libSceLibcInternal.hpp b/core/libs/prx/libSceLibcInternal/libSceLibcInternal.hpp index 8fb6498e98..9dd1e44ff4 100644 --- a/core/libs/prx/libSceLibcInternal/libSceLibcInternal.hpp +++ b/core/libs/prx/libSceLibcInternal/libSceLibcInternal.hpp @@ -4,4 +4,5 @@ #include "prx/libSceLibcInternal/include/RuntimeInternals.hpp" #include "prx/libSceLibcInternal/include/Api.hpp" #include "prx/libSceLibcInternal/include/MathInternals.hpp" +#include "prx/libSceLibcInternal/include/MultiPrecision.hpp" #include "prx/libSceLibcInternal/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index 67f824516f..1585a0d28d 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Bignum.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rsa.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509StoreContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Pkey.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/EvpCipherCtx.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Evp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ec.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/OcspResponse.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/EvpCipher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/EvpAes.cpp) +add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Bignum.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rsa.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509StoreContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Pkey.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/EvpCipherCtx.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Evp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ec.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/OcspResponse.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/EvpCipher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/EvpAes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509Name.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ssl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/SslCtx.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/SslCtxCallbacks.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/SslSession.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/PkeyLifecycle.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/UiString.cpp) target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto) configure_windows_unwind(libSceLibreSsl) @@ -40,6 +40,17 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_libressl_pkey_lifecycle_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslPkeyLifecycle.cpp) + target_include_directories(guest_libressl_pkey_lifecycle_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_pkey_lifecycle_tests PRIVATE libSceLibreSsl libc) + configure_windows_unwind(guest_libressl_pkey_lifecycle_tests) + add_test(NAME guest_libressl_pkey_lifecycle COMMAND guest_libressl_pkey_lifecycle_tests) + set_tests_properties(guest_libressl_pkey_lifecycle PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_pkey_lifecycle PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_libressl_digest_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslDigest.cpp) target_include_directories(guest_libressl_digest_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_libressl_digest_tests PRIVATE libSceLibreSsl libc) @@ -165,7 +176,7 @@ if(BUILD_TESTING) add_test_executable(guest_libressl_rsa_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslRsa.cpp) target_include_directories(guest_libressl_rsa_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_libressl_rsa_tests PRIVATE libSceLibreSsl) + target_link_libraries(guest_libressl_rsa_tests PRIVATE libSceLibreSsl libc) configure_windows_unwind(guest_libressl_rsa_tests) add_test(NAME guest_libressl_rsa COMMAND guest_libressl_rsa_tests) set_tests_properties(guest_libressl_rsa PROPERTIES TIMEOUT 30) @@ -195,6 +206,28 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_libressl_ssl_ctx_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslSslCtx.cpp) + target_include_directories(guest_libressl_ssl_ctx_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_ssl_ctx_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_ssl_ctx_tests) + add_test(NAME guest_libressl_ssl_ctx COMMAND guest_libressl_ssl_ctx_tests) + set_tests_properties(guest_libressl_ssl_ctx PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_ssl_ctx PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + + add_test_executable(guest_libressl_ssl_ctx_fields_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslSslCtxFields.cpp) + target_include_directories(guest_libressl_ssl_ctx_fields_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_ssl_ctx_fields_tests PRIVATE libSceLibreSsl libc) + configure_windows_unwind(guest_libressl_ssl_ctx_fields_tests) + add_test(NAME guest_libressl_ssl_ctx_fields COMMAND guest_libressl_ssl_ctx_fields_tests) + set_tests_properties(guest_libressl_ssl_ctx_fields PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_ssl_ctx_fields PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_libressl_evp_cipher_ctx_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslEvpCipherCtx.cpp) target_include_directories(guest_libressl_evp_cipher_ctx_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_libressl_evp_cipher_ctx_tests PRIVATE libSceLibreSsl libc) @@ -228,6 +261,17 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_libressl_x509_name_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslX509Name.cpp) + target_include_directories(guest_libressl_x509_name_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_x509_name_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_x509_name_tests) + add_test(NAME guest_libressl_x509_name COMMAND guest_libressl_x509_name_tests) + set_tests_properties(guest_libressl_x509_name PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_x509_name PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_libressl_ocsp_response_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslOcspResponse.cpp) target_include_directories(guest_libressl_ocsp_response_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_libressl_ocsp_response_tests PRIVATE libSceLibreSsl) @@ -238,6 +282,39 @@ if(BUILD_TESTING) set_tests_properties(guest_libressl_ocsp_response PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_libressl_ui_string_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslUiString.cpp) + target_include_directories(guest_libressl_ui_string_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_ui_string_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_ui_string_tests) + add_test(NAME guest_libressl_ui_string COMMAND guest_libressl_ui_string_tests) + set_tests_properties(guest_libressl_ui_string PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_ui_string PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + + add_test_executable(guest_libressl_ssl_ctx_callbacks_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslSslCtxCallbacks.cpp) + target_include_directories(guest_libressl_ssl_ctx_callbacks_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_ssl_ctx_callbacks_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_ssl_ctx_callbacks_tests) + add_test(NAME guest_libressl_ssl_ctx_callbacks COMMAND guest_libressl_ssl_ctx_callbacks_tests) + set_tests_properties(guest_libressl_ssl_ctx_callbacks PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_ssl_ctx_callbacks PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + + add_test_executable(guest_libressl_ssl_session_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslSslSession.cpp) + target_include_directories(guest_libressl_ssl_session_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_ssl_session_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_ssl_session_tests) + add_test(NAME guest_libressl_ssl_session COMMAND guest_libressl_ssl_session_tests) + set_tests_properties(guest_libressl_ssl_session PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_ssl_session PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Cipher.cpp) @@ -268,6 +345,21 @@ if(BUILD_TESTING) endif() endif() +target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/OcspBasicResponse.cpp) + +if(BUILD_TESTING) + add_test_executable(guest_libressl_ocsp_basic_response_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslOcspBasicResponse.cpp) + target_include_directories(guest_libressl_ocsp_basic_response_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_ocsp_basic_response_tests PRIVATE libSceLibreSsl libc) + configure_windows_unwind(guest_libressl_ocsp_basic_response_tests) + add_test(NAME guest_libressl_ocsp_basic_response COMMAND guest_libressl_ocsp_basic_response_tests) + set_tests_properties(guest_libressl_ocsp_basic_response PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_ocsp_basic_response PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Alert.cpp) if(BUILD_TESTING) @@ -454,3 +546,29 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +if(BUILD_TESTING) + add_test_executable(guest_libressl_ssl_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslSsl.cpp) + target_include_directories(guest_libressl_ssl_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_ssl_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_ssl_tests) + add_test(NAME guest_libressl_ssl COMMAND guest_libressl_ssl_tests) + set_tests_properties(guest_libressl_ssl PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_ssl PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_libressl_ssl_settings_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslSslSettings.cpp) + target_include_directories(guest_libressl_ssl_settings_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_ssl_settings_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_ssl_settings_tests) + add_test(NAME guest_libressl_ssl_settings COMMAND guest_libressl_ssl_settings_tests) + set_tests_properties(guest_libressl_ssl_settings PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_ssl_settings PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 7f4efd2144..7fddf9b0f7 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -54,26 +54,11 @@ int APS5_VABI BIO_f_ssl_nid_postfix(void) { return 0; } -int APS5_VABI BIO_new_fp_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI BIO_new_socket_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI BIO_printf_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI BIO_s_file_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI BIO_write_nid_postfix(LibreSsl::Bio* bio, const void* source, int size) { return LibreSslBioWrite_nid_no_patch(bio, source, size); } @@ -300,11 +285,6 @@ int APS5_VABI EVP_PKEY_copy_parameters_nid_postfix(void) { return 0; } -int APS5_VABI EVP_PKEY_free_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI EVP_PKEY_get1_EC_KEY_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -320,11 +300,6 @@ int APS5_VABI EVP_PKEY_id_nid_postfix(void) { return 0; } -int APS5_VABI EVP_PKEY_new_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI EVP_PKEY_set1_RSA_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -366,21 +341,6 @@ int APS5_VABI OCSP_basic_verify_nid_postfix(void) { return 0; } -int APS5_VABI OCSP_check_validity_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI OCSP_resp_count_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI OCSP_resp_get0_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI OCSP_response_get1_basic_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -443,21 +403,11 @@ int APS5_VABI PKCS12_parse_nid_postfix(void) { return 0; } -int APS5_VABI RSA_free_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI RSA_generate_key_ex_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI RSA_new_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI RSA_padding_add_PKCS1_PSS_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -473,26 +423,11 @@ int APS5_VABI RSA_private_encrypt_nid_postfix(void) { return 0; } -int APS5_VABI RSA_public_decrypt_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI RSA_public_encrypt_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI RSA_sign_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI RSA_verify_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI RSA_verify_PKCS1_PSS_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -503,41 +438,21 @@ int APS5_VABI SSL_CTX_add_client_CA_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_callback_ctrl_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_check_private_key_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI SSL_CTX_ctrl_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_free_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI SSL_CTX_get_cert_store_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_get_ex_data_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_load_verify_locations_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -553,11 +468,6 @@ int APS5_VABI SSL_CTX_new_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_sess_set_new_cb_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_set1_groups_list_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -568,75 +478,22 @@ int APS5_VABI SSL_CTX_set_alpn_protos_nid_postfix(void) { return 0; } -int APS5_VABI SSL_CTX_set_alpn_select_cb_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_set_cert_store_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI SSL_CTX_set_cert_verify_callback_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_set_cipher_list_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI SSL_CTX_set_client_cert_cb_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_CTX_set_ex_data_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI SSL_CTX_set_info_callback_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} -int APS5_VABI SSL_CTX_set_msg_callback_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SSL_CTX_set_next_proto_select_cb_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SSL_CTX_set_session_id_context_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SSL_CTX_set_verify_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} int APS5_VABI SSL_CTX_use_PrivateKey_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); @@ -693,21 +550,11 @@ int APS5_VABI SSL_connect_nid_postfix(void) { return 0; } -int APS5_VABI SSL_ctrl_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_free_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI SSL_get0_alpn_selected_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_get0_param_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -718,20 +565,6 @@ int APS5_VABI SSL_get1_session_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_SSL_CTX_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SSL_get_certificate_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SSL_get_current_cipher_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} int APS5_VABI SSL_get_error_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); @@ -758,40 +591,12 @@ int APS5_VABI SSL_get_peer_certificate_nid_postfix(void) { return 0; } -int APS5_VABI SSL_get_privatekey_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SSL_get_rbio_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} int APS5_VABI SSL_get_servername_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI SSL_get_session_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SSL_get_shutdown_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SSL_get_verify_result_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SSL_get_wbio_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} int APS5_VABI SSL_library_init_nid_postfix(void) { return 1; @@ -829,11 +634,6 @@ int APS5_VABI SSL_set_bio_nid_postfix(void) { return 0; } -int APS5_VABI SSL_set_connect_state_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_set_ex_data_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -849,11 +649,6 @@ int APS5_VABI SSL_set_session_nid_postfix(void) { return 0; } -int APS5_VABI SSL_set_verify_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_shutdown_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -869,16 +664,6 @@ int APS5_VABI SSL_use_certificate_ASN1_nid_postfix(void) { return 0; } -int APS5_VABI SSL_version_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI SSL_want_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI SSL_write_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -908,21 +693,6 @@ int APS5_VABI UI_OpenSSL_nid_postfix(void) { return 0; } -int APS5_VABI UI_get0_user_data_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI UI_get_input_flags_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI UI_get_string_type_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI X509V3_EXT_print_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -954,26 +724,6 @@ int APS5_VABI X509_INFO_free_nid_postfix(void) { return 0; } -int APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI X509_NAME_get_entry_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI X509_NAME_get_text_by_NID_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI X509_NAME_oneline_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1124,11 +874,6 @@ int APS5_VABI d2i_PKCS12_fp_nid_postfix(void) { return 0; } -int APS5_VABI d2i_RSA_PUBKEY_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI d2i_X509_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/MemoryBio.cmake b/core/libs/prx/libSceLibreSsl/MemoryBio.cmake index a9ed5d36c8..187cfe01c4 100644 --- a/core/libs/prx/libSceLibreSsl/MemoryBio.cmake +++ b/core/libs/prx/libSceLibreSsl/MemoryBio.cmake @@ -1,4 +1,5 @@ -target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_LIST_DIR}/src/Bio.cpp) +target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_LIST_DIR}/src/Bio.cpp ${CMAKE_CURRENT_LIST_DIR}/src/BioFileSocket.cpp) +target_link_libraries(libSceLibreSsl PRIVATE libc libSceNet) if(BUILD_TESTING) add_test_executable(guest_libressl_bio_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslBio.cpp) @@ -21,7 +22,8 @@ if(BUILD_TESTING) target_link_libraries(guest_libressl_bio_patched_tests PRIVATE libc) endif() add_custom_target(libressl_bio_patched_libraries DEPENDS - ${PATCHED_DIR}/libSceLibreSsl.prx ${PATCHED_DIR}/libc.prx ${PATCHED_DIR}/libkernel.prx) + ${PATCHED_DIR}/libSceLibreSsl.prx ${PATCHED_DIR}/libc.prx ${PATCHED_DIR}/libkernel.prx + ${PATCHED_DIR}/libSceNet.prx ${PATCHED_DIR}/libSceNetCtl.prx) add_dependencies(guest_libressl_bio_patched_tests libressl_bio_patched_libraries) add_test(NAME guest_libressl_bio_patched COMMAND guest_libressl_bio_patched_tests ${PATCHED_DIR}) set_tests_properties(guest_libressl_bio_patched PROPERTIES TIMEOUT 30) diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index 3c2a5e02b6..458dd916db 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -265,6 +265,147 @@ struct LibreSslX509StoreContext { void* exData; }; +struct LibreSslSslInternal { + unsigned char beforeOptions[8]; + std::uint64_t options; + std::uint64_t mode; + unsigned char beforeHandshakeFunc[32]; + void* handshakeFunc; + void* msgCallback; + void* msgCallbackArg; + unsigned char beforeVerifyCallback[8]; + void* verifyCallback; + unsigned char beforeRwstate[60]; + int rwstate; + unsigned char beforeShutdown[8]; + int shutdown; + unsigned char beforeReadAhead[40]; + int readAhead; + unsigned char beforeAeadReadCtx[24]; + void* aeadReadCtx; + void* aeadWriteCtx; + void* encWriteCtx; + void* writeHash; + unsigned char beforeMaxCertList[24]; + std::int64_t maxCertList; + unsigned char beforeState[108]; + int state; + unsigned char rest[16]; +}; + +struct LibreSslSsl { + int version; + const void* method; + void* rbio; + void* wbio; + void* bbio; + int server; + void* s3; + void* d1; + void* param; + void* cipherList; + void* cert; + unsigned int sidCtxLength; + unsigned char sidCtx[32]; + void* session; + int verifyMode; + int error; + int errorCode; + void* ctx; + std::int64_t verifyResult; + int references; + int clientVersion; + unsigned int maxSendFragment; + char* tlsextHostname; + int tlsextStatusType; + void* initialCtx; + void* encReadCtx; + void* readHash; + LibreSslSslInternal* internal; +}; + +struct LibreSslCtxInternal { + std::uint16_t minVersion; + std::uint16_t maxVersion; + std::uint64_t options; + std::uint64_t mode; + void* newSessionCb; + void* removeSessionCb; + void* getSessionCb; + void* appVerifyCallback; + void* appVerifyArg; + void* clientCertCb; + void* appGenCookieCb; + void* appVerifyCookieCb; + void* infoCallback; + void* msgCallback; + void* msgCallbackArg; + void* defaultVerifyCallback; + void* generateSessionId; + void* tlsextServernameCallback; + void* tlsextServernameArg; + void* tlsextTicketKeyCb; + void* tlsextStatusCb; + void* tlsextStatusArg; + void* sessions; + std::uint64_t sessionCacheSize; + void* sessionCacheHead; + void* sessionCacheTail; + int sessionCacheMode; + int sessConnect; + int sessConnectRenegotiate; + int sessConnectGood; + int sessAccept; + int sessAcceptRenegotiate; + int sessAcceptGood; + int sessMiss; + int sessTimeout; + int sessCacheFull; + int sessHit; + int sessCbHit; + void* exData; + void* cipherListById; + void* cert; + void* clientCa; + std::int64_t maxCertList; + int readAhead; + int quietShutdown; + unsigned int maxSendFragment; + void* clientCertEngine; + unsigned char tlsextTickKeyName[16]; + unsigned char tlsextTickHmacKey[16]; + unsigned char tlsextTickAesKey[16]; + void* srtpProfiles; + void* nextProtosAdvertisedCb; + void* nextProtosAdvertisedCbArg; + void* nextProtoSelectCb; + void* nextProtoSelectCbArg; + void* alpnSelectCb; + void* alpnSelectCbArg; + unsigned char* alpnClientProtoList; + unsigned int alpnClientProtoListLen; + std::uint64_t tlsextEcpointformatlistLength; + std::uint8_t* tlsextEcpointformatlist; + std::uint64_t tlsextSupportedgroupsLength; + std::uint16_t* tlsextSupportedgroups; +}; + +struct LibreSslCtx { + const void* method; + void* cipherList; + void* certStore; + std::int64_t sessionTimeout; + int references; + void* extraCerts; + int verifyMode; + unsigned int sidCtxLength; + unsigned char sidCtx[32]; + void* param; + void* defaultPasswdCallback; + void* defaultPasswdCallbackUserdata; + LibreSslCtxInternal* internal; +}; + struct EvpMdContext; struct EvpMd { @@ -367,6 +508,38 @@ struct LibreSslOcspSingleResponse { void* singleExtensions; }; +struct LibreSslX509NameEntry { + void* object; + void* value; + int set; + int size; +}; + +struct LibreSslX509Name { + LibreSslStack* entries; + int modified; + void* bytes; + unsigned char* canonEnc; + int canonEnclen; +}; + +struct LibreSslCertKey { + void* x509; + void* privatekey; + const void* digest; +}; + +struct LibreSslCert { + LibreSslCertKey* key; + unsigned char rest[248]; +}; + +struct LibreSslSession { + unsigned char beforeCipher[168]; + const void* cipher; + unsigned char rest[56]; +}; + extern "C" { std::int64_t APS5_VABI ASN1_INTEGER_get_nid_postfix(const Asn1String* value); @@ -380,12 +553,12 @@ std::int64_t APS5_VABI BIO_ctrl_nid_postfix(LibreSsl::Bio* bio, int command, std int APS5_VABI BIO_f_ssl_nid_postfix(void); int APS5_VABI BIO_free_nid_postfix(LibreSsl::Bio* bio); LibreSsl::Bio* APS5_VABI BIO_new_nid_postfix(const LibreSsl::BioMethod* method); -int APS5_VABI BIO_new_fp_nid_postfix(void); +LibreSsl::Bio* APS5_VABI BIO_new_fp_nid_postfix(void* stream, int closeFlag); LibreSsl::Bio* APS5_VABI BIO_new_mem_buf_nid_postfix(const void* source, int size); -int APS5_VABI BIO_new_socket_nid_postfix(void); +LibreSsl::Bio* APS5_VABI BIO_new_socket_nid_postfix(int socket, int closeFlag); int APS5_VABI BIO_printf_nid_postfix(void); int APS5_VABI BIO_puts_nid_postfix(LibreSsl::Bio* bio, const char* source); -int APS5_VABI BIO_s_file_nid_postfix(void); +const LibreSsl::BioMethod* APS5_VABI BIO_s_file_nid_postfix(); const LibreSsl::BioMethod* APS5_VABI BIO_s_mem_nid_postfix(); int APS5_VABI BIO_write_nid_postfix(LibreSsl::Bio* bio, const void* source, int size); void APS5_VABI BN_CTX_end_nid_postfix(BignumContext* ctx); @@ -493,11 +666,11 @@ void APS5_VABI EVP_MD_CTX_destroy_nid_postfix(EvpMdContext* context); void APS5_VABI EVP_MD_CTX_init_nid_postfix(EvpMdContext* context); int APS5_VABI EVP_MD_size_nid_postfix(const EvpMd* md); int APS5_VABI EVP_PKEY_copy_parameters_nid_postfix(void); -int APS5_VABI EVP_PKEY_free_nid_postfix(void); +void APS5_VABI EVP_PKEY_free_nid_postfix(void* pkey); int APS5_VABI EVP_PKEY_get1_EC_KEY_nid_postfix(void); int APS5_VABI EVP_PKEY_get1_RSA_nid_postfix(void); int APS5_VABI EVP_PKEY_id_nid_postfix(void); -int APS5_VABI EVP_PKEY_new_nid_postfix(void); +void* APS5_VABI EVP_PKEY_new_nid_postfix(void); int APS5_VABI EVP_PKEY_set1_RSA_nid_postfix(void); int APS5_VABI EVP_PKEY_type_nid_postfix(int type); const LibreSslCipher* APS5_VABI EVP_aes_128_cbc_nid_postfix(); @@ -536,10 +709,10 @@ int APS5_VABI OCSP_RESPONSE_free_nid_postfix(void); int APS5_VABI OCSP_basic_add1_cert_nid_postfix(void); int APS5_VABI OCSP_basic_verify_nid_postfix(void); const char* APS5_VABI OCSP_cert_status_str_nid_postfix(std::int64_t status); -int APS5_VABI OCSP_check_validity_nid_postfix(void); +int APS5_VABI OCSP_check_validity_nid_postfix(const Asn1String* thisUpdate, const Asn1String* nextUpdate, std::int64_t slack, std::int64_t maxAge); const char* APS5_VABI OCSP_crl_reason_str_nid_postfix(std::int64_t reason); -int APS5_VABI OCSP_resp_count_nid_postfix(void); -int APS5_VABI OCSP_resp_get0_nid_postfix(void); +int APS5_VABI OCSP_resp_count_nid_postfix(const void* basicResponse); +void* APS5_VABI OCSP_resp_get0_nid_postfix(const void* basicResponse, std::int32_t index); int APS5_VABI OCSP_response_get1_basic_nid_postfix(void); int APS5_VABI OCSP_response_status_nid_postfix(const LibreSslOcspResponse* response); const char* APS5_VABI OCSP_response_status_str_nid_postfix(std::int64_t status); @@ -557,50 +730,50 @@ void APS5_VABI PKCS12_PBE_add_nid_postfix(void); int APS5_VABI PKCS12_free_nid_postfix(void); int APS5_VABI PKCS12_parse_nid_postfix(void); int APS5_VABI RSA_flags_nid_postfix(const LibreSslRsa* rsa); -int APS5_VABI RSA_free_nid_postfix(void); +void APS5_VABI RSA_free_nid_postfix(LibreSslRsa* rsa); int APS5_VABI RSA_generate_key_ex_nid_postfix(void); -int APS5_VABI RSA_new_nid_postfix(void); +LibreSslRsa* APS5_VABI RSA_new_nid_postfix(void); int APS5_VABI RSA_padding_add_PKCS1_PSS_nid_postfix(void); int APS5_VABI RSA_private_decrypt_nid_postfix(void); int APS5_VABI RSA_private_encrypt_nid_postfix(void); -int APS5_VABI RSA_public_decrypt_nid_postfix(void); -int APS5_VABI RSA_public_encrypt_nid_postfix(void); +int APS5_VABI RSA_public_decrypt_nid_postfix(int length, const unsigned char* from, unsigned char* to, LibreSslRsa* rsa, int padding); +int APS5_VABI RSA_public_encrypt_nid_postfix(int length, const unsigned char* from, unsigned char* to, LibreSslRsa* rsa, int padding); int APS5_VABI RSA_sign_nid_postfix(void); int APS5_VABI RSA_size_nid_postfix(const LibreSslRsa* rsa); -int APS5_VABI RSA_verify_nid_postfix(void); +int APS5_VABI RSA_verify_nid_postfix(int type, const unsigned char* digest, unsigned int digestLength, const unsigned char* signature, unsigned int signatureLength, LibreSslRsa* rsa); int APS5_VABI RSA_verify_PKCS1_PSS_nid_postfix(void); int APS5_VABI SSL_CIPHER_get_bits_nid_postfix(const void* cipher, std::int32_t* algorithmBits); const char* APS5_VABI SSL_CIPHER_get_name_nid_postfix(const void* cipher); char* APS5_VABI SSL_CIPHER_get_version_nid_postfix(const void* cipher); int APS5_VABI SSL_CTX_add_client_CA_nid_postfix(void); -int APS5_VABI SSL_CTX_callback_ctrl_nid_postfix(void); +std::int64_t APS5_VABI SSL_CTX_callback_ctrl_nid_postfix(LibreSslCtx* ctx, int cmd, void* callback); int APS5_VABI SSL_CTX_check_private_key_nid_postfix(void); -int APS5_VABI SSL_CTX_ctrl_nid_postfix(void); +std::int64_t APS5_VABI SSL_CTX_ctrl_nid_postfix(LibreSslCtx* ctx, int cmd, std::int64_t larg, void* parg); int APS5_VABI SSL_CTX_free_nid_postfix(void); -int APS5_VABI SSL_CTX_get_cert_store_nid_postfix(void); +void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(LibreSslCtx* ctx); int APS5_VABI SSL_CTX_get_ex_data_nid_postfix(void); int APS5_VABI SSL_CTX_get_ex_new_index_nid_postfix(std::int64_t argl, void* argp, LibreSslExNew newFunction, LibreSslExDup dupFunction, LibreSslExFree freeFunction); -int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(void); +int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(LibreSslCtx* ctx); int APS5_VABI SSL_CTX_load_verify_locations_nid_postfix(void); int APS5_VABI SSL_CTX_load_verify_mem_nid_postfix(void); int APS5_VABI SSL_CTX_new_nid_postfix(void); -int APS5_VABI SSL_CTX_sess_set_new_cb_nid_postfix(void); +void APS5_VABI SSL_CTX_sess_set_new_cb_nid_postfix(LibreSslCtx* ctx, void* callback); int APS5_VABI SSL_CTX_set1_groups_list_nid_postfix(void); int APS5_VABI SSL_CTX_set_alpn_protos_nid_postfix(void); -int APS5_VABI SSL_CTX_set_alpn_select_cb_nid_postfix(void); +void APS5_VABI SSL_CTX_set_alpn_select_cb_nid_postfix(LibreSslCtx* ctx, void* callback, void* arg); int APS5_VABI SSL_CTX_set_cert_store_nid_postfix(void); -int APS5_VABI SSL_CTX_set_cert_verify_callback_nid_postfix(void); +void APS5_VABI SSL_CTX_set_cert_verify_callback_nid_postfix(LibreSslCtx* ctx, void* callback, void* arg); int APS5_VABI SSL_CTX_set_cipher_list_nid_postfix(void); -int APS5_VABI SSL_CTX_set_client_cert_cb_nid_postfix(void); -int APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(void); -int APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(void); +void APS5_VABI SSL_CTX_set_client_cert_cb_nid_postfix(LibreSslCtx* ctx, void* callback); +void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(LibreSslCtx* ctx, void* callback); +void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(LibreSslCtx* ctx, void* userdata); int APS5_VABI SSL_CTX_set_ex_data_nid_postfix(void); -int APS5_VABI SSL_CTX_set_info_callback_nid_postfix(void); -int APS5_VABI SSL_CTX_set_msg_callback_nid_postfix(void); -int APS5_VABI SSL_CTX_set_next_proto_select_cb_nid_postfix(void); -int APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(void); -int APS5_VABI SSL_CTX_set_session_id_context_nid_postfix(void); -int APS5_VABI SSL_CTX_set_verify_nid_postfix(void); +void APS5_VABI SSL_CTX_set_msg_callback_nid_postfix(LibreSslCtx* ctx, void* callback); +void APS5_VABI SSL_CTX_set_info_callback_nid_postfix(LibreSslCtx* ctx, void* callback); +void APS5_VABI SSL_CTX_set_next_proto_select_cb_nid_postfix(LibreSslCtx* ctx, void* callback, void* arg); +void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(LibreSslCtx* ctx, int mode); +int APS5_VABI SSL_CTX_set_session_id_context_nid_postfix(LibreSslCtx* ctx, const unsigned char* sidCtx, unsigned int length); +void APS5_VABI SSL_CTX_set_verify_nid_postfix(LibreSslCtx* ctx, int mode, void* callback); int APS5_VABI SSL_CTX_use_PrivateKey_nid_postfix(void); int APS5_VABI SSL_CTX_use_PrivateKey_ASN1_nid_postfix(void); int APS5_VABI SSL_CTX_use_PrivateKey_file_nid_postfix(void); @@ -613,14 +786,14 @@ int APS5_VABI SSL_accept_nid_postfix(void); const char* APS5_VABI SSL_alert_desc_string_long_nid_postfix(int value); int APS5_VABI SSL_check_private_key_nid_postfix(void); int APS5_VABI SSL_connect_nid_postfix(void); -int APS5_VABI SSL_ctrl_nid_postfix(void); +std::int64_t APS5_VABI SSL_ctrl_nid_postfix(LibreSslSsl* ssl, int command, std::int64_t argument, void* pointer); int APS5_VABI SSL_free_nid_postfix(void); -int APS5_VABI SSL_get0_alpn_selected_nid_postfix(void); +void APS5_VABI SSL_get0_alpn_selected_nid_postfix(const LibreSslSsl* ssl, const unsigned char** data, unsigned int* length); int APS5_VABI SSL_get0_param_nid_postfix(void); int APS5_VABI SSL_get1_session_nid_postfix(void); -int APS5_VABI SSL_get_SSL_CTX_nid_postfix(void); -int APS5_VABI SSL_get_certificate_nid_postfix(void); -int APS5_VABI SSL_get_current_cipher_nid_postfix(void); +void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const LibreSslSsl* ssl); +void* APS5_VABI SSL_get_certificate_nid_postfix(const LibreSslSsl* ssl); +const void* APS5_VABI SSL_get_current_cipher_nid_postfix(const LibreSslSsl* ssl); int APS5_VABI SSL_get_error_nid_postfix(void); int APS5_VABI SSL_get_ex_data_nid_postfix(void); int APS5_VABI SSL_get_ex_data_X509_STORE_CTX_idx_nid_postfix(void); @@ -628,13 +801,13 @@ int APS5_VABI SSL_get_ex_new_index_nid_postfix(std::int64_t argl, void* argp, Li int APS5_VABI SSL_get_fd_nid_postfix(void); int APS5_VABI SSL_get_peer_cert_chain_nid_postfix(void); int APS5_VABI SSL_get_peer_certificate_nid_postfix(void); -int APS5_VABI SSL_get_privatekey_nid_postfix(void); -int APS5_VABI SSL_get_rbio_nid_postfix(void); +void* APS5_VABI SSL_get_rbio_nid_postfix(const LibreSslSsl* ssl); +void* APS5_VABI SSL_get_privatekey_nid_postfix(const LibreSslSsl* ssl); int APS5_VABI SSL_get_servername_nid_postfix(void); -int APS5_VABI SSL_get_session_nid_postfix(void); -int APS5_VABI SSL_get_shutdown_nid_postfix(void); -int APS5_VABI SSL_get_verify_result_nid_postfix(void); -int APS5_VABI SSL_get_wbio_nid_postfix(void); +int APS5_VABI SSL_get_shutdown_nid_postfix(const LibreSslSsl* ssl); +std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const LibreSslSsl* ssl); +void* APS5_VABI SSL_get_wbio_nid_postfix(const LibreSslSsl* ssl); +LibreSslSession* APS5_VABI SSL_get_session_nid_postfix(const LibreSslSsl* ssl); int APS5_VABI SSL_library_init_nid_postfix(void); void APS5_VABI SSL_load_error_strings_nid_postfix(void); int APS5_VABI SSL_new_nid_postfix(void); @@ -644,16 +817,16 @@ int APS5_VABI SSL_read_nid_postfix(void); int APS5_VABI SSL_select_next_proto_nid_postfix(unsigned char** output, unsigned char* outputLength, const unsigned char* peer, std::uint32_t peerLength, const unsigned char* supported, std::uint32_t supportedLength); int APS5_VABI SSL_set_SSL_CTX_nid_postfix(void); int APS5_VABI SSL_set_bio_nid_postfix(void); -int APS5_VABI SSL_set_connect_state_nid_postfix(void); +void APS5_VABI SSL_set_connect_state_nid_postfix(LibreSslSsl* ssl); int APS5_VABI SSL_set_ex_data_nid_postfix(void); int APS5_VABI SSL_set_fd_nid_postfix(void); int APS5_VABI SSL_set_session_nid_postfix(void); -int APS5_VABI SSL_set_verify_nid_postfix(void); +void APS5_VABI SSL_set_verify_nid_postfix(LibreSslSsl* ssl, int mode, void* callback); int APS5_VABI SSL_shutdown_nid_postfix(void); int APS5_VABI SSL_use_PrivateKey_ASN1_nid_postfix(void); int APS5_VABI SSL_use_certificate_ASN1_nid_postfix(void); -int APS5_VABI SSL_version_nid_postfix(void); -int APS5_VABI SSL_want_nid_postfix(void); +int APS5_VABI SSL_version_nid_postfix(const LibreSslSsl* ssl); +int APS5_VABI SSL_want_nid_postfix(const LibreSslSsl* ssl); int APS5_VABI SSL_write_nid_postfix(void); std::uint64_t APS5_VABI SSLeay_nid_postfix(void); int APS5_VABI SSLv23_client_method_nid_postfix(void); @@ -662,9 +835,9 @@ int APS5_VABI TLS_client_method_nid_postfix(void); int APS5_VABI UI_OpenSSL_nid_postfix(void); LibreSslUiMethod* APS5_VABI UI_create_method_nid_postfix(char* name); void APS5_VABI UI_destroy_method_nid_postfix(LibreSslUiMethod* method); -int APS5_VABI UI_get0_user_data_nid_postfix(void); -int APS5_VABI UI_get_input_flags_nid_postfix(void); -int APS5_VABI UI_get_string_type_nid_postfix(void); +void* APS5_VABI UI_get0_user_data_nid_postfix(const LibreSslUi* ui); +int APS5_VABI UI_get_input_flags_nid_postfix(const LibreSslUiString* string); +int APS5_VABI UI_get_string_type_nid_postfix(const LibreSslUiString* string); UiSessionFunction APS5_VABI UI_method_get_closer_nid_postfix(const LibreSslUiMethod* method); UiSessionFunction APS5_VABI UI_method_get_opener_nid_postfix(const LibreSslUiMethod* method); UiStringFunction APS5_VABI UI_method_get_reader_nid_postfix(const LibreSslUiMethod* method); @@ -680,10 +853,10 @@ void* APS5_VABI X509_EXTENSION_get_data_nid_postfix(const void* extension); void* APS5_VABI X509_EXTENSION_get_object_nid_postfix(const void* extension); int APS5_VABI X509_INFO_free_nid_postfix(void); LibreSslX509LookupMethod* APS5_VABI X509_LOOKUP_file_nid_postfix(void); -int APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(void); -int APS5_VABI X509_NAME_get_entry_nid_postfix(void); -int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(void); -int APS5_VABI X509_NAME_get_text_by_NID_nid_postfix(void); +void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const LibreSslX509NameEntry* entry); +LibreSslX509NameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const LibreSslX509Name* name, int loc); +int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(const LibreSslX509Name* name, int nid, int lastpos); +int APS5_VABI X509_NAME_get_text_by_NID_nid_postfix(const LibreSslX509Name* name, int nid, char* buf, int len); int APS5_VABI X509_NAME_oneline_nid_postfix(void); int APS5_VABI X509_NAME_print_ex_nid_postfix(void); LibreSslStack* APS5_VABI X509_STORE_CTX_get1_chain_nid_postfix(LibreSslX509StoreContext* context); @@ -716,7 +889,7 @@ const char* APS5_VABI X509_verify_cert_error_string_nid_postfix(std::int64_t err int APS5_VABI d2i_OCSP_RESPONSE_nid_postfix(void); int APS5_VABI d2i_PKCS12_bio_nid_postfix(void); int APS5_VABI d2i_PKCS12_fp_nid_postfix(void); -int APS5_VABI d2i_RSA_PUBKEY_nid_postfix(void); +LibreSslRsa* APS5_VABI d2i_RSA_PUBKEY_nid_postfix(LibreSslRsa** out, const unsigned char** input, long length); int APS5_VABI d2i_X509_nid_postfix(void); int APS5_VABI i2a_ASN1_OBJECT_nid_postfix(void); int APS5_VABI i2d_X509_PUBKEY_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/BioFileSocket.cpp b/core/libs/prx/libSceLibreSsl/src/BioFileSocket.cpp new file mode 100644 index 0000000000..086430f9a7 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/BioFileSocket.cpp @@ -0,0 +1,166 @@ +#include "prx/libSceLibreSsl/libSceLibreSsl.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libSceNet/include/Net.hpp" +#include "prx/libSceLibreSsl/include/Error.hpp" +#include +#include +#include +#include + +namespace { + +using LibreSsl::Bio; +using LibreSsl::BioMethod; + +int APS5_VABI FileWrite(Bio* bio, const char* data, int size) { + if (!bio->data || size < 0 || (size && !data)) return -1; + return static_cast(fwrite_nid_postfix(data, 1, static_cast(size), static_cast(bio->data))); +} + +int APS5_VABI FileRead(Bio* bio, char* data, int size) { + if (!bio->data || size < 0 || (size && !data)) return -1; + auto* stream = static_cast(bio->data); + const auto count = fread_nid_postfix(data, 1, static_cast(size), stream); + const int readError = errno; + if (size > 0 && count == 0 && ferror_nid_postfix(stream) != 0) { + LibreSslPutError_nid_no_patch(2, 0, readError, __FILE__, __LINE__); + LibreSslPutError_nid_no_patch(32, 0, 2, __FILE__, __LINE__); + return -1; + } + return static_cast(count); +} + +int APS5_VABI FilePuts(Bio* bio, const char* data) { + if (!data) return -1; + const std::size_t size = std::strlen(data); + if (size > INT_MAX) return -1; + return FileWrite(bio, data, static_cast(size)); +} + +int APS5_VABI FileGets(Bio* bio, char* data, int size) { + if (!data || size <= 0) return 0; + int count = 0; + while (count < size - 1) { + char character = 0; + if (FileRead(bio, &character, 1) != 1) break; + data[count++] = character; + if (character == '\n') break; + } + data[count] = '\0'; + return count; +} + +std::int64_t APS5_VABI FileControl(Bio* bio, int command, std::int64_t argument, void* pointer) { + auto* stream = static_cast(bio->data); + switch (command) { + case 1: + return stream ? fseek_nid_postfix(stream, 0, SEEK_SET) : -1; + case 2: + return stream ? feof_nid_postfix(stream) : 1; + case 8: + return bio->shutdown; + case 9: + bio->shutdown = argument != 0; + return 1; + case 10: + return 0; + case 11: + return stream && fflush_nid_postfix(stream) == 0; + case 12: + return 1; + case 107: + if (pointer) *static_cast(pointer) = stream; + return 1; + default: + return 0; + } +} + +int APS5_VABI FileDestroy(Bio* bio) { + if (bio->shutdown && bio->initialized && bio->data) + fclose_nid_postfix(static_cast(bio->data)); + bio->data = nullptr; + bio->initialized = 0; + return 1; +} + +int APS5_VABI SocketWrite(Bio* bio, const char* data, int size) { + if (bio->number < 0 || size < 0 || (size && !data)) return -1; + const auto result = sceNetSend(bio->number, data, static_cast(size), 0); + return result >= 0 && result <= INT_MAX ? static_cast(result) : -1; +} + +int APS5_VABI SocketRead(Bio* bio, char* data, int size) { + if (bio->number < 0 || size < 0 || (size && !data)) return -1; + const auto result = sceNetRecv(bio->number, data, static_cast(size), 0); + return result >= 0 && result <= INT_MAX ? static_cast(result) : -1; +} + +int APS5_VABI SocketPuts(Bio* bio, const char* data) { + if (!data) return -1; + const std::size_t size = std::strlen(data); + if (size > INT_MAX) return -1; + return SocketWrite(bio, data, static_cast(size)); +} + +std::int64_t APS5_VABI SocketControl(Bio* bio, int command, std::int64_t argument, void*) { + switch (command) { + case 1: + return 0; + case 2: + return 0; + case 8: + return bio->shutdown; + case 9: + bio->shutdown = argument != 0; + return 1; + case 10: + return 0; + case 11: + case 12: + return 1; + default: + return 0; + } +} + +int APS5_VABI SocketDestroy(Bio* bio) { + if (bio->shutdown && bio->initialized && bio->number >= 0) + sceNetSocketClose(bio->number); + bio->number = -1; + bio->initialized = 0; + return 1; +} + +const BioMethod FileMethod{0x402, "FILE pointer", FileWrite, FileRead, FilePuts, FileGets, FileControl, nullptr, FileDestroy, nullptr}; +const BioMethod SocketMethod{0x405, "socket", SocketWrite, SocketRead, SocketPuts, nullptr, SocketControl, nullptr, SocketDestroy, nullptr}; + +} // namespace + +extern "C" { + +const LibreSsl::BioMethod* APS5_VABI BIO_s_file_nid_postfix() { + return &FileMethod; +} + +LibreSsl::Bio* APS5_VABI BIO_new_fp_nid_postfix(void* value, int closeFlag) { + if (!value) return nullptr; + auto* bio = BIO_new_nid_postfix(&FileMethod); + if (!bio) return nullptr; + bio->data = value; + bio->shutdown = closeFlag != 0; + bio->initialized = 1; + return bio; +} + +LibreSsl::Bio* APS5_VABI BIO_new_socket_nid_postfix(int socket, int closeFlag) { + if (socket < 0) return nullptr; + auto* bio = BIO_new_nid_postfix(&SocketMethod); + if (!bio) return nullptr; + bio->number = socket; + bio->shutdown = closeFlag != 0; + bio->initialized = 1; + return bio; +} + +} diff --git a/core/libs/prx/libSceLibreSsl/src/Ocsp.cpp b/core/libs/prx/libSceLibreSsl/src/Ocsp.cpp index e64c504978..a27827f88c 100644 --- a/core/libs/prx/libSceLibreSsl/src/Ocsp.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Ocsp.cpp @@ -1,6 +1,10 @@ #include "prx/libSceLibreSsl/include/Api.hpp" #include #include +#include +#include "prx/libSceLibreSsl/include/Error.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/General.hpp" namespace { struct OcspString { @@ -15,6 +19,45 @@ const char* TableToString(std::int64_t code, const OcspString (&table)[Count]) { } return "(UNKNOWN)"; } + +constexpr int OcspLibrary = 39; +constexpr int OcspFunction = 0xfff; +constexpr int ErrorInNextUpdate = 122; +constexpr int ErrorInThisUpdate = 123; +constexpr int NextUpdateBeforeThisUpdate = 124; +constexpr int StatusExpired = 125; +constexpr int StatusNotYetValid = 126; +constexpr int StatusTooOld = 127; +constexpr int GeneralizedTimeLength = 15; + +int Fail(int reason) { + LibreSslPutError_nid_no_patch(OcspLibrary, OcspFunction, reason); + return 0; +} + +bool ParseGeneralizedTime(const Asn1String& time, GuestTm& result) { + if (!time.data || time.length != GeneralizedTimeLength || time.data[GeneralizedTimeLength - 1] != 'Z') return false; + for (int i = 0; i < GeneralizedTimeLength - 1; ++i) { + if (time.data[i] < '0' || time.data[i] > '9') return false; + } + const auto pair = [&time](int index) { return (time.data[index] - '0') * 10 + (time.data[index + 1] - '0'); }; + result.tm_year = pair(0) * 100 + pair(2) - 1900; + result.tm_mon = pair(4) - 1; + result.tm_mday = pair(6); + result.tm_hour = pair(8); + result.tm_min = pair(10); + result.tm_sec = pair(12); + return result.tm_mon <= 11 && result.tm_mon >= 0 && result.tm_mday >= 1 && result.tm_mday <= 31 && result.tm_hour <= 23 && result.tm_min <= 59 && result.tm_sec <= 59; +} + +bool TimeAt(std::int64_t now, std::uint64_t offset, GuestTm& result) { + const auto time = static_cast(static_cast(now) + offset); + return gmtime_r_nid_postfix(&time, &result) != nullptr; +} + +auto Fields(const GuestTm& time) { + return std::tie(time.tm_year, time.tm_mon, time.tm_mday, time.tm_hour, time.tm_min, time.tm_sec); +} } extern "C" { @@ -54,4 +97,26 @@ const char* APS5_VABI OCSP_crl_reason_str_nid_postfix(std::int64_t reason) { return TableToString(reason, table); } +int APS5_VABI OCSP_check_validity_nid_postfix(const Asn1String* thisUpdate, const Asn1String* nextUpdate, std::int64_t slack, std::int64_t maxAge) { + if (!thisUpdate) NotImplemented_nid_no_patch(__func__); + std::int64_t now = 0; + time_nid_postfix(&now); + GuestTm thisTime{}; + GuestTm limit{}; + if (!ParseGeneralizedTime(*thisUpdate, thisTime)) return Fail(ErrorInThisUpdate); + if (!TimeAt(now, static_cast(slack), limit)) return 0; + if (Fields(thisTime) > Fields(limit)) return Fail(StatusNotYetValid); + if (maxAge >= 0) { + if (!TimeAt(now, 0 - static_cast(maxAge), limit)) return 0; + if (Fields(thisTime) < Fields(limit)) return Fail(StatusTooOld); + } + if (!nextUpdate) return 1; + GuestTm nextTime{}; + if (!ParseGeneralizedTime(*nextUpdate, nextTime)) return Fail(ErrorInNextUpdate); + if (!TimeAt(now, 0 - static_cast(slack), limit)) return 0; + if (Fields(nextTime) < Fields(limit)) return Fail(StatusExpired); + if (Fields(nextTime) < Fields(thisTime)) return Fail(NextUpdateBeforeThisUpdate); + return 1; +} + } diff --git a/core/libs/prx/libSceLibreSsl/src/OcspBasicResponse.cpp b/core/libs/prx/libSceLibreSsl/src/OcspBasicResponse.cpp new file mode 100644 index 0000000000..bf9c246ae9 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/OcspBasicResponse.cpp @@ -0,0 +1,61 @@ +#include +#include +#include "prx/libSceLibreSsl/libSceLibreSsl.hpp" +#include "prx/libc/include/General.hpp" + +namespace { + +struct Stack { + std::int32_t num; + void** data; + std::int32_t sorted; + std::int32_t numAlloc; + void* comp; +}; + +struct ResponseData { + void* version; + void* responderId; + void* producedAt; + const Stack* responses; + void* responseExtensions; +}; + +struct BasicResponse { + const ResponseData* tbsResponseData; + void* signatureAlgorithm; + void* signature; + void* certs; +}; + +static_assert(sizeof(Stack) == 32); +static_assert(offsetof(Stack, data) == 8); +static_assert(sizeof(ResponseData) == 40); +static_assert(offsetof(ResponseData, responses) == 24); +static_assert(sizeof(BasicResponse) == 32); + +const Stack* Responses(const void* basicResponse, const char* function) { + const ResponseData* data = static_cast(basicResponse)->tbsResponseData; + if (!data) NotImplemented_nid_no_patch(function); + return data->responses; +} + +} // namespace + +extern "C" { + +int APS5_VABI OCSP_resp_count_nid_postfix(const void* basicResponse) { + if (!basicResponse) return -1; + const Stack* responses = Responses(basicResponse, __func__); + return responses ? responses->num : -1; +} + +void* APS5_VABI OCSP_resp_get0_nid_postfix(const void* basicResponse, std::int32_t index) { + if (!basicResponse) return nullptr; + const Stack* responses = Responses(basicResponse, __func__); + if (!responses || index < 0 || index >= responses->num) return nullptr; + if (!responses->data) NotImplemented_nid_no_patch(__func__); + return responses->data[index]; +} + +} diff --git a/core/libs/prx/libSceLibreSsl/src/PkeyLifecycle.cpp b/core/libs/prx/libSceLibreSsl/src/PkeyLifecycle.cpp new file mode 100644 index 0000000000..0bf07fb988 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/PkeyLifecycle.cpp @@ -0,0 +1,47 @@ +#include +#include +#include +#include +#include "prx/libSceLibreSsl/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/General.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +namespace { +struct Pkey { + int type; + int saveType; + int references; + const void* ameth; + void* engine; + void* key; + int saveParameters; + void* attributes; +}; +static_assert(sizeof(int) == 4 && sizeof(void*) == 8, "layout assumes LP64"); +} + +extern "C" { + +void* APS5_VABI EVP_PKEY_new_nid_postfix(void) { + auto* pkey = static_cast(malloc_nid_postfix(sizeof(Pkey))); + if (!pkey) return nullptr; + std::memset(pkey, 0, sizeof(Pkey)); + pkey->references = 1; + pkey->saveParameters = 1; + return pkey; +} + +void APS5_VABI EVP_PKEY_free_nid_postfix(void* value) { + if (!value) return; + auto* pkey = static_cast(value); + auto references = std::atomic_ref(pkey->references); + if (references.fetch_sub(1, std::memory_order_acq_rel) - 1 > 0) return; + if (pkey->ameth || pkey->engine || pkey->key || pkey->attributes) { + references.fetch_add(1, std::memory_order_relaxed); + NotImplemented_nid_no_patch("EVP_PKEY_free of a key that holds algorithm data"); + } + free_nid_postfix(pkey); +} + +} diff --git a/core/libs/prx/libSceLibreSsl/src/Rsa.cpp b/core/libs/prx/libSceLibreSsl/src/Rsa.cpp index 66ff820ecc..f9e1d45bec 100644 --- a/core/libs/prx/libSceLibreSsl/src/Rsa.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Rsa.cpp @@ -1,11 +1,18 @@ #include "prx/libSceLibreSsl/include/Api.hpp" +#include #include #include #include #include +#include #include #include +#include +#include "mbedtls/bignum.h" +#include "mbedtls/sha1.h" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/HostEntropy.hpp" struct LibreSslBignum { std::uint64_t* d; @@ -26,6 +33,592 @@ static_assert(offsetof(LibreSslRsa, flags) == 108); namespace { +using RsaCallback = int (APS5_VABI*)(LibreSslRsa* rsa); +using RsaCrypt = int (APS5_VABI*)(int length, const unsigned char* from, unsigned char* to, LibreSslRsa* rsa, int padding); +using RsaVerify = int (APS5_VABI*)(int type, const unsigned char* digest, unsigned int digestLength, const unsigned char* signature, unsigned int signatureLength, const LibreSslRsa* rsa); +using Words = std::vector; + +constexpr int OperationPublicEncrypt = 0; +constexpr int OperationPublicDecrypt = 1; +constexpr int OperationBignumModExp = 5; +constexpr int OperationInit = 6; +constexpr int OperationFinish = 7; +constexpr int RsaFlagCachePublic = 0x0002; +constexpr int RsaFlagCachePrivate = 0x0004; +constexpr int RsaFlagSignVerify = 0x0040; +constexpr int RsaFlagNonFipsAllow = 0x0400; +constexpr int BignumFlagStaticData = 0x02; +constexpr int BignumFlagMalloced = 0x01; +constexpr int PaddingPkcs1 = 1; +constexpr int PaddingSslv23 = 2; +constexpr int PaddingNone = 3; +constexpr int PaddingOaep = 4; +constexpr int PaddingX931 = 5; +constexpr int MaxModulusBits = 16384; +constexpr int SmallModulusBits = 3072; +constexpr int MaxPublicExponentBits = 64; +constexpr int Sha1Bytes = 20; +constexpr int DigestMd5Sha1 = 114; +constexpr int ObjectTypeCount = 956; +constexpr unsigned int Md5Sha1Bytes = 36; + +template TFunction Operation(const LibreSslRsaMethod& method, int index) { + return reinterpret_cast(method.operations[index]); +} + +void Wipe(void* data, std::size_t size) { + auto* bytes = static_cast(data); + for (std::size_t index = 0; index < size; ++index) bytes[index] = 0; +} + +void ClearFreeBignum(LibreSslBignum* number) { + if (!number) return; + if (number->d && !(number->flags & BignumFlagStaticData)) { + if (number->dmax < 0) throw std::invalid_argument("BN_clear_free: negative word capacity"); + Wipe(number->d, static_cast(number->dmax) * sizeof(number->d[0])); + free_nid_postfix(number->d); + } + const bool allocated = number->flags & BignumFlagMalloced; + Wipe(number, sizeof(LibreSslBignum)); + if (allocated) free_nid_postfix(number); +} + +LibreSslBignum* NewBignum(const unsigned char* bytes, std::size_t length) { + constexpr std::size_t maxWords = INT_MAX / (4 * 64); + auto* number = static_cast(malloc_nid_postfix(sizeof(LibreSslBignum))); + if (!number) return nullptr; + *number = {nullptr, 0, 0, 0, BignumFlagMalloced}; + if (!length) return number; + const std::size_t words = (length - 1) / 8 + 1; + if (words <= maxWords) number->d = static_cast(malloc_nid_postfix(words * sizeof(std::uint64_t))); + if (!number->d) { + ClearFreeBignum(number); + return nullptr; + } + number->dmax = static_cast(words); + for (std::size_t word = 0; word < words; ++word) { + std::uint64_t value = 0; + for (std::size_t byte = 0; byte < 8; ++byte) { + const std::size_t position = word * 8 + byte; + if (position < length) value |= std::uint64_t(bytes[length - 1 - position]) << (8 * byte); + } + number->d[word] = value; + } + number->top = static_cast(words); + while (number->top > 0 && !number->d[number->top - 1]) --number->top; + return number; +} + +struct Element { + int tag; + int tagClass; + bool constructed; + bool indefinite; + const unsigned char* content; + long length; + const unsigned char* end; +}; + +struct Cursor { + const unsigned char* data; + long length; +}; + +[[noreturn]] void UnsupportedEncoding(const char* function, const char* what) { + NotImplemented_nid_no_patch((std::string(function) + ": BER " + what).c_str()); +} + +bool ReadHeader(const unsigned char* start, long available, Element& element) { + if (available <= 0) return false; + const unsigned char* data = start; + long remaining = available; + const bool constructed = *data & 0x20; + const int tagClass = *data & 0xC0; + int tag = *data & 0x1F; + if (tag == 0x1F) { + ++data; + if (--remaining == 0) return false; + long value = 0; + while (*data & 0x80) { + value = value << 7 | (*data++ & 0x7F); + if (--remaining == 0) return false; + if (value > (INT_MAX >> 7)) return false; + } + value = value << 7 | (*data++ & 0x7F); + tag = static_cast(value); + if (--remaining == 0) return false; + } else { + ++data; + if (--remaining == 0) return false; + } + int lengthBytes = static_cast(remaining); + if (lengthBytes-- < 1) return false; + bool indefinite = false; + unsigned long length = 0; + if (*data == 0x80) { + indefinite = true; + ++data; + } else { + unsigned count = *data & 0x7F; + if (*data++ & 0x80) { + if (lengthBytes < static_cast(count)) return false; + while (count && !*data) { + ++data; + --count; + } + if (count > sizeof(long)) return false; + while (count-- > 0) length = length << 8 | *data++; + } else { + length = count; + } + } + if (length > static_cast(LONG_MAX)) return false; + if (indefinite && !constructed) return false; + const long header = data - start; + if (static_cast(length) > available - header) return false; + element = {tag, tagClass, constructed, indefinite, data, static_cast(length), data + length}; + return true; +} + +bool ReadTop(const unsigned char* data, long length, int tag, Element& element) { + return ReadHeader(data, length, element) && element.tag == tag && element.tagClass == 0; +} + +enum class Field { Present, Absent, Failed }; + +Field ReadField(Cursor& cursor, int tag, Element& element) { + if (!cursor.length) return Field::Absent; + if (cursor.length >= 2 && !cursor.data[0] && !cursor.data[1]) return Field::Failed; + if (!ReadHeader(cursor.data, cursor.length, element)) return Field::Failed; + if (tag >= 0 && (element.tag != tag || element.tagClass != 0)) return Field::Failed; + return Field::Present; +} + +void Advance(Cursor& cursor, const Element& element) { + cursor.length -= element.end - cursor.data; + cursor.data = element.end; +} + +bool ValidObject(const Element& element) { + if (element.length <= 0 || element.length > INT_MAX || (element.content[element.length - 1] & 0x80)) return false; + for (long index = 0; index < element.length; ++index) + if (element.content[index] == 0x80 && (!index || !(element.content[index - 1] & 0x80))) return false; + return true; +} + +bool ValidAny(const Element& element, const char* function) { + if (element.tagClass != 0) { + if (element.indefinite) UnsupportedEncoding(function, "indefinite-length parameter"); + return true; + } + if (element.tag == 16 || element.tag == 17) { + if (!element.constructed) return false; + if (element.indefinite) UnsupportedEncoding(function, "indefinite-length parameter"); + return true; + } + if (element.constructed) UnsupportedEncoding(function, "constructed parameter"); + switch (element.tag) { + case 1: return element.length == 1; + case 3: return element.length >= 1; + case 5: return element.length == 0; + case 6: return ValidObject(element); + case 28: return element.length % 4 == 0; + case 30: return element.length % 2 == 0; + default: return true; + } +} + +struct Algorithm { + const unsigned char* object; + long objectLength; + bool parameter; + bool nullParameter; +}; + +bool ReadSequence(Cursor& cursor, Element& element, const char* function) { + if (ReadField(cursor, 16, element) != Field::Present || !element.constructed) return false; + if (element.indefinite) UnsupportedEncoding(function, "indefinite-length SEQUENCE"); + return true; +} + +bool ReadPrimitive(Cursor& cursor, int tag, Element& element, const char* function) { + if (ReadField(cursor, tag, element) != Field::Present) return false; + if (element.constructed) UnsupportedEncoding(function, "constructed string"); + Advance(cursor, element); + return true; +} + +bool ReadAlgorithm(Cursor& cursor, Algorithm& algorithm, const char* function) { + Element sequence; + if (!ReadSequence(cursor, sequence, function)) return false; + Advance(cursor, sequence); + Cursor fields {sequence.content, sequence.length}; + Element object; + if (!ReadPrimitive(fields, 6, object, function) || !ValidObject(object)) return false; + algorithm = {object.content, object.length, false, false}; + if (fields.length) { + Element parameter; + if (ReadField(fields, -1, parameter) != Field::Present || !ValidAny(parameter, function)) return false; + Advance(fields, parameter); + algorithm.parameter = true; + algorithm.nullParameter = parameter.tagClass == 0 && parameter.tag == 5; + } + return !fields.length; +} + +template bool SameObject(const Algorithm& algorithm, const unsigned char (&object)[Size]) { + return algorithm.objectLength == static_cast(Size) && !std::memcmp(algorithm.object, object, Size); +} + +constexpr unsigned char RsaEncryptionObject[] {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01}; +constexpr unsigned char RsaObject[] {0x55, 0x08, 0x01, 0x01}; + +LibreSslRsa* DecodeRsaPublicKey(const unsigned char* data, long length, const char* function) { + Element sequence; + if (!ReadTop(data, length, 16, sequence) || !sequence.constructed) return nullptr; + if (sequence.indefinite) UnsupportedEncoding(function, "indefinite-length SEQUENCE"); + Cursor fields {sequence.content, sequence.length}; + Element modulus; + Element exponent; + if (!ReadPrimitive(fields, 2, modulus, function) || !ReadPrimitive(fields, 2, exponent, function) || fields.length) return nullptr; + auto* rsa = RSA_new_nid_postfix(); + if (!rsa) return nullptr; + rsa->n = NewBignum(modulus.content, static_cast(modulus.length)); + rsa->e = rsa->n ? NewBignum(exponent.content, static_cast(exponent.length)) : nullptr; + if (!rsa->e) { + RSA_free_nid_postfix(rsa); + return nullptr; + } + return rsa; +} + +const LibreSslBignum& RequireNumber(const void* number, const char* function, const char* name) { + if (!number) throw std::invalid_argument(std::string(function) + ": null " + name); + const auto& value = *static_cast(number); + if (value.top < 0 || value.top > INT_MAX / 64) throw std::invalid_argument(std::string(function) + ": " + name + " word count " + std::to_string(value.top)); + if (value.top && !value.d) throw std::invalid_argument(std::string(function) + ": null " + name + " words"); + if (value.neg) NotImplemented_nid_no_patch((std::string(function) + ": negative " + name).c_str()); + return value; +} + +int Bits(const LibreSslBignum& value) { + if (!value.top) return 0; + return (value.top - 1) * 64 + static_cast(std::bit_width(value.d[value.top - 1])); +} + +int Compare(const std::uint64_t* left, int leftTop, const std::uint64_t* right, int rightTop) { + if (leftTop != rightTop) return leftTop - rightTop; + for (int index = leftTop - 1; index >= 0; --index) + if (left[index] != right[index]) return left[index] > right[index] ? 1 : -1; + return 0; +} + +Words FromBytes(const unsigned char* bytes, std::size_t length) { + Words words((length + 7) / 8); + for (std::size_t position = 0; position < length; ++position) words[position / 8] |= std::uint64_t(bytes[length - 1 - position]) << (8 * (position % 8)); + while (!words.empty() && !words.back()) words.pop_back(); + return words; +} + +std::vector ToBytes(const Words& words) { + std::size_t length = words.size() * 8; + if (!words.empty()) length -= static_cast(std::countl_zero(words.back()) / 8); + std::vector bytes(length); + for (std::size_t position = 0; position < length; ++position) bytes[length - 1 - position] = static_cast(words[position / 8] >> (8 * (position % 8))); + return bytes; +} + +bool ModExp(const Words& base, const LibreSslBignum& exponent, const LibreSslBignum& modulus, Words& result) { + if (!modulus.top || !(modulus.d[0] & 1)) return false; + if (!Bits(exponent)) { + result = modulus.top == 1 && modulus.d[0] == 1 ? Words {} : Words {1}; + return true; + } + if (Bits(modulus) > MBEDTLS_MPI_MAX_BITS) NotImplemented_nid_no_patch("BN_mod_exp_mont_ct: a modulus over 8192 bits"); + mbedtls_mpi value, power, mod, output; + for (auto* number : {&value, &power, &mod, &output}) mbedtls_mpi_init(number); + int status = mbedtls_mpi_read_binary_le(&value, reinterpret_cast(base.data()), base.size() * 8); + if (!status) status = mbedtls_mpi_read_binary_le(&power, reinterpret_cast(exponent.d), static_cast(exponent.top) * 8); + if (!status) status = mbedtls_mpi_read_binary_le(&mod, reinterpret_cast(modulus.d), static_cast(modulus.top) * 8); + if (!status) status = mbedtls_mpi_exp_mod(&output, &value, &power, &mod, nullptr); + if (!status) { + result.assign((mbedtls_mpi_size(&output) + 7) / 8, 0); + status = mbedtls_mpi_write_binary_le(&output, reinterpret_cast(result.data()), result.size() * 8); + while (!result.empty() && !result.back()) result.pop_back(); + } + for (auto* number : {&value, &power, &mod, &output}) mbedtls_mpi_free(number); + return !status; +} + +void RequireBuffer(const void* buffer, long length, const char* function, const char* name) { + if (length > 0 && !buffer) throw std::invalid_argument(std::string(function) + ": null " + name); +} + +bool Mgf1Sha1(unsigned char* mask, long length, const unsigned char* seed, long seedLength) { + mbedtls_sha1_context context; + mbedtls_sha1_init(&context); + bool done = true; + for (std::uint32_t counter = 0; done && length > 0; ++counter) { + const unsigned char count[4] {static_cast(counter >> 24), static_cast(counter >> 16), static_cast(counter >> 8), static_cast(counter)}; + unsigned char digest[Sha1Bytes]; + done = !mbedtls_sha1_starts(&context) && !mbedtls_sha1_update(&context, seed, static_cast(seedLength)) && + !mbedtls_sha1_update(&context, count, sizeof(count)) && !mbedtls_sha1_finish(&context, digest); + const long part = length < Sha1Bytes ? length : Sha1Bytes; + std::memcpy(mask, digest, static_cast(part)); + mask += part; + length -= part; + } + mbedtls_sha1_free(&context); + return done; +} + +int AddPkcs1Type2(unsigned char* to, int size, const unsigned char* from, int length, const char* function) { + if (length > size - 11) return 0; + if (length < 0) throw std::invalid_argument(std::string(function) + ": negative input length"); + RequireBuffer(from, length, function, "input"); + to[0] = 0; + to[1] = 2; + unsigned char* padding = to + 2; + const int random = size - 3 - length; + HostEntropy_nid_no_patch(padding, static_cast(random)); + for (int index = 0; index < random; ++index, ++padding) + while (!*padding) HostEntropy_nid_no_patch(padding, 1); + *padding++ = 0; + if (length) std::memcpy(padding, from, static_cast(length)); + return 1; +} + +int AddOaep(unsigned char* to, int size, const unsigned char* from, int length, const char* function) { + const int encoded = size - 1; + if (length > encoded - 2 * Sha1Bytes - 1) return 0; + if (encoded < 2 * Sha1Bytes + 1) return 0; + if (length < 0) throw std::invalid_argument(std::string(function) + ": negative input length"); + RequireBuffer(from, length, function, "input"); + to[0] = 0; + unsigned char* seed = to + 1; + unsigned char* block = to + Sha1Bytes + 1; + static constexpr unsigned char empty[1] {}; + if (mbedtls_sha1(empty, 0, block)) return 0; + std::memset(block + Sha1Bytes, 0, static_cast(encoded - length - 2 * Sha1Bytes - 1)); + block[encoded - length - Sha1Bytes - 1] = 0x01; + if (length) std::memcpy(block + encoded - length - Sha1Bytes, from, static_cast(length)); + HostEntropy_nid_no_patch(seed, Sha1Bytes); + std::vector blockMask(static_cast(encoded - Sha1Bytes)); + if (!Mgf1Sha1(blockMask.data(), encoded - Sha1Bytes, seed, Sha1Bytes)) return 0; + for (int index = 0; index < encoded - Sha1Bytes; ++index) block[index] ^= blockMask[static_cast(index)]; + unsigned char seedMask[Sha1Bytes]; + if (!Mgf1Sha1(seedMask, Sha1Bytes, block, encoded - Sha1Bytes)) return 0; + for (int index = 0; index < Sha1Bytes; ++index) seed[index] ^= seedMask[index]; + return 1; +} + +int AddNone(unsigned char* to, int size, const unsigned char* from, int length, const char* function) { + if (length > size || length < size) return 0; + RequireBuffer(from, length, function, "input"); + std::memcpy(to, from, static_cast(length)); + return 1; +} + +int CheckPkcs1Type1(unsigned char* to, int size, const unsigned char* from, int length, int modulusLength, const char* function) { + if (modulusLength != length + 1 || !length || from[0] != 0x01) return -1; + const unsigned char* data = from + 1; + int remaining = length - 1; + int index = 0; + for (; index < remaining; ++index, ++data) { + if (*data == 0xFF) continue; + if (*data) return -1; + ++data; + break; + } + if (index == remaining || index < 8) return -1; + remaining -= index + 1; + if (remaining > size) return -1; + RequireBuffer(to, remaining, function, "output"); + if (remaining) std::memcpy(to, data, static_cast(remaining)); + return remaining; +} + +int CheckNone(unsigned char* to, int size, const unsigned char* from, int length, const char* function) { + if (length > size) return -1; + RequireBuffer(to, size, function, "output"); + std::memset(to, 0, static_cast(size - length)); + if (length) std::memcpy(to + size - length, from, static_cast(length)); + return size; +} + +int APS5_VABI DefaultBignumModExp(void*, const void*, const void*, const void*, void*, void*); + +bool UsablePublicKey(const LibreSslRsa* rsa, const char* function, const LibreSslBignum*& modulus, const LibreSslBignum*& exponent) { + if (!rsa) throw std::invalid_argument(std::string(function) + ": null key"); + modulus = &RequireNumber(rsa->n, function, "modulus"); + const int bits = Bits(*modulus); + if (bits > MaxModulusBits) return false; + exponent = &RequireNumber(rsa->e, function, "exponent"); + if (Compare(modulus->d, modulus->top, exponent->d, exponent->top) <= 0) return false; + return bits <= SmallModulusBits || Bits(*exponent) <= MaxPublicExponentBits; +} + +bool PublicOperation(const LibreSslRsa& rsa, const Words& value, const LibreSslBignum& exponent, const LibreSslBignum& modulus, Words& result, const char* function) { + if (!rsa.meth) throw std::invalid_argument(std::string(function) + ": null method"); + if (rsa.meth->operations[OperationBignumModExp] != reinterpret_cast(DefaultBignumModExp)) + NotImplemented_nid_no_patch((std::string(function) + ": a method with its own bn_mod_exp").c_str()); + return ModExp(value, exponent, modulus, result); +} + +int APS5_VABI DefaultPublicEncrypt(int length, const unsigned char* from, unsigned char* to, LibreSslRsa* rsa, int padding) { + constexpr const char* function = "RSA_eay_public_encrypt"; + const LibreSslBignum* modulus; + const LibreSslBignum* exponent; + if (!UsablePublicKey(rsa, function, modulus, exponent)) return -1; + const int size = (Bits(*modulus) + 7) / 8; + std::vector block(static_cast(size)); + int padded = 0; + switch (padding) { + case PaddingPkcs1: + padded = AddPkcs1Type2(block.data(), size, from, length, function); + break; + case PaddingOaep: + padded = AddOaep(block.data(), size, from, length, function); + break; + case PaddingSslv23: + NotImplemented_nid_no_patch("RSA_padding_add_SSLv23"); + case PaddingNone: + padded = AddNone(block.data(), size, from, length, function); + break; + default: + return -1; + } + if (padded <= 0) return -1; + const auto value = FromBytes(block.data(), block.size()); + if (Compare(value.data(), static_cast(value.size()), modulus->d, modulus->top) >= 0) return -1; + Words result; + if (!PublicOperation(*rsa, value, *exponent, *modulus, result, function)) return -1; + const auto bytes = ToBytes(result); + RequireBuffer(to, size, function, "output"); + std::memset(to, 0, static_cast(size) - bytes.size()); + std::memcpy(to + size - bytes.size(), bytes.data(), bytes.size()); + return size; +} + +int APS5_VABI DefaultPublicDecrypt(int length, const unsigned char* from, unsigned char* to, LibreSslRsa* rsa, int padding) { + constexpr const char* function = "RSA_eay_public_decrypt"; + const LibreSslBignum* modulus; + const LibreSslBignum* exponent; + if (!UsablePublicKey(rsa, function, modulus, exponent)) return -1; + const int size = (Bits(*modulus) + 7) / 8; + if (length > size || length < 0) return -1; + RequireBuffer(from, length, function, "input"); + const auto value = FromBytes(from, static_cast(length)); + if (Compare(value.data(), static_cast(value.size()), modulus->d, modulus->top) >= 0) return -1; + Words result; + if (!PublicOperation(*rsa, value, *exponent, *modulus, result, function)) return -1; + if (padding == PaddingX931) NotImplemented_nid_no_patch("RSA_padding_check_X931"); + const auto bytes = ToBytes(result); + switch (padding) { + case PaddingPkcs1: + return CheckPkcs1Type1(to, size, bytes.data(), static_cast(bytes.size()), size, function); + case PaddingNone: + return CheckNone(to, size, bytes.data(), static_cast(bytes.size()), function); + default: + return -1; + } +} + +struct DigestObject { + int type; + long length; + unsigned char object[9]; +}; + +constexpr DigestObject DigestObjects[] { + {3, 8, {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x02}}, + {4, 8, {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05}}, + {41, 5, {0x2B, 0x0E, 0x03, 0x02, 0x12}}, + {64, 5, {0x2B, 0x0E, 0x03, 0x02, 0x1A}}, + {95, 4, {0x55, 0x08, 0x03, 0x65}}, + {117, 5, {0x2B, 0x24, 0x03, 0x02, 0x01}}, + {257, 8, {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x04}}, + {672, 9, {0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01}}, + {673, 9, {0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02}}, + {674, 9, {0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03}}, + {675, 9, {0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04}}, + {804, 6, {0x28, 0xCF, 0x06, 0x03, 0x00, 0x37}}, + {809, 6, {0x2A, 0x85, 0x03, 0x02, 0x02, 0x09}}, + {941, 8, {0x2A, 0x85, 0x03, 0x07, 0x01, 0x01, 0x02, 0x02}}, + {942, 8, {0x2A, 0x85, 0x03, 0x07, 0x01, 0x01, 0x02, 0x03}}, +}; + +bool SameDigestType(const Algorithm& algorithm, int type, const char* function) { + if (type < 0 || type >= ObjectTypeCount) return false; + for (const auto& digest : DigestObjects) { + const bool same = algorithm.objectLength == digest.length && !std::memcmp(algorithm.object, digest.object, static_cast(digest.length)); + if (digest.type == type) return same; + if (same) return false; + } + NotImplemented_nid_no_patch((std::string(function) + ": digest type " + std::to_string(type) + " with an object outside the digest table").c_str()); +} + +int VerifyDigestInfo(int type, const unsigned char* digest, unsigned int digestLength, const unsigned char* data, long length, const char* function) { + Element sequence; + if (!ReadTop(data, length, 16, sequence) || !sequence.constructed) return 0; + if (sequence.indefinite) UnsupportedEncoding(function, "indefinite-length SEQUENCE"); + Cursor fields {sequence.content, sequence.length}; + Algorithm algorithm; + Element hash; + if (!ReadAlgorithm(fields, algorithm, function) || !ReadPrimitive(fields, 4, hash, function) || fields.length) return 0; + if (sequence.end != data + length) return 0; + if (algorithm.parameter && !algorithm.nullParameter) return 0; + if (!SameDigestType(algorithm, type, function)) return 0; + if (static_cast(hash.length) != digestLength) return 0; + RequireBuffer(digest, digestLength, function, "digest"); + return !digestLength || !std::memcmp(digest, hash.content, digestLength); +} + +int APS5_VABI DefaultPrivateEncrypt(int, const unsigned char*, unsigned char*, LibreSslRsa*, int) { + NotImplemented_nid_no_patch("RSA_eay_private_encrypt"); +} + +int APS5_VABI DefaultPrivateDecrypt(int, const unsigned char*, unsigned char*, LibreSslRsa*, int) { + NotImplemented_nid_no_patch("RSA_eay_private_decrypt"); +} + +int APS5_VABI DefaultModExp(void*, const void*, LibreSslRsa*, void*) { + NotImplemented_nid_no_patch("RSA_eay_mod_exp"); +} + +int APS5_VABI DefaultBignumModExp(void*, const void*, const void*, const void*, void*, void*) { + NotImplemented_nid_no_patch("BN_mod_exp_mont_ct"); +} + +int APS5_VABI DefaultInit(LibreSslRsa* rsa) { + rsa->flags |= RsaFlagCachePublic | RsaFlagCachePrivate; + return 1; +} + +int APS5_VABI DefaultFinish(LibreSslRsa* rsa) { + if (rsa->methodModN || rsa->methodModP || rsa->methodModQ) NotImplemented_nid_no_patch("RSA_eay_finish: freeing a Montgomery context"); + return 1; +} + +const LibreSslRsaMethod DefaultMethod { + "Eric Young's PKCS#1 RSA", + { + reinterpret_cast(DefaultPublicEncrypt), + reinterpret_cast(DefaultPublicDecrypt), + reinterpret_cast(DefaultPrivateEncrypt), + reinterpret_cast(DefaultPrivateDecrypt), + reinterpret_cast(DefaultModExp), + reinterpret_cast(DefaultBignumModExp), + reinterpret_cast(DefaultInit), + reinterpret_cast(DefaultFinish), + }, + 0, + nullptr, + nullptr, + nullptr, + nullptr, +}; + int ByteCount(const LibreSslBignum* number, const char* function) { if (!number) throw std::invalid_argument(std::string(function) + ": null modulus"); if (number->top == 0) return 0; @@ -51,4 +644,98 @@ int APS5_VABI RSA_size_nid_postfix(const LibreSslRsa* rsa) { return ByteCount(static_cast(rsa->n), __func__); } +LibreSslRsa* APS5_VABI RSA_new_nid_postfix(void) { + auto* rsa = static_cast(malloc_nid_postfix(sizeof(LibreSslRsa))); + if (!rsa) return nullptr; + rsa->meth = &DefaultMethod; + rsa->engine = nullptr; + rsa->pad = 0; + rsa->version = 0; + rsa->n = rsa->e = rsa->d = rsa->p = rsa->q = rsa->dmp1 = rsa->dmq1 = rsa->iqmp = nullptr; + rsa->references = 1; + rsa->methodModN = rsa->methodModP = rsa->methodModQ = nullptr; + rsa->blinding = rsa->mtBlinding = nullptr; + rsa->flags = rsa->meth->flags & ~RsaFlagNonFipsAllow; + rsa->exData = nullptr; + const auto init = Operation(*rsa->meth, OperationInit); + if (init && !init(rsa)) { + free_nid_postfix(rsa); + return nullptr; + } + return rsa; +} + +void APS5_VABI RSA_free_nid_postfix(LibreSslRsa* rsa) { + if (!rsa) return; + if (std::atomic_ref(rsa->references).fetch_sub(1) - 1 > 0) return; + if (!rsa->meth) throw std::invalid_argument(std::string(__func__) + ": null method"); + if (rsa->engine) NotImplemented_nid_no_patch("RSA_free: releasing an ENGINE"); + if (rsa->exData) NotImplemented_nid_no_patch("RSA_free: freeing ex_data"); + if (rsa->blinding || rsa->mtBlinding) NotImplemented_nid_no_patch("RSA_free: freeing blinding"); + if (const auto finish = Operation(*rsa->meth, OperationFinish)) finish(rsa); + for (void* number : {rsa->n, rsa->e, rsa->d, rsa->p, rsa->q, rsa->dmp1, rsa->dmq1, rsa->iqmp}) + ClearFreeBignum(static_cast(number)); + free_nid_postfix(rsa); +} + +int APS5_VABI RSA_public_encrypt_nid_postfix(int length, const unsigned char* from, unsigned char* to, LibreSslRsa* rsa, int padding) { + if (!rsa || !rsa->meth) throw std::invalid_argument(std::string(__func__) + ": null key or method"); + const auto encrypt = Operation(*rsa->meth, OperationPublicEncrypt); + if (!encrypt) throw std::invalid_argument(std::string(__func__) + ": method without rsa_pub_enc"); + return encrypt(length, from, to, rsa, padding); +} + +int APS5_VABI RSA_public_decrypt_nid_postfix(int length, const unsigned char* from, unsigned char* to, LibreSslRsa* rsa, int padding) { + if (!rsa || !rsa->meth) throw std::invalid_argument(std::string(__func__) + ": null key or method"); + const auto decrypt = Operation(*rsa->meth, OperationPublicDecrypt); + if (!decrypt) throw std::invalid_argument(std::string(__func__) + ": method without rsa_pub_dec"); + return decrypt(length, from, to, rsa, padding); +} + +int APS5_VABI RSA_verify_nid_postfix(int type, const unsigned char* digest, unsigned int digestLength, const unsigned char* signature, unsigned int signatureLength, LibreSslRsa* rsa) { + if (!rsa) throw std::invalid_argument(std::string(__func__) + ": null key"); + if (rsa->flags & RsaFlagSignVerify) { + if (!rsa->meth) throw std::invalid_argument(std::string(__func__) + ": null method"); + if (rsa->meth->verify) return reinterpret_cast(rsa->meth->verify)(type, digest, digestLength, signature, signatureLength, rsa); + } + if (signatureLength != static_cast(RSA_size_nid_postfix(rsa))) return 0; + if (type == DigestMd5Sha1 && digestLength != Md5Sha1Bytes) return 0; + std::vector block(signatureLength); + const int length = RSA_public_decrypt_nid_postfix(static_cast(signatureLength), signature, block.data(), rsa, PaddingPkcs1); + if (length <= 0) return 0; + if (type != DigestMd5Sha1) return VerifyDigestInfo(type, digest, digestLength, block.data(), length, __func__); + if (length != static_cast(Md5Sha1Bytes)) return 0; + RequireBuffer(digest, Md5Sha1Bytes, __func__, "digest"); + return !std::memcmp(block.data(), digest, Md5Sha1Bytes); +} + +LibreSslRsa* APS5_VABI d2i_RSA_PUBKEY_nid_postfix(LibreSslRsa** out, const unsigned char** input, long length) { + if (!input) throw std::invalid_argument(std::string(__func__) + ": null input pointer"); + const unsigned char* data = *input; + if (length <= 0) return nullptr; + if (!data) throw std::invalid_argument(std::string(__func__) + ": null input"); + Element publicKey; + if (!ReadTop(data, length, 16, publicKey) || !publicKey.constructed) return nullptr; + if (publicKey.indefinite) UnsupportedEncoding(__func__, "indefinite-length SEQUENCE"); + Cursor fields {publicKey.content, publicKey.length}; + Algorithm algorithm; + Element bits; + if (!ReadAlgorithm(fields, algorithm, __func__) || !ReadPrimitive(fields, 3, bits, __func__) || bits.length < 1 || fields.length) return nullptr; + if (!SameObject(algorithm, RsaEncryptionObject) && !SameObject(algorithm, RsaObject)) return nullptr; + const unsigned unused = bits.content[0]; + std::vector key(bits.content + 1, bits.content + bits.length); + if (!key.empty()) { + if (unused >= 32) UnsupportedEncoding(__func__, "BIT STRING with 32 or more unused bits"); + key.back() &= static_cast(0xFF << unused); + } + auto* rsa = DecodeRsaPublicKey(key.data(), static_cast(key.size()), __func__); + if (!rsa) return nullptr; + *input = publicKey.end; + if (out) { + RSA_free_nid_postfix(*out); + *out = rsa; + } + return rsa; +} + } diff --git a/core/libs/prx/libSceLibreSsl/src/Ssl.cpp b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp new file mode 100644 index 0000000000..81c42a3152 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/Ssl.cpp @@ -0,0 +1,250 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include +#include +#include +#include +#include "prx/libc/include/General.hpp" + +static_assert(sizeof(LibreSslSslInternal) == 432); +static_assert(offsetof(LibreSslSslInternal, rwstate) == 156); +static_assert(offsetof(LibreSslSslInternal, shutdown) == 168); +static_assert(sizeof(LibreSslSsl) == 232); +static_assert(offsetof(LibreSslSsl, rbio) == 16); +static_assert(offsetof(LibreSslSsl, wbio) == 24); +static_assert(offsetof(LibreSslSsl, session) == 128); +static_assert(offsetof(LibreSslSsl, ctx) == 152); +static_assert(offsetof(LibreSslSsl, verifyResult) == 160); +static_assert(offsetof(LibreSslSsl, internal) == 224); +static_assert(offsetof(LibreSslSslInternal, options) == 8); +static_assert(offsetof(LibreSslSslInternal, mode) == 16); +static_assert(offsetof(LibreSslSslInternal, handshakeFunc) == 56); +static_assert(offsetof(LibreSslSslInternal, msgCallback) == 64); +static_assert(offsetof(LibreSslSslInternal, msgCallbackArg) == 72); +static_assert(offsetof(LibreSslSslInternal, verifyCallback) == 88); +static_assert(offsetof(LibreSslSslInternal, readAhead) == 212); +static_assert(offsetof(LibreSslSslInternal, aeadReadCtx) == 240); +static_assert(offsetof(LibreSslSslInternal, aeadWriteCtx) == 248); +static_assert(offsetof(LibreSslSslInternal, encWriteCtx) == 256); +static_assert(offsetof(LibreSslSslInternal, writeHash) == 264); +static_assert(offsetof(LibreSslSslInternal, maxCertList) == 296); +static_assert(offsetof(LibreSslSslInternal, state) == 412); +static_assert(offsetof(LibreSslSsl, method) == 8); +static_assert(offsetof(LibreSslSsl, server) == 40); +static_assert(offsetof(LibreSslSsl, s3) == 48); +static_assert(offsetof(LibreSslSsl, d1) == 56); +static_assert(offsetof(LibreSslSsl, verifyMode) == 136); +static_assert(offsetof(LibreSslSsl, maxSendFragment) == 176); +static_assert(offsetof(LibreSslSsl, encReadCtx) == 208); +static_assert(offsetof(LibreSslSsl, readHash) == 216); + +namespace { + +const LibreSslSsl& Connection(const LibreSslSsl* ssl, const char* function) { + if (!ssl) throw std::invalid_argument(std::string(function) + ": null connection"); + return *ssl; +} + +const LibreSslSslInternal& Internal(const LibreSslSsl* ssl, const char* function) { + const LibreSslSsl& connection = Connection(ssl, function); + if (!connection.internal) throw std::invalid_argument(std::string(function) + ": null internal state"); + return *connection.internal; +} + +struct Ssl3StateInternal { + unsigned char beforeSendConnectionBinding[1012]; + int sendConnectionBinding; + unsigned char beforeAlpnSelected[8]; + const unsigned char* alpnSelected; + unsigned int alpnSelectedLength; + unsigned char rest[4]; +}; + +struct Ssl3State { + unsigned char beforeInternal[136]; + const Ssl3StateInternal* internal; +}; + +struct Dtls1StateInternal { + unsigned char beforeMtu[636]; + std::uint32_t mtu; +}; + +struct Dtls1State { + unsigned char beforeInternal[32]; + Dtls1StateInternal* internal; +}; + +struct SslMethodInternal { + int version; + unsigned char beforeConnect[36]; + void* connect; + unsigned char rest[112]; +}; + +struct SslMethod { + unsigned char beforeInternal[40]; + const SslMethodInternal* internal; +}; + +static_assert(sizeof(Ssl3StateInternal) == 1040); +static_assert(offsetof(Ssl3StateInternal, sendConnectionBinding) == 1012); +static_assert(offsetof(Ssl3StateInternal, alpnSelected) == 1024); +static_assert(offsetof(Ssl3StateInternal, alpnSelectedLength) == 1032); +static_assert(sizeof(Ssl3State) == 144); +static_assert(offsetof(Dtls1StateInternal, mtu) == 636); +static_assert(sizeof(Dtls1State) == 40); +static_assert(sizeof(SslMethodInternal) == 160); +static_assert(offsetof(SslMethodInternal, connect) == 40); +static_assert(sizeof(SslMethod) == 48); + +constexpr int ControlSetMessageCallbackArgument = 16; +constexpr int ControlSetMtu = 17; +constexpr int ControlOptions = 32; +constexpr int ControlMode = 33; +constexpr int ControlGetReadAhead = 40; +constexpr int ControlSetReadAhead = 41; +constexpr int ControlGetMaxCertList = 50; +constexpr int ControlSetMaxCertList = 51; +constexpr int ControlSetMaxSendFragment = 52; +constexpr int ControlGetRenegotiationInfoSupport = 76; +constexpr int ControlClearOptions = 77; +constexpr int ControlClearMode = 78; +constexpr int Dtls1Version = 0xFEFF; +constexpr int StateConnectBefore = 0x5000; +constexpr std::int64_t DtlsMinimumMtu = 228; +constexpr std::int64_t MinimumSendFragment = 512; +constexpr std::int64_t MaximumPlainLength = 16384; + +LibreSslSsl& Connection(LibreSslSsl* ssl, const char* function) { + if (!ssl) throw std::invalid_argument(std::string(function) + ": null connection"); + return *ssl; +} + +LibreSslSslInternal& Internal(LibreSslSsl* ssl, const char* function) { + LibreSslSsl& connection = Connection(ssl, function); + if (!connection.internal) throw std::invalid_argument(std::string(function) + ": null internal state"); + return *connection.internal; +} + +const SslMethodInternal& Method(const LibreSslSsl& connection, const char* function) { + const auto* method = static_cast(connection.method); + if (!method || !method->internal) throw std::invalid_argument(std::string(function) + ": null method"); + return *method->internal; +} + +const Ssl3StateInternal* Ssl3Internal(const LibreSslSsl& connection, const char* function) { + const auto* state = static_cast(connection.s3); + if (!state) return nullptr; + if (!state->internal) throw std::invalid_argument(std::string(function) + ": null SSL3 state"); + return state->internal; +} + +} + +extern "C" { + +void* APS5_VABI SSL_get_SSL_CTX_nid_postfix(const LibreSslSsl* ssl) { + return Connection(ssl, __func__).ctx; +} + +void* APS5_VABI SSL_get_rbio_nid_postfix(const LibreSslSsl* ssl) { + return Connection(ssl, __func__).rbio; +} + +void* APS5_VABI SSL_get_wbio_nid_postfix(const LibreSslSsl* ssl) { + return Connection(ssl, __func__).wbio; +} + +int APS5_VABI SSL_version_nid_postfix(const LibreSslSsl* ssl) { + return Connection(ssl, __func__).version; +} + +std::int64_t APS5_VABI SSL_get_verify_result_nid_postfix(const LibreSslSsl* ssl) { + return Connection(ssl, __func__).verifyResult; +} + +int APS5_VABI SSL_get_shutdown_nid_postfix(const LibreSslSsl* ssl) { + return Internal(ssl, __func__).shutdown; +} + +int APS5_VABI SSL_want_nid_postfix(const LibreSslSsl* ssl) { + return Internal(ssl, __func__).rwstate; +} + +void APS5_VABI SSL_set_verify_nid_postfix(LibreSslSsl* ssl, int mode, void* callback) { + LibreSslSsl& connection = Connection(ssl, __func__); + if (callback) Internal(ssl, __func__).verifyCallback = callback; + connection.verifyMode = mode; +} + +void APS5_VABI SSL_get0_alpn_selected_nid_postfix(const LibreSslSsl* ssl, const unsigned char** data, unsigned int* length) { + if (!data || !length) throw std::invalid_argument(std::string(__func__) + ": null output"); + const Ssl3StateInternal* state = Ssl3Internal(Connection(ssl, __func__), __func__); + *data = state ? state->alpnSelected : nullptr; + *length = state ? state->alpnSelectedLength : 0; +} + +void APS5_VABI SSL_set_connect_state_nid_postfix(LibreSslSsl* ssl) { + LibreSslSsl& connection = Connection(ssl, __func__); + LibreSslSslInternal& internal = Internal(ssl, __func__); + const SslMethodInternal& method = Method(connection, __func__); + if (connection.encReadCtx || connection.readHash || internal.encWriteCtx || internal.aeadReadCtx || internal.aeadWriteCtx || internal.writeHash) + NotImplemented_nid_no_patch("SSL_set_connect_state: freeing a live cipher or hash context"); + connection.server = 0; + internal.shutdown = 0; + internal.state = StateConnectBefore; + internal.handshakeFunc = method.connect; +} + +std::int64_t APS5_VABI SSL_ctrl_nid_postfix(LibreSslSsl* ssl, int command, std::int64_t argument, void* pointer) { + switch (command) { + case ControlGetReadAhead: + return Internal(ssl, __func__).readAhead; + case ControlSetReadAhead: { + LibreSslSslInternal& internal = Internal(ssl, __func__); + const int previous = internal.readAhead; + internal.readAhead = static_cast(argument); + return previous; + } + case ControlSetMessageCallbackArgument: + Internal(ssl, __func__).msgCallbackArg = pointer; + return 1; + case ControlOptions: + return static_cast(Internal(ssl, __func__).options |= static_cast(argument)); + case ControlClearOptions: + return static_cast(Internal(ssl, __func__).options &= ~static_cast(argument)); + case ControlMode: + return static_cast(Internal(ssl, __func__).mode |= static_cast(argument)); + case ControlClearMode: + return static_cast(Internal(ssl, __func__).mode &= ~static_cast(argument)); + case ControlGetMaxCertList: + return Internal(ssl, __func__).maxCertList; + case ControlSetMaxCertList: { + LibreSslSslInternal& internal = Internal(ssl, __func__); + const std::int64_t previous = internal.maxCertList; + internal.maxCertList = argument; + return previous; + } + case ControlSetMtu: { + if (argument < DtlsMinimumMtu) return 0; + const LibreSslSsl& connection = Connection(ssl, __func__); + if (Method(connection, __func__).version != Dtls1Version) return 0; + const auto* state = static_cast(connection.d1); + if (!state || !state->internal) throw std::invalid_argument(std::string(__func__) + ": null DTLS state"); + state->internal->mtu = static_cast(argument); + return argument; + } + case ControlSetMaxSendFragment: + if (argument < MinimumSendFragment || argument > MaximumPlainLength) return 0; + Connection(ssl, __func__).maxSendFragment = static_cast(argument); + return 1; + case ControlGetRenegotiationInfoSupport: { + const Ssl3StateInternal* state = Ssl3Internal(Connection(ssl, __func__), __func__); + return state ? state->sendConnectionBinding : 0; + } + default: + NotImplemented_nid_no_patch(("SSL_ctrl: command " + std::to_string(command)).c_str()); + } +} + +} diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp new file mode 100644 index 0000000000..3cda2bf40d --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/SslCtx.cpp @@ -0,0 +1,274 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include "prx/libSceLibreSsl/include/Error.hpp" +#include "prx/libc/include/General.hpp" + +static_assert(sizeof(LibreSslCtxInternal) == 464); +static_assert(offsetof(LibreSslCtxInternal, quietShutdown) == 292); +static_assert(sizeof(LibreSslCtx) == 120); +static_assert(offsetof(LibreSslCtx, certStore) == 16); +static_assert(offsetof(LibreSslCtx, verifyMode) == 48); +static_assert(offsetof(LibreSslCtx, defaultPasswdCallback) == 96); +static_assert(offsetof(LibreSslCtx, defaultPasswdCallbackUserdata) == 104); +static_assert(offsetof(LibreSslCtx, internal) == 112); +static_assert(offsetof(LibreSslCtx, extraCerts) == 40); +static_assert(offsetof(LibreSslCtx, sidCtxLength) == 52); +static_assert(offsetof(LibreSslCtx, sidCtx) == 56); +static_assert(offsetof(LibreSslCtxInternal, options) == 8); +static_assert(offsetof(LibreSslCtxInternal, mode) == 16); +static_assert(offsetof(LibreSslCtxInternal, msgCallback) == 96); +static_assert(offsetof(LibreSslCtxInternal, msgCallbackArg) == 104); +static_assert(offsetof(LibreSslCtxInternal, defaultVerifyCallback) == 112); +static_assert(offsetof(LibreSslCtxInternal, tlsextServernameCallback) == 128); +static_assert(offsetof(LibreSslCtxInternal, tlsextServernameArg) == 136); +static_assert(offsetof(LibreSslCtxInternal, tlsextTicketKeyCb) == 144); +static_assert(offsetof(LibreSslCtxInternal, tlsextStatusCb) == 152); +static_assert(offsetof(LibreSslCtxInternal, tlsextStatusArg) == 160); +static_assert(offsetof(LibreSslCtxInternal, sessionCacheSize) == 176); +static_assert(offsetof(LibreSslCtxInternal, sessionCacheMode) == 200); +static_assert(offsetof(LibreSslCtxInternal, sessConnect) == 204); +static_assert(offsetof(LibreSslCtxInternal, sessConnectRenegotiate) == 208); +static_assert(offsetof(LibreSslCtxInternal, sessConnectGood) == 212); +static_assert(offsetof(LibreSslCtxInternal, sessAccept) == 216); +static_assert(offsetof(LibreSslCtxInternal, sessAcceptRenegotiate) == 220); +static_assert(offsetof(LibreSslCtxInternal, sessAcceptGood) == 224); +static_assert(offsetof(LibreSslCtxInternal, sessMiss) == 228); +static_assert(offsetof(LibreSslCtxInternal, sessTimeout) == 232); +static_assert(offsetof(LibreSslCtxInternal, sessCacheFull) == 236); +static_assert(offsetof(LibreSslCtxInternal, sessHit) == 240); +static_assert(offsetof(LibreSslCtxInternal, sessCbHit) == 244); +static_assert(offsetof(LibreSslCtxInternal, maxCertList) == 280); +static_assert(offsetof(LibreSslCtxInternal, readAhead) == 288); +static_assert(offsetof(LibreSslCtxInternal, maxSendFragment) == 296); +static_assert(offsetof(LibreSslCtxInternal, tlsextTickKeyName) == 312); +static_assert(offsetof(LibreSslCtxInternal, tlsextTickHmacKey) == 328); +static_assert(offsetof(LibreSslCtxInternal, tlsextTickAesKey) == 344); + +namespace { + +enum Command : int { + NeedTmpRsa = 1, + SetTmpRsa = 2, + SetTmpDh = 3, + SetTmpEcdh = 4, + SetTmpRsaCb = 5, + SetTmpDhCb = 6, + SetTmpEcdhCb = 7, + ExtraChainCert = 14, + SetMsgCallback = 15, + SetMsgCallbackArg = 16, + SessNumber = 20, + SessConnect = 21, + SessConnectGood = 22, + SessConnectRenegotiate = 23, + SessAccept = 24, + SessAcceptGood = 25, + SessAcceptRenegotiate = 26, + SessHit = 27, + SessCbHit = 28, + SessMisses = 29, + SessTimeouts = 30, + SessCacheFull = 31, + Options = 32, + Mode = 33, + GetReadAhead = 40, + SetReadAhead = 41, + SetSessCacheSize = 42, + GetSessCacheSize = 43, + SetSessCacheMode = 44, + GetSessCacheMode = 45, + GetMaxCertList = 50, + SetMaxCertList = 51, + SetMaxSendFragment = 52, + SetTlsextServernameCb = 53, + SetTlsextServernameArg = 54, + GetTlsextTicketKeys = 58, + SetTlsextTicketKeys = 59, + SetTlsextStatusReqCb = 63, + SetTlsextStatusReqCbArg = 64, + SetTlsextTicketKeyCb = 72, + ClearOptions = 77, + ClearMode = 78, + GetExtraChainCerts = 82, + ClearExtraChainCerts = 83, + SetGroups = 91, + SetGroupsList = 92, + SetEcdhAuto = 94, + SetDhAuto = 118, +}; + +constexpr int ErrLibSsl = 20; +constexpr int ErrFunctionNone = 0xfff; +constexpr int ShouldNotHaveBeenCalled = 66; +constexpr int SessionIdContextTooLong = 273; +constexpr int InvalidTicketKeysLength = 325; +constexpr std::int64_t TicketKeysLength = 48; +constexpr std::int64_t MinSendFragment = 512; +constexpr std::int64_t MaxPlainLength = 16384; + +LibreSslCtx& Context(LibreSslCtx* ctx, const char* function) { + if (!ctx) throw std::invalid_argument(std::string(function) + ": null context"); + return *ctx; +} + +LibreSslCtxInternal& Internal(LibreSslCtx* ctx, const char* function) { + LibreSslCtx& context = Context(ctx, function); + if (!context.internal) throw std::invalid_argument(std::string(function) + ": null internal state"); + return *context.internal; +} + +[[noreturn]] void UnsupportedCommand(const char* function, int cmd) { + NotImplemented_nid_no_patch((std::string(function) + " command " + std::to_string(cmd)).c_str()); +} + +void PutSslError(int reason) { + LibreSslPutError_nid_no_patch(ErrLibSsl, ErrFunctionNone, reason); +} + +} + +extern "C" { + +int APS5_VABI SSL_CTX_get_verify_mode_nid_postfix(LibreSslCtx* ctx) { + return Context(ctx, __func__).verifyMode; +} + +void* APS5_VABI SSL_CTX_get_cert_store_nid_postfix(LibreSslCtx* ctx) { + return Context(ctx, __func__).certStore; +} + +void APS5_VABI SSL_CTX_set_quiet_shutdown_nid_postfix(LibreSslCtx* ctx, int mode) { + LibreSslCtx& context = Context(ctx, __func__); + if (!context.internal) throw std::invalid_argument(std::string(__func__) + ": null internal state"); + context.internal->quietShutdown = mode; +} + +void APS5_VABI SSL_CTX_set_default_passwd_cb_nid_postfix(LibreSslCtx* ctx, void* callback) { + Context(ctx, __func__).defaultPasswdCallback = callback; +} + +void APS5_VABI SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(LibreSslCtx* ctx, void* userdata) { + Context(ctx, __func__).defaultPasswdCallbackUserdata = userdata; +} + +std::int64_t APS5_VABI SSL_CTX_ctrl_nid_postfix(LibreSslCtx* ctx, int cmd, std::int64_t larg, void* parg) { + LibreSslCtxInternal& internal = Internal(ctx, __func__); + switch (cmd) { + case GetReadAhead: return internal.readAhead; + case SetReadAhead: return std::exchange(internal.readAhead, static_cast(larg)); + case SetMsgCallbackArg: internal.msgCallbackArg = parg; return 1; + case GetMaxCertList: return internal.maxCertList; + case SetMaxCertList: return std::exchange(internal.maxCertList, larg); + case SetSessCacheSize: return static_cast(std::exchange(internal.sessionCacheSize, static_cast(larg))); + case GetSessCacheSize: return static_cast(internal.sessionCacheSize); + case SetSessCacheMode: return std::exchange(internal.sessionCacheMode, static_cast(larg)); + case GetSessCacheMode: return internal.sessionCacheMode; + case SessConnect: return internal.sessConnect; + case SessConnectGood: return internal.sessConnectGood; + case SessConnectRenegotiate: return internal.sessConnectRenegotiate; + case SessAccept: return internal.sessAccept; + case SessAcceptGood: return internal.sessAcceptGood; + case SessAcceptRenegotiate: return internal.sessAcceptRenegotiate; + case SessHit: return internal.sessHit; + case SessCbHit: return internal.sessCbHit; + case SessMisses: return internal.sessMiss; + case SessTimeouts: return internal.sessTimeout; + case SessCacheFull: return internal.sessCacheFull; + case Options: return static_cast(internal.options |= static_cast(larg)); + case ClearOptions: return static_cast(internal.options &= ~static_cast(larg)); + case Mode: return static_cast(internal.mode |= static_cast(larg)); + case ClearMode: return static_cast(internal.mode &= ~static_cast(larg)); + case SetMaxSendFragment: + if (larg < MinSendFragment || larg > MaxPlainLength) return 0; + internal.maxSendFragment = static_cast(larg); + return 1; + case NeedTmpRsa: return 0; + case SetTmpRsa: + case SetTmpRsaCb: + case SetTmpDhCb: + case SetTmpEcdhCb: + PutSslError(ShouldNotHaveBeenCalled); + return 0; + case SetTlsextServernameArg: internal.tlsextServernameArg = parg; return 1; + case GetTlsextTicketKeys: + case SetTlsextTicketKeys: { + auto* keys = static_cast(parg); + if (!keys) return TicketKeysLength; + if (larg != TicketKeysLength) { + PutSslError(InvalidTicketKeysLength); + return 0; + } + if (cmd == SetTlsextTicketKeys) { + std::memcpy(internal.tlsextTickKeyName, keys, 16); + std::memcpy(internal.tlsextTickHmacKey, keys + 16, 16); + std::memcpy(internal.tlsextTickAesKey, keys + 32, 16); + } else { + std::memcpy(keys, internal.tlsextTickKeyName, 16); + std::memcpy(keys + 16, internal.tlsextTickHmacKey, 16); + std::memcpy(keys + 32, internal.tlsextTickAesKey, 16); + } + return 1; + } + case SetTlsextStatusReqCbArg: internal.tlsextStatusArg = parg; return 1; + case GetExtraChainCerts: + if (!parg) throw std::invalid_argument(std::string(__func__) + ": null extra chain certificates output"); + *static_cast(parg) = ctx->extraCerts; + return 1; + case SetTmpDh: + case SetTmpEcdh: + case ExtraChainCert: + case SessNumber: + case ClearExtraChainCerts: + case SetGroups: + case SetGroupsList: + case SetEcdhAuto: + case SetDhAuto: UnsupportedCommand(__func__, cmd); + default: return 0; + } +} + +std::int64_t APS5_VABI SSL_CTX_callback_ctrl_nid_postfix(LibreSslCtx* ctx, int cmd, void* callback) { + LibreSslCtxInternal& internal = Internal(ctx, __func__); + switch (cmd) { + case SetMsgCallback: internal.msgCallback = callback; return 1; + case SetTmpRsaCb: + PutSslError(ShouldNotHaveBeenCalled); + return 0; + case SetTmpDhCb: + case SetTmpEcdhCb: UnsupportedCommand(__func__, cmd); + case SetTlsextServernameCb: internal.tlsextServernameCallback = callback; return 1; + case SetTlsextStatusReqCb: internal.tlsextStatusCb = callback; return 1; + case SetTlsextTicketKeyCb: internal.tlsextTicketKeyCb = callback; return 1; + default: return 0; + } +} + +void APS5_VABI SSL_CTX_set_msg_callback_nid_postfix(LibreSslCtx* ctx, void* callback) { + Internal(ctx, __func__).msgCallback = callback; +} + +void APS5_VABI SSL_CTX_set_verify_nid_postfix(LibreSslCtx* ctx, int mode, void* callback) { + LibreSslCtxInternal& internal = Internal(ctx, __func__); + ctx->verifyMode = mode; + internal.defaultVerifyCallback = callback; +} + +int APS5_VABI SSL_CTX_set_session_id_context_nid_postfix(LibreSslCtx* ctx, const unsigned char* sidCtx, unsigned int length) { + LibreSslCtx& context = Context(ctx, __func__); + if (length > sizeof context.sidCtx) { + PutSslError(SessionIdContextTooLong); + return 0; + } + if (!sidCtx && length) throw std::invalid_argument(std::string(__func__) + ": null session id context"); + context.sidCtxLength = length; + std::copy_n(sidCtx, length, context.sidCtx); + return 1; +} + +} diff --git a/core/libs/prx/libSceLibreSsl/src/SslCtxCallbacks.cpp b/core/libs/prx/libSceLibreSsl/src/SslCtxCallbacks.cpp new file mode 100644 index 0000000000..4bc521cb11 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/SslCtxCallbacks.cpp @@ -0,0 +1,60 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include +#include +#include +#include "prx/libc/include/General.hpp" + +static_assert(sizeof(LibreSslCtxInternal) == 464); +static_assert(offsetof(LibreSslCtxInternal, newSessionCb) == 24); +static_assert(offsetof(LibreSslCtxInternal, appVerifyCallback) == 48); +static_assert(offsetof(LibreSslCtxInternal, appVerifyArg) == 56); +static_assert(offsetof(LibreSslCtxInternal, clientCertCb) == 64); +static_assert(offsetof(LibreSslCtxInternal, infoCallback) == 88); +static_assert(offsetof(LibreSslCtxInternal, nextProtoSelectCb) == 384); +static_assert(offsetof(LibreSslCtxInternal, nextProtoSelectCbArg) == 392); +static_assert(offsetof(LibreSslCtxInternal, alpnSelectCb) == 400); +static_assert(offsetof(LibreSslCtxInternal, alpnSelectCbArg) == 408); + +namespace { + +LibreSslCtxInternal& Callbacks(LibreSslCtx* ctx, const char* function) { + if (!ctx) throw std::invalid_argument(std::string(function) + ": null context"); + if (!ctx->internal) throw std::invalid_argument(std::string(function) + ": null internal state"); + return *ctx->internal; +} + +} + +extern "C" { + +void APS5_VABI SSL_CTX_set_info_callback_nid_postfix(LibreSslCtx* ctx, void* callback) { + Callbacks(ctx, __func__).infoCallback = callback; +} + +void APS5_VABI SSL_CTX_sess_set_new_cb_nid_postfix(LibreSslCtx* ctx, void* callback) { + Callbacks(ctx, __func__).newSessionCb = callback; +} + +void APS5_VABI SSL_CTX_set_cert_verify_callback_nid_postfix(LibreSslCtx* ctx, void* callback, void* arg) { + LibreSslCtxInternal& callbacks = Callbacks(ctx, __func__); + callbacks.appVerifyCallback = callback; + callbacks.appVerifyArg = arg; +} + +void APS5_VABI SSL_CTX_set_client_cert_cb_nid_postfix(LibreSslCtx* ctx, void* callback) { + Callbacks(ctx, __func__).clientCertCb = callback; +} + +void APS5_VABI SSL_CTX_set_next_proto_select_cb_nid_postfix(LibreSslCtx* ctx, void* callback, void* arg) { + LibreSslCtxInternal& callbacks = Callbacks(ctx, __func__); + callbacks.nextProtoSelectCb = callback; + callbacks.nextProtoSelectCbArg = arg; +} + +void APS5_VABI SSL_CTX_set_alpn_select_cb_nid_postfix(LibreSslCtx* ctx, void* callback, void* arg) { + LibreSslCtxInternal& callbacks = Callbacks(ctx, __func__); + callbacks.alpnSelectCb = callback; + callbacks.alpnSelectCbArg = arg; +} + +} diff --git a/core/libs/prx/libSceLibreSsl/src/SslSession.cpp b/core/libs/prx/libSceLibreSsl/src/SslSession.cpp new file mode 100644 index 0000000000..800b903fd4 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/SslSession.cpp @@ -0,0 +1,54 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include +#include +#include +#include "prx/libc/include/General.hpp" + +static_assert(sizeof(LibreSslCertKey) == 24); +static_assert(offsetof(LibreSslCertKey, privatekey) == 8); +static_assert(sizeof(LibreSslCert) == 256); +static_assert(sizeof(LibreSslSession) == 232); +static_assert(offsetof(LibreSslSession, cipher) == 168); + +namespace { + +const LibreSslSsl& Connection(const LibreSslSsl* ssl, const char* function) { + if (!ssl) throw std::invalid_argument(std::string(function) + ": null connection"); + return *ssl; +} + +LibreSslSession* Session(const LibreSslSsl* ssl, const char* function) { + return static_cast(Connection(ssl, function).session); +} + +const LibreSslCertKey* CurrentKey(const LibreSslSsl* ssl, const char* function) { + const LibreSslCert* cert = static_cast(Connection(ssl, function).cert); + if (!cert) return nullptr; + if (!cert->key) throw std::invalid_argument(std::string(function) + ": null current key"); + return cert->key; +} + +} + +extern "C" { + +LibreSslSession* APS5_VABI SSL_get_session_nid_postfix(const LibreSslSsl* ssl) { + return Session(ssl, __func__); +} + +const void* APS5_VABI SSL_get_current_cipher_nid_postfix(const LibreSslSsl* ssl) { + const LibreSslSession* session = Session(ssl, __func__); + return session ? session->cipher : nullptr; +} + +void* APS5_VABI SSL_get_certificate_nid_postfix(const LibreSslSsl* ssl) { + const LibreSslCertKey* key = CurrentKey(ssl, __func__); + return key ? key->x509 : nullptr; +} + +void* APS5_VABI SSL_get_privatekey_nid_postfix(const LibreSslSsl* ssl) { + const LibreSslCertKey* key = CurrentKey(ssl, __func__); + return key ? key->privatekey : nullptr; +} + +} diff --git a/core/libs/prx/libSceLibreSsl/src/UiString.cpp b/core/libs/prx/libSceLibreSsl/src/UiString.cpp new file mode 100644 index 0000000000..29f3ddea53 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/UiString.cpp @@ -0,0 +1,36 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include +#include +#include +#include "prx/libc/include/General.hpp" + +static_assert(sizeof(LibreSslUi) == 40); +static_assert(offsetof(LibreSslUi, userData) == 16); +static_assert(sizeof(LibreSslUiString) == 64); +static_assert(offsetof(LibreSslUiString, inputFlags) == 16); +static_assert(offsetof(LibreSslUiString, flags) == 56); + +namespace { + +constexpr int UiTypeNone = 0; + +} + +extern "C" { + +void* APS5_VABI UI_get0_user_data_nid_postfix(const LibreSslUi* ui) { + if (!ui) throw std::invalid_argument(std::string(__func__) + ": null UI"); + return ui->userData; +} + +int APS5_VABI UI_get_string_type_nid_postfix(const LibreSslUiString* string) { + if (!string) return UiTypeNone; + return string->type; +} + +int APS5_VABI UI_get_input_flags_nid_postfix(const LibreSslUiString* string) { + if (!string) return 0; + return string->inputFlags; +} + +} diff --git a/core/libs/prx/libSceLibreSsl/src/X509Name.cpp b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp new file mode 100644 index 0000000000..38e7d1819a --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/X509Name.cpp @@ -0,0 +1,157 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include +#include +#include +#include +#include +#include "prx/libc/include/General.hpp" + +static_assert(sizeof(LibreSslX509NameEntry) == 24); +static_assert(offsetof(LibreSslX509NameEntry, value) == 8); +static_assert(sizeof(LibreSslX509Name) == 40); +static_assert(offsetof(LibreSslX509Name, entries) == 0); + +namespace { + +struct Asn1Object { + const char* shortName; + const char* longName; + int nid; + int length; + const unsigned char* data; + int flags; +}; + +static_assert(sizeof(Asn1Object) == 40 && offsetof(Asn1Object, length) == 20 && offsetof(Asn1Object, data) == 24); + +struct NameAttribute { + int nid; + int length; + unsigned char der[10]; +}; + +constexpr NameAttribute NameAttributes[] { + {0, 0, {}}, + {13, 3, {0x55, 0x04, 0x03}}, + {14, 3, {0x55, 0x04, 0x06}}, + {15, 3, {0x55, 0x04, 0x07}}, + {16, 3, {0x55, 0x04, 0x08}}, + {17, 3, {0x55, 0x04, 0x0a}}, + {18, 3, {0x55, 0x04, 0x0b}}, + {48, 9, {0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01}}, + {99, 3, {0x55, 0x04, 0x2a}}, + {100, 3, {0x55, 0x04, 0x04}}, + {101, 3, {0x55, 0x04, 0x2b}}, + {105, 3, {0x55, 0x04, 0x05}}, + {106, 3, {0x55, 0x04, 0x0c}}, + {107, 3, {0x55, 0x04, 0x0d}}, + {173, 3, {0x55, 0x04, 0x29}}, + {174, 3, {0x55, 0x04, 0x2e}}, + {391, 10, {0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19}}, + {400, 3, {0x55, 0x04, 0x48}}, + {458, 10, {0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x01}}, + {503, 3, {0x55, 0x04, 0x2d}}, + {509, 3, {0x55, 0x04, 0x2c}}, + {510, 3, {0x55, 0x04, 0x41}}, + {660, 3, {0x55, 0x04, 0x09}}, + {661, 3, {0x55, 0x04, 0x11}}, + {859, 3, {0x55, 0x04, 0x0e}}, + {860, 3, {0x55, 0x04, 0x0f}}, + {861, 3, {0x55, 0x04, 0x10}}, + {862, 3, {0x55, 0x04, 0x12}}, + {863, 3, {0x55, 0x04, 0x13}}, + {864, 3, {0x55, 0x04, 0x14}}, + {865, 3, {0x55, 0x04, 0x15}}, + {866, 3, {0x55, 0x04, 0x16}}, + {867, 3, {0x55, 0x04, 0x17}}, + {868, 3, {0x55, 0x04, 0x18}}, + {869, 3, {0x55, 0x04, 0x19}}, + {870, 3, {0x55, 0x04, 0x1a}}, + {871, 3, {0x55, 0x04, 0x1b}}, + {872, 3, {0x55, 0x04, 0x1c}}, + {873, 3, {0x55, 0x04, 0x1d}}, + {874, 3, {0x55, 0x04, 0x1e}}, + {875, 3, {0x55, 0x04, 0x1f}}, + {876, 3, {0x55, 0x04, 0x20}}, + {877, 3, {0x55, 0x04, 0x21}}, + {878, 3, {0x55, 0x04, 0x22}}, + {879, 3, {0x55, 0x04, 0x23}}, + {880, 3, {0x55, 0x04, 0x24}}, + {881, 3, {0x55, 0x04, 0x25}}, + {882, 3, {0x55, 0x04, 0x26}}, + {883, 3, {0x55, 0x04, 0x27}}, + {884, 3, {0x55, 0x04, 0x28}}, + {885, 3, {0x55, 0x04, 0x2f}}, + {886, 3, {0x55, 0x04, 0x30}}, + {887, 3, {0x55, 0x04, 0x31}}, + {888, 3, {0x55, 0x04, 0x32}}, + {889, 3, {0x55, 0x04, 0x33}}, + {890, 3, {0x55, 0x04, 0x34}}, + {891, 3, {0x55, 0x04, 0x35}}, + {892, 3, {0x55, 0x04, 0x36}}, +}; + +const NameAttribute* FindAttribute(int nid, const char* function) { + for (const auto& attribute : NameAttributes) { + if (attribute.nid == nid) return &attribute; + } + NotImplemented_nid_no_patch((std::string(function) + ": NID " + std::to_string(nid) + " is not a name attribute type in the object identifier subset").c_str()); +} + +int IndexOf(const LibreSslX509Name* name, const NameAttribute& attribute, int lastpos, const char* function) { + if (!name) return -1; + if (lastpos == INT_MAX) throw std::invalid_argument(std::string(function) + ": last position overflows"); + if (lastpos < 0) lastpos = -1; + const int count = name->entries ? name->entries->num : -1; + for (++lastpos; lastpos < count; ++lastpos) { + const auto* entry = X509_NAME_get_entry_nid_postfix(name, lastpos); + if (!entry || !entry->object) throw std::invalid_argument(std::string(function) + ": null name entry or object"); + const auto* object = static_cast(entry->object); + if (object->length != attribute.length) continue; + if (attribute.length == 0) return lastpos; + if (!object->data) throw std::invalid_argument(std::string(function) + ": null object data"); + if (std::memcmp(object->data, attribute.der, attribute.length) == 0) return lastpos; + } + return -1; +} + +} + +extern "C" { + +LibreSslX509NameEntry* APS5_VABI X509_NAME_get_entry_nid_postfix(const LibreSslX509Name* name, int loc) { + if (!name || loc < 0 || !name->entries || name->entries->num <= loc) return nullptr; + if (!name->entries->data) throw std::invalid_argument(std::string(__func__) + ": null entry array"); + return reinterpret_cast(name->entries->data[loc]); +} + +void* APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(const LibreSslX509NameEntry* entry) { + if (!entry) return nullptr; + return entry->value; +} + +int APS5_VABI X509_NAME_get_index_by_NID_nid_postfix(const LibreSslX509Name* name, int nid, int lastpos) { + const auto* attribute = FindAttribute(nid, __func__); + return IndexOf(name, *attribute, lastpos, __func__); +} + +int APS5_VABI X509_NAME_get_text_by_NID_nid_postfix(const LibreSslX509Name* name, int nid, char* buf, int len) { + const auto* attribute = FindAttribute(nid, __func__); + const int index = IndexOf(name, *attribute, -1, __func__); + if (index < 0) return -1; + const auto* data = static_cast(X509_NAME_ENTRY_get_data_nid_postfix(X509_NAME_get_entry_nid_postfix(name, index))); + if (!data) throw std::invalid_argument(std::string(__func__) + ": null entry value"); + if (len == INT_MIN) throw std::invalid_argument(std::string(__func__) + ": buffer length overflows"); + const int count = data->length > len - 1 ? len - 1 : data->length; + if (!buf) return data->length; + if (count >= 0) { + if (count) { + if (!data->data) throw std::invalid_argument(std::string(__func__) + ": null entry string"); + std::memcpy(buf, data->data, count); + } + buf[count] = '\0'; + } + return count; +} + +} diff --git a/core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp b/core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp index df0619e169..8fcd31009e 100644 --- a/core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp +++ b/core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp @@ -335,10 +335,6 @@ int APS5_VABI X509_get_ext_by_NID_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI EVP_PKEY_security_bits_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI X509_check_akid_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -422,10 +418,6 @@ int APS5_VABI X509_new_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI RSA_get0_factors_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI SSL_get0_verified_chain_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -535,10 +527,6 @@ int APS5_VABI PEM_write_bio_PrivateKey_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI RSA_get0_crt_params_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI BIO_set_data_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -653,10 +641,6 @@ int APS5_VABI tls_config_verify_client_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI EVP_PKEY_cmp_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI d2i_ASN1_SEQUENCE_ANY_nid_postfix() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libSceLibreSsl3/CMakeLists.txt b/core/libs/prx/libSceLibreSsl3/CMakeLists.txt index 99d71c09e2..b280f70808 100644 --- a/core/libs/prx/libSceLibreSsl3/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl3/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceLibreSsl3 SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/IntegerDecoder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Key.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/EvpDigest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509PubkeyParam.cpp) +add_library(libSceLibreSsl3 SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/IntegerDecoder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Key.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/EvpDigest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509PubkeyParam.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/ObjCmp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509Extensions.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Asn1.cpp) target_include_directories(libSceLibreSsl3 PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibreSsl3 PRIVATE libSceLibreSsl libc) configure_windows_unwind(libSceLibreSsl3) @@ -35,6 +35,31 @@ if(BUILD_TESTING) set_tests_properties(guest_libressl3_pkey_getters PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_libressl3_obj_cmp_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSsl3ObjCmp.cpp) + target_include_directories(guest_libressl3_obj_cmp_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl3_obj_cmp_tests PRIVATE libSceLibreSsl3 libc) + configure_windows_unwind(guest_libressl3_obj_cmp_tests) + add_test(NAME guest_libressl3_obj_cmp COMMAND guest_libressl3_obj_cmp_tests) + set_tests_properties(guest_libressl3_obj_cmp PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl3_obj_cmp PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_libressl3_rsa_getters_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/RsaGetters.cpp) + target_include_directories(guest_libressl3_rsa_getters_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl3_rsa_getters_tests PRIVATE ${CMAKE_DL_LIBS}) + if(WIN32) + target_link_libraries(guest_libressl3_rsa_getters_tests PRIVATE libc) + endif() + add_dependencies(guest_libressl3_rsa_getters_tests libs) + configure_windows_unwind(guest_libressl3_rsa_getters_tests) + add_test(NAME guest_libressl3_rsa_getters COMMAND guest_libressl3_rsa_getters_tests + ${PATCHED_DIR}/libSceLibreSsl3.prx) + set_tests_properties(guest_libressl3_rsa_getters PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_libressl3_rsa_getters PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:${PATCHED_DIR}") + endif() add_test_executable(guest_crypto_memcmp_tests ${LIBS_INCLUDE_DIR}/tests/GuestCryptoMemcmp.cpp) target_include_directories(guest_crypto_memcmp_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_crypto_memcmp_tests PRIVATE libSceLibreSsl3) @@ -120,6 +145,28 @@ if(BUILD_TESTING) set_tests_properties(guest_crypto_memcmp guest_libressl3_digest guest_libressl3_asn1 guest_libressl3_key_reference guest_libressl3_evp guest_libressl3_version guest_libressl3_x509_pubkey_param PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_libressl3_asn1_string_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSsl3Asn1.cpp) + target_include_directories(guest_libressl3_asn1_string_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl3_asn1_string_tests PRIVATE libSceLibreSsl3 libc) + configure_windows_unwind(guest_libressl3_asn1_string_tests) + add_test(NAME guest_libressl3_asn1_string COMMAND guest_libressl3_asn1_string_tests) + set_tests_properties(guest_libressl3_asn1_string PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl3_asn1_string PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + + add_test_executable(guest_libressl3_x509_extensions_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSsl3X509Extensions.cpp) + target_include_directories(guest_libressl3_x509_extensions_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl3_x509_extensions_tests PRIVATE libSceLibreSsl3) + configure_windows_unwind(guest_libressl3_x509_extensions_tests) + add_test(NAME guest_libressl3_x509_extensions COMMAND guest_libressl3_x509_extensions_tests) + set_tests_properties(guest_libressl3_x509_extensions PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl3_x509_extensions PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() if(BUILD_TESTING AND CMAKE_SYSTEM_NAME STREQUAL "Linux" AND EXISTS "${LIBS_INCLUDE_DIR}/prx/libSceLibreSsl/src/Evp.cpp") @@ -131,13 +178,15 @@ if(BUILD_TESTING AND CMAKE_SYSTEM_NAME STREQUAL "Linux" AND EXISTS "${LIBS_INCLU add_dependencies(guest_libressl3_mixed_evp_consumer libSceLibreSsl3) add_test_executable(guest_libressl3_mixed_evp_loader ${LIBS_INCLUDE_DIR}/tests/GuestLibreSsl3MixedLoader.cpp) target_link_libraries(guest_libressl3_mixed_evp_loader PRIVATE ${CMAKE_DL_LIBS}) - add_dependencies(guest_libressl3_mixed_evp_loader guest_libressl3_mixed_evp_consumer libc libkernel libSceLibcInternal nid_patcher) + add_dependencies(guest_libressl3_mixed_evp_loader guest_libressl3_mixed_evp_consumer libc libkernel libSceLibcInternal libSceLibreSsl libSceNet libSceNetCtl nid_patcher) add_test(NAME guest_libressl3_mixed_evp COMMAND ${CMAKE_COMMAND} -DCONSUMER=$ -DPATCHER=$ -DLIBC=$ -DKERNEL=$ -DINTERNAL=$ + -DNETCTL=$ + -DNET=$ -DLEGACY=$ -DSSL3=$ -DWORK_DIR=${CMAKE_CURRENT_BINARY_DIR}/mixed-evp @@ -145,3 +194,17 @@ if(BUILD_TESTING AND CMAKE_SYSTEM_NAME STREQUAL "Linux" AND EXISTS "${LIBS_INCLU -- $) set_tests_properties(guest_libressl3_mixed_evp PROPERTIES TIMEOUT 20) endif() + +target_sources(libSceLibreSsl3 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/PkeyQueries.cpp) +if(BUILD_TESTING) + add_test_executable(guest_libressl3_pkey_queries_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/PkeyQueries.cpp) + target_include_directories(guest_libressl3_pkey_queries_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl3_pkey_queries_tests PRIVATE libSceLibreSsl3) + configure_windows_unwind(guest_libressl3_pkey_queries_tests) + add_test(NAME guest_libressl3_pkey_queries COMMAND guest_libressl3_pkey_queries_tests) + set_tests_properties(guest_libressl3_pkey_queries PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_libressl3_pkey_queries PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceLibreSsl3/Export.cpp b/core/libs/prx/libSceLibreSsl3/Export.cpp index 47dc6effdf..5640a04af4 100644 --- a/core/libs/prx/libSceLibreSsl3/Export.cpp +++ b/core/libs/prx/libSceLibreSsl3/Export.cpp @@ -8,21 +8,11 @@ extern "C" { APS5_DUMMY_FUN -int APS5_VABI ASN1_INTEGER_free_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI ASN1_OBJECT_free_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI ASN1_STRING_get0_data_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI BIO_up_ref_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -139,11 +129,6 @@ int APS5_VABI EVP_PKEY_set1_EC_KEY_nid_postfix(void) { return 0; } -int APS5_VABI EVP_PKEY_size_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI EVP_SignFinal_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -169,11 +154,6 @@ int APS5_VABI EVP_aes_256_wrap_nid_postfix(void) { return 0; } -int APS5_VABI OBJ_cmp_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI OBJ_txt2obj_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -291,11 +271,6 @@ int APS5_VABI X509_STORE_up_ref_nid_postfix(void) { return 0; } -int APS5_VABI X509_get0_extensions_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI X509_get0_notAfter_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -311,16 +286,6 @@ int APS5_VABI X509_get0_signature_nid_postfix(void) { return 0; } -int APS5_VABI X509_get_ext_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI X509_get_ext_count_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI X509_get_version_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl3/include/Api.hpp b/core/libs/prx/libSceLibreSsl3/include/Api.hpp index cfd1bc9bdd..c58e34499f 100644 --- a/core/libs/prx/libSceLibreSsl3/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl3/include/Api.hpp @@ -6,6 +6,25 @@ #include "prx/libSceLibreSsl/include/Api.hpp" #include "prx/libSceLibreSsl3/include/Cipher.hpp" +struct LibreSsl3Stack { + int num; + char** data; + int sorted; + int numAlloc; + void* comp; +}; + +struct LibreSsl3ExtensionsCertInfo { + unsigned char beforeExtensions[72]; + const LibreSsl3Stack* extensions; + unsigned char rest[24]; +}; + +struct LibreSsl3ExtensionsX509 { + const LibreSsl3ExtensionsCertInfo* certInfo; + unsigned char rest[208]; +}; + struct LibreSsl3Asn1String { int length; int type; @@ -32,9 +51,9 @@ static_assert(offsetof(LibreSsl3X509Pubkey, publicKey) == 8); extern "C" { -int APS5_VABI ASN1_INTEGER_free_nid_postfix(void); +void APS5_VABI ASN1_INTEGER_free_nid_postfix(Asn1String* integer); int APS5_VABI ASN1_OBJECT_free_nid_postfix(void); -int APS5_VABI ASN1_STRING_get0_data_nid_postfix(void); +const unsigned char* APS5_VABI ASN1_STRING_get0_data_nid_postfix(const Asn1String* string); int APS5_VABI BIO_up_ref_nid_postfix(void); int APS5_VABI BIO_vfree_nid_postfix(void); int APS5_VABI CRYPTO_memcmp_nid_postfix(const void* first, const void* second, std::size_t length); @@ -65,7 +84,7 @@ void* APS5_VABI EVP_PKEY_get0_DSA_nid_postfix(LibreSsl3EvpPkey* key); LibreSsl3Rsa* APS5_VABI EVP_PKEY_get0_RSA_nid_postfix(LibreSsl3EvpPkey* key); int APS5_VABI EVP_PKEY_new_mac_key_nid_postfix(void); int APS5_VABI EVP_PKEY_set1_EC_KEY_nid_postfix(void); -int APS5_VABI EVP_PKEY_size_nid_postfix(void); +int APS5_VABI EVP_PKEY_size_nid_postfix(const LibreSsl3EvpPkey* key); int APS5_VABI EVP_PKEY_up_ref_nid_postfix(LibreSsl3EvpPkey* key); int APS5_VABI EVP_SignFinal_nid_postfix(void); int APS5_VABI EVP_VerifyFinal_nid_postfix(void); @@ -74,7 +93,7 @@ int APS5_VABI EVP_aes_192_wrap_nid_postfix(void); int APS5_VABI EVP_aes_256_wrap_nid_postfix(void); const LibreSsl3::DigestMethod* APS5_VABI EVP_sha1_nid_postfix(); const LibreSsl3::DigestMethod* APS5_VABI EVP_sha224_nid_postfix(); -int APS5_VABI OBJ_cmp_nid_postfix(void); +int APS5_VABI OBJ_cmp_nid_postfix(const void* first, const void* second); int APS5_VABI OBJ_txt2obj_nid_postfix(void); int APS5_VABI OCSP_CERTID_free_nid_postfix(void); int APS5_VABI OCSP_cert_to_id_nid_postfix(void); @@ -99,12 +118,12 @@ int APS5_VABI X509_STORE_CTX_free_nid_postfix(void); int APS5_VABI X509_STORE_CTX_init_nid_postfix(void); int APS5_VABI X509_STORE_CTX_new_nid_postfix(void); int APS5_VABI X509_STORE_up_ref_nid_postfix(void); -int APS5_VABI X509_get0_extensions_nid_postfix(void); +const LibreSsl3Stack* APS5_VABI X509_get0_extensions_nid_postfix(const LibreSsl3ExtensionsX509* certificate); int APS5_VABI X509_get0_notAfter_nid_postfix(void); int APS5_VABI X509_get0_notBefore_nid_postfix(void); int APS5_VABI X509_get0_signature_nid_postfix(void); -int APS5_VABI X509_get_ext_nid_postfix(void); -int APS5_VABI X509_get_ext_count_nid_postfix(void); +void* APS5_VABI X509_get_ext_nid_postfix(const LibreSsl3ExtensionsX509* certificate, int loc); +int APS5_VABI X509_get_ext_count_nid_postfix(const LibreSsl3ExtensionsX509* certificate); int APS5_VABI X509_get_version_nid_postfix(void); int APS5_VABI X509_getm_notAfter_nid_postfix(void); int APS5_VABI X509_getm_notBefore_nid_postfix(void); @@ -209,7 +228,7 @@ int APS5_VABI tls_config_set_key_mem_nid_postfix(); int APS5_VABI Unknown_AiNud8LR8B4_nid_no_patch(); int APS5_VABI X509_cmp_current_time_nid_postfix(); int APS5_VABI X509_get_ext_by_NID_nid_postfix(); -int APS5_VABI EVP_PKEY_security_bits_nid_postfix(); +int APS5_VABI EVP_PKEY_security_bits_nid_postfix(const LibreSsl3EvpPkey* key); int APS5_VABI X509_check_akid_nid_postfix(); int APS5_VABI SSL_set_quic_use_legacy_codepoint_nid_postfix(); int APS5_VABI SSL_CTX_set_default_verify_paths_nid_postfix(); @@ -230,7 +249,7 @@ int APS5_VABI OpenSSL_add_all_digests_nid_postfix(); int APS5_VABI EVP_PKEY_base_id_nid_postfix(); int APS5_VABI Unknown_PBYz0kpXwg8_nid_no_patch(); int APS5_VABI X509_new_nid_postfix(); -int APS5_VABI RSA_get0_factors_nid_postfix(); +void APS5_VABI RSA_get0_factors_nid_postfix(const LibreSsl3Rsa* rsa, const void** p, const void** q); int APS5_VABI SSL_get0_verified_chain_nid_postfix(); int APS5_VABI RSA_set0_crt_params_nid_postfix(); int APS5_VABI EVP_PKEY_derive_nid_postfix(); @@ -258,7 +277,7 @@ int APS5_VABI d2i_PKCS8_PRIV_KEY_INFO_nid_postfix(); int APS5_VABI ECDSA_do_sign_nid_postfix(); int APS5_VABI SSL_CTX_set_ciphersuites_nid_postfix(); int APS5_VABI PEM_write_bio_PrivateKey_nid_postfix(); -int APS5_VABI RSA_get0_crt_params_nid_postfix(); +void APS5_VABI RSA_get0_crt_params_nid_postfix(const LibreSsl3Rsa* rsa, const void** dmp1, const void** dmq1, const void** iqmp); int APS5_VABI BIO_set_data_nid_postfix(); int APS5_VABI SSL_CTX_set_keylog_callback_nid_postfix(); int APS5_VABI X509_ALGOR_get0_nid_postfix(); @@ -287,7 +306,7 @@ int APS5_VABI EC_POINT_dup_nid_postfix(); int APS5_VABI EVP_aes_128_ccm_nid_postfix(); int APS5_VABI RSA_set0_key_nid_postfix(); int APS5_VABI tls_config_verify_client_nid_postfix(); -int APS5_VABI EVP_PKEY_cmp_nid_postfix(); +int APS5_VABI EVP_PKEY_cmp_nid_postfix(const LibreSsl3EvpPkey* first, const LibreSsl3EvpPkey* second); int APS5_VABI d2i_ASN1_SEQUENCE_ANY_nid_postfix(); int APS5_VABI PKCS5_PBKDF2_HMAC_nid_postfix(); int APS5_VABI ASN1_OCTET_STRING_free_nid_postfix(); diff --git a/core/libs/prx/libSceLibreSsl3/include/Key.hpp b/core/libs/prx/libSceLibreSsl3/include/Key.hpp index 4dac85fd57..f48ebc2ce8 100644 --- a/core/libs/prx/libSceLibreSsl3/include/Key.hpp +++ b/core/libs/prx/libSceLibreSsl3/include/Key.hpp @@ -53,3 +53,8 @@ static_assert(sizeof(LibreSsl3EcKey) == 64); static_assert(offsetof(LibreSsl3EcKey, references) == 48); static_assert(sizeof(LibreSsl3Rsa) == 152); static_assert(offsetof(LibreSsl3Rsa, references) == 104); +static_assert(offsetof(LibreSsl3Rsa, p) == 48); +static_assert(offsetof(LibreSsl3Rsa, q) == 56); +static_assert(offsetof(LibreSsl3Rsa, dmp1) == 64); +static_assert(offsetof(LibreSsl3Rsa, dmq1) == 72); +static_assert(offsetof(LibreSsl3Rsa, iqmp) == 80); diff --git a/core/libs/prx/libSceLibreSsl3/src/Asn1.cpp b/core/libs/prx/libSceLibreSsl3/src/Asn1.cpp new file mode 100644 index 0000000000..8948cdf2f6 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl3/src/Asn1.cpp @@ -0,0 +1,32 @@ +#include "prx/libSceLibreSsl3/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include "prx/libc/include/General.hpp" + +namespace { + +constexpr std::int64_t Asn1StringFlagNdef = 0x010; + +} + +extern "C" { +const unsigned char* APS5_VABI ASN1_STRING_get0_data_nid_postfix(const Asn1String* string) { + if (!string) throw std::invalid_argument(std::string(__func__) + ": null string"); + return string->data; +} + +void APS5_VABI ASN1_INTEGER_free_nid_postfix(Asn1String* integer) { + if (!integer) return; + if (!(integer->flags & Asn1StringFlagNdef) && integer->data) { + if (integer->length < 0) throw std::invalid_argument(std::string(__func__) + ": negative length"); + std::memset(integer->data, 0, static_cast(integer->length)); + free_nid_postfix(integer->data); + } + free_nid_postfix(integer); +} + +} diff --git a/core/libs/prx/libSceLibreSsl3/src/IntegerDecoder.cpp b/core/libs/prx/libSceLibreSsl3/src/IntegerDecoder.cpp index 6279653e22..5abbadbdf3 100644 --- a/core/libs/prx/libSceLibreSsl3/src/IntegerDecoder.cpp +++ b/core/libs/prx/libSceLibreSsl3/src/IntegerDecoder.cpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace { @@ -118,7 +119,7 @@ void FreeInteger(void* value) { auto& integer = *static_cast(value); if (!(integer.flags & 0x10)) { if (integer.data) { - if (integer.length < 0) NotImplemented_nid_no_patch(__func__); + if (integer.length < 0) throw std::invalid_argument("ASN1_STRING: negative length"); ClearAndFree(integer.data, static_cast(integer.length)); } } @@ -135,7 +136,7 @@ extern "C" { void* APS5_VABI d2i_ASN1_INTEGER_nid_postfix(void** output, const unsigned char** input, std::int64_t length) { if (length < 0) return nullptr; - if (!input || !*input) NotImplemented_nid_no_patch(__func__); + if (!input || !*input) throw std::invalid_argument("d2i_ASN1_INTEGER: null input"); std::span remaining(*input, static_cast(length)); auto content = ReadInteger(remaining); if (!content || content->size() > static_cast(std::numeric_limits::max())) return nullptr; diff --git a/core/libs/prx/libSceLibreSsl3/src/Key.cpp b/core/libs/prx/libSceLibreSsl3/src/Key.cpp index dfca8410ad..6e5877b5dd 100644 --- a/core/libs/prx/libSceLibreSsl3/src/Key.cpp +++ b/core/libs/prx/libSceLibreSsl3/src/Key.cpp @@ -1,3 +1,61 @@ +/* $OpenBSD: rsa_lib.c,v 1.50 2024/03/27 01:22:30 tb Exp $ */ +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + #include "prx/libSceLibreSsl3/libSceLibreSsl3.hpp" #include @@ -41,4 +99,24 @@ int APS5_VABI RSA_up_ref_nid_postfix(LibreSsl3Rsa* rsa) { return Retain_nid_no_patch(rsa->references); } +void APS5_VABI RSA_get0_factors_nid_postfix(const LibreSsl3Rsa* rsa, const void** p, const void** q) { + if (!rsa && (p || q)) + throw std::invalid_argument("RSA_get0_factors requires a key when outputs are requested"); + if (p) + *p = rsa->p; + if (q) + *q = rsa->q; +} + +void APS5_VABI RSA_get0_crt_params_nid_postfix(const LibreSsl3Rsa* rsa, const void** dmp1, const void** dmq1, const void** iqmp) { + if (!rsa && (dmp1 || dmq1 || iqmp)) + throw std::invalid_argument("RSA_get0_crt_params requires a key when outputs are requested"); + if (dmp1) + *dmp1 = rsa->dmp1; + if (dmq1) + *dmq1 = rsa->dmq1; + if (iqmp) + *iqmp = rsa->iqmp; +} + } diff --git a/core/libs/prx/libSceLibreSsl3/src/ObjCmp.cpp b/core/libs/prx/libSceLibreSsl3/src/ObjCmp.cpp new file mode 100644 index 0000000000..ffc2cd3530 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl3/src/ObjCmp.cpp @@ -0,0 +1,31 @@ +#include +#include +#include +#include +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceLibreSsl3/include/Api.hpp" + +namespace { +struct Object { + const char* shortName; + const char* longName; + int nid; + int length; + const unsigned char* data; + int flags; +}; +static_assert(sizeof(Object) == 40 && offsetof(Object, length) == 20 && offsetof(Object, data) == 24); +} + +extern "C" int APS5_VABI OBJ_cmp_nid_postfix(const void* first, const void* second) { + if (!first || !second) throw std::invalid_argument(std::string(__func__) + ": null object"); + const auto* left = static_cast(first); + const auto* right = static_cast(second); + if (left->length < 0 || right->length < 0) throw std::invalid_argument(std::string(__func__) + ": negative encoded length"); + if (const int difference = left->length - right->length) return difference; + if (left->length > 0 && (!left->data || !right->data)) throw std::invalid_argument(std::string(__func__) + ": invalid encoded data"); + for (int index = 0; index < left->length; ++index) { + if (left->data[index] != right->data[index]) return static_cast(left->data[index]) - static_cast(right->data[index]); + } + return 0; +} diff --git a/core/libs/prx/libSceLibreSsl3/src/PkeyQueries.cpp b/core/libs/prx/libSceLibreSsl3/src/PkeyQueries.cpp new file mode 100644 index 0000000000..6a5a06e372 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl3/src/PkeyQueries.cpp @@ -0,0 +1,59 @@ +#include "prx/libSceLibreSsl3/libSceLibreSsl3.hpp" + +#include +#include + +namespace { + +using PkeyQuery = int (APS5_VABI *)(const LibreSsl3EvpPkey*); +using PkeyCompare = int (APS5_VABI *)(const LibreSsl3EvpPkey*, const LibreSsl3EvpPkey*); + +static_assert(sizeof(PkeyQuery) == 8); +static_assert(sizeof(PkeyCompare) == 8); +static_assert(offsetof(LibreSsl3EvpPkey, method) == 8); + +template +TCallback ReadCallback_nid_no_patch(const void* method, std::size_t offset) { + TCallback callback; + std::memcpy(&callback, static_cast(method) + offset, sizeof(callback)); + return callback; +} + +} // namespace + +extern "C" { + +int APS5_VABI EVP_PKEY_size_nid_postfix(const LibreSsl3EvpPkey* key) { + if (!key || !key->method) + return 0; + const auto size = ReadCallback_nid_no_patch(key->method, 96); + return size ? size(key) : 0; +} + +int APS5_VABI EVP_PKEY_security_bits_nid_postfix(const LibreSsl3EvpPkey* key) { + if (!key) + return 0; + if (!key->method) + return -2; + const auto securityBits = ReadCallback_nid_no_patch(key->method, 112); + return securityBits ? securityBits(key) : -2; +} + +int APS5_VABI EVP_PKEY_cmp_nid_postfix(const LibreSsl3EvpPkey* first, const LibreSsl3EvpPkey* second) { + if (!first || !second) + throw std::invalid_argument("EVP_PKEY_cmp requires two keys"); + if (first->type != second->type) + return -1; + if (!first->method) + return -2; + const auto compareParameters = ReadCallback_nid_no_patch(first->method, 160); + if (compareParameters) { + const int result = compareParameters(first, second); + if (result <= 0) + return result; + } + const auto comparePublic = ReadCallback_nid_no_patch(first->method, 56); + return comparePublic ? comparePublic(first, second) : -2; +} + +} diff --git a/core/libs/prx/libSceLibreSsl3/src/X509Extensions.cpp b/core/libs/prx/libSceLibreSsl3/src/X509Extensions.cpp new file mode 100644 index 0000000000..d9aa68ab68 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl3/src/X509Extensions.cpp @@ -0,0 +1,41 @@ +#include "prx/libSceLibreSsl3/include/Api.hpp" +#include +#include +#include +#include "prx/libc/include/General.hpp" + +static_assert(sizeof(LibreSsl3Stack) == 32); +static_assert(offsetof(LibreSsl3Stack, data) == 8); +static_assert(sizeof(LibreSsl3ExtensionsCertInfo) == 104); +static_assert(offsetof(LibreSsl3ExtensionsCertInfo, extensions) == 72); +static_assert(sizeof(LibreSsl3ExtensionsX509) == 216); + +namespace { + +const LibreSsl3Stack* Extensions(const LibreSsl3ExtensionsX509* certificate, const char* function) { + if (!certificate) throw std::invalid_argument(std::string(function) + ": null certificate"); + if (!certificate->certInfo) throw std::invalid_argument(std::string(function) + ": null certificate info"); + return certificate->certInfo->extensions; +} + +} + +extern "C" { + +const LibreSsl3Stack* APS5_VABI X509_get0_extensions_nid_postfix(const LibreSsl3ExtensionsX509* certificate) { + return Extensions(certificate, __func__); +} + +int APS5_VABI X509_get_ext_count_nid_postfix(const LibreSsl3ExtensionsX509* certificate) { + const LibreSsl3Stack* extensions = Extensions(certificate, __func__); + return extensions ? extensions->num : 0; +} + +void* APS5_VABI X509_get_ext_nid_postfix(const LibreSsl3ExtensionsX509* certificate, int loc) { + const LibreSsl3Stack* extensions = Extensions(certificate, __func__); + if (!extensions || loc < 0 || loc >= extensions->num) return nullptr; + if (!extensions->data) throw std::invalid_argument(std::string(__func__) + ": null extension array"); + return extensions->data[loc]; +} + +} diff --git a/core/libs/prx/libSceLibreSsl3/tests/Asn1.cpp b/core/libs/prx/libSceLibreSsl3/tests/Asn1.cpp index f3a0bdc49e..3d139adfb0 100644 --- a/core/libs/prx/libSceLibreSsl3/tests/Asn1.cpp +++ b/core/libs/prx/libSceLibreSsl3/tests/Asn1.cpp @@ -29,6 +29,17 @@ void Require(bool condition, std::source_location location = std::source_locatio std::abort(); } +template +void RequireInvalidArgument(Callable call) { + bool thrown = false; + try { + call(); + } catch (const std::invalid_argument&) { + thrown = true; + } + Require(thrown); +} + std::unordered_map allocations; std::size_t freeCalls = 0; void* dataToClear = nullptr; @@ -169,6 +180,24 @@ void Ownership() { } +void FaultingArguments() { + const unsigned char encoded[] = {2, 1, 42}; + const unsigned char* cursor = nullptr; + RequireInvalidArgument([&] { d2i_ASN1_INTEGER_nid_postfix(nullptr, &cursor, 1); }); + RequireInvalidArgument([&] { d2i_ASN1_INTEGER_nid_postfix(nullptr, nullptr, 1); }); + auto* old = static_cast(Allocate_nid_no_patch(sizeof(String))); + unsigned char bytes[] = {0x2a}; + *old = {-1, 2, bytes, 0}; + void* output = old; + cursor = encoded; + RequireInvalidArgument([&] { d2i_ASN1_INTEGER_nid_postfix(&output, &cursor, sizeof(encoded)); }); + Require(output == old && cursor == encoded); + old->length = 0; + old->data = nullptr; + Release(old); + Require(allocations.empty()); +} + void BerExtremes() { std::vector negative(131, 0); negative[0] = 2; @@ -318,6 +347,7 @@ int main() { Integers(); BerExtremes(); Ownership(); + FaultingArguments(); Rejections(); AllocationFailures(); Require(allocations.empty()); diff --git a/core/libs/prx/libSceLibreSsl3/tests/PkeyQueries.cpp b/core/libs/prx/libSceLibreSsl3/tests/PkeyQueries.cpp new file mode 100644 index 0000000000..1eedb36135 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl3/tests/PkeyQueries.cpp @@ -0,0 +1,213 @@ +#include "prx/libSceLibreSsl3/libSceLibreSsl3.hpp" + +#include +#include +#include +#include +#include +#include + +namespace { + +using MethodBytes = std::array; + +int checks = 0; +int cases = 0; +int queryCalls = 0; +int parameterCalls = 0; +int publicCalls = 0; +int queryResult = 0; +int parameterResult = 0; +int publicResult = 0; +const LibreSsl3EvpPkey* expectedFirst; +const LibreSsl3EvpPkey* expectedSecond; +const void* replacementMethod; + +void Require(bool condition, const char* message) { + ++checks; + if (!condition) { + std::fprintf(stderr, "%s\n", message); + std::abort(); + } +} + +template +void SetCallback(MethodBytes& method, std::size_t offset, TCallback callback) { + std::memcpy(method.data() + offset, &callback, sizeof(callback)); +} + +int APS5_VABI Query(const LibreSsl3EvpPkey* key) { + Require(key == expectedFirst, "query key identity"); + ++queryCalls; + return queryResult; +} + +int APS5_VABI CompareParameters(const LibreSsl3EvpPkey* first, const LibreSsl3EvpPkey* second) { + Require(first == expectedFirst && second == expectedSecond, "parameter operands"); + ++parameterCalls; + if (replacementMethod) + const_cast(first)->method = replacementMethod; + return parameterResult; +} + +int APS5_VABI ComparePublic(const LibreSsl3EvpPkey* first, const LibreSsl3EvpPkey* second) { + Require(first == expectedFirst && second == expectedSecond, "public operands"); + ++publicCalls; + return publicResult; +} + +int APS5_VABI WrongQuery(const LibreSsl3EvpPkey*) { + Require(false, "query dispatched neighboring callback"); + return 0; +} + +int APS5_VABI WrongComparison(const LibreSsl3EvpPkey*, const LibreSsl3EvpPkey*) { + Require(false, "comparison dispatched neighboring callback"); + return 0; +} + +int APS5_VABI ThrowQuery(const LibreSsl3EvpPkey*) { + throw std::runtime_error("query callback failure"); +} + +int APS5_VABI ThrowComparison(const LibreSsl3EvpPkey*, const LibreSsl3EvpPkey*) { + throw std::runtime_error("comparison callback failure"); +} + +template +void RequireThrows(TAction action) { + bool caught = false; + try { + action(); + } catch (const TException&) { + caught = true; + } + Require(caught, "required exception missing"); +} + +template +auto Snapshot(const TObject& object) { + std::array bytes; + std::memcpy(bytes.data(), &object, bytes.size()); + return bytes; +} + +constexpr std::array Results{std::numeric_limits::min(), -7, -2, -1, 0, 1, 2, 128, 256, 4096, std::numeric_limits::max()}; + +void CheckQueries() { + Require(EVP_PKEY_size_nid_postfix(nullptr) == 0, "null key size"); + Require(EVP_PKEY_security_bits_nid_postfix(nullptr) == 0, "null key security bits"); + std::array payload{}; + LibreSsl3EvpPkey key{6, 17, nullptr, payload.data(), -3}; + const auto original = Snapshot(key); + Require(EVP_PKEY_size_nid_postfix(&key) == 0, "missing size method"); + Require(EVP_PKEY_security_bits_nid_postfix(&key) == -2, "missing security method"); + Require(Snapshot(key) == original, "missing-method queries mutated key"); + MethodBytes method; + method.fill(0xa5); + for (std::size_t offset : {88, 104, 120}) + SetCallback(method, offset, WrongQuery); + key.method = method.data(); + expectedFirst = &key; + for (int mask = 0; mask != 4; ++mask) { + SetCallback(method, 96, (mask & 1) ? Query : nullptr); + SetCallback(method, 112, (mask & 2) ? Query : nullptr); + for (int result : Results) { + ++cases; + queryResult = result; + const auto before = Snapshot(key); + const auto methodBefore = method; + const auto payloadBefore = payload; + int calls = queryCalls; + Require(EVP_PKEY_size_nid_postfix(&key) == ((mask & 1) ? result : 0), "size callback result"); + Require(queryCalls == calls + ((mask & 1) != 0), "size callback count"); + calls = queryCalls; + Require(EVP_PKEY_security_bits_nid_postfix(&key) == ((mask & 2) ? result : -2), "security callback result"); + Require(queryCalls == calls + ((mask & 2) != 0), "security callback count"); + Require(Snapshot(key) == before && method == methodBefore && payload == payloadBefore, "query mutated input"); + } + } + SetCallback(method, 96, ThrowQuery); + SetCallback(method, 112, ThrowQuery); + RequireThrows([&] { EVP_PKEY_size_nid_postfix(&key); }); + RequireThrows([&] { EVP_PKEY_security_bits_nid_postfix(&key); }); +} + +void CheckComparison() { + std::array firstPayload{}; + std::array secondPayload{}; + MethodBytes method; + method.fill(0x5a); + for (std::size_t offset : {48, 64, 152, 168}) + SetCallback(method, offset, WrongComparison); + LibreSsl3EvpPkey first{6, 41, method.data(), firstPayload.data(), 1}; + LibreSsl3EvpPkey second{6, 73, nullptr, secondPayload.data(), 0}; + expectedFirst = &first; + expectedSecond = &second; + RequireThrows([&] { EVP_PKEY_cmp_nid_postfix(nullptr, &second); }); + RequireThrows([&] { EVP_PKEY_cmp_nid_postfix(&first, nullptr); }); + RequireThrows([&] { EVP_PKEY_cmp_nid_postfix(nullptr, nullptr); }); + for (int mask = 0; mask != 16; ++mask) { + first.type = (mask & 1) ? 6 : 912; + first.method = (mask & 2) ? method.data() : nullptr; + SetCallback(method, 160, (mask & 4) ? CompareParameters : nullptr); + SetCallback(method, 56, (mask & 8) ? ComparePublic : nullptr); + for (int parameters : Results) { + for (int publicKey : Results) { + ++cases; + parameterResult = parameters; + publicResult = publicKey; + const auto firstBefore = Snapshot(first); + const auto secondBefore = Snapshot(second); + const auto methodBefore = method; + const auto payloadBefore = firstPayload; + const auto otherPayloadBefore = secondPayload; + const int parameterBefore = parameterCalls; + const int publicBefore = publicCalls; + const bool invokesParameters = (mask & 7) == 7; + const bool invokesPublic = (mask & 11) == 11 && (!invokesParameters || parameters > 0); + int expected = -2; + if (!(mask & 1)) expected = -1; + else if (invokesParameters && parameters <= 0) expected = parameters; + else if (invokesPublic) expected = publicKey; + Require(EVP_PKEY_cmp_nid_postfix(&first, &second) == expected, "comparison result"); + Require(parameterCalls == parameterBefore + invokesParameters, "parameter dispatch count"); + Require(publicCalls == publicBefore + invokesPublic, "public dispatch count"); + Require(Snapshot(first) == firstBefore && Snapshot(second) == secondBefore, "comparison mutated keys"); + Require(method == methodBefore && firstPayload == payloadBefore && secondPayload == otherPayloadBefore, "comparison mutated methods or payloads"); + } + } + } + first.type = second.type; + first.method = method.data(); + SetCallback(method, 160, ThrowComparison); + const int calls = publicCalls; + RequireThrows([&] { EVP_PKEY_cmp_nid_postfix(&first, &second); }); + Require(publicCalls == calls, "public comparison after parameter exception"); + SetCallback(method, 160, static_cast(nullptr)); + SetCallback(method, 56, ThrowComparison); + RequireThrows([&] { EVP_PKEY_cmp_nid_postfix(&first, &second); }); + SetCallback(method, 56, ComparePublic); + expectedSecond = &first; + publicResult = -17; + Require(EVP_PKEY_cmp_nid_postfix(&first, &first) == -17, "self comparison must dispatch"); + expectedSecond = &second; + MethodBytes nextMethod{}; + SetCallback(nextMethod, 56, ComparePublic); + SetCallback(method, 56, WrongComparison); + SetCallback(method, 160, CompareParameters); + parameterResult = 1; + publicResult = 29; + replacementMethod = nextMethod.data(); + Require(EVP_PKEY_cmp_nid_postfix(&first, &second) == 29, "public method refresh after parameter callback"); + replacementMethod = nullptr; +} + +} // namespace + +int main() { + CheckQueries(); + CheckComparison(); + std::printf("PKEY queries: %d matrix cases, %d checks\n", cases, checks); + return 0; +} diff --git a/core/libs/prx/libSceLibreSsl3/tests/RsaGetters.cpp b/core/libs/prx/libSceLibreSsl3/tests/RsaGetters.cpp new file mode 100644 index 0000000000..bc8d5aa43d --- /dev/null +++ b/core/libs/prx/libSceLibreSsl3/tests/RsaGetters.cpp @@ -0,0 +1,127 @@ +#include "prx/libSceLibreSsl3/libSceLibreSsl3.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +namespace { + +using Factors = void (APS5_VABI*)(const LibreSsl3Rsa*, const void**, const void**); +using CrtParams = void (APS5_VABI*)(const LibreSsl3Rsa*, const void**, const void**, const void**); + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL3 RSA getter check failed at line %u\n", location.line()); + std::abort(); +} + +void Run(Factors factors, CrtParams crtParams) { + std::array values{}; + for (unsigned fields = 0; fields < 32; ++fields) { + LibreSsl3Rsa rsa{}; + rsa.p = fields & 1 ? &values[0] : nullptr; + rsa.q = fields & 2 ? &values[1] : nullptr; + rsa.dmp1 = fields & 4 ? &values[2] : nullptr; + rsa.dmq1 = fields & 8 ? &values[3] : nullptr; + rsa.iqmp = fields & 16 ? &values[4] : nullptr; + rsa.references = 37; + rsa.flags = 123; + std::array before; + std::memcpy(before.data(), &rsa, sizeof(rsa)); + const void* const sentinel = &values[5]; + for (unsigned outputs = 0; outputs < 4; ++outputs) { + const void* p = sentinel; + const void* q = sentinel; + factors(&rsa, outputs & 1 ? &p : nullptr, outputs & 2 ? &q : nullptr); + Require(p == (outputs & 1 ? rsa.p : sentinel)); + Require(q == (outputs & 2 ? rsa.q : sentinel)); + const void* shared = sentinel; + factors(&rsa, outputs & 1 ? &shared : nullptr, outputs & 2 ? &shared : nullptr); + Require(shared == (outputs & 2 ? rsa.q : outputs & 1 ? rsa.p : sentinel)); + Require(std::memcmp(before.data(), &rsa, sizeof(rsa)) == 0); + } + for (unsigned outputs = 0; outputs < 8; ++outputs) { + const void* dmp1 = sentinel; + const void* dmq1 = sentinel; + const void* iqmp = sentinel; + crtParams(&rsa, outputs & 1 ? &dmp1 : nullptr, outputs & 2 ? &dmq1 : nullptr, + outputs & 4 ? &iqmp : nullptr); + Require(dmp1 == (outputs & 1 ? rsa.dmp1 : sentinel)); + Require(dmq1 == (outputs & 2 ? rsa.dmq1 : sentinel)); + Require(iqmp == (outputs & 4 ? rsa.iqmp : sentinel)); + const void* shared = sentinel; + crtParams(&rsa, outputs & 1 ? &shared : nullptr, outputs & 2 ? &shared : nullptr, + outputs & 4 ? &shared : nullptr); + Require(shared == (outputs & 4 ? rsa.iqmp : outputs & 2 ? rsa.dmq1 : outputs & 1 ? rsa.dmp1 : sentinel)); + Require(std::memcmp(before.data(), &rsa, sizeof(rsa)) == 0); + } + } + factors(nullptr, nullptr, nullptr); + crtParams(nullptr, nullptr, nullptr, nullptr); + for (unsigned outputs = 1; outputs < 4; ++outputs) { + const void* value = &values[5]; + bool threw = false; + try { factors(nullptr, outputs & 1 ? &value : nullptr, outputs & 2 ? &value : nullptr); } + catch (const std::exception&) { threw = true; } + Require(threw && value == &values[5]); + } + for (unsigned outputs = 1; outputs < 8; ++outputs) { + const void* value = &values[5]; + bool threw = false; + try { + crtParams(nullptr, outputs & 1 ? &value : nullptr, outputs & 2 ? &value : nullptr, + outputs & 4 ? &value : nullptr); + } + catch (const std::exception&) { threw = true; } + Require(threw && value == &values[5]); + } +} + +} + +int main(int argc, char** argv) { + Require(argc == 2); +#ifdef _WIN32 + const auto library = LoadLibraryExW(std::filesystem::path(argv[1]).c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); + Require(library != nullptr); + const auto factors = std::bit_cast(GetProcAddress(library, "PcUZS7XHwHk")); + const auto crtParams = std::bit_cast(GetProcAddress(library, "eyrIagsax0w")); +#else + const auto library = dlopen(argv[1], RTLD_NOW | RTLD_LOCAL); + if (!library) std::fprintf(stderr, "%s\n", dlerror()); + Require(library != nullptr); + const auto factors = reinterpret_cast(dlsym(library, "PcUZS7XHwHk")); + const auto crtParams = reinterpret_cast(dlsym(library, "eyrIagsax0w")); +#endif + Require(factors != nullptr && crtParams != nullptr); + bool failed = false; + try { factors(nullptr, nullptr, nullptr); } + catch (const std::exception& error) { + std::fprintf(stderr, "RSA_get0_factors threw: %s\n", error.what()); + failed = true; + } + try { crtParams(nullptr, nullptr, nullptr, nullptr); } + catch (const std::exception& error) { + std::fprintf(stderr, "RSA_get0_crt_params threw: %s\n", error.what()); + failed = true; + } + if (failed) return 1; + try { Run(factors, crtParams); } + catch (const std::exception& error) { + std::fprintf(stderr, "RSA getter threw: %s\n", error.what()); + failed = true; + } + if (failed) return 1; + std::puts("LibreSSL3 RSA getter tests passed"); +} diff --git a/core/libs/prx/libSceMouse/CMakeLists.txt b/core/libs/prx/libSceMouse/CMakeLists.txt index 9d75d16ebd..f621067da4 100644 --- a/core/libs/prx/libSceMouse/CMakeLists.txt +++ b/core/libs/prx/libSceMouse/CMakeLists.txt @@ -17,6 +17,15 @@ if(BUILD_TESTING) set_tests_properties(mouse_absolute_position PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(mouse_query_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/MouseQueries.cpp) + target_include_directories(mouse_query_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(mouse_query_tests PRIVATE libSceMouse libkernel libc) + configure_windows_unwind(mouse_query_tests) + add_test(NAME mouse_queries COMMAND mouse_query_tests) + if(WIN32) + set_tests_properties(mouse_queries PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() if(BUILD_TESTING) diff --git a/core/libs/prx/libSceMouse/Export.cpp b/core/libs/prx/libSceMouse/Export.cpp index 7fc6bae2e4..b7e5fdb127 100644 --- a/core/libs/prx/libSceMouse/Export.cpp +++ b/core/libs/prx/libSceMouse/Export.cpp @@ -2,6 +2,9 @@ #include "prx/libSceMouse/include/MouseState.hpp" #include "prx/libc/include/General.hpp" +#include +#include + extern "C" { int APS5_VABI sceMouseInit() { @@ -57,9 +60,11 @@ int APS5_VABI sceMouseDisconnectPort() { return 0; } -int APS5_VABI sceMouseGetDeviceInfo() { +int APS5_VABI sceMouseGetDeviceInfo(std::int32_t device, void* info) { + static_cast(device); + if (!Mouse::IsInitialized()) return MOUSE_ERROR_NOT_INITIALIZED; + if (info == nullptr) return MOUSE_ERROR_INVALID_ARG; NotImplemented_nid_no_patch(__func__); - return 0; } int APS5_VABI sceMouseMbusInit() { @@ -67,14 +72,18 @@ int APS5_VABI sceMouseMbusInit() { return 0; } -int APS5_VABI sceMouseSetHandType() { +int APS5_VABI sceMouseSetHandType(std::int32_t handle, std::uint32_t type) { + if (!Mouse::IsInitialized()) return MOUSE_ERROR_NOT_INITIALIZED; + if (type > MOUSE_MAX_HAND_TYPE) return MOUSE_ERROR_INVALID_ARG; + static_cast(handle); NotImplemented_nid_no_patch(__func__); - return 0; } -int APS5_VABI sceMouseSetPointerSpeed() { +int APS5_VABI sceMouseSetPointerSpeed(std::int32_t handle, std::uint32_t speed) { + if (!Mouse::IsInitialized()) return MOUSE_ERROR_NOT_INITIALIZED; + if (speed > MOUSE_MAX_POINTER_SPEED) return MOUSE_ERROR_INVALID_ARG; + static_cast(handle); NotImplemented_nid_no_patch(__func__); - return 0; } int APS5_VABI sceMouseSetProcessPrivilege() { @@ -94,4 +103,48 @@ int APS5_VABI sceMouseSetAbsoluteVirtualScreenSize(std::int32_t handle, std::int return Mouse::SetAbsoluteVirtualScreenSize(handle, size); } +int APS5_VABI sceMouseGetHandle(int userId, std::int32_t type, std::int32_t index) { + static_cast(index); + return Mouse::GetHandle(userId, type); +} + +int APS5_VABI sceMouseExtensionRead(std::int32_t handle, void* data, std::uint32_t size, std::int32_t num) { + if (size != MOUSE_EXTENSION_DATA_SIZE) return MOUSE_ERROR_INVALID_ARG; + if (const int error = Mouse::CheckRead(handle, data, num)) return error; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceMouseExtensionReadState(std::int32_t handle, void* state, std::uint32_t size) { + if (size != MOUSE_EXTENSION_STATE_SIZE || state == nullptr) return MOUSE_ERROR_INVALID_ARG; + std::memset(state, 0, MOUSE_EXTENSION_STATE_SIZE); + if (const int error = Mouse::CheckRead(handle, state, MOUSE_MAX_DATA_NUM)) return error; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceMouseExtensionSetConfig(std::int32_t handle, const void* config) { + return Mouse::SetExtensionConfig(handle, static_cast(config)); +} + +int APS5_VABI sceMouseExtensionSetCursor(std::int32_t handle, std::int32_t cursor) { + if (!Mouse::IsInitialized()) return MOUSE_ERROR_NOT_INITIALIZED; + if (const int error = Mouse::CheckHandle(handle)) return error; + static_cast(cursor); + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceMouseExtensionGetCursor(std::int32_t handle, void* cursor) { + if (!Mouse::IsInitialized()) return MOUSE_ERROR_NOT_INITIALIZED; + if (cursor == nullptr) return MOUSE_ERROR_INVALID_ARG; + if (const int error = Mouse::CheckHandle(handle)) return error; + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceMouseExtensionScalePosition(std::uint16_t* position, std::uint16_t scale) { + if (!Mouse::IsInitialized()) return MOUSE_ERROR_NOT_INITIALIZED; + if (position == nullptr || scale == 0) return MOUSE_ERROR_INVALID_ARG; + const std::uint32_t scaled = static_cast(*position) * (scale - 1u); + *position = static_cast((scaled + 0xfffeu) / 0xffffu); + return MOUSE_OK; +} + } diff --git a/core/libs/prx/libSceMouse/InventoryExports.cpp b/core/libs/prx/libSceMouse/InventoryExports.cpp index 83c4ba5342..64379f1e83 100644 --- a/core/libs/prx/libSceMouse/InventoryExports.cpp +++ b/core/libs/prx/libSceMouse/InventoryExports.cpp @@ -7,34 +7,6 @@ int APS5_VABI sceMouseDisconnectAll() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI sceMouseExtensionGetCursor() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sceMouseExtensionRead() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sceMouseExtensionReadState() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sceMouseExtensionScalePosition() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sceMouseExtensionSetConfig() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sceMouseExtensionSetCursor() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sceMouseGetHandle() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI sceMouseVirtualDeviceAddDevice() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libSceMouse/include/Api.hpp b/core/libs/prx/libSceMouse/include/Api.hpp index 6ed1540333..7bdb53f3ca 100644 --- a/core/libs/prx/libSceMouse/include/Api.hpp +++ b/core/libs/prx/libSceMouse/include/Api.hpp @@ -15,13 +15,20 @@ int APS5_VABI sceMouseDebugGetDeviceId(); int APS5_VABI sceMouseDeviceOpen(); int APS5_VABI sceMouseDisconnectDevice(); int APS5_VABI sceMouseDisconnectPort(); -int APS5_VABI sceMouseGetDeviceInfo(); +int APS5_VABI sceMouseGetDeviceInfo(std::int32_t device, void* info); int APS5_VABI sceMouseMbusInit(); -int APS5_VABI sceMouseSetHandType(); -int APS5_VABI sceMouseSetPointerSpeed(); +int APS5_VABI sceMouseSetHandType(std::int32_t handle, std::uint32_t type); +int APS5_VABI sceMouseSetPointerSpeed(std::int32_t handle, std::uint32_t speed); int APS5_VABI sceMouseSetProcessPrivilege(); int APS5_VABI sceMouseReadAbsolutePosition(std::int32_t handle, MouseAbsoluteData* data, std::int32_t num); int APS5_VABI sceMouseSetAbsoluteHighPrecisionMode(std::int32_t handle, std::int32_t mode); int APS5_VABI sceMouseSetAbsoluteVirtualScreenSize(std::int32_t handle, std::int32_t size); +int APS5_VABI sceMouseGetHandle(int userId, std::int32_t type, std::int32_t index); +int APS5_VABI sceMouseExtensionRead(std::int32_t handle, void* data, std::uint32_t size, std::int32_t num); +int APS5_VABI sceMouseExtensionReadState(std::int32_t handle, void* state, std::uint32_t size); +int APS5_VABI sceMouseExtensionSetConfig(std::int32_t handle, const void* config); +int APS5_VABI sceMouseExtensionSetCursor(std::int32_t handle, std::int32_t cursor); +int APS5_VABI sceMouseExtensionGetCursor(std::int32_t handle, void* cursor); +int APS5_VABI sceMouseExtensionScalePosition(std::uint16_t* position, std::uint16_t scale); } diff --git a/core/libs/prx/libSceMouse/include/InventoryExports.hpp b/core/libs/prx/libSceMouse/include/InventoryExports.hpp index 3b3b422051..896da3b6c0 100644 --- a/core/libs/prx/libSceMouse/include/InventoryExports.hpp +++ b/core/libs/prx/libSceMouse/include/InventoryExports.hpp @@ -5,13 +5,6 @@ extern "C" { int APS5_VABI sceMouseDisconnectAll(); -int APS5_VABI sceMouseExtensionGetCursor(); -int APS5_VABI sceMouseExtensionRead(); -int APS5_VABI sceMouseExtensionReadState(); -int APS5_VABI sceMouseExtensionScalePosition(); -int APS5_VABI sceMouseExtensionSetConfig(); -int APS5_VABI sceMouseExtensionSetCursor(); -int APS5_VABI sceMouseGetHandle(); int APS5_VABI sceMouseVirtualDeviceAddDevice(); int APS5_VABI sceMouseVirtualDeviceDeleteDevice(); int APS5_VABI sceMouseVirtualDeviceGetCursor(); diff --git a/core/libs/prx/libSceMouse/include/MouseState.hpp b/core/libs/prx/libSceMouse/include/MouseState.hpp index b6a161f68d..25e219071f 100644 --- a/core/libs/prx/libSceMouse/include/MouseState.hpp +++ b/core/libs/prx/libSceMouse/include/MouseState.hpp @@ -23,6 +23,11 @@ int Initialize(); int Open(int userId, std::int32_t type, std::int32_t index, const MouseOpenParam* param); int Close(std::int32_t handle); int Read(std::int32_t handle, MouseData* data, std::int32_t num); +int CheckRead(std::int32_t handle, const void* data, std::int32_t num); +int CheckHandle(std::int32_t handle); +bool IsInitialized(); +int GetHandle(int userId, std::int32_t type); +int SetExtensionConfig(std::int32_t handle, const std::uint8_t* config); int ResetAbsolutePosition(std::int32_t handle, std::uint16_t x, std::uint16_t y); int SetAbsoluteVirtualScreenSize(std::int32_t handle, std::int32_t size); int SetAbsoluteHighPrecisionMode(std::int32_t handle, std::int32_t mode); diff --git a/core/libs/prx/libSceMouse/include/mouse_structs.h b/core/libs/prx/libSceMouse/include/mouse_structs.h index a1da9bb48d..d9944793e0 100644 --- a/core/libs/prx/libSceMouse/include/mouse_structs.h +++ b/core/libs/prx/libSceMouse/include/mouse_structs.h @@ -50,4 +50,11 @@ static_assert(offsetof(MouseData, wheel) == 24); static_assert(offsetof(MouseData, tilt) == 28); static_assert(offsetof(MouseData, reserved) == 32); +constexpr int MOUSE_ERROR_NO_HANDLE = static_cast(0x80df0008u); +constexpr std::uint32_t MOUSE_MAX_HAND_TYPE = 1; +constexpr std::uint32_t MOUSE_MAX_POINTER_SPEED = 8; +constexpr std::uint32_t MOUSE_EXTENSION_DATA_SIZE = 0x30; +constexpr std::uint32_t MOUSE_EXTENSION_STATE_SIZE = 0x40; +constexpr std::size_t MOUSE_EXTENSION_CONFIG_SIZE = 0x20; + #endif diff --git a/core/libs/prx/libSceMouse/src/mouse_impl.cpp b/core/libs/prx/libSceMouse/src/mouse_impl.cpp index ac1b19e9d8..101a4fafb1 100644 --- a/core/libs/prx/libSceMouse/src/mouse_impl.cpp +++ b/core/libs/prx/libSceMouse/src/mouse_impl.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include namespace { @@ -14,6 +15,9 @@ int head = 0; int count = 0; bool initialized = false; bool opened = false; +int openedUser = 0; +std::uint32_t extensionConfig = 0; +std::uint8_t extensionFlags = 0; bool connected = true; std::uint32_t buttons = 0; constexpr std::array SCREEN_WIDTHS{1280, 1920, 2560, 3840}; @@ -135,6 +139,9 @@ int Open(int userId, std::int32_t type, std::int32_t index, const MouseOpenParam } if (opened) return MOUSE_ERROR_ALREADY_OPENED; opened = true; + openedUser = userId; + extensionConfig = 0; + extensionFlags = 0; buttons = 0; head = 0; count = 0; @@ -167,11 +174,52 @@ int Close(std::int32_t handle) { return MOUSE_OK; } -int Read(std::int32_t handle, MouseData* data, std::int32_t num) { - std::lock_guard lock(mouseMutex); +namespace { +int checkRead(std::int32_t handle, const void* data, std::int32_t num) { if (!initialized) return MOUSE_ERROR_NOT_INITIALIZED; if (handle != MOUSE_HANDLE || !opened) return MOUSE_ERROR_INVALID_HANDLE; if (data == nullptr || num <= 0 || num > MOUSE_MAX_DATA_NUM) return MOUSE_ERROR_INVALID_ARG; + return MOUSE_OK; +} +} + +int CheckRead(std::int32_t handle, const void* data, std::int32_t num) { + std::lock_guard lock(mouseMutex); + return checkRead(handle, data, num); +} + +int CheckHandle(std::int32_t handle) { + std::lock_guard lock(mouseMutex); + return handle == MOUSE_HANDLE && opened ? MOUSE_OK : MOUSE_ERROR_INVALID_HANDLE; +} + +bool IsInitialized() { + std::lock_guard lock(mouseMutex); + return initialized; +} + +int GetHandle(int userId, std::int32_t type) { + std::lock_guard lock(mouseMutex); + if (!initialized) return MOUSE_ERROR_NOT_INITIALIZED; + return opened && openedUser == userId && type == 0 ? MOUSE_HANDLE : MOUSE_ERROR_NO_HANDLE; +} + +int SetExtensionConfig(std::int32_t handle, const std::uint8_t* config) { + std::lock_guard lock(mouseMutex); + if (!initialized) return MOUSE_ERROR_NOT_INITIALIZED; + if (config == nullptr) return MOUSE_ERROR_INVALID_ARG; + for (std::size_t offset = 5; offset < MOUSE_EXTENSION_CONFIG_SIZE; ++offset) { + if (config[offset] != 0) return MOUSE_ERROR_INVALID_ARG; + } + if (handle != MOUSE_HANDLE || !opened) return MOUSE_ERROR_INVALID_HANDLE; + std::memcpy(&extensionConfig, config, sizeof(extensionConfig)); + extensionFlags = config[4]; + return MOUSE_OK; +} + +int Read(std::int32_t handle, MouseData* data, std::int32_t num) { + std::lock_guard lock(mouseMutex); + if (const int error = checkRead(handle, data, num)) return error; const int available = std::min(count, num); for (int i = 0; i < available; ++i) { data[i] = history[head]; diff --git a/core/libs/prx/libSceMouse/tests/MouseQueries.cpp b/core/libs/prx/libSceMouse/tests/MouseQueries.cpp new file mode 100644 index 0000000000..da9a34068d --- /dev/null +++ b/core/libs/prx/libSceMouse/tests/MouseQueries.cpp @@ -0,0 +1,111 @@ +#include "prx/libSceMouse/libSceMouse.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include +#include +#include +#include + +namespace { + +constexpr int Ok = 0; +constexpr int InvalidArg = static_cast(0x80df0001u); +constexpr int InvalidHandle = static_cast(0x80df0003u); +constexpr int NotInitialized = static_cast(0x80df0005u); +constexpr int NoHandle = static_cast(0x80df0008u); + +using GetHandleFn = int (APS5_VABI*)(int, std::int32_t, std::int32_t); +using DeviceInfoFn = int (APS5_VABI*)(std::int32_t, void*); +using HandleValueFn = int (APS5_VABI*)(std::int32_t, std::uint32_t); +using ReadFn = int (APS5_VABI*)(std::int32_t, void*, std::uint32_t, std::int32_t); +using ReadStateFn = int (APS5_VABI*)(std::int32_t, void*, std::uint32_t); +using SetConfigFn = int (APS5_VABI*)(std::int32_t, const void*); +using SetCursorFn = int (APS5_VABI*)(std::int32_t, std::int32_t); +using GetCursorFn = int (APS5_VABI*)(std::int32_t, void*); +using ScaleFn = int (APS5_VABI*)(std::uint16_t*, std::uint16_t); + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "Mouse queries: %s\n", message); + std::abort(); +} + +template +Function As(Export function) { + return reinterpret_cast(function); +} + +} + +int main() { + const auto getHandle = As(&sceMouseGetHandle); + const auto getDeviceInfo = As(&sceMouseGetDeviceInfo); + const auto setHandType = As(&sceMouseSetHandType); + const auto setPointerSpeed = As(&sceMouseSetPointerSpeed); + const auto extensionRead = As(&sceMouseExtensionRead); + const auto extensionReadState = As(&sceMouseExtensionReadState); + const auto setConfig = As(&sceMouseExtensionSetConfig); + const auto setCursor = As(&sceMouseExtensionSetCursor); + const auto getCursor = As(&sceMouseExtensionGetCursor); + const auto scale = As(&sceMouseExtensionScalePosition); + + std::array buffer{}; + std::array config{}; + std::uint16_t position = 100; + + Require(getHandle(1, 0, 0) == NotInitialized, "GetHandle answered before sceMouseInit"); + Require(getDeviceInfo(0, buffer.data()) == NotInitialized, "GetDeviceInfo answered before sceMouseInit"); + Require(setHandType(1, 0) == NotInitialized && setPointerSpeed(1, 0) == NotInitialized, "SetHandType or SetPointerSpeed answered before sceMouseInit"); + Require(setConfig(1, config.data()) == NotInitialized, "ExtensionSetConfig answered before sceMouseInit"); + Require(setCursor(1, 0) == NotInitialized && getCursor(1, buffer.data()) == NotInitialized, "the cursor calls answered before sceMouseInit"); + Require(scale(&position, 1920) == NotInitialized && position == 100, "ExtensionScalePosition scaled before sceMouseInit"); + + Require(sceMouseInit() == Ok, "sceMouseInit failed"); + Require(getHandle(1, 0, 0) == NoHandle, "GetHandle found a handle before sceMouseOpen"); + const int handle = sceMouseOpen(1, 0, 0, nullptr); + Require(handle > 0, "sceMouseOpen failed"); + Require(getHandle(1, 0, 0) == handle && getHandle(1, 0, 1) == handle, "GetHandle didn't return the open handle for its user and type"); + Require(getHandle(2, 0, 0) == NoHandle && getHandle(1, 1, 0) == NoHandle, "GetHandle matched another user or type"); + + Require(scale(nullptr, 1920) == InvalidArg, "ExtensionScalePosition took a null position"); + Require(scale(&position, 0) == InvalidArg && position == 100, "ExtensionScalePosition took a scale of 0"); + const std::array, 9> rows{{{65535, 1920, 1919}, {32768, 1920, 960}, {0, 1920, 0}, {65535, 1, 0}, {1, 65535, 1}, {40000, 1080, 659}, {1, 2, 1}, {32767, 2, 1}, {100, 1920, 3}}}; + for (const auto& [input, size, expected] : rows) { + position = input; + Require(scale(&position, size) == Ok && position == expected, "ExtensionScalePosition didn't round position * (scale - 1) / 65535 up"); + } + + Require(setConfig(handle, nullptr) == InvalidArg, "ExtensionSetConfig took a null config"); + config[5] = 1; + Require(setConfig(handle, config.data()) == InvalidArg, "ExtensionSetConfig took a non-zero byte 5"); + config[5] = 0; + config[0x1f] = 1; + Require(setConfig(handle, config.data()) == InvalidArg, "ExtensionSetConfig took a non-zero byte 0x1f"); + config[0x1f] = 0; + config[0] = 0x12; + config[4] = 1; + Require(setConfig(handle + 1, config.data()) == InvalidHandle, "ExtensionSetConfig took an unopened handle"); + Require(setConfig(handle, config.data()) == Ok, "ExtensionSetConfig refused a valid config"); + + Require(setHandType(handle, 2) == InvalidArg, "SetHandType took a type above 1"); + Require(setPointerSpeed(handle, 9) == InvalidArg, "SetPointerSpeed took a speed above 8"); + Require(getDeviceInfo(0, nullptr) == InvalidArg, "GetDeviceInfo took a null output"); + + Require(extensionRead(handle, buffer.data(), 0x20, 1) == InvalidArg, "ExtensionRead took a record size other than 0x30"); + Require(extensionRead(handle + 1, buffer.data(), 0x30, 1) == InvalidHandle, "ExtensionRead took an unopened handle"); + Require(extensionRead(handle, nullptr, 0x30, 1) == InvalidArg && extensionRead(handle, buffer.data(), 0x30, 0) == InvalidArg, "ExtensionRead took a null buffer or a count of 0"); + Require(extensionReadState(handle, buffer.data(), 0x30) == InvalidArg && extensionReadState(handle, nullptr, 0x40) == InvalidArg, "ExtensionReadState took a size other than 0x40 or a null state"); + std::memset(buffer.data(), 0xaa, buffer.size()); + Require(extensionReadState(handle + 1, buffer.data(), 0x40) == InvalidHandle, "ExtensionReadState took an unopened handle"); + for (const auto byte : buffer) Require(byte == 0, "ExtensionReadState didn't clear the state before the handle check"); + + Require(setCursor(handle + 1, 0) == InvalidHandle, "ExtensionSetCursor took an unopened handle"); + Require(getCursor(handle, nullptr) == InvalidArg && getCursor(handle + 1, nullptr) == InvalidArg, "ExtensionGetCursor didn't check its output before the handle"); + Require(getCursor(handle + 1, buffer.data()) == InvalidHandle, "ExtensionGetCursor took an unopened handle"); + + Require(sceMouseClose(handle) == Ok, "sceMouseClose failed"); + Require(getHandle(1, 0, 0) == NoHandle, "GetHandle returned a closed handle"); + std::puts("Mouse query tests passed"); + return 0; +} diff --git a/core/libs/prx/libSceMove/Export.cpp b/core/libs/prx/libSceMove/Export.cpp index e55eded120..aeef175127 100644 --- a/core/libs/prx/libSceMove/Export.cpp +++ b/core/libs/prx/libSceMove/Export.cpp @@ -124,51 +124,71 @@ int APS5_VABI sceMoveResetLightSphere(int32_t handle) { return checkHandle(handle); } int APS5_VABI sceMoveGetBluetoothAddress(void) { + std::lock_guard lock(stateMutex); + if (!initialized) return MOVE_ERROR_NOT_INIT; NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceMoveGetCalibrationInfo(void) { + std::lock_guard lock(stateMutex); + if (!initialized) return MOVE_ERROR_NOT_INIT; NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceMoveGetDeviceId(void) { + std::lock_guard lock(stateMutex); + if (!initialized) return MOVE_ERROR_NOT_INIT; NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceMoveIsReproductionModel(void) { + std::lock_guard lock(stateMutex); + if (!initialized) return MOVE_ERROR_NOT_INIT; NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceMoveIsValidHandle(void) { + std::lock_guard lock(stateMutex); + if (!initialized) return 0; NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceMoveReadStateRecentForTracker(void) { + std::lock_guard lock(stateMutex); + if (!initialized) return MOVE_ERROR_NOT_INIT; NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceMoveSetCalibrationMode(void) { + std::lock_guard lock(stateMutex); + if (!initialized) return MOVE_ERROR_NOT_INIT; NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceMoveSetExtensionPortOutput(void) { + std::lock_guard lock(stateMutex); + if (!initialized) return MOVE_ERROR_NOT_INIT; NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceMoveSetLightSphereForTracker(void) { + std::lock_guard lock(stateMutex); + if (!initialized) return MOVE_ERROR_NOT_INIT; NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceMoveWriteCalibrationInfo(void) { + std::lock_guard lock(stateMutex); + if (!initialized) return MOVE_ERROR_NOT_INIT; NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceMoveTracker/Export.cpp b/core/libs/prx/libSceMoveTracker/Export.cpp index f285fddbbf..aa15464487 100644 --- a/core/libs/prx/libSceMoveTracker/Export.cpp +++ b/core/libs/prx/libSceMoveTracker/Export.cpp @@ -1,4 +1,5 @@ #include "prx/libSceMoveTracker/include/Api.hpp" +#include #include #include #include "SceTypes.hpp" @@ -47,8 +48,18 @@ int APS5_VABI sceMoveTrackerGetVersion() { int APS5_VABI sceMoveTrackerGetWorkingMemorySize(std::uint32_t* totalSize, std::uint32_t* processingSize) { if (totalSize == nullptr || processingSize == nullptr) return static_cast(0x80ef0003); - *totalSize = 0xa00000; - *processingSize = 0x800000; + std::uint32_t originalTotal = 0; + const int savedErrno = errno; + const bool readableTotal = GuestCopyIn_nid_no_patch(&originalTotal, totalSize, sizeof(originalTotal)); + const std::uint32_t total = 0xa00000; + const std::uint32_t processing = 0x800000; + const bool copiedTotal = GuestCopyOut_nid_no_patch(totalSize, &total, sizeof(total)); + const bool copiedProcessing = copiedTotal ? GuestCopyOut_nid_no_patch(processingSize, &processing, sizeof(processing)) : false; + if (copiedTotal && !copiedProcessing && readableTotal) { + GuestCopyOut_nid_no_patch(totalSize, &originalTotal, sizeof(originalTotal)); + } + errno = savedErrno; + if (!copiedTotal || !copiedProcessing) return static_cast(0x80ef0003); return 0; } diff --git a/core/libs/prx/libSceNKWebKit/CMakeLists.txt b/core/libs/prx/libSceNKWebKit/CMakeLists.txt index 57b1083fb6..af9128bc4e 100644 --- a/core/libs/prx/libSceNKWebKit/CMakeLists.txt +++ b/core/libs/prx/libSceNKWebKit/CMakeLists.txt @@ -65,6 +65,18 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endforeach() + + add_test_executable(nkwebkit_wtftext_allocation_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/allocation/WtfTextAllocation.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/StringBuilder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/WtfString.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/WtfNumberFormat.cpp) + target_include_directories(nkwebkit_wtftext_allocation_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(nkwebkit_wtftext_allocation_tests PRIVATE libc) + configure_windows_unwind(nkwebkit_wtftext_allocation_tests) + add_test(NAME nkwebkit_wtftext_allocation COMMAND nkwebkit_wtftext_allocation_tests) + set_tests_properties(nkwebkit_wtftext_allocation PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(nkwebkit_wtftext_allocation PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() if(BUILD_TESTING) diff --git a/core/libs/prx/libSceNKWebKit/Export.cpp b/core/libs/prx/libSceNKWebKit/Export.cpp index ff1b9f1640..2ec79c1f0d 100644 --- a/core/libs/prx/libSceNKWebKit/Export.cpp +++ b/core/libs/prx/libSceNKWebKit/Export.cpp @@ -12334,10 +12334,6 @@ int APS5_VABI _ZN7WebCore27TranslateTransformOperationC1ERKNS_6LengthES3_S3_NS_1 NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTF13StringBuilder12appendNumberEf() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN7WebCore22EmptyFrameLoaderClient35dispatchDidClearWindowObjectInWorldERNS_15DOMWrapperWorldE() { NotImplemented_nid_no_patch(__func__); } @@ -18430,78 +18426,6 @@ int APS5_VABI _ZN3WTF10SymbolImpl6createERNS_10StringImplE() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTF10TextStream10startGroupEv() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStream11writeIndentEv() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStream7releaseEv() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStream8endGroupEv() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStream8nextLineEv() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStreamlsEPKc() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStreamlsEPKv() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStreamlsERKNS_6StringE() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStreamlsEb() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStreamlsEc() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStreamlsEd() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStreamlsEf() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStreamlsEi() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStreamlsEj() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStreamlsEl() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStreamlsEm() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStreamlsEx() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF10TextStreamlsEy() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3WTF10URLHelpers14userVisibleURLERKNS_7CStringE() { NotImplemented_nid_no_patch(__func__); } @@ -18670,10 +18594,6 @@ int APS5_VABI _ZN3WTF11TimingScope11scopeDidEndEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTF11portAllowedERKNS_3URLE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3WTF11setDataFileEPKc() { NotImplemented_nid_no_patch(__func__); } @@ -18750,26 +18670,6 @@ int APS5_VABI _ZN3WTF13ReadWriteLock9writeLockEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTF13StringBuilder11shrinkToFitEv() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF13StringBuilder15reserveCapacityEj() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF13StringBuilder16appendCharactersEPKDsj() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF13StringBuilder16appendCharactersEPKhj() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF13StringBuilder22appendQuotedJSONStringERKNS_6StringE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3WTF13aboutBlankURLEv() { NotImplemented_nid_no_patch(__func__); } @@ -18958,18 +18858,10 @@ int APS5_VABI _ZN3WTF14FileSystemImpl14setMetadataURLERKNS_6StringES3_S3_() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTF14FileSystemImpl17encodeForFileNameERKNS_6StringE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3WTF14FileSystemImpl18createSymbolicLinkERKNS_6StringES3_() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTF14FileSystemImpl18decodeFromFilenameERKNS_6StringE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3WTF14FileSystemImpl18hardLinkOrCopyFileERKNS_6StringES3_() { NotImplemented_nid_no_patch(__func__); } @@ -19014,10 +18906,6 @@ int APS5_VABI _ZN3WTF14FileSystemImpl34stringFromFileSystemRepresentationEPKc() NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTF14FileSystemImpl40lastComponentOfPathIgnoringTrailingSlashERKNS_6StringE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3WTF14FileSystemImpl8hardLinkERKNS_6StringES3_() { NotImplemented_nid_no_patch(__func__); } @@ -19246,10 +19134,6 @@ int APS5_VABI _ZN3WTF17double_conversion23DoubleToStringConverter19EcmaScriptCon NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTF17languageDidChangeEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3WTF17wordBreakIteratorENS_10StringViewE() { NotImplemented_nid_no_patch(__func__); } @@ -19562,10 +19446,6 @@ int APS5_VABI _ZN3WTF25activateSignalHandlersForENS_6SignalE() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTF25addLanguageChangeObserverEPvPFvS0_E() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3WTF26getAndResetAccumulatedLogsEv() { NotImplemented_nid_no_patch(__func__); } @@ -19574,14 +19454,6 @@ int APS5_VABI _ZN3WTF26normalizeLineEndingsToCRLFEONS_6VectorIhLm0ENS_15CrashOnO NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTF27protocolHostAndPortAreEqualERKNS_3URLES2_() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF28displayNameForLanguageLocaleERKNS_6StringE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3WTF28encodeWithURLEscapeSequencesERKNS_6StringE() { NotImplemented_nid_no_patch(__func__); } @@ -19590,22 +19462,10 @@ int APS5_VABI _ZN3WTF28normalizeLineEndingsToNativeEONS_6VectorIhLm0ENS_15CrashO NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTF28removeLanguageChangeObserverEPv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3WTF30numCodeUnitsInGraphemeClustersENS_10StringViewEj() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTF30overrideUserPreferredLanguagesERKNS_6VectorINS_6StringELm0ENS_15CrashOnOverflowELm16ENS_10FastMallocEEE() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF30userPreferredLanguagesOverrideEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3WTF31NonSharedCharacterBreakIteratorC1ENS_10StringViewE() { NotImplemented_nid_no_patch(__func__); } @@ -19622,10 +19482,6 @@ int APS5_VABI _ZN3WTF31NonSharedCharacterBreakIteratorD2Ev() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTF33indexOfBestMatchingLanguageInListERKNS_6StringERKNS_6VectorIS0_Lm0ENS_15CrashOnOverflowELm16ENS_10FastMallocEEERb() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3WTF37canCurrentThreadAccessThreadLocalDataERNS_6ThreadE() { NotImplemented_nid_no_patch(__func__); } @@ -20198,14 +20054,6 @@ int APS5_VABI _ZN3WTF9URLParser9serializeERKNS_6VectorINS_12KeyValuePairINS_6Str NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTFlsERNS_10TextStreamENS_7SecondsE() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTFlsERNS_10TextStreamERKNS_3URLE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN6WebKit14WebProcessMainEiPPc() { NotImplemented_nid_no_patch(__func__); } @@ -32782,22 +32630,6 @@ int APS5_VABI _ZN7bmalloc6IsoTLS8scavengeEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN7bmalloc8FreeList14initializeBumpEPcj() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN7bmalloc8FreeList14initializeListEPNS_8FreeCellEmj() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN7bmalloc8FreeList5clearEv() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN7bmalloc8FreeListC1Ev() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN7bmalloc8FreeListD1Ev() { NotImplemented_nid_no_patch(__func__); } @@ -33318,14 +33150,6 @@ int APS5_VABI _ZN9Inspector17BackendDispatcherD2Ev() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN9Inspector18IdentifiersFactory16createIdentifierEv() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN9Inspector18IdentifiersFactory9requestIdEm() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN9Inspector18InjectedScriptBaseC1ERKN3WTF6StringE() { NotImplemented_nid_no_patch(__func__); } @@ -36434,10 +36258,6 @@ int APS5_VABI _ZNK3WTF13MonotonicTime4dumpERNS_11PrintStreamE() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK3WTF13StringBuilder11reifyStringEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK3WTF14MediaTimeRange12toJSONStringEv() { NotImplemented_nid_no_patch(__func__); } @@ -36474,10 +36294,6 @@ int APS5_VABI _ZNK3WTF3URL16isMatchingDomainENS_10StringViewE() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK3WTF3URL17lastPathComponentEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK3WTF3URL19protocolHostAndPortEv() { NotImplemented_nid_no_patch(__func__); } @@ -36486,10 +36302,6 @@ int APS5_VABI _ZNK3WTF3URL21truncatedForUseAsBaseEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK3WTF3URL24strippedForUseAsReferrerEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK3WTF3URL4dumpERNS_11PrintStreamE() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libSceNKWebKit/include/Api.hpp b/core/libs/prx/libSceNKWebKit/include/Api.hpp index 524c1e8c4e..077eaa063b 100644 --- a/core/libs/prx/libSceNKWebKit/include/Api.hpp +++ b/core/libs/prx/libSceNKWebKit/include/Api.hpp @@ -78,7 +78,6 @@ int APS5_VABI _ZN3WTF9URLParser23maybeCanonicalizeSchemeERKNS_6StringE(); int APS5_VABI _ZN3WTF21MemoryPressureHandler15setProcessStateENS_18WebsamProcessStateE(); int APS5_VABI _ZThn16_N9Inspector21InspectorConsoleAgent6enableERN3WTF6StringE(); int APS5_VABI _ZN7WebCore27TranslateTransformOperationC1ERKNS_6LengthES3_S3_NS_18TransformOperation13OperationTypeE(); -int APS5_VABI _ZN3WTF13StringBuilder12appendNumberEf(); int APS5_VABI _ZN7WebCore22EmptyFrameLoaderClient35dispatchDidClearWindowObjectInWorldERNS_15DOMWrapperWorldE(); int APS5_VABI WKDownloadCancel(); int APS5_VABI _ZNK7WebCore14SecurityOrigin20isSameSchemeHostPortERKS0_(); diff --git a/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp b/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp index c5f917a3a0..ae2d8f1091 100644 --- a/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp +++ b/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp @@ -1564,24 +1564,6 @@ int APS5_VABI _ZN3WTF10StringView16GraphemeClusters8IteratorppEv(); int APS5_VABI _ZN3WTF10SymbolImpl16createNullSymbolEv(); int APS5_VABI _ZN3WTF10SymbolImpl17nextHashForSymbolEv(); int APS5_VABI _ZN3WTF10SymbolImpl6createERNS_10StringImplE(); -int APS5_VABI _ZN3WTF10TextStream10startGroupEv(); -int APS5_VABI _ZN3WTF10TextStream11writeIndentEv(); -int APS5_VABI _ZN3WTF10TextStream7releaseEv(); -int APS5_VABI _ZN3WTF10TextStream8endGroupEv(); -int APS5_VABI _ZN3WTF10TextStream8nextLineEv(); -int APS5_VABI _ZN3WTF10TextStreamlsEPKc(); -int APS5_VABI _ZN3WTF10TextStreamlsEPKv(); -int APS5_VABI _ZN3WTF10TextStreamlsERKNS_6StringE(); -int APS5_VABI _ZN3WTF10TextStreamlsEb(); -int APS5_VABI _ZN3WTF10TextStreamlsEc(); -int APS5_VABI _ZN3WTF10TextStreamlsEd(); -int APS5_VABI _ZN3WTF10TextStreamlsEf(); -int APS5_VABI _ZN3WTF10TextStreamlsEi(); -int APS5_VABI _ZN3WTF10TextStreamlsEj(); -int APS5_VABI _ZN3WTF10TextStreamlsEl(); -int APS5_VABI _ZN3WTF10TextStreamlsEm(); -int APS5_VABI _ZN3WTF10TextStreamlsEx(); -int APS5_VABI _ZN3WTF10TextStreamlsEy(); int APS5_VABI _ZN3WTF10URLHelpers14userVisibleURLERKNS_7CStringE(); int APS5_VABI _ZN3WTF10WorkerPool8postTaskEONS_8FunctionIFvvEEE(); int APS5_VABI _ZN3WTF10WorkerPoolC1ENS_12ASCIILiteralEjNS_7SecondsE(); @@ -1624,7 +1606,6 @@ int APS5_VABI _ZN3WTF11ThreadGroup3addERKNS_14AbstractLockerERNS_6ThreadE(); int APS5_VABI _ZN3WTF11ThreadGroup3addERNS_6ThreadE(); int APS5_VABI _ZN3WTF11ThreadGroupD2Ev(); int APS5_VABI _ZN3WTF11TimingScope11scopeDidEndEv(); -int APS5_VABI _ZN3WTF11portAllowedERKNS_3URLE(); int APS5_VABI _ZN3WTF11setDataFileEPKc(); int APS5_VABI _ZN3WTF11setNonBlockEi(); int APS5_VABI _ZN3WTF12CountingLock10unlockSlowEv(); @@ -1644,11 +1625,6 @@ int APS5_VABI _ZN3WTF13ReadWriteLock10readUnlockEv(); int APS5_VABI _ZN3WTF13ReadWriteLock11writeUnlockEv(); int APS5_VABI _ZN3WTF13ReadWriteLock8readLockEv(); int APS5_VABI _ZN3WTF13ReadWriteLock9writeLockEv(); -int APS5_VABI _ZN3WTF13StringBuilder11shrinkToFitEv(); -int APS5_VABI _ZN3WTF13StringBuilder15reserveCapacityEj(); -int APS5_VABI _ZN3WTF13StringBuilder16appendCharactersEPKDsj(); -int APS5_VABI _ZN3WTF13StringBuilder16appendCharactersEPKhj(); -int APS5_VABI _ZN3WTF13StringBuilder22appendQuotedJSONStringERKNS_6StringE(); int APS5_VABI _ZN3WTF13aboutBlankURLEv(); int APS5_VABI _ZN3WTF13dumpCharacterERNS_11PrintStreamEc(); int APS5_VABI _ZN3WTF13isMainRunLoopEv(); @@ -1696,9 +1672,7 @@ int APS5_VABI _ZN3WTF14FileSystemImpl14MappedFileDataC2ERKNS_6StringENS0_14Mappe int APS5_VABI _ZN3WTF14FileSystemImpl14MappedFileDataD1Ev(); int APS5_VABI _ZN3WTF14FileSystemImpl14MappedFileDataD2Ev(); int APS5_VABI _ZN3WTF14FileSystemImpl14setMetadataURLERKNS_6StringES3_S3_(); -int APS5_VABI _ZN3WTF14FileSystemImpl17encodeForFileNameERKNS_6StringE(); int APS5_VABI _ZN3WTF14FileSystemImpl18createSymbolicLinkERKNS_6StringES3_(); -int APS5_VABI _ZN3WTF14FileSystemImpl18decodeFromFilenameERKNS_6StringE(); int APS5_VABI _ZN3WTF14FileSystemImpl18hardLinkOrCopyFileERKNS_6StringES3_(); int APS5_VABI _ZN3WTF14FileSystemImpl18makeAllDirectoriesERKNS_6StringE(); int APS5_VABI _ZN3WTF14FileSystemImpl18unlockAndCloseFileEi(); @@ -1711,7 +1685,6 @@ int APS5_VABI _ZN3WTF14FileSystemImpl27isSafeToUseMemoryMapForPathERKNS_6StringE int APS5_VABI _ZN3WTF14FileSystemImpl29makeSafeToUseMemoryMapForPathERKNS_6StringE(); int APS5_VABI _ZN3WTF14FileSystemImpl30appendFileContentsToFileHandleERKNS_6StringERi(); int APS5_VABI _ZN3WTF14FileSystemImpl34stringFromFileSystemRepresentationEPKc(); -int APS5_VABI _ZN3WTF14FileSystemImpl40lastComponentOfPathIgnoringTrailingSlashERKNS_6StringE(); int APS5_VABI _ZN3WTF14FileSystemImpl8hardLinkERKNS_6StringES3_(); int APS5_VABI _ZN3WTF14FileSystemImpl8moveFileERKNS_6StringES3_(); int APS5_VABI _ZN3WTF14FileSystemImpl8openFileERKNS_6StringENS0_12FileOpenModeENS0_20FileAccessPermissionEb(); @@ -1779,7 +1752,6 @@ float APS5_VABI _ZN3WTF17double_conversion23StringToDoubleConverter13StringToFlo float APS5_VABI _ZN3WTF17double_conversion23StringToDoubleConverter13StringToFloatEPKtmPm(const std::uint16_t* buffer, std::size_t length, std::size_t* processedCharactersCount); double APS5_VABI _ZN3WTF17double_conversion23StringToDoubleConverter14StringToDoubleEPKcmPm(const char* buffer, std::size_t length, std::size_t* processedCharactersCount); double APS5_VABI _ZN3WTF17double_conversion23StringToDoubleConverter14StringToDoubleEPKtmPm(const std::uint16_t* buffer, std::size_t length, std::size_t* processedCharactersCount); -int APS5_VABI _ZN3WTF17languageDidChangeEv(); int APS5_VABI _ZN3WTF17wordBreakIteratorENS_10StringViewE(); int APS5_VABI _ZN3WTF18ExternalStringImpl6createEPKDsjONS_8FunctionIFvPS0_PvjEEE(); int APS5_VABI _ZN3WTF18ExternalStringImpl6createEPKhjONS_8FunctionIFvPS0_PvjEEE(); @@ -1860,22 +1832,15 @@ int APS5_VABI _ZN3WTF24callOnMainRunLoopAndWaitEONS_8FunctionIFvvEEE(); int APS5_VABI _ZN3WTF24currentTextBreakLocaleIDEv(); int APS5_VABI _ZN3WTF24normalizeLineEndingsToLFEONS_6VectorIhLm0ENS_15CrashOnOverflowELm16ENS_10FastMallocEEE(); int APS5_VABI _ZN3WTF25activateSignalHandlersForENS_6SignalE(); -int APS5_VABI _ZN3WTF25addLanguageChangeObserverEPvPFvS0_E(); int APS5_VABI _ZN3WTF26getAndResetAccumulatedLogsEv(); int APS5_VABI _ZN3WTF26normalizeLineEndingsToCRLFEONS_6VectorIhLm0ENS_15CrashOnOverflowELm16ENS_10FastMallocEEE(); -int APS5_VABI _ZN3WTF27protocolHostAndPortAreEqualERKNS_3URLES2_(); -int APS5_VABI _ZN3WTF28displayNameForLanguageLocaleERKNS_6StringE(); int APS5_VABI _ZN3WTF28encodeWithURLEscapeSequencesERKNS_6StringE(); int APS5_VABI _ZN3WTF28normalizeLineEndingsToNativeEONS_6VectorIhLm0ENS_15CrashOnOverflowELm16ENS_10FastMallocEEE(); -int APS5_VABI _ZN3WTF28removeLanguageChangeObserverEPv(); int APS5_VABI _ZN3WTF30numCodeUnitsInGraphemeClustersENS_10StringViewEj(); -int APS5_VABI _ZN3WTF30overrideUserPreferredLanguagesERKNS_6VectorINS_6StringELm0ENS_15CrashOnOverflowELm16ENS_10FastMallocEEE(); -int APS5_VABI _ZN3WTF30userPreferredLanguagesOverrideEv(); int APS5_VABI _ZN3WTF31NonSharedCharacterBreakIteratorC1ENS_10StringViewE(); int APS5_VABI _ZN3WTF31NonSharedCharacterBreakIteratorC2ENS_10StringViewE(); int APS5_VABI _ZN3WTF31NonSharedCharacterBreakIteratorD1Ev(); int APS5_VABI _ZN3WTF31NonSharedCharacterBreakIteratorD2Ev(); -int APS5_VABI _ZN3WTF33indexOfBestMatchingLanguageInListERKNS_6StringERKNS_6VectorIS0_Lm0ENS_15CrashOnOverflowELm16ENS_10FastMallocEEERb(); int APS5_VABI _ZN3WTF37canCurrentThreadAccessThreadLocalDataERNS_6ThreadE(); int APS5_VABI _ZN3WTF37parseDateFromNullTerminatedCharactersEPKc(); double APS5_VABI _ZN3WTF37parseDateFromNullTerminatedCharactersEPKcRb(const char* dateString, bool* isLocalTime); @@ -2020,8 +1985,6 @@ int APS5_VABI _ZN3WTF9URLParser14allValuesEqualERKNS_3URLES3_(); int APS5_VABI _ZN3WTF9URLParser19parseURLEncodedFormENS_10StringViewE(); int APS5_VABI _ZN3WTF9URLParser24internalValuesConsistentERKNS_3URLE(); int APS5_VABI _ZN3WTF9URLParser9serializeERKNS_6VectorINS_12KeyValuePairINS_6StringES3_EELm0ENS_15CrashOnOverflowELm16ENS_10FastMallocEEE(); -int APS5_VABI _ZN3WTFlsERNS_10TextStreamENS_7SecondsE(); -int APS5_VABI _ZN3WTFlsERNS_10TextStreamERKNS_3URLE(); int APS5_VABI _ZN6WebKit14WebProcessMainEiPPc(); int APS5_VABI _ZN6WebKit18NetworkProcessMainEiPPc(); int APS5_VABI _ZN7Nicosia6Buffer6createERKN7WebCore7IntSizeEj(); @@ -5187,10 +5150,6 @@ int APS5_VABI _ZN7bmalloc5Cache30tryReallocateSlowCaseNullCacheENS_8HeapKindEPvm int APS5_VABI _ZN7bmalloc5Cache8scavengeENS_8HeapKindE(); int APS5_VABI _ZN7bmalloc6IsoTLS13ensureEntriesEj(); int APS5_VABI _ZN7bmalloc6IsoTLS8scavengeEv(); -int APS5_VABI _ZN7bmalloc8FreeList14initializeBumpEPcj(); -int APS5_VABI _ZN7bmalloc8FreeList14initializeListEPNS_8FreeCellEmj(); -int APS5_VABI _ZN7bmalloc8FreeList5clearEv(); -int APS5_VABI _ZN7bmalloc8FreeListC1Ev(); int APS5_VABI _ZN7bmalloc8FreeListD1Ev(); int APS5_VABI _ZN7bmalloc9Allocator12allocateImplEmmNS_13FailureActionE(); int APS5_VABI _ZN7bmalloc9Scavenger29scheduleIfUnderMemoryPressureEm(); @@ -5321,8 +5280,6 @@ int APS5_VABI _ZN9Inspector17BackendDispatcher27registerDispatcherForDomainERKN3 int APS5_VABI _ZN9Inspector17BackendDispatcher8dispatchERKN3WTF6StringE(); int APS5_VABI _ZN9Inspector17BackendDispatcherD1Ev(); int APS5_VABI _ZN9Inspector17BackendDispatcherD2Ev(); -int APS5_VABI _ZN9Inspector18IdentifiersFactory16createIdentifierEv(); -int APS5_VABI _ZN9Inspector18IdentifiersFactory9requestIdEm(); int APS5_VABI _ZN9Inspector18InjectedScriptBaseC1ERKN3WTF6StringE(); int APS5_VABI _ZN9Inspector18InjectedScriptBaseC1ERKS0_(); int APS5_VABI _ZN9Inspector18InjectedScriptBaseC2ERKN3WTF6StringE(); @@ -6103,7 +6060,6 @@ int APS5_VABI _ZNK3WTF10StringView16GraphemeClusters8IteratoreqERKS2_(); int APS5_VABI _ZNK3WTF11Persistence7Decoder28bufferIsLargeEnoughToContainEm(); int APS5_VABI _ZNK3WTF12ASCIILiteral4dumpERNS_11PrintStreamE(); int APS5_VABI _ZNK3WTF13MonotonicTime4dumpERNS_11PrintStreamE(); -int APS5_VABI _ZNK3WTF13StringBuilder11reifyStringEv(); int APS5_VABI _ZNK3WTF14MediaTimeRange12toJSONStringEv(); int APS5_VABI _ZNK3WTF15AutomaticThread19hasUnderlyingThreadERKNS_14AbstractLockerE(); int APS5_VABI _ZNK3WTF15AutomaticThread4nameEv(); @@ -6113,10 +6069,8 @@ int APS5_VABI _ZNK3WTF24TimeWithDynamicClockType4dumpERNS_11PrintStreamE(); int APS5_VABI _ZNK3WTF3URL11hostAndPortEv(); int APS5_VABI _ZNK3WTF3URL14fileSystemPathEv(); int APS5_VABI _ZNK3WTF3URL16isMatchingDomainENS_10StringViewE(); -int APS5_VABI _ZNK3WTF3URL17lastPathComponentEv(); int APS5_VABI _ZNK3WTF3URL19protocolHostAndPortEv(); int APS5_VABI _ZNK3WTF3URL21truncatedForUseAsBaseEv(); -int APS5_VABI _ZNK3WTF3URL24strippedForUseAsReferrerEv(); int APS5_VABI _ZNK3WTF3URL4dumpERNS_11PrintStreamE(); int APS5_VABI _ZNK3WTF3URL4userEv(); int APS5_VABI _ZNK3WTF3URL8passwordEv(); diff --git a/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp b/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp index 4dc794e0c8..398ba27dda 100644 --- a/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp +++ b/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp @@ -3,6 +3,7 @@ #include "SceTypes.hpp" #include "prx/libSceNKWebKit/src/PalTypes.hpp" #include "prx/libSceNKWebKit/src/WtfStringAbi.hpp" +#include "prx/libSceNKWebKit/src/WtfTextAbi.hpp" #include "prx/libSceNKWebKit/src/WtfTimeTypes.hpp" #include "prx/libSceNKWebKit/src/WtfMemoryTypes.hpp" #include @@ -64,6 +65,24 @@ NKWebKit::WtfAbi::String APS5_VABI _ZN3WTF10StringImpl23convertToASCIIUppercaseE NKWebKit::WtfAbi::String APS5_VABI _ZN3WTF10StringImpl6createEPKDsj(const char16_t* data, unsigned length); NKWebKit::WtfAbi::String APS5_VABI _ZN3WTF10StringImpl6createEPKhj(const unsigned char* data, unsigned length); NKWebKit::WtfAbi::String APS5_VABI _ZN3WTF10StringImpl9substringEjj(const NKWebKit::WtfAbi::StringImpl* self, unsigned start, unsigned length); +void APS5_VABI _ZN3WTF10TextStream10startGroupEv(NKWebKit::WtfAbi::TextStream* self); +void APS5_VABI _ZN3WTF10TextStream11writeIndentEv(NKWebKit::WtfAbi::TextStream* self); +NKWebKit::WtfAbi::String APS5_VABI _ZN3WTF10TextStream7releaseEv(NKWebKit::WtfAbi::TextStream* self); +void APS5_VABI _ZN3WTF10TextStream8endGroupEv(NKWebKit::WtfAbi::TextStream* self); +void APS5_VABI _ZN3WTF10TextStream8nextLineEv(NKWebKit::WtfAbi::TextStream* self); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTF10TextStreamlsEPKc(NKWebKit::WtfAbi::TextStream* self, const char* string); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTF10TextStreamlsEPKv(NKWebKit::WtfAbi::TextStream* self, const void* pointer); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTF10TextStreamlsERKNS_6StringE(NKWebKit::WtfAbi::TextStream* self, const NKWebKit::WtfAbi::String& string); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTF10TextStreamlsEb(NKWebKit::WtfAbi::TextStream* self, bool value); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTF10TextStreamlsEc(NKWebKit::WtfAbi::TextStream* self, char value); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTF10TextStreamlsEd(NKWebKit::WtfAbi::TextStream* self, double value); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTF10TextStreamlsEf(NKWebKit::WtfAbi::TextStream* self, float value); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTF10TextStreamlsEi(NKWebKit::WtfAbi::TextStream* self, int value); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTF10TextStreamlsEj(NKWebKit::WtfAbi::TextStream* self, unsigned value); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTF10TextStreamlsEl(NKWebKit::WtfAbi::TextStream* self, std::int64_t value); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTF10TextStreamlsEm(NKWebKit::WtfAbi::TextStream* self, std::uint64_t value); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTF10TextStreamlsEx(NKWebKit::WtfAbi::TextStream* self, long long value); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTF10TextStreamlsEy(NKWebKit::WtfAbi::TextStream* self, unsigned long long value); void* APS5_VABI _ZN3WTF10fastCallocEmm(std::size_t count, std::size_t size); void* APS5_VABI _ZN3WTF10fastMallocEm(std::size_t size); char* APS5_VABI _ZN3WTF10fastStrDupEPKc(const char* str); @@ -71,7 +90,14 @@ bool APS5_VABI _ZN3WTF10hasElapsedERKNS_24TimeWithDynamicClockTypeE(const WTF::T bool APS5_VABI _ZN3WTF11CStringHash5equalERKNS_7CStringES3_(const NKWebKit::WtfAbi::CString& a, const NKWebKit::WtfAbi::CString& b); void* APS5_VABI _ZN3WTF11fastReallocEPvm(void* ptr, std::size_t size); void APS5_VABI _ZN3WTF11jsValueFreeEPv(void* ptr); +bool APS5_VABI _ZN3WTF11portAllowedERKNS_3URLE(const NKWebKit::WtfAbi::URL* url); WTF::MonotonicTime APS5_VABI _ZN3WTF13MonotonicTime3nowEv(); +void APS5_VABI _ZN3WTF13StringBuilder11shrinkToFitEv(NKWebKit::WtfAbi::StringBuilder* self); +void APS5_VABI _ZN3WTF13StringBuilder12appendNumberEf(NKWebKit::WtfAbi::StringBuilder* self, float number); +void APS5_VABI _ZN3WTF13StringBuilder15reserveCapacityEj(NKWebKit::WtfAbi::StringBuilder* self, unsigned newCapacity); +void APS5_VABI _ZN3WTF13StringBuilder16appendCharactersEPKDsj(NKWebKit::WtfAbi::StringBuilder* self, const char16_t* characters, unsigned length); +void APS5_VABI _ZN3WTF13StringBuilder16appendCharactersEPKhj(NKWebKit::WtfAbi::StringBuilder* self, const unsigned char* characters, unsigned length); +void APS5_VABI _ZN3WTF13StringBuilder22appendQuotedJSONStringERKNS_6StringE(NKWebKit::WtfAbi::StringBuilder* self, const NKWebKit::WtfAbi::String& string); NKWebKit::WtfAbi::ProcessPrivileges APS5_VABI _ZN3WTF13allPrivilegesEv(); void* APS5_VABI _ZN3WTF13jsValueMallocEm(std::size_t size); WTF::TryMallocReturnValue* APS5_VABI _ZN3WTF13tryFastCallocEmm(WTF::TryMallocReturnValue* result, std::size_t count, std::size_t size); @@ -84,6 +110,7 @@ void* APS5_VABI _ZN3WTF16fastZeroedMallocEm(std::size_t size); void* APS5_VABI _ZN3WTF16tryJSValueMallocEm(std::size_t size); void APS5_VABI _ZN3WTF17GregorianDateTime21setToCurrentLocalTimeEv(WTF::GregorianDateTime* self); void* APS5_VABI _ZN3WTF17fastAlignedMallocEmm(std::size_t alignment, std::size_t size); +void APS5_VABI _ZN3WTF17languageDidChangeEv(); int APS5_VABI _ZN3WTF18constantTimeMemcmpEPKvS1_m(const void* voidA, const void* voidB, std::size_t length); void APS5_VABI _ZN3WTF18fastEnableMiniModeEv(); std::size_t APS5_VABI _ZN3WTF18fastMallocGoodSizeEm(std::size_t size); @@ -106,13 +133,20 @@ void APS5_VABI _ZN3WTF22removeProcessPrivilegeENS_16ProcessPrivilegeE(unsigned p void APS5_VABI _ZN3WTF23fastCommitAlignedMemoryEPvm(void*, std::size_t); WTF::TimeWithDynamicClockType APS5_VABI _ZN3WTF24TimeWithDynamicClockType3nowENS_9ClockTypeE(WTF::ClockType type); WTF::LocalTimeOffset APS5_VABI _ZN3WTF24calculateLocalTimeOffsetEdNS_8TimeTypeE(double, WTF::TimeType); +void APS5_VABI _ZN3WTF25addLanguageChangeObserverEPvPFvS0_E(void* context, void (APS5_VABI *observer)(void*)); void APS5_VABI _ZN3WTF25fastDecommitAlignedMemoryEPvm(void*, std::size_t); void APS5_VABI _ZN3WTF25fastMallocDumpMallocStatsEv(); +bool APS5_VABI _ZN3WTF27protocolHostAndPortAreEqualERKNS_3URLES2_(const NKWebKit::WtfAbi::URL* a, const NKWebKit::WtfAbi::URL* b); void APS5_VABI _ZN3WTF27releaseFastMallocFreeMemoryEv(); +NKWebKit::WtfAbi::String APS5_VABI _ZN3WTF28displayNameForLanguageLocaleERKNS_6StringE(const NKWebKit::WtfAbi::String* localeName); +void APS5_VABI _ZN3WTF28removeLanguageChangeObserverEPv(void* context); void APS5_VABI _ZN3WTF29cryptographicallyRandomValuesEPvm(void* buffer, std::size_t length); bool APS5_VABI _ZN3WTF29equalIgnoringASCIICaseNonNullEPKNS_10StringImplES2_(const NKWebKit::WtfAbi::StringImpl* a, const NKWebKit::WtfAbi::StringImpl* b); +void APS5_VABI _ZN3WTF30overrideUserPreferredLanguagesERKNS_6VectorINS_6StringELm0ENS_15CrashOnOverflowELm16ENS_10FastMallocEEE(const NKWebKit::WtfAbi::StringVector* languages); +NKWebKit::WtfAbi::StringVector APS5_VABI _ZN3WTF30userPreferredLanguagesOverrideEv(); bool APS5_VABI _ZN3WTF31equalIgnoringFragmentIdentifierERKNS_3URLES2_(const NKWebKit::WtfAbi::URL* a, const NKWebKit::WtfAbi::URL* b); unsigned APS5_VABI _ZN3WTF31sixCharacterHashStringToIntegerEPKc(const char* data); +std::size_t APS5_VABI _ZN3WTF33indexOfBestMatchingLanguageInListERKNS_6StringERKNS_6VectorIS0_Lm0ENS_15CrashOnOverflowELm16ENS_10FastMallocEEERb(const NKWebKit::WtfAbi::String* language, const NKWebKit::WtfAbi::StringVector* languageList, bool* exactMatch); void APS5_VABI _ZN3WTF3URL10invalidateEv(NKWebKit::WtfAbi::URL* self); double APS5_VABI _ZN3WTF40parseES5DateFromNullTerminatedCharactersEPKcRb(const char* dateString, bool& isLocalTime); void APS5_VABI _ZN3WTF40releaseFastMallocFreeMemoryForThisThreadEv(); @@ -160,6 +194,8 @@ std::size_t APS5_VABI _ZN3WTF8pageSizeEv(); void APS5_VABI _ZN3WTF9dataLogFVEPKcP13__va_list_tag(const char* format, void* argList); bool APS5_VABI _ZN3WTFeqERKNS_7CStringEPKc(const NKWebKit::WtfAbi::CString& a, const char* b); bool APS5_VABI _ZN3WTFeqERKNS_7CStringES2_(const NKWebKit::WtfAbi::CString& a, const NKWebKit::WtfAbi::CString& b); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTFlsERNS_10TextStreamENS_7SecondsE(NKWebKit::WtfAbi::TextStream& stream, WTF::Seconds seconds); +NKWebKit::WtfAbi::TextStream& APS5_VABI _ZN3WTFlsERNS_10TextStreamERKNS_3URLE(NKWebKit::WtfAbi::TextStream& stream, const NKWebKit::WtfAbi::URL& url); bool APS5_VABI _ZN3WTFltERKNS_7CStringES2_(const NKWebKit::WtfAbi::CString& a, const NKWebKit::WtfAbi::CString& b); bool APS5_VABI _ZN7WebCore21isBackForwardLoadTypeENS_13FrameLoadTypeE(std::uint8_t type); bool APS5_VABI _ZN7WebCore8isReloadENS_13FrameLoadTypeE(std::uint8_t type); @@ -176,6 +212,10 @@ void APS5_VABI _ZN7bmalloc3api23decommitAlignedPhysicalEPvmNS_8HeapKindE(void*, void* APS5_VABI _ZN7bmalloc3api29tryLargeZeroedMemalignVirtualEmmNS_8HeapKindE(std::size_t alignment, std::size_t size, std::uint32_t); void APS5_VABI _ZN7bmalloc3api8scavengeEv(); bool APS5_VABI _ZN7bmalloc3api9isEnabledENS_8HeapKindE(std::uint32_t); +void APS5_VABI _ZN7bmalloc8FreeList14initializeBumpEPcj(bmalloc::FreeList* self, char* payloadEnd, unsigned remaining); +void APS5_VABI _ZN7bmalloc8FreeList14initializeListEPNS_8FreeCellEmj(bmalloc::FreeList* self, bmalloc::FreeCell* head, std::uintptr_t secret, unsigned bytes); +void APS5_VABI _ZN7bmalloc8FreeList5clearEv(bmalloc::FreeList* self); +void APS5_VABI _ZN7bmalloc8FreeListC1Ev(bmalloc::FreeList* self); void* APS5_VABI _ZN8Gigacage10tryReallocENS_4KindEPvm(std::uint32_t, void* ptr, std::size_t size); void APS5_VABI _ZN8Gigacage11alignedFreeENS_4KindEPv(std::uint32_t, void* ptr); void* APS5_VABI _ZN8Gigacage11mallocArrayENS_4KindEmm(std::uint32_t, std::size_t count, std::size_t size); @@ -186,6 +226,8 @@ void* APS5_VABI _ZN8Gigacage29tryAllocateZeroedVirtualPagesENS_4KindEm(std::uint void APS5_VABI _ZN8Gigacage4freeENS_4KindEPv(std::uint32_t, void* ptr); void* APS5_VABI _ZN8Gigacage6mallocENS_4KindEm(std::uint32_t, std::size_t size); void* APS5_VABI _ZN8Gigacage9tryMallocENS_4KindEm(std::uint32_t, std::size_t size); +NKWebKit::WtfAbi::String APS5_VABI _ZN9Inspector18IdentifiersFactory16createIdentifierEv(); +NKWebKit::WtfAbi::String APS5_VABI _ZN9Inspector18IdentifiersFactory9requestIdEm(std::uint64_t identifier); bool APS5_VABI _ZNK3WTF10StringImpl10startsWithEDs(const NKWebKit::WtfAbi::StringImpl* self, char16_t value); bool APS5_VABI _ZNK3WTF10StringImpl10startsWithEPKcj(const NKWebKit::WtfAbi::StringImpl* self, const char* data, unsigned length); std::size_t APS5_VABI _ZNK3WTF10StringImpl11sizeInBytesEv(const NKWebKit::WtfAbi::StringImpl* self); @@ -203,6 +245,7 @@ std::size_t APS5_VABI _ZNK3WTF10StringView4findES0_j(const NKWebKit::WtfAbi::Str NKWebKit::WtfAbi::CString APS5_VABI _ZNK3WTF10StringView4utf8ENS_14ConversionModeE(const NKWebKit::WtfAbi::StringView* self, unsigned mode); bool APS5_VABI _ZNK3WTF10StringView8endsWithEDs(const NKWebKit::WtfAbi::StringView* self, char16_t value); WTF::WallTime APS5_VABI _ZNK3WTF13MonotonicTime19approximateWallTimeEv(const WTF::MonotonicTime* self); +void APS5_VABI _ZNK3WTF13StringBuilder11reifyStringEv(const NKWebKit::WtfAbi::StringBuilder* self); WTF::MonotonicTime APS5_VABI _ZNK3WTF24TimeWithDynamicClockType13monotonicTimeEv(const WTF::TimeWithDynamicClockType* self); WTF::TimeWithDynamicClockType APS5_VABI _ZNK3WTF24TimeWithDynamicClockType16nowWithSameClockEv(const WTF::TimeWithDynamicClockType* self); WTF::WallTime APS5_VABI _ZNK3WTF24TimeWithDynamicClockType19approximateWallTimeEv(const WTF::TimeWithDynamicClockType* self); @@ -214,14 +257,19 @@ bool APS5_VABI _ZNK3WTF24TimeWithDynamicClockTypeleERKS0_(const WTF::TimeWithDyn WTF::Seconds APS5_VABI _ZNK3WTF24TimeWithDynamicClockTypemiERKS0_(const WTF::TimeWithDynamicClockType* self, const WTF::TimeWithDynamicClockType& other); NKWebKit::WtfAbi::String APS5_VABI _ZN3WTF19mimeTypeFromDataURLENS_10StringViewE(NKWebKit::WtfAbi::StringView dataURL); NKWebKit::WtfAbi::String APS5_VABI _ZNK3WTF3URL30stringCenterEllipsizedToLengthEj(const NKWebKit::WtfAbi::URL* self, unsigned length); +NKWebKit::WtfAbi::String APS5_VABI _ZN3WTF14FileSystemImpl17encodeForFileNameERKNS_6StringE(const NKWebKit::WtfAbi::String* input); +NKWebKit::WtfAbi::String APS5_VABI _ZN3WTF14FileSystemImpl18decodeFromFilenameERKNS_6StringE(const NKWebKit::WtfAbi::String* input); +NKWebKit::WtfAbi::String APS5_VABI _ZN3WTF14FileSystemImpl40lastComponentOfPathIgnoringTrailingSlashERKNS_6StringE(const NKWebKit::WtfAbi::String* input); bool APS5_VABI _ZNK3WTF3URL10protocolIsENS_10StringViewE(const NKWebKit::WtfAbi::URL* self, NKWebKit::WtfAbi::StringView protocol); NKWebKit::WtfAbi::StringView APS5_VABI _ZNK3WTF3URL11encodedUserEv(const NKWebKit::WtfAbi::URL* self); bool APS5_VABI _ZNK3WTF3URL12isAboutBlankEv(const NKWebKit::WtfAbi::URL* self); bool APS5_VABI _ZNK3WTF3URL13isAboutSrcDocEv(const NKWebKit::WtfAbi::URL* self); bool APS5_VABI _ZNK3WTF3URL14isHierarchicalEv(const NKWebKit::WtfAbi::URL* self); NKWebKit::WtfAbi::StringView APS5_VABI _ZNK3WTF3URL15encodedPasswordEv(const NKWebKit::WtfAbi::URL* self); +NKWebKit::WtfAbi::StringView APS5_VABI _ZNK3WTF3URL17lastPathComponentEv(const NKWebKit::WtfAbi::URL* self); NKWebKit::WtfAbi::StringView APS5_VABI _ZNK3WTF3URL18fragmentIdentifierEv(const NKWebKit::WtfAbi::URL* self); bool APS5_VABI _ZNK3WTF3URL20protocolIsJavaScriptEv(const NKWebKit::WtfAbi::URL* self); +NKWebKit::WtfAbi::String APS5_VABI _ZNK3WTF3URL24strippedForUseAsReferrerEv(const NKWebKit::WtfAbi::URL* self); NKWebKit::WtfAbi::StringView APS5_VABI _ZNK3WTF3URL28queryWithLeadingQuestionMarkEv(const NKWebKit::WtfAbi::URL* self); NKWebKit::WtfAbi::StringView APS5_VABI _ZNK3WTF3URL39fragmentIdentifierWithLeadingNumberSignEv(const NKWebKit::WtfAbi::URL* self); NKWebKit::WtfAbi::StringView APS5_VABI _ZNK3WTF3URL4hostEv(const NKWebKit::WtfAbi::URL* self); diff --git a/core/libs/prx/libSceNKWebKit/src/BmallocFreeList.cpp b/core/libs/prx/libSceNKWebKit/src/BmallocFreeList.cpp new file mode 100644 index 0000000000..887ce5a304 --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/src/BmallocFreeList.cpp @@ -0,0 +1,91 @@ +// Derived from Sony PS5 WebKit 11.x revision 80f77e94c6, Source/bmalloc/bmalloc/FreeList.cpp +/* + * Copyright (C) 2016-2017 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Derived from Sony PS5 WebKit 11.x revision 80f77e94c6, Source/bmalloc/bmalloc/FreeList.h +/* + * Copyright (C) 2016-2017 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "prx/libSceNKWebKit/include/WtfExports.hpp" + +#include +#include + +namespace { + +bmalloc::FreeList& Checked(bmalloc::FreeList* self) { + if (!self) throw std::invalid_argument("bmalloc::FreeList: null self"); + return *self; +} + +} + +extern "C" void APS5_VABI _ZN7bmalloc8FreeListC1Ev(bmalloc::FreeList* self) { + Checked(self) = bmalloc::FreeList{}; +} + +extern "C" void APS5_VABI _ZN7bmalloc8FreeList5clearEv(bmalloc::FreeList* self) { + Checked(self) = bmalloc::FreeList{}; +} + +extern "C" void APS5_VABI _ZN7bmalloc8FreeList14initializeListEPNS_8FreeCellEmj(bmalloc::FreeList* self, bmalloc::FreeCell* head, std::uintptr_t secret, unsigned bytes) { + auto& list = Checked(self); + list.m_scrambledHead = reinterpret_cast(head) ^ secret; + list.m_secret = secret; + list.m_payloadEnd = nullptr; + list.m_remaining = 0; + list.m_originalSize = bytes; +} + +extern "C" void APS5_VABI _ZN7bmalloc8FreeList14initializeBumpEPcj(bmalloc::FreeList* self, char* payloadEnd, unsigned remaining) { + auto& list = Checked(self); + list.m_scrambledHead = 0; + list.m_secret = 0; + list.m_payloadEnd = payloadEnd; + list.m_remaining = remaining; + list.m_originalSize = remaining; +} diff --git a/core/libs/prx/libSceNKWebKit/src/InspectorIdentifiersFactory.cpp b/core/libs/prx/libSceNKWebKit/src/InspectorIdentifiersFactory.cpp new file mode 100644 index 0000000000..ee275a1843 --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/src/InspectorIdentifiersFactory.cpp @@ -0,0 +1,52 @@ +// Derived from Sony PS5 WebKit 11.x revision 80f77e94c6, Source/JavaScriptCore/inspector/IdentifiersFactory.cpp +/* + * Copyright (C) 2014-2018 Apple Inc. All rights reserved. + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "prx/libSceNKWebKit/include/WtfExports.hpp" + +#include +#include + +namespace { + +std::uint64_t lastUsedIdentifier = 0; + +NKWebKit::WtfAbi::String AddPrefixToIdentifier(std::uint64_t identifier) { + const std::string text = "0." + std::to_string(identifier); + return _ZN3WTF10StringImpl6createEPKhj(reinterpret_cast(text.data()), + static_cast(text.size())); +} + +} + +extern "C" NKWebKit::WtfAbi::String APS5_VABI _ZN9Inspector18IdentifiersFactory16createIdentifierEv() { + return AddPrefixToIdentifier(++lastUsedIdentifier); +} + +extern "C" NKWebKit::WtfAbi::String APS5_VABI _ZN9Inspector18IdentifiersFactory9requestIdEm( + std::uint64_t identifier) { + return identifier ? AddPrefixToIdentifier(identifier) : NKWebKit::WtfAbi::String(); +} diff --git a/core/libs/prx/libSceNKWebKit/src/StringBuilder.cpp b/core/libs/prx/libSceNKWebKit/src/StringBuilder.cpp new file mode 100644 index 0000000000..d75157be03 --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/src/StringBuilder.cpp @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2009-2019 Apple Inc. All rights reserved. + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Derived from Source/WTF/wtf/text/StringBuilder.cpp and Source/WTF/wtf/text/StringBuilder.h (WebKit c55364d53f97) + +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceNKWebKit/include/WtfExports.hpp" + +#include +#include +#include +#include +#include +#include + +#include "WtfTextAbi.hpp" + +namespace { + +using NKWebKit::WtfAbi::String; +using NKWebKit::WtfAbi::StringBuilder; +using NKWebKit::WtfAbi::StringImpl; + +constexpr unsigned MaxCapacity = 0x7FFFFFFF; + +StringBuilder& Checked(StringBuilder* builder) { + if (!builder) throw std::invalid_argument("null WTF::StringBuilder"); + return *builder; +} + +bool Is8Bit(const StringImpl* impl) { + return !impl || (impl->hashAndFlags & 4); +} + +const unsigned char* Characters8(const StringImpl* impl) { + return impl ? static_cast(impl->data) : nullptr; +} + +const char16_t* Characters16(const StringImpl* impl) { + return impl ? static_cast(impl->data) : nullptr; +} + +bool HasOneRef(const StringImpl* impl) { + return (impl->refCount & ~1U) == 2; +} + +unsigned Capacity(const StringBuilder& builder) { + return builder.buffer ? builder.buffer->length : static_cast(builder.length); +} + +unsigned ExpandedCapacity(unsigned capacity, unsigned requiredLength) { + constexpr unsigned minimumCapacity = 16; + return std::max(requiredLength, std::max(minimumCapacity, std::min(capacity * 2, MaxCapacity))); +} + +void SetBuffer(StringBuilder& builder, StringImpl* buffer) { + NKWebKit::WtfAbi::Release(std::exchange(builder.buffer, buffer)); +} + +void AllocateBuffer(StringBuilder& builder, const unsigned char* characters, unsigned requiredLength) { + if (!builder.is8Bit) throw std::logic_error("8-bit WTF::StringBuilder buffer for 16-bit contents"); + void* output; + auto* buffer = NKWebKit::WtfAbi::TryCreateUninitialized(requiredLength, true, output); + builder.characters8 = static_cast(output); + if (!buffer) return NKWebKit::WtfAbi::DidOverflow(builder); + if (builder.length) std::memcpy(builder.characters8, characters, static_cast(builder.length)); + SetBuffer(builder, buffer); + builder.string = String(); +} + +void AllocateBuffer(StringBuilder& builder, const char16_t* characters, unsigned requiredLength) { + if (builder.is8Bit) throw std::logic_error("16-bit WTF::StringBuilder buffer for 8-bit contents"); + void* output; + auto* buffer = NKWebKit::WtfAbi::TryCreateUninitialized(requiredLength, false, output); + builder.characters16 = static_cast(output); + if (!buffer) return NKWebKit::WtfAbi::DidOverflow(builder); + if (builder.length) std::memcpy(builder.characters16, characters, static_cast(builder.length) * sizeof(char16_t)); + SetBuffer(builder, buffer); + builder.string = String(); +} + +template void Reallocate(StringBuilder& builder, unsigned requiredLength) { + constexpr bool is8Bit = sizeof(CharacterType) == 1; + auto* original = std::exchange(builder.buffer, nullptr); + if (!requiredLength) { + NKWebKit::WtfAbi::Release(original); + builder.buffer = NKWebKit::WtfAbi::EmptyStringImpl(); + builder.characters8 = nullptr; + return; + } + void* output; + auto* buffer = NKWebKit::WtfAbi::TryCreateUninitialized(requiredLength, is8Bit, output); + if (!buffer) { + NKWebKit::WtfAbi::Release(original); + return NKWebKit::WtfAbi::DidOverflow(builder); + } + std::memcpy(output, original->data, std::size_t(std::min(original->length, requiredLength)) * sizeof(CharacterType)); + NKWebKit::WtfAbi::Release(original); + builder.buffer = buffer; + if constexpr (is8Bit) builder.characters8 = static_cast(output); + else builder.characters16 = static_cast(output); +} + +template void ReallocateBuffer(StringBuilder& builder, unsigned requiredLength) { + builder.string = String(); + if constexpr (sizeof(CharacterType) == 1) { + if (HasOneRef(builder.buffer)) Reallocate(builder, requiredLength); + else AllocateBuffer(builder, Characters8(builder.buffer), requiredLength); + } else { + if (Is8Bit(builder.buffer)) NKWebKit::WtfAbi::AllocateBufferUpConvert(builder, Characters8(builder.buffer), requiredLength); + else if (HasOneRef(builder.buffer)) Reallocate(builder, requiredLength); + else AllocateBuffer(builder, Characters16(builder.buffer), requiredLength); + } +} + +template CharacterType* BufferCharacters(StringBuilder& builder) { + if constexpr (sizeof(CharacterType) == 1) return builder.characters8; + else return builder.characters16; +} + +template const CharacterType* StringCharacters(const StringBuilder& builder) { + if constexpr (sizeof(CharacterType) == 1) return Characters8(builder.string.impl); + else return Characters16(builder.string.impl); +} + +template CharacterType* ExtendBufferSlowCase(StringBuilder& builder, unsigned requiredLength) { + if (builder.buffer) ReallocateBuffer(builder, ExpandedCapacity(Capacity(builder), requiredLength)); + else AllocateBuffer(builder, builder.length ? StringCharacters(builder) : nullptr, ExpandedCapacity(Capacity(builder), requiredLength)); + if (builder.overflowed) return nullptr; + auto* result = BufferCharacters(builder) + builder.length; + builder.length = static_cast(requiredLength); + return result; +} + +template CharacterType* ExtendBufferWithoutOverflowCheck(StringBuilder& builder, unsigned requiredLength) { + if (builder.buffer && requiredLength <= builder.buffer->length) { + unsigned currentLength = static_cast(builder.length); + builder.string = String(); + builder.length = static_cast(requiredLength); + return BufferCharacters(builder) + currentLength; + } + return ExtendBufferSlowCase(builder, requiredLength); +} + +std::uint64_t RequiredLength(const StringBuilder& builder, unsigned additionalLength) { + std::uint64_t requiredLength = std::uint64_t(static_cast(builder.length)) + additionalLength; + if (requiredLength > std::numeric_limits::max()) throw std::length_error("WTF::StringBuilder length overflow"); + return requiredLength; +} + +template CharacterType* ExtendBufferForAppending(StringBuilder& builder, unsigned additionalLength) { + auto requiredLength = RequiredLength(builder, additionalLength); + if (requiredLength > MaxCapacity) { + NKWebKit::WtfAbi::DidOverflow(builder); + return nullptr; + } + return ExtendBufferWithoutOverflowCheck(builder, static_cast(requiredLength)); +} + +unsigned char* ExtendBufferForAppending8(StringBuilder& builder, std::uint64_t requiredLength) { + if (requiredLength > MaxCapacity) { + NKWebKit::WtfAbi::DidOverflow(builder); + return nullptr; + } + return ExtendBufferWithoutOverflowCheck(builder, static_cast(requiredLength)); +} + +char16_t* ExtendBufferForAppending16(StringBuilder& builder, std::uint64_t requiredLength) { + if (requiredLength > MaxCapacity) { + NKWebKit::WtfAbi::DidOverflow(builder); + return nullptr; + } + if (builder.is8Bit) { + const unsigned char* characters = builder.buffer ? Characters8(builder.buffer) : Characters8(builder.string.impl); + NKWebKit::WtfAbi::AllocateBufferUpConvert(builder, characters, ExpandedCapacity(Capacity(builder), static_cast(requiredLength))); + if (builder.overflowed) return nullptr; + unsigned oldLength = static_cast(builder.length); + builder.length = static_cast(requiredLength); + return builder.characters16 + oldLength; + } + return ExtendBufferWithoutOverflowCheck(builder, static_cast(requiredLength)); +} + +bool CanShrink(const StringBuilder& builder) { + if (builder.overflowed) return false; + unsigned length = static_cast(builder.length); + return builder.buffer && builder.buffer->length > length + (length >> 2); +} + +void ReifyString(const StringBuilder& builder) { + if (builder.string.impl) return; + if (!builder.length) { + builder.string = String(NKWebKit::WtfAbi::EmptyStringImpl()); + return; + } + if (!builder.buffer) throw std::logic_error("WTF::StringBuilder has contents but no buffer"); + if (static_cast(builder.length) == builder.buffer->length) { + builder.buffer->refCount += 2; + builder.string = String(builder.buffer); + } else { + builder.buffer->refCount += 2; + String buffer(builder.buffer); + builder.string = NKWebKit::WtfAbi::SubstringSharingImpl(buffer, 0, static_cast(builder.length)); + } +} + +void ShrinkToFit(StringBuilder& builder) { + if (!CanShrink(builder)) return; + if (builder.is8Bit) ReallocateBuffer(builder, static_cast(builder.length)); + else ReallocateBuffer(builder, static_cast(builder.length)); + builder.string = String(std::exchange(builder.buffer, nullptr)); +} + +void AppendCharacters(StringBuilder& builder, const unsigned char* characters, unsigned length) { + if (!length || builder.overflowed) return; + if (!characters) throw std::invalid_argument("null WTF::StringBuilder characters"); + if (builder.is8Bit) { + auto* destination = ExtendBufferForAppending(builder, length); + if (!destination) return; + std::memcpy(destination, characters, length); + } else { + auto* destination = ExtendBufferForAppending(builder, length); + if (!destination) return; + std::copy(characters, characters + length, destination); + } +} + +void AppendCharacters(StringBuilder& builder, const char16_t* characters, unsigned length) { + if (!length || builder.overflowed) return; + if (!characters) throw std::invalid_argument("null WTF::StringBuilder characters"); + if (builder.is8Bit && length == 1 && characters[0] <= 0xFF) { + NKWebKit::WtfAbi::AppendCharacter(builder, static_cast(characters[0])); + return; + } + auto* destination = ExtendBufferForAppending16(builder, RequiredLength(builder, length)); + if (!destination) return; + std::memcpy(destination, characters, static_cast(length) * sizeof(char16_t)); +} + +} // namespace + +namespace NKWebKit::WtfAbi { + +void DidOverflow(StringBuilder& builder) { + builder.overflowed = true; + if (builder.shouldCrashOnOverflow) throw std::length_error("WTF::StringBuilder overflowed"); +} + +void AllocateBufferUpConvert(StringBuilder& builder, const unsigned char* characters, unsigned requiredLength) { + unsigned length = static_cast(builder.length); + if (length && !characters) throw std::logic_error("WTF::StringBuilder has contents but no 8-bit characters"); + void* output; + auto* buffer = TryCreateUninitialized(requiredLength, false, output); + builder.characters16 = static_cast(output); + if (!buffer) return DidOverflow(builder); + for (unsigned i = 0; i < length; ++i) builder.characters16[i] = characters[i]; + builder.is8Bit = false; + SetBuffer(builder, buffer); + builder.string = String(); +} + +void AppendCharacter(StringBuilder& builder, unsigned char character) { + if (builder.overflowed) return; + unsigned length = static_cast(builder.length); + if (builder.buffer && length < builder.buffer->length && !builder.string.impl) { + if (builder.is8Bit) builder.characters8[length] = character; + else builder.characters16[length] = character; + ++builder.length; + } else { + AppendCharacters(builder, &character, 1); + } +} + +void AppendString(StringBuilder& builder, const StringImpl* string) { + if (builder.overflowed || !string || !string->length) return; + if (!builder.length && !builder.buffer) { + const_cast(string)->refCount += 2; + builder.string = String(const_cast(string)); + builder.length = static_cast(string->length); + builder.is8Bit = Is8Bit(string); + return; + } + if (Is8Bit(string)) AppendCharacters(builder, Characters8(string), string->length); + else AppendCharacters(builder, Characters16(string), string->length); +} + +void AppendCString(StringBuilder& builder, const char* characters) { + if (characters) AppendCharacters(builder, reinterpret_cast(characters), static_cast(std::strlen(characters))); +} + +void AppendAdapted(StringBuilder& builder, const char* characters, unsigned length) { + std::uint64_t requiredLength = builder.overflowed ? std::uint64_t(MaxCapacity) + 1 : std::uint64_t(static_cast(builder.length)) + length; + if (builder.is8Bit) { + auto* destination = ExtendBufferForAppending8(builder, requiredLength); + if (destination) std::memcpy(destination, characters, length); + } else { + auto* destination = ExtendBufferForAppending16(builder, requiredLength); + if (destination) std::copy(characters, characters + length, destination); + } +} + +String ToString(StringBuilder& builder) { + if (builder.string.impl) return builder.string; + if (builder.overflowed) throw std::length_error("WTF::StringBuilder overflowed"); + ShrinkToFit(builder); + ReifyString(builder); + return builder.string; +} + +void ReserveCapacity(StringBuilder& builder, unsigned newCapacity) { + if (builder.overflowed) return; + if (builder.buffer) { + if (newCapacity > builder.buffer->length) { + if (Is8Bit(builder.buffer)) ReallocateBuffer(builder, newCapacity); + else ReallocateBuffer(builder, newCapacity); + } + return; + } + unsigned length = static_cast(builder.length); + if (newCapacity <= length) return; + if (!length) AllocateBuffer(builder, static_cast(nullptr), newCapacity); + else if (Is8Bit(builder.string.impl)) AllocateBuffer(builder, Characters8(builder.string.impl), newCapacity); + else AllocateBuffer(builder, Characters16(builder.string.impl), newCapacity); +} + +void Clear(StringBuilder& builder) { + builder.overflowed = false; + builder.length = 0; + builder.string = String(); + SetBuffer(builder, nullptr); + builder.characters8 = nullptr; + builder.is8Bit = true; +} + +} // namespace NKWebKit::WtfAbi + +extern "C" { + +void APS5_VABI _ZN3WTF13StringBuilder16appendCharactersEPKDsj(StringBuilder* self, const char16_t* characters, unsigned length) { + AppendCharacters(Checked(self), characters, length); +} + +void APS5_VABI _ZN3WTF13StringBuilder16appendCharactersEPKhj(StringBuilder* self, const unsigned char* characters, unsigned length) { + AppendCharacters(Checked(self), characters, length); +} + +void APS5_VABI _ZN3WTF13StringBuilder15reserveCapacityEj(StringBuilder* self, unsigned newCapacity) { + NKWebKit::WtfAbi::ReserveCapacity(Checked(self), newCapacity); +} + +void APS5_VABI _ZN3WTF13StringBuilder11shrinkToFitEv(StringBuilder* self) { + ShrinkToFit(Checked(self)); +} + +void APS5_VABI _ZNK3WTF13StringBuilder11reifyStringEv(const StringBuilder* self) { + ReifyString(Checked(const_cast(self))); +} + +void APS5_VABI _ZN3WTF13StringBuilder12appendNumberEf(StringBuilder* self, float number) { + auto& builder = Checked(self); + auto text = NKWebKit::WtfAbi::NumberToString(number); + NKWebKit::WtfAbi::AppendCString(builder, text.c_str()); +} + +} diff --git a/core/libs/prx/libSceNKWebKit/src/StringBuilderJSON.cpp b/core/libs/prx/libSceNKWebKit/src/StringBuilderJSON.cpp new file mode 100644 index 0000000000..2d59d0bbeb --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/src/StringBuilderJSON.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2010-2018 Apple Inc. All rights reserved. + * Copyright (C) 2012 Google Inc. All rights reserved. + * Copyright (C) 2017 Yusuke Suzuki . All rights reserved. + * Copyright (C) 2017 Mozilla Foundation. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +// Derived from Source/WTF/wtf/text/StringBuilderJSON.cpp (WebKit c55364d53f97) + +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceNKWebKit/include/WtfExports.hpp" + +#include +#include +#include + +#include "WtfTextAbi.hpp" + +namespace { + +using NKWebKit::WtfAbi::String; +using NKWebKit::WtfAbi::StringBuilder; + +constexpr unsigned MaxLength = 0x7FFFFFFF; + +constexpr unsigned char EscapedFormsForJSON[0x100] = { + 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', + 'b', 't', 'n', 'u', 'f', 'r', 'u', 'u', + 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', + 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', + 0, 0, '\"', 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, '\\', 0, 0, 0, +}; + +char LowerNibbleToLowercaseHex(unsigned value) { + return "0123456789abcdef"[value & 15]; +} + +template +void AppendQuotedJSONStringInternal(OutputCharacterType*& output, const InputCharacterType* input, unsigned length) { + for (auto* end = input + length; input != end; ++input) { + unsigned character = *input; + if (character <= 0xFF) { + auto escaped = EscapedFormsForJSON[character]; + if (!escaped) { + *output++ = static_cast(character); + continue; + } + *output++ = '\\'; + *output++ = escaped; + if (escaped == 'u') { + *output++ = '0'; + *output++ = '0'; + *output++ = LowerNibbleToLowercaseHex(character >> 4); + *output++ = LowerNibbleToLowercaseHex(character); + } + continue; + } + if (character < 0xD800 || character > 0xDFFF) { + *output++ = static_cast(character); + continue; + } + auto next = input + 1; + if (character <= 0xDBFF && next != end && *next >= 0xDC00 && *next <= 0xDFFF) { + *output++ = static_cast(character); + *output++ = static_cast(*next); + ++input; + continue; + } + unsigned upper = character >> 8; + unsigned lower = character & 0xFF; + *output++ = '\\'; + *output++ = 'u'; + *output++ = LowerNibbleToLowercaseHex(upper >> 4); + *output++ = LowerNibbleToLowercaseHex(upper); + *output++ = LowerNibbleToLowercaseHex(lower >> 4); + *output++ = LowerNibbleToLowercaseHex(lower); + } +} + +unsigned RoundUpToPowerOfTwo(unsigned value) { + value--; + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + value++; + return value; +} + +} // namespace + +extern "C" { + +void APS5_VABI _ZN3WTF13StringBuilder22appendQuotedJSONStringERKNS_6StringE(StringBuilder* self, const String& string) { + if (!self) throw std::invalid_argument("null WTF::StringBuilder"); + auto& builder = *self; + if (builder.overflowed) return; + const auto* impl = string.impl; + unsigned stringLength = impl ? impl->length : 0; + bool stringIs8Bit = !impl || (impl->hashAndFlags & 4); + std::uint64_t maximumCapacityRequired = std::uint64_t(static_cast(builder.length)) + 2 + std::uint64_t(stringLength) * 6; + if (maximumCapacityRequired > 0xFFFFFFFFULL) return NKWebKit::WtfAbi::DidOverflow(builder); + unsigned allocationSize = static_cast(maximumCapacityRequired); + allocationSize = std::max(allocationSize, RoundUpToPowerOfTwo(allocationSize)); + if (allocationSize > MaxLength) return NKWebKit::WtfAbi::DidOverflow(builder); + if (builder.is8Bit && !stringIs8Bit) NKWebKit::WtfAbi::AllocateBufferUpConvert(builder, builder.characters8, allocationSize); + else NKWebKit::WtfAbi::ReserveCapacity(builder, allocationSize); + if (builder.overflowed) return; + if (builder.is8Bit) { + auto* output = builder.characters8 + static_cast(builder.length); + *output++ = '"'; + AppendQuotedJSONStringInternal(output, impl ? static_cast(impl->data) : nullptr, stringLength); + *output++ = '"'; + builder.length = static_cast(output - builder.characters8); + } else { + auto* output = builder.characters16 + static_cast(builder.length); + *output++ = '"'; + if (stringIs8Bit) AppendQuotedJSONStringInternal(output, impl ? static_cast(impl->data) : nullptr, stringLength); + else AppendQuotedJSONStringInternal(output, static_cast(impl->data), stringLength); + *output++ = '"'; + builder.length = static_cast(output - builder.characters16); + } +} + +} diff --git a/core/libs/prx/libSceNKWebKit/src/TextStream.cpp b/core/libs/prx/libSceNKWebKit/src/TextStream.cpp new file mode 100644 index 0000000000..9fddcd9b3d --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/src/TextStream.cpp @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2004-2020 Apple Inc. All rights reserved. + * Copyright (C) 2012 Research In Motion Limited. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Derived from Source/WTF/wtf/text/TextStream.cpp, Source/WTF/wtf/Seconds.cpp and Source/WTF/wtf/URL.cpp (WebKit c55364d53f97) + +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libc/include/WindowsFormatting.hpp" +#include "prx/libSceNKWebKit/include/WtfExports.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "WtfTextAbi.hpp" + +namespace { + +using NKWebKit::WtfAbi::String; +using NKWebKit::WtfAbi::TextStream; +using NKWebKit::WtfAbi::URL; + +constexpr unsigned NumberRespectingIntegers = 1 << 1; + +TextStream& Checked(TextStream* stream) { + if (!stream) throw std::invalid_argument("null WTF::TextStream"); + return *stream; +} + +int TruncateToInt(double value) { + if (!(value > -2147483649.0 && value < 2147483648.0)) return std::numeric_limits::min(); + return static_cast(value); +} + +bool HasFractions(double value) { + constexpr double epsilon = 0.0001; + return std::fabs(value - static_cast(TruncateToInt(value))) > epsilon; +} + +template TextStream& AppendNumber(TextStream& stream, T value) { + char buffer[32]; + auto result = std::to_chars(buffer, buffer + sizeof(buffer) - 1, value); + if (result.ec != std::errc{}) throw std::runtime_error("WTF integer formatting failed"); + *result.ptr = 0; + NKWebKit::WtfAbi::AppendCString(stream.text, buffer); + return stream; +} + +TextStream& AppendFixedWidth(TextStream& stream, double value) { + auto text = NKWebKit::WtfAbi::NumberToFixedWidthString(value, 2); + NKWebKit::WtfAbi::AppendAdapted(stream.text, text.data(), static_cast(text.size())); + return stream; +} + +TextStream& AppendNumberRespectingIntegers(TextStream& stream, double value) { + if (HasFractions(value)) return AppendFixedWidth(stream, value); + return AppendNumber(stream, TruncateToInt(value)); +} + +TextStream& AppendCString(TextStream& stream, const char* string) { + NKWebKit::WtfAbi::AppendCString(stream.text, string); + return stream; +} + +TextStream& AppendFloatingPoint(TextStream& stream, double value) { + if (stream.formattingFlags & NumberRespectingIntegers) return AppendNumberRespectingIntegers(stream, value); + return AppendFixedWidth(stream, value); +} + +void WriteIndent(TextStream& stream) { + if (!stream.multiLineMode) return; + for (int i = 0; i < stream.indent; ++i) AppendCString(stream, " "); +} + +} // namespace + +extern "C" { + +TextStream& APS5_VABI _ZN3WTF10TextStreamlsEb(TextStream* self, bool value) { + return AppendCString(Checked(self), value ? "1" : "0"); +} + +TextStream& APS5_VABI _ZN3WTF10TextStreamlsEc(TextStream* self, char value) { + auto& stream = Checked(self); + NKWebKit::WtfAbi::AppendCharacter(stream.text, static_cast(value)); + return stream; +} + +TextStream& APS5_VABI _ZN3WTF10TextStreamlsEi(TextStream* self, int value) { + return AppendNumber(Checked(self), value); +} + +TextStream& APS5_VABI _ZN3WTF10TextStreamlsEj(TextStream* self, unsigned value) { + return AppendNumber(Checked(self), value); +} + +TextStream& APS5_VABI _ZN3WTF10TextStreamlsEl(TextStream* self, std::int64_t value) { + return AppendNumber(Checked(self), value); +} + +TextStream& APS5_VABI _ZN3WTF10TextStreamlsEm(TextStream* self, std::uint64_t value) { + return AppendNumber(Checked(self), value); +} + +TextStream& APS5_VABI _ZN3WTF10TextStreamlsEx(TextStream* self, long long value) { + return AppendNumber(Checked(self), value); +} + +TextStream& APS5_VABI _ZN3WTF10TextStreamlsEy(TextStream* self, unsigned long long value) { + return AppendNumber(Checked(self), value); +} + +TextStream& APS5_VABI _ZN3WTF10TextStreamlsEf(TextStream* self, float value) { + return AppendFloatingPoint(Checked(self), value); +} + +TextStream& APS5_VABI _ZN3WTF10TextStreamlsEd(TextStream* self, double value) { + return AppendFloatingPoint(Checked(self), value); +} + +TextStream& APS5_VABI _ZN3WTF10TextStreamlsEPKc(TextStream* self, const char* string) { + return AppendCString(Checked(self), string); +} + +TextStream& APS5_VABI _ZN3WTF10TextStreamlsEPKv(TextStream* self, const void* pointer) { + auto& stream = Checked(self); + auto text = LibcDetail::FormatPointer("%p", pointer); + return AppendCString(stream, text.c_str()); +} + +TextStream& APS5_VABI _ZN3WTF10TextStreamlsERKNS_6StringE(TextStream* self, const String& string) { + auto& stream = Checked(self); + NKWebKit::WtfAbi::AppendString(stream.text, string.impl); + return stream; +} + +String APS5_VABI _ZN3WTF10TextStream7releaseEv(TextStream* self) { + auto& stream = Checked(self); + String result = NKWebKit::WtfAbi::ToString(stream.text); + NKWebKit::WtfAbi::Clear(stream.text); + return result; +} + +void APS5_VABI _ZN3WTF10TextStream10startGroupEv(TextStream* self) { + auto& stream = Checked(self); + if (stream.multiLineMode) { + AppendCString(stream, "\n"); + WriteIndent(stream); + AppendCString(stream, "("); + ++stream.indent; + } else { + AppendCString(stream, " ("); + } +} + +void APS5_VABI _ZN3WTF10TextStream8endGroupEv(TextStream* self) { + auto& stream = Checked(self); + AppendCString(stream, ")"); + if (stream.multiLineMode) --stream.indent; +} + +void APS5_VABI _ZN3WTF10TextStream8nextLineEv(TextStream* self) { + auto& stream = Checked(self); + if (stream.multiLineMode) { + AppendCString(stream, "\n"); + WriteIndent(stream); + } else { + AppendCString(stream, " "); + } +} + +void APS5_VABI _ZN3WTF10TextStream11writeIndentEv(TextStream* self) { + WriteIndent(Checked(self)); +} + +TextStream& APS5_VABI _ZN3WTFlsERNS_10TextStreamENS_7SecondsE(TextStream& stream, WTF::Seconds seconds) { + AppendFloatingPoint(stream, seconds.m_value); + return AppendCString(stream, "s"); +} + +TextStream& APS5_VABI _ZN3WTFlsERNS_10TextStreamERKNS_3URLE(TextStream& stream, const URL& url) { + NKWebKit::WtfAbi::AppendString(stream.text, url.impl); + return stream; +} + +} diff --git a/core/libs/prx/libSceNKWebKit/src/WtfLanguage.cpp b/core/libs/prx/libSceNKWebKit/src/WtfLanguage.cpp new file mode 100644 index 0000000000..023b8fae80 --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/src/WtfLanguage.cpp @@ -0,0 +1,218 @@ +// Derived from Sony PS5 WebKit 11.x revision 80f77e94c6, Source/WTF/wtf/Language.cpp +/* + * Copyright (C) 2010, 2013, 2016, 2017 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "prx/libSceNKWebKit/include/WtfExports.hpp" + +#include +#include +#include +#include +#include +#include + +namespace { + +using NKWebKit::WtfAbi::String; +using NKWebKit::WtfAbi::StringVector; + +using Observer = void (APS5_VABI *)(void*); + +struct ObserverEntry { + void* context; + Observer observer; +}; + +std::mutex languagesLock; + +StringVector& PreferredLanguagesOverride() { + static auto* languages = new StringVector; + return *languages; +} + +std::vector& ObserverMap() { + static auto* map = new std::vector; + return *map; +} + +void CheckContext(void* context) { + if (!context || context == reinterpret_cast(-1)) throw std::invalid_argument("invalid WTF language change observer context"); +} + +bool IsRegistered(void* context) { + for (const auto& entry : ObserverMap()) { + if (entry.context == context) return true; + } + return false; +} + +unsigned Length(const String& string) { + return string.impl ? string.impl->length : 0; +} + +char16_t Character(const String& string, unsigned index) { + return string.impl->hashAndFlags & 4 ? static_cast(static_cast(string.impl->data)[index]) : static_cast(string.impl->data)[index]; +} + +String Lowercase(const String& string) { + return _ZNK3WTF6String23convertToASCIILowercaseEv(&string); +} + +String ReplaceAt(const String& string, unsigned index, char16_t value) { + if (string.impl->hashAndFlags & 4) { + unsigned char* data; + String result = _ZN3WTF10StringImpl19createUninitializedEjRPh(string.impl->length, data); + for (unsigned i = 0; i < string.impl->length; ++i) data[i] = static_cast(Character(string, i)); + data[index] = static_cast(value); + return result; + } + char16_t* data; + String result = _ZN3WTF10StringImpl19createUninitializedEjRPDs(string.impl->length, data); + for (unsigned i = 0; i < string.impl->length; ++i) data[i] = Character(string, i); + data[index] = value; + return result; +} + +String CanonicalLanguageIdentifier(const String& languageCode) { + String lowercase = Lowercase(languageCode); + if (Length(lowercase) >= 3 && Character(lowercase, 2) == '_') return ReplaceAt(lowercase, 2, '-'); + return lowercase; +} + +StringVector Copy(const StringVector& source) { + StringVector result; + if (!source.size) return result; + result.buffer = static_cast(_ZN3WTF10fastMallocEm(std::size_t{source.size} * sizeof(String))); + result.capacity = source.size; + for (; result.size < source.size; ++result.size) new (&result.buffer[result.size]) String(source.buffer[result.size]); + return result; +} + +void Swap(StringVector& a, StringVector& b) { + std::swap(a.buffer, b.buffer); + std::swap(a.capacity, b.capacity); + std::swap(a.size, b.size); +} + +} + +NKWebKit::WtfAbi::StringVector::~StringVector() { + for (unsigned i = 0; i < size; ++i) buffer[i].~String(); + if (buffer) _ZN3WTF8fastFreeEPv(buffer); +} + +extern "C" void APS5_VABI _ZN3WTF17languageDidChangeEv() { + const std::vector observers = ObserverMap(); + for (const auto& observer : observers) { + if (!IsRegistered(observer.context)) continue; + if (!observer.observer) throw std::invalid_argument("null WTF language change observer"); + observer.observer(observer.context); + } +} + +extern "C" void APS5_VABI _ZN3WTF25addLanguageChangeObserverEPvPFvS0_E(void* context, Observer observer) { + CheckContext(context); + auto& map = ObserverMap(); + for (auto& entry : map) { + if (entry.context == context) { + entry.observer = observer; + return; + } + } + map.push_back({context, observer}); +} + +extern "C" NKWebKit::WtfAbi::String APS5_VABI _ZN3WTF28displayNameForLanguageLocaleERKNS_6StringE(const String* localeName) { + if (!localeName) throw std::invalid_argument("null WTF string"); + return *localeName; +} + +extern "C" void APS5_VABI _ZN3WTF28removeLanguageChangeObserverEPv(void* context) { + CheckContext(context); + auto& map = ObserverMap(); + for (auto entry = map.begin(); entry != map.end(); ++entry) { + if (entry->context == context) { + map.erase(entry); + return; + } + } +} + +extern "C" void APS5_VABI _ZN3WTF30overrideUserPreferredLanguagesERKNS_6VectorINS_6StringELm0ENS_15CrashOnOverflowELm16ENS_10FastMallocEEE(const StringVector* languages) { + if (!languages) throw std::invalid_argument("null WTF string vector"); + { + std::lock_guard locker(languagesLock); + StringVector copy = Copy(*languages); + Swap(copy, PreferredLanguagesOverride()); + } + _ZN3WTF17languageDidChangeEv(); +} + +extern "C" NKWebKit::WtfAbi::StringVector APS5_VABI _ZN3WTF30userPreferredLanguagesOverrideEv() { + std::lock_guard locker(languagesLock); + return Copy(PreferredLanguagesOverride()); +} + +extern "C" std::size_t APS5_VABI _ZN3WTF33indexOfBestMatchingLanguageInListERKNS_6StringERKNS_6VectorIS0_Lm0ENS_15CrashOnOverflowELm16ENS_10FastMallocEEERb(const String* language, const StringVector* languageList, bool* exactMatch) { + if (!language || !languageList || !exactMatch) throw std::invalid_argument("null WTF language match argument"); + String lowercaseLanguage = Lowercase(*language); + const String* languageWithoutLocaleMatch = nullptr; + const String* languageMatchButNotLocale = nullptr; + std::size_t languageWithoutLocaleMatchIndex = 0; + std::size_t languageMatchButNotLocaleMatchIndex = 0; + const unsigned languageLength = Length(lowercaseLanguage); + const bool canMatchLanguageOnly = languageLength == 2 || (languageLength >= 3 && Character(lowercaseLanguage, 2) == '-'); + + for (std::size_t i = 0; i < languageList->size; ++i) { + String canonicalizedLanguageFromList = CanonicalLanguageIdentifier(languageList->buffer[i]); + + if (_ZN3WTF5equalEPKNS_10StringImplES2_(lowercaseLanguage.impl, canonicalizedLanguageFromList.impl)) { + *exactMatch = true; + return i; + } + + const unsigned canonicalizedLength = Length(canonicalizedLanguageFromList); + if (canMatchLanguageOnly && canonicalizedLength >= 2) { + if (Character(lowercaseLanguage, 0) == Character(canonicalizedLanguageFromList, 0) && Character(lowercaseLanguage, 1) == Character(canonicalizedLanguageFromList, 1)) { + if (!languageWithoutLocaleMatch && canonicalizedLength == 2) { + languageWithoutLocaleMatch = &languageList->buffer[i]; + languageWithoutLocaleMatchIndex = i; + } + if (!languageMatchButNotLocale && canonicalizedLength >= 3) { + languageMatchButNotLocale = &languageList->buffer[i]; + languageMatchButNotLocaleMatchIndex = i; + } + } + } + } + + *exactMatch = false; + + if (languageWithoutLocaleMatch) return languageWithoutLocaleMatchIndex; + + if (languageMatchButNotLocale) return languageMatchButNotLocaleMatchIndex; + + return languageList->size; +} diff --git a/core/libs/prx/libSceNKWebKit/src/WtfMemoryTypes.hpp b/core/libs/prx/libSceNKWebKit/src/WtfMemoryTypes.hpp index 208695f345..963dc5296b 100644 --- a/core/libs/prx/libSceNKWebKit/src/WtfMemoryTypes.hpp +++ b/core/libs/prx/libSceNKWebKit/src/WtfMemoryTypes.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include struct FastMallocStatistics { std::size_t reservedVMBytes; @@ -14,4 +15,26 @@ struct TryMallocReturnValue { }; } // namespace WTF +namespace bmalloc { + +struct FreeCell { + std::uintptr_t scrambledNext; +}; + +struct FreeList { + std::uintptr_t m_scrambledHead; + std::uintptr_t m_secret; + char* m_payloadEnd; + unsigned m_remaining; + unsigned m_originalSize; +}; + +} // namespace bmalloc + static_assert(sizeof(WTF::TryMallocReturnValue) == sizeof(void*)); +static_assert(sizeof(bmalloc::FreeList) == 32); +static_assert(offsetof(bmalloc::FreeList, m_scrambledHead) == 0); +static_assert(offsetof(bmalloc::FreeList, m_secret) == 8); +static_assert(offsetof(bmalloc::FreeList, m_payloadEnd) == 16); +static_assert(offsetof(bmalloc::FreeList, m_remaining) == 24); +static_assert(offsetof(bmalloc::FreeList, m_originalSize) == 28); diff --git a/core/libs/prx/libSceNKWebKit/src/WtfNumberFormat.cpp b/core/libs/prx/libSceNKWebKit/src/WtfNumberFormat.cpp new file mode 100644 index 0000000000..74ace980e4 --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/src/WtfNumberFormat.cpp @@ -0,0 +1,146 @@ +// Copyright 2010 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Derived from Source/WTF/wtf/dtoa/double-conversion.cc (WebKit c55364d53f97) + +#include +#include +#include +#include +#include + +#include "WtfTextAbi.hpp" + +namespace { + +constexpr int DecimalInShortestLow = -6; +constexpr int DecimalInShortestHigh = 21; +constexpr int MaxFixedDigitsAfterPoint = 100; +constexpr double FirstNonFixed = 1e21; + +std::string SpecialValue(double value) { + if (std::isnan(value)) return "NaN"; + return value < 0 ? "-Infinity" : "Infinity"; +} + +void CreateDecimalRepresentation(std::string& result, const std::string& digits, int decimalPoint, int digitsAfterPoint) { + int length = static_cast(digits.size()); + if (decimalPoint <= 0) { + result += '0'; + if (digitsAfterPoint > 0) { + result += '.'; + result.append(-decimalPoint, '0'); + result += digits; + result.append(digitsAfterPoint - -decimalPoint - length, '0'); + } + } else if (decimalPoint >= length) { + result += digits; + result.append(decimalPoint - length, '0'); + if (digitsAfterPoint > 0) { + result += '.'; + result.append(digitsAfterPoint, '0'); + } + } else { + result.append(digits, 0, decimalPoint); + result += '.'; + result.append(digits, decimalPoint); + result.append(digitsAfterPoint - (length - decimalPoint), '0'); + } +} + +void CreateExponentialRepresentation(std::string& result, const std::string& digits, int exponent) { + result += digits[0]; + if (digits.size() != 1) { + result += '.'; + result.append(digits, 1); + } + result += 'e'; + if (exponent < 0) { + result += '-'; + exponent = -exponent; + } else { + result += '+'; + } + result += std::to_string(exponent); +} + +} // namespace + +namespace NKWebKit::WtfAbi { + +std::string NumberToString(float number) { + if (!std::isfinite(number)) return SpecialValue(number); + if (number == 0) return "0"; + char buffer[64]; + auto converted = std::to_chars(buffer, buffer + sizeof(buffer), std::fabs(number), std::chars_format::scientific); + if (converted.ec != std::errc{}) throw std::runtime_error("WTF float formatting failed"); + std::string digits; + const char* cursor = buffer; + for (; cursor != converted.ptr && *cursor != 'e'; ++cursor) { + if (*cursor != '.') digits += *cursor; + } + int exponent = 0; + if (cursor == converted.ptr || std::from_chars(cursor + (cursor[1] == '+' ? 2 : 1), converted.ptr, exponent).ec != std::errc{}) + throw std::runtime_error("WTF float exponent formatting failed"); + std::string result = number < 0 ? "-" : ""; + int decimalPoint = exponent + 1; + int length = static_cast(digits.size()); + if (DecimalInShortestLow <= exponent && exponent < DecimalInShortestHigh) + CreateDecimalRepresentation(result, digits, decimalPoint, length > decimalPoint ? length - decimalPoint : 0); + else + CreateExponentialRepresentation(result, digits, exponent); + return result; +} + +std::string NumberToFixedWidthString(double number, unsigned decimalPlaces) { + if (!std::isfinite(number)) return SpecialValue(number); + if (decimalPlaces > MaxFixedDigitsAfterPoint) return std::string(); + if (number >= FirstNonFixed || number <= -FirstNonFixed) return std::string(); + char buffer[1200]; + auto converted = std::to_chars(buffer, buffer + sizeof(buffer), std::fabs(number), std::chars_format::fixed, 1100); + if (converted.ec != std::errc{}) throw std::runtime_error("WTF fixed formatting failed"); + std::string exact(buffer, converted.ptr); + auto point = exact.find('.'); + std::string digits = exact.substr(0, point) + exact.substr(point + 1, decimalPlaces); + if (exact[point + 1 + decimalPlaces] >= '5') { + std::size_t index = digits.size(); + while (index && digits[index - 1] == '9') digits[--index] = '0'; + if (index) ++digits[index - 1]; + else digits.insert(digits.begin(), '1'); + } + std::size_t leadingZeros = 0; + while (leadingZeros < digits.size() && digits[leadingZeros] == '0') ++leadingZeros; + int decimalPoint = static_cast(digits.size() - decimalPlaces) - static_cast(leadingZeros); + digits.erase(0, leadingZeros); + while (!digits.empty() && digits.back() == '0') digits.pop_back(); + if (digits.empty()) decimalPoint = -static_cast(decimalPlaces); + std::string result = std::signbit(number) && number != 0.0 ? "-" : ""; + CreateDecimalRepresentation(result, digits, decimalPoint, static_cast(decimalPlaces)); + return result; +} + +} // namespace NKWebKit::WtfAbi diff --git a/core/libs/prx/libSceNKWebKit/src/WtfString.cpp b/core/libs/prx/libSceNKWebKit/src/WtfString.cpp index 0ec203f32c..f6ed222e4b 100644 --- a/core/libs/prx/libSceNKWebKit/src/WtfString.cpp +++ b/core/libs/prx/libSceNKWebKit/src/WtfString.cpp @@ -1,3 +1,29 @@ +/* + * Copyright (C) 2004-2020 Apple Inc. All rights reserved. + * Copyright (C) 2012 Research In Motion Limited. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libSceNKWebKit/include/WtfExports.hpp" @@ -10,6 +36,7 @@ #include #include #include +#include #include #include @@ -165,6 +192,7 @@ double APS5_VABI _ZN3WTF8Internal25parseDoubleFromLongStringEPKDsmRm( namespace NKWebKit { void* Allocate(std::size_t size, std::size_t alignment = 16); +void* TryAllocate(std::size_t size, std::size_t alignment = 16); void Deallocate(void* ptr); } // namespace NKWebKit @@ -242,17 +270,26 @@ StringImpl* Retain(StringImpl* impl) { return impl; } +bool ExceedsMaxLength(unsigned length, bool is8Bit) { + return length > std::min(MaxStringLength, (0xFFFFFFFFULL - sizeof(StringImpl)) / (is8Bit ? 1 : 2)); +} + +std::size_t ImplAllocationSize(unsigned length, bool is8Bit) { + return std::max(sizeof(StringImpl), 20 + (is8Bit ? 1 : 2) * std::size_t(length)); +} + +StringImpl* ConstructImpl(void* storage, unsigned length, bool is8Bit, void*& characters) { + characters = static_cast(storage) + 20; + return new (storage) StringImpl{2, length, characters, is8Bit ? 4U : 0U}; +} + StringImpl* NewImpl(unsigned length, bool is8Bit, void*& characters) { if (!length) { characters = nullptr; return EmptyImpl(); } - const std::size_t width = is8Bit ? 1 : 2; - if (length > std::min(MaxStringLength, (0xFFFFFFFFULL - sizeof(StringImpl)) / width)) - throw std::length_error("WTF string exceeds maximum length"); - auto* storage = static_cast(NKWebKit::Allocate(std::max(sizeof(StringImpl), 20 + width * length))); - characters = storage + 20; - return new (storage) StringImpl{2, length, characters, is8Bit ? 4U : 0U}; + if (ExceedsMaxLength(length, is8Bit)) throw std::length_error("WTF string exceeds maximum length"); + return ConstructImpl(NKWebKit::Allocate(ImplAllocationSize(length, is8Bit)), length, is8Bit, characters); } StringImpl* CopyImpl(StringView view, bool narrow = false) { @@ -453,6 +490,13 @@ String SharedSubstring(const String* string, unsigned start, unsigned length) { return String(result); } +std::size_t ReverseFind(StringView view, char16_t value, unsigned start) { + if (!view.length) return NotFound; + std::size_t i = std::min(start, view.length - 1); + do { if (Unit(view, static_cast(i)) == value) return i; } while (i--); + return NotFound; +} + template String IntegerString(T value) { char buffer[32]; auto result = std::to_chars(buffer, buffer + sizeof(buffer), value); @@ -462,6 +506,31 @@ template String IntegerString(T value) { } // namespace +namespace NKWebKit::WtfAbi { + +StringImpl* EmptyStringImpl() { + return EmptyImpl(); +} + +StringImpl* TryCreateUninitialized(unsigned length, bool is8Bit, void*& characters) { + if (!length) { + characters = nullptr; + return EmptyImpl(); + } + void* storage = ExceedsMaxLength(length, is8Bit) ? nullptr : NKWebKit::TryAllocate(ImplAllocationSize(length, is8Bit)); + if (!storage) { + characters = nullptr; + return nullptr; + } + return ConstructImpl(storage, length, is8Bit, characters); +} + +String SubstringSharingImpl(const String& string, unsigned start, unsigned length) { + return SharedSubstring(&string, start, length); +} + +} // namespace NKWebKit::WtfAbi + extern "C" { String APS5_VABI _ZN3WTF10StringImpl6createEPKDsj(const char16_t* data, unsigned length) { return String(CopyImpl({data, length, false})); @@ -671,11 +740,7 @@ std::int32_t APS5_VABI _ZNK3WTF6String19characterStartingAtEj(const String* self } std::size_t APS5_VABI _ZN3WTF10StringImpl11reverseFindEDsj(const StringImpl* self, char16_t value, unsigned start) { - auto view = View(&Checked(self)); - if (!view.length) return NotFound; - std::size_t i = std::min(start, view.length - 1); - do { if (Unit(view, static_cast(i)) == value) return i; } while (i--); - return NotFound; + return ReverseFind(View(&Checked(self)), value, start); } String APS5_VABI _ZN3WTF10StringImpl9substringEjj(const StringImpl* self, unsigned start, unsigned length) { @@ -877,6 +942,12 @@ unsigned HostStart(const URL& url) { return url.passwordEnd == url.userStart ? url.passwordEnd : url.passwordEnd + 1; } +unsigned CredentialsEnd(const URL& url, StringView view) { + unsigned end = url.passwordEnd; + if (end != url.hostEnd && end < view.length && Unit(view, end) == '@') ++end; + return end; +} + unsigned PathStart(const URL& url) { unsigned start = url.hostEnd + ((url.bits >> 3) & 7); auto view = View(url.impl); @@ -951,15 +1022,121 @@ String ConcatViews(const StringView* parts, unsigned count) { return String(impl); } +bool IsLeadSurrogate(char16_t value) { return (value & 0xFC00) == 0xD800; } + +bool IsTrailSurrogate(char16_t value) { return (value & 0xFC00) == 0xDC00; } + +bool NeedsFileNameEscape(char16_t value, char16_t previous, char16_t next) { + if (value <= 127) { + if (value < 0x20 || value == 0x7F) return true; + switch (value) { + case ' ': case '"': case '%': case '*': case '/': case ':': case '<': case '>': case '?': case '\\': case '|': + return true; + default: + return false; + } + } + if (IsLeadSurrogate(value) && !IsTrailSurrogate(next)) return true; + return IsTrailSurrogate(value) && !IsLeadSurrogate(previous); +} + +void AppendHex(std::u16string& output, unsigned value, unsigned digits) { + static constexpr char16_t Digits[] = u"0123456789ABCDEF"; + for (unsigned shift = digits * 4; shift;) { + shift -= 4; + output.push_back(Digits[(value >> shift) & 15]); + } +} + +bool IsHexUnit(char16_t value) { + return (value >= '0' && value <= '9') || ((value | 32) >= 'a' && (value | 32) <= 'f'); +} + +unsigned HexUnitValue(char16_t value) { return value < 'A' ? value - '0' : (value - 'A' + 10) & 15; } + +String FinishFileName(const std::u16string& output) { + if (output.size() > MaxStringLength) throw std::length_error("WTF string exceeds maximum length"); + return String(CopyImpl({output.data(), static_cast(output.size()), false}, true)); +} + +bool IsUnicodeCompatibleASCIIWhitespace(char32_t c) { + return c <= ' ' && (c == ' ' || c == '\n' || c == '\t' || c == '\r' || c == '\f'); +} + +std::optional URLPort(const URL& url) { + unsigned portLength = (url.bits >> 3) & 7; + if (portLength <= 1) return std::nullopt; + auto view = View(url.impl); + if (url.hostEnd > view.length || portLength > view.length - url.hostEnd) return std::nullopt; + auto portView = Slice(view, url.hostEnd + 1, portLength - 1); + if (!portView.length) return std::nullopt; + unsigned i = 0; + while (i < portView.length && IsUnicodeCompatibleASCIIWhitespace(Unit(portView, i))) ++i; + if (i < portView.length && Unit(portView, i) == '+') ++i; + if (i == portView.length) return std::nullopt; + auto firstChar = Unit(portView, i); + if (firstChar < '0' || firstChar > '9') return std::nullopt; + std::uint32_t val = 0; + while (i < portView.length) { + auto ch = Unit(portView, i); + if (ch < '0' || ch > '9') break; + val = val * 10 + (ch - '0'); + if (val > 65535) return std::nullopt; + ++i; + } + while (i < portView.length && IsUnicodeCompatibleASCIIWhitespace(Unit(portView, i))) ++i; + if (i != portView.length) return std::nullopt; + return static_cast(val); +} + } // namespace extern "C" { +bool APS5_VABI _ZN3WTF11portAllowedERKNS_3URLE(const URL* urlPtr) { + const auto& url = Checked(urlPtr); + auto port = URLPort(url); + if (!port) return true; + static constexpr std::uint16_t blockedPortList[] = { + 1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 69, + 77, 79, 87, 95, 101, 102, 103, 104, 109, 110, 111, 113, 115, 117, 119, 123, + 135, 137, 139, 143, 161, 179, 389, 427, 465, 512, 513, 514, 515, 526, 530, + 531, 532, 540, 548, 554, 556, 563, 587, 601, 636, 989, 990, 993, 995, 1719, + 1720, 1723, 2049, 3659, 4045, 4190, 5060, 5061, 6000, 6566, 6665, 6666, + 6667, 6668, 6669, 6679, 6697, 10080 + }; + if (!std::binary_search(std::begin(blockedPortList), std::end(blockedPortList), *port)) return true; + if ((*port == 21 || *port == 22) && URLProtocol(url, {"ftp", 3, true})) return true; + if (URLProtocol(url, {"file", 4, true})) return true; + return false; +} + bool APS5_VABI _ZN3WTF31equalIgnoringFragmentIdentifierERKNS_3URLES2_(const URL* a, const URL* b) { const auto& first = Checked(a); const auto& second = Checked(b); return EqualViews(ViewWithoutFragment(first), ViewWithoutFragment(second)); } +bool APS5_VABI _ZN3WTF27protocolHostAndPortAreEqualERKNS_3URLES2_(const URL* a, const URL* b) { + const auto& first = Checked(a); + const auto& second = Checked(b); + unsigned schemeEndA = first.bits >> 6; + unsigned schemeEndB = second.bits >> 6; + if (schemeEndA != schemeEndB) return false; + unsigned hostStartA = HostStart(first); + unsigned hostStartB = HostStart(second); + if (first.hostEnd < hostStartA || second.hostEnd < hostStartB) return false; + unsigned hostLengthA = first.hostEnd - hostStartA; + unsigned hostLengthB = second.hostEnd - hostStartB; + if (hostLengthA != hostLengthB) return false; + auto viewA = View(first.impl); + auto viewB = View(second.impl); + if (schemeEndA > viewA.length || schemeEndB > viewB.length) return false; + if (hostStartA + hostLengthA > viewA.length || hostStartB + hostLengthB > viewB.length) return false; + if (!EqualViews(Slice(viewA, 0, schemeEndA), Slice(viewB, 0, schemeEndB), true)) return false; + if (!EqualViews(Slice(viewA, hostStartA, hostLengthA), Slice(viewB, hostStartB, hostLengthB), true)) return false; + return URLPort(first) == URLPort(second); +} + void APS5_VABI _ZN3WTF3URL10invalidateEv(URL* self) { auto& url = Checked(self); url.bits = 0; @@ -990,6 +1167,46 @@ StringView APS5_VABI _ZNK3WTF3URL4pathEv(const URL* self) { return Slice(View(url.impl), start, url.pathEnd - start); } +StringView APS5_VABI _ZNK3WTF3URL17lastPathComponentEv(const URL* self) { + const auto& url = Checked(self); + if (url.pathEnd <= PathStart(url)) return {nullptr, 0, true}; + auto view = View(url.impl); + if (url.pathEnd > view.length) throw std::out_of_range("invalid WTF URL path boundary"); + unsigned end = url.pathEnd - 1; + if (Unit(view, end) == '/') --end; + std::size_t start = NotFound; + for (std::size_t i = std::min(end, view.length - 1) + 1; i--;) { + if (Unit(view, static_cast(i)) == '/') { + start = i; + break; + } + } + if (start < PathStart(url)) return {nullptr, 0, true}; + ++start; + return Slice(view, static_cast(start), end - static_cast(start) + 1); +} + +String APS5_VABI _ZNK3WTF3URL24strippedForUseAsReferrerEv(const URL* self) { + const auto& url = Checked(self); + auto view = View(url.impl); + if (!(url.bits & 1)) return String(Retain(url.impl)); + if (url.userStart > url.passwordEnd || url.queryEnd > view.length) throw std::out_of_range("invalid WTF URL credential boundary"); + unsigned end = CredentialsEnd(url, view); + if (url.userStart == end && url.queryEnd == view.length) return String(Retain(url.impl)); + if (end > url.queryEnd) throw std::out_of_range("invalid WTF URL query boundary"); + unsigned length = url.userStart + (url.queryEnd - end); + void* output; + auto* impl = NewImpl(length, view.is8Bit, output); + String result(impl); + if (!length) return result; + unsigned width = view.is8Bit ? 1 : 2; + auto* source = static_cast(view.data); + auto* target = static_cast(output); + std::memcpy(target, source, std::size_t(url.userStart) * width); + std::memcpy(target + std::size_t(url.userStart) * width, source + std::size_t(end) * width, std::size_t(url.queryEnd - end) * width); + return result; +} + StringView APS5_VABI _ZNK3WTF3URL11encodedUserEv(const URL* self) { const auto& url = Checked(self); return Slice(View(url.impl), url.userStart, url.userEnd - url.userStart); @@ -1073,4 +1290,74 @@ String APS5_VABI _ZNK3WTF3URL30stringCenterEllipsizedToLengthEj(const URL* self, return ConcatViews(parts, 3); } +String APS5_VABI _ZN3WTF14FileSystemImpl17encodeForFileNameERKNS_6StringE(const String* input) { + const auto& string = Checked(input); + auto view = View(string.impl); + if (!view.length) return String(Retain(string.impl)); + std::u16string output; + output.reserve(view.length); + for (unsigned i = 0; i < view.length; ++i) { + auto value = Unit(view, i); + auto previous = i ? Unit(view, i - 1) : char16_t{0}; + auto next = i + 1 < view.length ? Unit(view, i + 1) : char16_t{0}; + if (!NeedsFileNameEscape(value, previous, next)) { + output.push_back(value); + continue; + } + if (value <= 255) { + output.push_back(u'%'); + AppendHex(output, value, 2); + } else { + output.push_back(u'%'); + output.push_back(u'+'); + AppendHex(output, value, 4); + } + if (output.size() > MaxStringLength) throw std::length_error("WTF string exceeds maximum length"); + } + return FinishFileName(output); +} + +String APS5_VABI _ZN3WTF14FileSystemImpl18decodeFromFilenameERKNS_6StringE(const String* input) { + const auto& string = Checked(input); + auto view = View(string.impl); + if (!view.length) return String(Retain(string.impl)); + std::u16string output; + output.reserve(view.length); + for (unsigned i = 0; i < view.length; ++i) { + if (Unit(view, i) != '%') { + output.push_back(Unit(view, i)); + continue; + } + if (std::size_t(i) + 2 >= view.length) return String(); + if (Unit(view, i + 1) != '+') { + if (!IsHexUnit(Unit(view, i + 1)) || !IsHexUnit(Unit(view, i + 2))) return String(); + output.push_back(static_cast(HexUnitValue(Unit(view, i + 1)) << 4 | HexUnitValue(Unit(view, i + 2)))); + i += 2; + continue; + } + if (std::size_t(i) + 5 >= view.length) return String(); + unsigned value = 0; + for (unsigned j = 2; j < 6; ++j) { + if (!IsHexUnit(Unit(view, i + j))) return String(); + value = value << 4 | HexUnitValue(Unit(view, i + j)); + } + output.push_back(static_cast(value)); + i += 5; + } + return FinishFileName(output); +} + +String APS5_VABI _ZN3WTF14FileSystemImpl40lastComponentOfPathIgnoringTrailingSlashERKNS_6StringE(const String* input) { + const auto& string = Checked(input); + auto view = View(string.impl); + std::size_t position = ReverseFind(view, u'/', 0xFFFFFFFFU); + if (position == NotFound) return String(Retain(string.impl)); + std::size_t end = view.length - 1; + if (position == end) { + --end; + position = ReverseFind(view, u'/', static_cast(end)); + } + return Substring(string.impl, static_cast(position + 1), static_cast(end - position)); +} + } diff --git a/core/libs/prx/libSceNKWebKit/src/WtfStringAbi.hpp b/core/libs/prx/libSceNKWebKit/src/WtfStringAbi.hpp index 1f4bdc17e6..1a0ac574dc 100644 --- a/core/libs/prx/libSceNKWebKit/src/WtfStringAbi.hpp +++ b/core/libs/prx/libSceNKWebKit/src/WtfStringAbi.hpp @@ -57,6 +57,23 @@ struct String { String(const String& other) : impl(other.impl) { if (impl) impl->refCount += 2; } String(String&& other) noexcept : impl(std::exchange(other.impl, nullptr)) {} ~String() { Release(impl); } + String& operator=(String other) noexcept { std::swap(impl, other.impl); return *this; } +}; + +StringImpl* EmptyStringImpl(); +StringImpl* TryCreateUninitialized(unsigned length, bool is8Bit, void*& characters); +String SubstringSharingImpl(const String& string, unsigned start, unsigned length); + +struct StringVector { + String* buffer; + unsigned capacity; + unsigned size; + StringVector() : buffer(nullptr), capacity(0), size(0) {} + StringVector(StringVector&& other) noexcept + : buffer(std::exchange(other.buffer, nullptr)), capacity(std::exchange(other.capacity, 0)), size(std::exchange(other.size, 0)) {} + StringVector(const StringVector&) = delete; + StringVector& operator=(const StringVector&) = delete; + ~StringVector(); }; struct CString { @@ -83,6 +100,11 @@ static_assert(offsetof(StringImpl, data) == 8); static_assert(offsetof(StringImpl, hashAndFlags) == 16); static_assert(sizeof(String) == 8); static_assert(offsetof(String, impl) == 0); +static_assert(sizeof(StringVector) == 16); +static_assert(offsetof(StringVector, buffer) == 0); +static_assert(offsetof(StringVector, capacity) == 8); +static_assert(offsetof(StringVector, size) == 12); +static_assert(!std::is_trivially_destructible_v); static_assert(sizeof(StringView) == 16); static_assert(offsetof(StringView, data) == 0); static_assert(offsetof(StringView, length) == 8); diff --git a/core/libs/prx/libSceNKWebKit/src/WtfTextAbi.hpp b/core/libs/prx/libSceNKWebKit/src/WtfTextAbi.hpp new file mode 100644 index 0000000000..60d1d94ebc --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/src/WtfTextAbi.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include "WtfStringAbi.hpp" + +#include +#include +#include + +namespace NKWebKit::WtfAbi { + +struct StringBuilder { + mutable String string; + StringImpl* buffer; + union { + unsigned char* characters8; + char16_t* characters16; + }; + bool overflowed; + bool shouldCrashOnOverflow; + std::int32_t length; + bool is8Bit; +}; + +struct TextStream { + StringBuilder text; + unsigned formattingFlags; + int indent; + bool multiLineMode; +}; + +void AppendString(StringBuilder& builder, const StringImpl* string); +void AppendCharacter(StringBuilder& builder, unsigned char character); +void AppendCString(StringBuilder& builder, const char* characters); +void ReserveCapacity(StringBuilder& builder, unsigned newCapacity); +void AppendAdapted(StringBuilder& builder, const char* characters, unsigned length); +String ToString(StringBuilder& builder); +void Clear(StringBuilder& builder); +void DidOverflow(StringBuilder& builder); +void AllocateBufferUpConvert(StringBuilder& builder, const unsigned char* characters, unsigned requiredLength); +std::string NumberToString(float number); +std::string NumberToFixedWidthString(double number, unsigned decimalPlaces); + +static_assert(sizeof(StringBuilder) == 40); +static_assert(alignof(StringBuilder) == 8); +static_assert(offsetof(StringBuilder, string) == 0); +static_assert(offsetof(StringBuilder, buffer) == 8); +static_assert(offsetof(StringBuilder, characters8) == 16); +static_assert(offsetof(StringBuilder, characters16) == 16); +static_assert(offsetof(StringBuilder, overflowed) == 24); +static_assert(offsetof(StringBuilder, shouldCrashOnOverflow) == 25); +static_assert(offsetof(StringBuilder, length) == 28); +static_assert(offsetof(StringBuilder, is8Bit) == 32); +static_assert(sizeof(TextStream) == 56); +static_assert(offsetof(TextStream, text) == 0); +static_assert(offsetof(TextStream, formattingFlags) == 40); +static_assert(offsetof(TextStream, indent) == 44); +static_assert(offsetof(TextStream, multiLineMode) == 48); + +} // namespace NKWebKit::WtfAbi diff --git a/core/libs/prx/libSceNKWebKit/tests/BmallocFreeList.cpp b/core/libs/prx/libSceNKWebKit/tests/BmallocFreeList.cpp new file mode 100644 index 0000000000..ae80d34c70 --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/tests/BmallocFreeList.cpp @@ -0,0 +1,143 @@ +#include "prx/libSceNKWebKit/libSceNKWebKit.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (!condition) { + std::fprintf(stderr, "bmalloc::FreeList check failed at line %u\n", location.line()); + std::abort(); + } +} + +template +void RequireThrows(Function function) { + bool threw = false; + try { + function(); + } catch (const std::exception&) { + threw = true; + } + Require(threw); +} + +using Constructor = void (APS5_VABI *)(bmalloc::FreeList*); +using InitializeBump = void (APS5_VABI *)(bmalloc::FreeList*, char*, unsigned); +using InitializeList = void (APS5_VABI *)(bmalloc::FreeList*, bmalloc::FreeCell*, std::uintptr_t, unsigned); +using Clear = void (APS5_VABI *)(bmalloc::FreeList*); +static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(std::is_same_v); + +void Poison(bmalloc::FreeList& list) { + std::memset(&list, 0xAB, sizeof(bmalloc::FreeList)); +} + +void RequireZero(const bmalloc::FreeList& list) { + Require(list.m_scrambledHead == 0); + Require(list.m_secret == 0); + Require(list.m_payloadEnd == nullptr); + Require(list.m_remaining == 0); + Require(list.m_originalSize == 0); +} + +void TestConstructor() { + bmalloc::FreeList list; + Poison(list); + _ZN7bmalloc8FreeListC1Ev(&list); + RequireZero(list); + std::printf("bmalloc::FreeList constructor: all fields zero\n"); +} + +void TestInitializeList() { + bmalloc::FreeCell cell{}; + bmalloc::FreeList list; + constexpr std::uintptr_t secret = 0xFEDCBA9876543210; + const auto address = reinterpret_cast(&cell); + + Poison(list); + _ZN7bmalloc8FreeList14initializeListEPNS_8FreeCellEmj(&list, &cell, secret, 4096); + Require(list.m_scrambledHead == (address ^ secret)); + Require((list.m_scrambledHead ^ list.m_secret) == address); + Require(list.m_secret == secret); + Require(list.m_payloadEnd == nullptr); + Require(list.m_remaining == 0); + Require(list.m_originalSize == 4096); + + Poison(list); + _ZN7bmalloc8FreeList14initializeListEPNS_8FreeCellEmj(&list, nullptr, secret, 8); + Require(list.m_scrambledHead == secret); + + Poison(list); + _ZN7bmalloc8FreeList14initializeListEPNS_8FreeCellEmj(&list, &cell, 0, 8); + Require(list.m_scrambledHead == address); + + Poison(list); + _ZN7bmalloc8FreeList14initializeListEPNS_8FreeCellEmj(&list, &cell, secret, 0xFFFFFFFFu); + Require(list.m_originalSize == 0xFFFFFFFFu); + + std::printf("bmalloc::FreeList initializeList: scrambling and fields passed\n"); +} + +void TestInitializeBump() { + bmalloc::FreeList list; + char end = 0; + + Poison(list); + _ZN7bmalloc8FreeList14initializeBumpEPcj(&list, &end, 100); + Require(list.m_scrambledHead == 0); + Require(list.m_secret == 0); + Require(list.m_payloadEnd == &end); + Require(list.m_remaining == 100); + Require(list.m_originalSize == 100); + + Poison(list); + _ZN7bmalloc8FreeList14initializeBumpEPcj(&list, nullptr, 0); + RequireZero(list); + + std::printf("bmalloc::FreeList initializeBump: fields and empty bump passed\n"); +} + +void TestClear() { + bmalloc::FreeCell cell{}; + bmalloc::FreeList list; + char end = 0; + + Poison(list); + _ZN7bmalloc8FreeList14initializeListEPNS_8FreeCellEmj(&list, &cell, 0xFEDCBA9876543210, 16); + _ZN7bmalloc8FreeList5clearEv(&list); + RequireZero(list); + + Poison(list); + _ZN7bmalloc8FreeList14initializeBumpEPcj(&list, &end, 32); + _ZN7bmalloc8FreeList5clearEv(&list); + RequireZero(list); + + std::printf("bmalloc::FreeList clear: both initialized states reset\n"); +} + +void TestNullSelf() { + RequireThrows([] { _ZN7bmalloc8FreeListC1Ev(nullptr); }); + RequireThrows([] { _ZN7bmalloc8FreeList5clearEv(nullptr); }); + RequireThrows([] { _ZN7bmalloc8FreeList14initializeListEPNS_8FreeCellEmj(nullptr, nullptr, 0, 0); }); + RequireThrows([] { _ZN7bmalloc8FreeList14initializeBumpEPcj(nullptr, nullptr, 0); }); + std::printf("bmalloc::FreeList: null self throws for all four exports\n"); +} + +} + +int main() { + TestConstructor(); + TestInitializeList(); + TestInitializeBump(); + TestClear(); + TestNullSelf(); +} diff --git a/core/libs/prx/libSceNKWebKit/tests/InspectorIdentifiersFactory.cpp b/core/libs/prx/libSceNKWebKit/tests/InspectorIdentifiersFactory.cpp new file mode 100644 index 0000000000..26a7faae26 --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/tests/InspectorIdentifiersFactory.cpp @@ -0,0 +1,59 @@ +#include "prx/libSceNKWebKit/libSceNKWebKit.hpp" + +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (!condition) { + std::fprintf(stderr, "Inspector::IdentifiersFactory check failed at line %u\n", location.line()); + std::abort(); + } +} + +void RequireString(const NKWebKit::WtfAbi::String& value, const char* expected) { + const auto length = static_cast(std::strlen(expected)); + Require(value.impl != nullptr); + Require(value.impl->length == length); + Require((value.impl->hashAndFlags & 4) != 0); + Require(std::memcmp(value.impl->data, expected, length) == 0); +} + +using CreateIdentifier = NKWebKit::WtfAbi::String (APS5_VABI *)(); +using RequestId = NKWebKit::WtfAbi::String (APS5_VABI *)(std::uint64_t); +static_assert(std::is_same_v); +static_assert(std::is_same_v); + +void TestCreateIdentifier() { + RequireString(_ZN9Inspector18IdentifiersFactory16createIdentifierEv(), "0.1"); + RequireString(_ZN9Inspector18IdentifiersFactory16createIdentifierEv(), "0.2"); + std::printf("Inspector::IdentifiersFactory::createIdentifier: first identifiers passed\n"); +} + +void TestRequestId() { + const auto zero = _ZN9Inspector18IdentifiersFactory9requestIdEm(0); + Require(zero.impl == nullptr); + RequireString(_ZN9Inspector18IdentifiersFactory9requestIdEm(42), "0.42"); + RequireString(_ZN9Inspector18IdentifiersFactory9requestIdEm(1), "0.1"); + RequireString(_ZN9Inspector18IdentifiersFactory9requestIdEm(18446744073709551615ULL), + "0.18446744073709551615"); + std::printf("Inspector::IdentifiersFactory::requestId: zero, decimal and largest value passed\n"); +} + +void TestRequestIdDoesNotAdvance() { + RequireString(_ZN9Inspector18IdentifiersFactory16createIdentifierEv(), "0.3"); + std::printf("Inspector::IdentifiersFactory: requestId leaves the counter alone\n"); +} + +} + +int main() { + TestCreateIdentifier(); + TestRequestId(); + TestRequestIdDoesNotAdvance(); +} diff --git a/core/libs/prx/libSceNKWebKit/tests/Wtf.cpp b/core/libs/prx/libSceNKWebKit/tests/Wtf.cpp index 8508298479..d28828914b 100644 --- a/core/libs/prx/libSceNKWebKit/tests/Wtf.cpp +++ b/core/libs/prx/libSceNKWebKit/tests/Wtf.cpp @@ -779,9 +779,9 @@ void Run() { } // namespace Wtf2Tests -namespace Wtf2Tests { void TestURLsAndReallocation(); } +namespace Wtf2Tests { void TestURLsAndReallocation(); void TestURLLastPathComponentAndReferrer(); void TestFileNameHelpers(); } -void TestAdditionalWtf() { Wtf2Tests::Run(); Wtf2Tests::TestURLsAndReallocation(); } +void TestAdditionalWtf() { Wtf2Tests::Run(); Wtf2Tests::TestURLsAndReallocation(); Wtf2Tests::TestURLLastPathComponentAndReferrer(); Wtf2Tests::TestFileNameHelpers(); } namespace Wtf2Tests { @@ -803,9 +803,13 @@ const auto URLSrcdoc = &_ZNK3WTF3URL13isAboutSrcDocEv; const auto URLJavaScript = &_ZNK3WTF3URL20protocolIsJavaScriptEv; const auto URLInvalidate = &_ZN3WTF3URL10invalidateEv; const auto URLEqualIgnoringFragment = &_ZN3WTF31equalIgnoringFragmentIdentifierERKNS_3URLES2_; +const auto URLPortAllowed = &_ZN3WTF11portAllowedERKNS_3URLE; +const auto URLProtocolHostAndPortAreEqual = &_ZN3WTF27protocolHostAndPortAreEqualERKNS_3URLES2_; const auto IsJavaScript = &_ZN3WTF20protocolIsJavaScriptENS_10StringViewE; const auto IsHTTP = &_ZN3WTF22protocolIsInHTTPFamilyENS_10StringViewE; const auto JSRealloc = &_ZN3WTF14jsValueReallocEPvm; +const auto URLLastPathComponent = &_ZNK3WTF3URL17lastPathComponentEv; +const auto URLStrippedForReferrer = &_ZNK3WTF3URL24strippedForUseAsReferrerEv; std::string ViewText(StringView view) { return Bytes(ViewUTF8(&view, 1)); } @@ -883,6 +887,190 @@ void TestURLStringHelpers() { Text(DataURLMime(ViewOf(widePlain)), "text/plain"); } +const auto EncodeName = &_ZN3WTF14FileSystemImpl17encodeForFileNameERKNS_6StringE; +const auto DecodeName = &_ZN3WTF14FileSystemImpl18decodeFromFilenameERKNS_6StringE; +const auto LastComponent = &_ZN3WTF14FileSystemImpl40lastComponentOfPathIgnoringTrailingSlashERKNS_6StringE; + +bool NameIs8Bit(const String& value) { return value.impl && (value.impl->hashAndFlags & 4); } + +std::u16string NameUnits(const String& value) { + Check(value.impl != nullptr); + std::u16string units; + for (unsigned i = 0; i < value.impl->length; ++i) { + units.push_back(NameIs8Bit(value) ? static_cast(value.impl->data)[i] : static_cast(value.impl->data)[i]); + } + return units; +} + +String Wide(const std::u16string& units) { return Create16(units.data(), static_cast(units.size())); } + +String NameString(const char* text) { return Create8(U8(text), static_cast(std::strlen(text))); } + +void TestFileNameHelpers() { + for (unsigned c = 0; c < 128; ++c) { + char16_t unit = static_cast(c); + auto encoded = EncodeName(&(const String&)Wide(std::u16string(1, unit))); + bool escaped = c < 0x20 || c == 0x7F || std::strchr(" \"%*/:<>?\\|", static_cast(c)) != nullptr; + if (escaped) { + static const char Hex[] = "0123456789ABCDEF"; + std::u16string expected{u'%', static_cast(Hex[c >> 4]), static_cast(Hex[c & 15])}; + Check(NameUnits(encoded) == expected); + } else { + Check(NameUnits(encoded) == std::u16string(1, unit)); + } + Check(NameIs8Bit(encoded)); + } + + Text(EncodeName(&(const String&)NameString("a b/c%")), "a%20b%2Fc%25"); + Text(EncodeName(&(const String&)NameString("#+.~ -_")), "#+.~%20-_"); + Text(EncodeName(&(const String&)NameString("a\\b|c:d*e?fh\"i")), "a%5Cb%7Cc%3Ad%2Ae%3Ff%3Cg%3Eh%22i"); + + auto accented = Wide(u"café"); + Check(!NameIs8Bit(accented)); + auto accentedEncoded = EncodeName(&accented); + Check(NameUnits(accentedEncoded) == u"café" && NameIs8Bit(accentedEncoded)); + Check(accentedEncoded.impl != accented.impl); + + const unsigned char latin1Source[] = {'c', 'a', 'f', 0xE9, '/'}; + auto latin1Bytes = Create8(latin1Source, 5); + auto latin1Encoded = EncodeName(&latin1Bytes); + Check(NameUnits(latin1Encoded) == u"café%2F" && NameIs8Bit(latin1Encoded)); + auto latin1Decoded = DecodeName(&latin1Encoded); + Check(NameUnits(latin1Decoded) == u"café/" && NameIs8Bit(latin1Decoded)); + + auto wide = Wide(u"a中 b"); + auto wideEncoded = EncodeName(&wide); + Check(NameUnits(wideEncoded) == u"a中%20b" && !NameIs8Bit(wideEncoded)); + + auto paired = Wide(u"\xD83D\xDE00"); + auto pairedEncoded = EncodeName(&paired); + Check(NameUnits(pairedEncoded) == u"\xD83D\xDE00" && !NameIs8Bit(pairedEncoded)); + + auto loneLeadEnd = Wide(u"a\xD83D"); + Check(NameUnits(EncodeName(&loneLeadEnd)) == u"a%+D83D"); + auto loneLeadMiddle = Wide(u"\xD83D" u"a"); + Check(NameUnits(EncodeName(&loneLeadMiddle)) == u"%+D83Da"); + auto loneTrailStart = Wide(u"\xDE00" u"a"); + Check(NameUnits(EncodeName(&loneTrailStart)) == u"%+DE00a"); + auto loneTrailMiddle = Wide(u"a\xDE00"); + Check(NameUnits(EncodeName(&loneTrailMiddle)) == u"a%+DE00"); + auto doubleLead = Wide(u"\xD83D\xD83D\xDE00"); + Check(NameUnits(EncodeName(&doubleLead)) == u"%+D83D\xD83D\xDE00"); + auto doubleTrail = Wide(u"\xD83D\xDE00\xDE00"); + Check(NameUnits(EncodeName(&doubleTrail)) == u"\xD83D\xDE00%+DE00"); + auto reversed = Wide(u"\xDE00\xD83D"); + Check(NameUnits(EncodeName(&reversed)) == u"%+DE00%+D83D"); + + auto latinWide = Wide(u"a/é"); + Check(!NameIs8Bit(latinWide) && !NameIs8Bit(wide) && !NameIs8Bit(paired)); + auto latinWideEncoded = EncodeName(&latinWide); + Check(NameUnits(latinWideEncoded) == u"a%2Fé" && NameIs8Bit(latinWideEncoded)); + auto highByte = Wide(u"\x00ff\x0100"); + Check(NameUnits(EncodeName(&highByte)) == u"\x00ff\x0100"); + + String null; + Check(EncodeName(&null).impl == nullptr); + auto empty = NameString(""); + auto emptyEncoded = EncodeName(&empty); + Check(emptyEncoded.impl == empty.impl && empty.impl->length == 0); + Throws([&] { EncodeName(nullptr); }); + + StringImpl privateEmpty{4, 0, nullptr, 0}; + String privateHolder(&privateEmpty); + Check(EncodeName(&privateHolder).impl == &privateEmpty); + Check(DecodeName(&privateHolder).impl == &privateEmpty); + Check(LastComponent(&privateHolder).impl == &privateEmpty); + Check(privateEmpty.refCount == 4); + + auto plain = NameString("name.txt"); + auto plainEncoded = EncodeName(&plain); + Text(plainEncoded, "name.txt"); + Check(plainEncoded.impl != plain.impl); + + auto roundTrip = Wide(u"a b/\x01\x7Fé中\xD83D\xDE00\xD83D%\xDE00"); + auto roundTripEncoded = EncodeName(&roundTrip); + Check(NameUnits(DecodeName(&roundTripEncoded)) == NameUnits(roundTrip)); + + Text(DecodeName(&(const String&)NameString("a%20b%2fc%2Fd")), "a b/c/d"); + Text(DecodeName(&(const String&)NameString("%41")), "A"); + Text(DecodeName(&(const String&)NameString("%+0041")), "A"); + Text(DecodeName(&(const String&)NameString("plain+text")), "plain+text"); + Text(DecodeName(&(const String&)NameString("%41%42")), "AB"); + auto e9 = DecodeName(&(const String&)NameString("%+00e9")); + Check(NameUnits(e9) == u"é" && NameIs8Bit(e9)); + auto latin1 = DecodeName(&(const String&)NameString("%E9")); + Check(NameUnits(latin1) == u"é" && NameIs8Bit(latin1)); + auto cjk = DecodeName(&(const String&)NameString("x%+4E2Dy")); + Check(NameUnits(cjk) == u"x中y" && !NameIs8Bit(cjk)); + auto lone = DecodeName(&(const String&)NameString("%+d83D")); + Check(NameUnits(lone) == u"\xD83D"); + auto nul = DecodeName(&(const String&)NameString("a%00b")); + Check(NameUnits(nul) == std::u16string(u"a\0b", 3)); + auto widePlain = Wide(u"中%41"); + Check(NameUnits(DecodeName(&widePlain)) == u"中A"); + + for (const char* invalid : {"%", "%4", "a%4", "%G1", "%1G", "%+", "%+4", "%+41", "%+004", "%+00G1", "%+G041", "%+0G41", "%+00", "x%", "x%4"}) { + auto result = DecodeName(&(const String&)NameString(invalid)); + Check(result.impl == nullptr); + } + auto truncatedInSharedParent = [&](const char* parentText, unsigned length) { + auto parent = NameString(parentText); + auto part = SharedSubstring(&parent, 0, length); + Check((part.impl->hashAndFlags & 3) == 2 && part.impl->length == length); + Check(DecodeName(&part).impl == nullptr); + }; + truncatedInSharedParent("aaaaaaaaaaaaaaaa%41", 18); + truncatedInSharedParent("aaaaaaaaaaaaaaaa%+0041", 17); + truncatedInSharedParent("aaaaaaaaaaaaaaaa%+0041", 19); + truncatedInSharedParent("aaaaaaaaaaaaaaaa%+4141", 20); + + auto invalidWide = Wide(u"中%4"); + Check(DecodeName(&invalidWide).impl == nullptr); + auto nonHexWide = Wide(u"%١٢"); + Check(DecodeName(&nonHexWide).impl == nullptr); + + Check(DecodeName(&null).impl == nullptr); + auto emptyDecoded = DecodeName(&empty); + Check(emptyDecoded.impl == empty.impl); + Throws([&] { DecodeName(nullptr); }); + auto unchanged = NameString("abc"); + Check(DecodeName(&unchanged).impl != unchanged.impl); + + auto sameImpl = [&](const char* path) { + auto input = NameString(path); + auto result = LastComponent(&input); + Check(result.impl == input.impl); + }; + sameImpl("abc"); + sameImpl(""); + Check(LastComponent(&null).impl == nullptr); + + Text(LastComponent(&(const String&)NameString("/abc")), "abc"); + Text(LastComponent(&(const String&)NameString("a/b/c")), "c"); + Text(LastComponent(&(const String&)NameString("a/b/")), "b"); + Text(LastComponent(&(const String&)NameString("abc/")), "abc"); + Text(LastComponent(&(const String&)NameString("a/")), "a"); + Text(LastComponent(&(const String&)NameString("x/abc/")), "abc"); + Text(LastComponent(&(const String&)NameString("/a/")), "a"); + Text(LastComponent(&(const String&)NameString("/")), ""); + Text(LastComponent(&(const String&)NameString("//")), ""); + Text(LastComponent(&(const String&)NameString("a//")), ""); + Text(LastComponent(&(const String&)NameString("///")), ""); + Text(LastComponent(&(const String&)NameString("a/b//")), ""); + Text(LastComponent(&(const String&)NameString("dir/file.txt")), "file.txt"); + sameImpl("a\\b\\c"); + sameImpl("a\\b\\c\\"); + + auto widePath = Wide(u"中/文/"); + auto wideLast = LastComponent(&widePath); + Check(NameUnits(wideLast) == u"文" && !NameIs8Bit(wideLast)); + auto wideNoSlash = Wide(u"中文"); + Check(LastComponent(&wideNoSlash).impl == wideNoSlash.impl); + auto wideTail = Wide(u"a/文"); + Check(NameUnits(LastComponent(&wideTail)) == u"文"); + Throws([&] { LastComponent(nullptr); }); +} + void TestURLsAndReallocation() { struct PinnedBits { unsigned valid : 1; @@ -1029,6 +1217,297 @@ void TestURLsAndReallocation() { Check(!URLEqualIgnoringFragment(&absent, &javascript)); Throws([&] { URLEqualIgnoringFragment(nullptr, &url); }); Throws([&] { URLEqualIgnoringFragment(&url, nullptr); }); + auto urlNoPortStr = FromUTF8Z(U8("http://example.test/path")); + URL urlNoPort{urlNoPortStr.impl, 1U | 2U | (0U << 3) | (4U << 6), 7, 7, 7, 19, 20, 24, 24}; + Check(URLPortAllowed(&urlNoPort)); + auto urlPort80Str = FromUTF8Z(U8("http://example.test:80/path")); + URL urlPort80{urlPort80Str.impl, 1U | 2U | (3U << 3) | (4U << 6), 7, 7, 7, 19, 23, 27, 27}; + Check(URLPortAllowed(&urlPort80)); + auto urlPort443Str = FromUTF8Z(U8("http://example.test:443/path")); + URL urlPort443{urlPort443Str.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(URLPortAllowed(&urlPort443)); + auto urlPort8080Str = FromUTF8Z(U8("http://example.test:8080/path")); + URL urlPort8080{urlPort8080Str.impl, 1U | 2U | (5U << 3) | (4U << 6), 7, 7, 7, 19, 25, 29, 29}; + Check(URLPortAllowed(&urlPort8080)); + auto urlPort0Str = FromUTF8Z(U8("http://example.test:0/path")); + URL urlPort0{urlPort0Str.impl, 1U | 2U | (2U << 3) | (4U << 6), 7, 7, 7, 19, 22, 26, 26}; + Check(URLPortAllowed(&urlPort0)); + auto urlPort10081Str = FromUTF8Z(U8("http://example.test:10081/path")); + URL urlPort10081{urlPort10081Str.impl, 1U | 2U | (6U << 3) | (4U << 6), 7, 7, 7, 19, 26, 30, 30}; + Check(URLPortAllowed(&urlPort10081)); + auto urlPort1Str = FromUTF8Z(U8("http://example.test:1/path")); + URL urlPort1{urlPort1Str.impl, 1U | 2U | (2U << 3) | (4U << 6), 7, 7, 7, 19, 22, 26, 26}; + Check(!URLPortAllowed(&urlPort1)); + auto urlPort7Str = FromUTF8Z(U8("http://example.test:7/path")); + URL urlPort7{urlPort7Str.impl, 1U | 2U | (2U << 3) | (4U << 6), 7, 7, 7, 19, 22, 26, 26}; + Check(!URLPortAllowed(&urlPort7)); + auto urlPort21Str = FromUTF8Z(U8("http://example.test:21/path")); + URL urlPort21{urlPort21Str.impl, 1U | 2U | (3U << 3) | (4U << 6), 7, 7, 7, 19, 23, 27, 27}; + Check(!URLPortAllowed(&urlPort21)); + auto urlPort22Str = FromUTF8Z(U8("http://example.test:22/path")); + URL urlPort22{urlPort22Str.impl, 1U | 2U | (3U << 3) | (4U << 6), 7, 7, 7, 19, 23, 27, 27}; + Check(!URLPortAllowed(&urlPort22)); + auto urlPort25Str = FromUTF8Z(U8("http://example.test:25/path")); + URL urlPort25{urlPort25Str.impl, 1U | 2U | (3U << 3) | (4U << 6), 7, 7, 7, 19, 23, 27, 27}; + Check(!URLPortAllowed(&urlPort25)); + auto urlPort87Str = FromUTF8Z(U8("http://example.test:87/path")); + URL urlPort87{urlPort87Str.impl, 1U | 2U | (3U << 3) | (4U << 6), 7, 7, 7, 19, 23, 27, 27}; + Check(!URLPortAllowed(&urlPort87)); + auto urlPort10080Str = FromUTF8Z(U8("http://example.test:10080/path")); + URL urlPort10080{urlPort10080Str.impl, 1U | 2U | (6U << 3) | (4U << 6), 7, 7, 7, 19, 26, 30, 30}; + Check(!URLPortAllowed(&urlPort10080)); + auto ftpPort20Str = FromUTF8Z(U8("ftp://example.test:20/path")); + URL ftpPort20{ftpPort20Str.impl, 1U | 0U | (3U << 3) | (3U << 6), 6, 6, 6, 18, 22, 26, 26}; + Check(!URLPortAllowed(&ftpPort20)); + auto ftpPort21Str = FromUTF8Z(U8("ftp://example.test:21/path")); + URL ftpPort21{ftpPort21Str.impl, 1U | 0U | (3U << 3) | (3U << 6), 6, 6, 6, 18, 22, 26, 26}; + Check(URLPortAllowed(&ftpPort21)); + auto ftpPort22Str = FromUTF8Z(U8("ftp://example.test:22/path")); + URL ftpPort22{ftpPort22Str.impl, 1U | 0U | (3U << 3) | (3U << 6), 6, 6, 6, 18, 22, 26, 26}; + Check(URLPortAllowed(&ftpPort22)); + auto ftpPort25Str = FromUTF8Z(U8("ftp://example.test:25/path")); + URL ftpPort25{ftpPort25Str.impl, 1U | 0U | (3U << 3) | (3U << 6), 6, 6, 6, 18, 22, 26, 26}; + Check(!URLPortAllowed(&ftpPort25)); + auto ftpUpper21Str = FromUTF8Z(U8("FTP://example.test:21/path")); + URL ftpUpper21{ftpUpper21Str.impl, 1U | 0U | (3U << 3) | (3U << 6), 6, 6, 6, 18, 22, 26, 26}; + Check(URLPortAllowed(&ftpUpper21)); + auto ftpUpper22Str = FromUTF8Z(U8("FTP://example.test:22/path")); + URL ftpUpper22{ftpUpper22Str.impl, 1U | 0U | (3U << 3) | (3U << 6), 6, 6, 6, 18, 22, 26, 26}; + Check(URLPortAllowed(&ftpUpper22)); + auto ftpUpper25Str = FromUTF8Z(U8("FTP://example.test:25/path")); + URL ftpUpper25{ftpUpper25Str.impl, 1U | 0U | (3U << 3) | (3U << 6), 6, 6, 6, 18, 22, 26, 26}; + Check(!URLPortAllowed(&ftpUpper25)); + auto filePort1Str = FromUTF8Z(U8("file://example.test:1/path")); + URL filePort1{filePort1Str.impl, 1U | 0U | (2U << 3) | (4U << 6), 7, 7, 7, 19, 22, 26, 26}; + Check(URLPortAllowed(&filePort1)); + auto filePort21Str = FromUTF8Z(U8("file://example.test:21/path")); + URL filePort21{filePort21Str.impl, 1U | 0U | (3U << 3) | (4U << 6), 7, 7, 7, 19, 23, 27, 27}; + Check(URLPortAllowed(&filePort21)); + auto filePort25Str = FromUTF8Z(U8("file://example.test:25/path")); + URL filePort25{filePort25Str.impl, 1U | 0U | (3U << 3) | (4U << 6), 7, 7, 7, 19, 23, 27, 27}; + Check(URLPortAllowed(&filePort25)); + auto filePort10080Str = FromUTF8Z(U8("file://example.test:10080/path")); + URL filePort10080{filePort10080Str.impl, 1U | 0U | (6U << 3) | (4U << 6), 7, 7, 7, 19, 26, 30, 30}; + Check(URLPortAllowed(&filePort10080)); + auto fileUpper25Str = FromUTF8Z(U8("FILE://example.test:25/path")); + URL fileUpper25{fileUpper25Str.impl, 1U | 0U | (3U << 3) | (4U << 6), 7, 7, 7, 19, 23, 27, 27}; + Check(URLPortAllowed(&fileUpper25)); + auto urlMinusPortStr = FromUTF8Z(U8("http://example.test:-80/path")); + URL urlMinusPort{urlMinusPortStr.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(URLPortAllowed(&urlMinusPort)); + auto urlOverflowPortStr = FromUTF8Z(U8("http://example.test:65536/path")); + URL urlOverflowPort{urlOverflowPortStr.impl, 1U | 2U | (6U << 3) | (4U << 6), 7, 7, 7, 19, 26, 30, 30}; + Check(URLPortAllowed(&urlOverflowPort)); + auto urlBarePlusPortStr = FromUTF8Z(U8("http://example.test:+/path")); + URL urlBarePlusPort{urlBarePlusPortStr.impl, 1U | 2U | (2U << 3) | (4U << 6), 7, 7, 7, 19, 22, 26, 26}; + Check(URLPortAllowed(&urlBarePlusPort)); + auto urlPlus25PortStr = FromUTF8Z(U8("http://example.test:+25/path")); + URL urlPlus25Port{urlPlus25PortStr.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(!URLPortAllowed(&urlPlus25Port)); + auto urlSpace25PortStr = FromUTF8Z(U8("http://example.test: 25 /path")); + URL urlSpace25Port{urlSpace25PortStr.impl, 1U | 2U | (5U << 3) | (4U << 6), 7, 7, 7, 19, 25, 29, 29}; + Check(!URLPortAllowed(&urlSpace25Port)); + auto urlTab25PortStr = FromUTF8Z(U8("http://example.test:\t25/path")); + URL urlTab25Port{urlTab25PortStr.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(!URLPortAllowed(&urlTab25Port)); + auto urlLf25PortStr = FromUTF8Z(U8("http://example.test:\n25/path")); + URL urlLf25Port{urlLf25PortStr.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(!URLPortAllowed(&urlLf25Port)); + auto urlCr25PortStr = FromUTF8Z(U8("http://example.test:\r25/path")); + URL urlCr25Port{urlCr25PortStr.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(!URLPortAllowed(&urlCr25Port)); + auto urlFf25PortStr = FromUTF8Z(U8("http://example.test:\f25/path")); + URL urlFf25Port{urlFf25PortStr.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(!URLPortAllowed(&urlFf25Port)); + auto urlTrailingTab25PortStr = FromUTF8Z(U8("http://example.test:25\t/path")); + URL urlTrailingTab25Port{urlTrailingTab25PortStr.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(!URLPortAllowed(&urlTrailingTab25Port)); + auto urlTrailingLf25PortStr = FromUTF8Z(U8("http://example.test:25\n/path")); + URL urlTrailingLf25Port{urlTrailingLf25PortStr.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(!URLPortAllowed(&urlTrailingLf25Port)); + auto urlTrailingCr25PortStr = FromUTF8Z(U8("http://example.test:25\r/path")); + URL urlTrailingCr25Port{urlTrailingCr25PortStr.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(!URLPortAllowed(&urlTrailingCr25Port)); + auto urlTrailingFf25PortStr = FromUTF8Z(U8("http://example.test:25\f/path")); + URL urlTrailingFf25Port{urlTrailingFf25PortStr.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(!URLPortAllowed(&urlTrailingFf25Port)); + auto urlJunk25PortStr = FromUTF8Z(U8("http://example.test:25x/path")); + URL urlJunk25Port{urlJunk25PortStr.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(URLPortAllowed(&urlJunk25Port)); + auto urlJunk80PortStr = FromUTF8Z(U8("http://example.test:80junk/path")); + URL urlJunk80Port{urlJunk80PortStr.impl, 1U | 2U | (7U << 3) | (4U << 6), 7, 7, 7, 19, 27, 31, 31}; + Check(URLPortAllowed(&urlJunk80Port)); + auto widePort8080 = Create16(u"http://wide.test:8080/path", 26); + URL urlWidePort8080{widePort8080.impl, 1U | 2U | (5U << 3) | (4U << 6), 7, 7, 7, 16, 22, 26, 26}; + Check(URLPortAllowed(&urlWidePort8080)); + auto widePort25 = Create16(u"http://wide.test:25/path", 24); + URL urlWidePort25{widePort25.impl, 1U | 2U | (3U << 3) | (4U << 6), 7, 7, 7, 16, 20, 24, 24}; + Check(!URLPortAllowed(&urlWidePort25)); + auto wideFtpPort21 = Create16(u"ftp://wide.test:21/path", 23); + URL urlWideFtpPort21{wideFtpPort21.impl, 1U | 0U | (3U << 3) | (3U << 6), 6, 6, 6, 15, 19, 23, 23}; + Check(URLPortAllowed(&urlWideFtpPort21)); + auto wideFilePort25 = Create16(u"file://wide.test:25/path", 24); + URL urlWideFilePort25{wideFilePort25.impl, 1U | 0U | (3U << 3) | (4U << 6), 7, 7, 7, 16, 20, 24, 24}; + Check(URLPortAllowed(&urlWideFilePort25)); + auto wideFtpUpper21 = Create16(u"FTP://wide.test:21/path", 23); + URL urlWideFtpUpper21{wideFtpUpper21.impl, 1U | 0U | (3U << 3) | (3U << 6), 6, 6, 6, 15, 19, 23, 23}; + Check(URLPortAllowed(&urlWideFtpUpper21)); + auto wideFileUpper25 = Create16(u"FILE://wide.test:25/path", 24); + URL urlWideFileUpper25{wideFileUpper25.impl, 1U | 0U | (3U << 3) | (4U << 6), 7, 7, 7, 16, 20, 24, 24}; + Check(URLPortAllowed(&urlWideFileUpper25)); + auto wideTab25 = Create16(u"http://wide.test:\t25/path", 25); + URL urlWideTab25{wideTab25.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 16, 21, 25, 25}; + Check(!URLPortAllowed(&urlWideTab25)); + auto wideLf25 = Create16(u"http://wide.test:\n25/path", 25); + URL urlWideLf25{wideLf25.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 16, 21, 25, 25}; + Check(!URLPortAllowed(&urlWideLf25)); + auto wideCr25 = Create16(u"http://wide.test:\r25/path", 25); + URL urlWideCr25{wideCr25.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 16, 21, 25, 25}; + Check(!URLPortAllowed(&urlWideCr25)); + auto wideFf25 = Create16(u"http://wide.test:\f25/path", 25); + URL urlWideFf25{wideFf25.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 16, 21, 25, 25}; + Check(!URLPortAllowed(&urlWideFf25)); + auto wideTrailingTab25 = Create16(u"http://wide.test:25\t/path", 25); + URL urlWideTrailingTab25{wideTrailingTab25.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 16, 21, 25, 25}; + Check(!URLPortAllowed(&urlWideTrailingTab25)); + auto wideTrailingLf25 = Create16(u"http://wide.test:25\n/path", 25); + URL urlWideTrailingLf25{wideTrailingLf25.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 16, 21, 25, 25}; + Check(!URLPortAllowed(&urlWideTrailingLf25)); + auto wideTrailingCr25 = Create16(u"http://wide.test:25\r/path", 25); + URL urlWideTrailingCr25{wideTrailingCr25.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 16, 21, 25, 25}; + Check(!URLPortAllowed(&urlWideTrailingCr25)); + auto wideTrailingFf25 = Create16(u"http://wide.test:25\f/path", 25); + URL urlWideTrailingFf25{wideTrailingFf25.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 16, 21, 25, 25}; + Check(!URLPortAllowed(&urlWideTrailingFf25)); + auto wideJunk25 = Create16(u"http://wide.test:25x/path", 25); + URL urlWideJunk25{wideJunk25.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 16, 21, 25, 25}; + Check(URLPortAllowed(&urlWideJunk25)); + auto urlWraparoundPortStr = FromUTF8Z(U8("25://example.test/path")); + URL urlWraparoundPort{urlWraparoundPortStr.impl, 1U | 2U | (3U << 3) | (2U << 6), 5, 5, 5, ~0U, 23, 22, 22}; + Check(URLPortAllowed(&urlWraparoundPort)); + Check(URLPortAllowed(&absent)); + Throws([&] { URLPortAllowed(nullptr); }); + { + Check(URLProtocolHostAndPortAreEqual(&url, &url)); + Check(URLProtocolHostAndPortAreEqual(&url, &urlNoFrag)); + Check(URLProtocolHostAndPortAreEqual(&url, &urlDiffQuery)); + Check(URLProtocolHostAndPortAreEqual(&url, &urlDiffPath)); + auto urlUpperSchemeStr = FromUTF8Z(U8("HTTPS://u%20:p%40@example.test:8443/a/b?q=1#frag")); + URL urlUpperScheme = url; + urlUpperScheme.impl = urlUpperSchemeStr.impl; + Check(URLProtocolHostAndPortAreEqual(&url, &urlUpperScheme)); + auto urlUpperHostStr = FromUTF8Z(U8("https://u%20:p%40@EXAMPLE.TEST:8443/a/b?q=1#frag")); + URL urlUpperHost = url; + urlUpperHost.impl = urlUpperHostStr.impl; + Check(URLProtocolHostAndPortAreEqual(&url, &urlUpperHost)); + auto urlHttpStr = FromUTF8Z(U8("http://u%20:p%40@example.test:8443/a/b?q=1#frag")); + URL urlHttp = url; + urlHttp.impl = urlHttpStr.impl; + urlHttp.bits = 1U | 2U | (5U << 3) | (4U << 6); + Check(!URLProtocolHostAndPortAreEqual(&url, &urlHttp)); + Check(!URLProtocolHostAndPortAreEqual(&url, &urlDiffHost)); + auto urlShortHostStr = FromUTF8Z(U8("https://u%20:p%40@ex.test:8443/a/b?q=1#frag")); + URL urlShortHost = url; + urlShortHost.impl = urlShortHostStr.impl; + urlShortHost.hostEnd = 25; + Check(!URLProtocolHostAndPortAreEqual(&url, &urlShortHost)); + auto urlDiffPortStr = FromUTF8Z(U8("https://u%20:p%40@example.test:9443/a/b?q=1#frag")); + URL urlDiffPort = url; + urlDiffPort.impl = urlDiffPortStr.impl; + Check(!URLProtocolHostAndPortAreEqual(&url, &urlDiffPort)); + auto urlPort80Str = FromUTF8Z(U8("http://example.test:80/path")); + URL urlPort80{urlPort80Str.impl, 1U | 2U | (3U << 3) | (4U << 6), 7, 7, 7, 19, 23, 27, 27}; + auto urlNoPortStr = FromUTF8Z(U8("http://example.test/path")); + URL urlNoPort{urlNoPortStr.impl, 1U | 2U | (0U << 3) | (4U << 6), 7, 7, 7, 19, 20, 24, 24}; + Check(!URLProtocolHostAndPortAreEqual(&urlPort80, &urlNoPort)); + auto urlNoPort2Str = FromUTF8Z(U8("http://example.test/other")); + URL urlNoPort2{urlNoPort2Str.impl, 1U | 2U | (0U << 3) | (4U << 6), 7, 7, 7, 19, 20, 25, 25}; + Check(URLProtocolHostAndPortAreEqual(&urlNoPort, &urlNoPort2)); + auto widePort = Create16(u"http://wide.test:8080/path", 26); + URL urlWidePort{widePort.impl, 1U | 2U | (5U << 3) | (4U << 6), 7, 7, 7, 16, 22, 26, 26}; + auto widePortMatch = Create16(u"HTTP://WIDE.TEST:8080/other", 27); + URL urlWideMatch{widePortMatch.impl, 1U | 2U | (5U << 3) | (4U << 6), 7, 7, 7, 16, 22, 27, 27}; + Check(URLProtocolHostAndPortAreEqual(&urlWidePort, &urlWideMatch)); + auto narrowPortStr = FromUTF8Z(U8("http://wide.test:8080/narrow")); + URL urlNarrowPort{narrowPortStr.impl, 1U | 2U | (5U << 3) | (4U << 6), 7, 7, 7, 16, 22, 28, 28}; + Check(URLProtocolHostAndPortAreEqual(&urlWidePort, &urlNarrowPort)); + auto urlPort0Str = FromUTF8Z(U8("http://example.test:0/path")); + URL urlPort0{urlPort0Str.impl, 1U | 2U | (2U << 3) | (4U << 6), 7, 7, 7, 19, 22, 26, 26}; + Check(URLProtocolHostAndPortAreEqual(&urlPort0, &urlPort0)); + Check(!URLProtocolHostAndPortAreEqual(&urlPort0, &urlNoPort)); + Check(!URLProtocolHostAndPortAreEqual(&urlPort0, &urlPort80)); + auto urlPortMaxStr = FromUTF8Z(U8("http://example.test:65535/path")); + URL urlPortMax{urlPortMaxStr.impl, 1U | 2U | (6U << 3) | (4U << 6), 7, 7, 7, 19, 26, 30, 30}; + Check(URLProtocolHostAndPortAreEqual(&urlPortMax, &urlPortMax)); + Check(!URLProtocolHostAndPortAreEqual(&urlPortMax, &urlPort80)); + auto urlEmptyPortStr = FromUTF8Z(U8("http://example.test:/path")); + URL urlEmptyPort{urlEmptyPortStr.impl, 1U | 2U | (1U << 3) | (4U << 6), 7, 7, 7, 19, 21, 25, 25}; + Check(!URLProtocolHostAndPortAreEqual(&urlEmptyPort, &urlPort80)); + Check(URLProtocolHostAndPortAreEqual(&urlEmptyPort, &urlNoPort)); + auto urlMalformedPortStr = FromUTF8Z(U8("http://example.test:8x/path")); + URL urlMalformedPort{urlMalformedPortStr.impl, 1U | 2U | (3U << 3) | (4U << 6), 7, 7, 7, 19, 23, 27, 27}; + Check(!URLProtocolHostAndPortAreEqual(&urlMalformedPort, &urlPort80)); + Check(URLProtocolHostAndPortAreEqual(&urlMalformedPort, &urlNoPort)); + auto urlOutOfRangePortStr = FromUTF8Z(U8("http://example.test:70000/path")); + URL urlOutOfRangePort{urlOutOfRangePortStr.impl, 1U | 2U | (6U << 3) | (4U << 6), 7, 7, 7, 19, 26, 30, 30}; + Check(!URLProtocolHostAndPortAreEqual(&urlOutOfRangePort, &urlPort80)); + Check(URLProtocolHostAndPortAreEqual(&urlOutOfRangePort, &urlNoPort)); + auto urlPlusPort80Str = FromUTF8Z(U8("http://example.test:+80/path")); + URL urlPlusPort80{urlPlusPort80Str.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(URLProtocolHostAndPortAreEqual(&urlPlusPort80, &urlPort80)); + Check(!URLProtocolHostAndPortAreEqual(&urlPlusPort80, &urlNoPort)); + auto urlSpacePort80Str = FromUTF8Z(U8("http://example.test: 80 /path")); + URL urlSpacePort80{urlSpacePort80Str.impl, 1U | 2U | (5U << 3) | (4U << 6), 7, 7, 7, 19, 25, 29, 29}; + Check(URLProtocolHostAndPortAreEqual(&urlSpacePort80, &urlPort80)); + Check(!URLProtocolHostAndPortAreEqual(&urlSpacePort80, &urlNoPort)); + auto urlTabPort80Str = FromUTF8Z(U8("http://example.test:\t80/path")); + URL urlTabPort80{urlTabPort80Str.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(URLProtocolHostAndPortAreEqual(&urlTabPort80, &urlPort80)); + auto urlNewlinePort80Str = FromUTF8Z(U8("http://example.test:\n80/path")); + URL urlNewlinePort80{urlNewlinePort80Str.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(URLProtocolHostAndPortAreEqual(&urlNewlinePort80, &urlPort80)); + auto urlReturnPort80Str = FromUTF8Z(U8("http://example.test:\r80/path")); + URL urlReturnPort80{urlReturnPort80Str.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(URLProtocolHostAndPortAreEqual(&urlReturnPort80, &urlPort80)); + auto urlFormFeedPort80Str = FromUTF8Z(U8("http://example.test:\f80/path")); + URL urlFormFeedPort80{urlFormFeedPort80Str.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(URLProtocolHostAndPortAreEqual(&urlFormFeedPort80, &urlPort80)); + auto widePlusPort = Create16(u"http://wide.test:+8080/path", 27); + URL urlWidePlusPort{widePlusPort.impl, 1U | 2U | (6U << 3) | (4U << 6), 7, 7, 7, 16, 23, 27, 27}; + Check(URLProtocolHostAndPortAreEqual(&urlWidePlusPort, &urlWidePort)); + auto wideSpacePort = Create16(u"http://wide.test: 8080 /path", 28); + URL urlWideSpacePort{wideSpacePort.impl, 1U | 2U | (7U << 3) | (4U << 6), 7, 7, 7, 16, 24, 28, 28}; + Check(URLProtocolHostAndPortAreEqual(&urlWideSpacePort, &urlWidePort)); + auto wideTabPort = Create16(u"http://wide.test:\t8080/path", 27); + URL urlWideTabPort{wideTabPort.impl, 1U | 2U | (6U << 3) | (4U << 6), 7, 7, 7, 16, 23, 27, 27}; + Check(URLProtocolHostAndPortAreEqual(&urlWideTabPort, &urlWidePort)); + auto wideNewlinePort = Create16(u"http://wide.test:\n8080/path", 27); + URL urlWideNewlinePort{wideNewlinePort.impl, 1U | 2U | (6U << 3) | (4U << 6), 7, 7, 7, 16, 23, 27, 27}; + Check(URLProtocolHostAndPortAreEqual(&urlWideNewlinePort, &urlWidePort)); + auto wideReturnPort = Create16(u"http://wide.test:\r8080/path", 27); + URL urlWideReturnPort{wideReturnPort.impl, 1U | 2U | (6U << 3) | (4U << 6), 7, 7, 7, 16, 23, 27, 27}; + Check(URLProtocolHostAndPortAreEqual(&urlWideReturnPort, &urlWidePort)); + auto wideFormFeedPort = Create16(u"http://wide.test:\f8080/path", 27); + URL urlWideFormFeedPort{wideFormFeedPort.impl, 1U | 2U | (6U << 3) | (4U << 6), 7, 7, 7, 16, 23, 27, 27}; + Check(URLProtocolHostAndPortAreEqual(&urlWideFormFeedPort, &urlWidePort)); + auto urlMinusPortStr = FromUTF8Z(U8("http://example.test:-80/path")); + URL urlMinusPort{urlMinusPortStr.impl, 1U | 2U | (4U << 3) | (4U << 6), 7, 7, 7, 19, 24, 28, 28}; + Check(!URLProtocolHostAndPortAreEqual(&urlMinusPort, &urlPort80)); + Check(URLProtocolHostAndPortAreEqual(&urlMinusPort, &urlNoPort)); + auto urlBarePlusPortStr = FromUTF8Z(U8("http://example.test:+/path")); + URL urlBarePlusPort{urlBarePlusPortStr.impl, 1U | 2U | (2U << 3) | (4U << 6), 7, 7, 7, 19, 22, 26, 26}; + Check(!URLProtocolHostAndPortAreEqual(&urlBarePlusPort, &urlPort80)); + Check(URLProtocolHostAndPortAreEqual(&urlBarePlusPort, &urlNoPort)); + auto urlPortOutOfBoundsStr = FromUTF8Z(U8("http://example.test:80")); + URL urlPortOutOfBounds{urlPortOutOfBoundsStr.impl, 1U | 2U | (5U << 3) | (4U << 6), 7, 7, 7, 19, 24, 22, 22}; + Check(URLProtocolHostAndPortAreEqual(&urlPortOutOfBounds, &urlNoPort)); + Check(!URLProtocolHostAndPortAreEqual(&urlPortOutOfBounds, &urlPort80)); + Check(URLProtocolHostAndPortAreEqual(&absent, &absent)); + Check(!URLProtocolHostAndPortAreEqual(&url, &absent)); + Check(!URLProtocolHostAndPortAreEqual(&absent, &url)); + Throws([&] { URLProtocolHostAndPortAreEqual(nullptr, &url); }); + Throws([&] { URLProtocolHostAndPortAreEqual(&url, nullptr); }); + } URLInvalidate(&url); Check(url.impl == string.impl && url.bits == 0 && url.userStart == 0 && url.userEnd == 0); Check(url.passwordEnd == 0 && url.hostEnd == 0 && url.pathEnd == 0 && url.pathAfterLastSlash == 0 && url.queryEnd == 0); @@ -1044,4 +1523,95 @@ void TestURLsAndReallocation() { TestURLStringHelpers(); } +void TestURLLastPathComponentAndReferrer() { + auto httpBits = [](unsigned schemeEnd, unsigned portLength) { return 1U | 2U | (portLength << 3) | (schemeEnd << 6); }; + auto simple = FromUTF8Z(U8("http://h/a/b")); + URL url{simple.impl, httpBits(4, 0), 7, 7, 7, 8, 11, 12, 12}; + Check(ViewText(URLLastPathComponent(&url)) == "b"); + auto trailing = FromUTF8Z(U8("http://h/a/b/")); + URL slashed{trailing.impl, httpBits(4, 0), 7, 7, 7, 8, 13, 13, 13}; + Check(ViewText(URLLastPathComponent(&slashed)) == "b"); + auto root = FromUTF8Z(U8("http://h/")); + URL rootUrl{root.impl, httpBits(4, 0), 7, 7, 7, 8, 9, 9, 9}; + Check(!URLLastPathComponent(&rootUrl).data); + auto hostOnly = FromUTF8Z(U8("http://h")); + URL noPath{hostOnly.impl, httpBits(4, 0), 7, 7, 7, 8, 8, 8, 8}; + Check(!URLLastPathComponent(&noPath).data); + auto opaque = FromUTF8Z(U8("mailto:abc")); + URL mailto{opaque.impl, 1U | 4U | (6U << 6), 7, 7, 7, 7, 7, 10, 10}; + Check(ViewText(URLLastPathComponent(&mailto)) == "mailto:abc"); + auto wide = Create16(u"http://h/a/b", 12); + URL wideUrl{wide.impl, httpBits(4, 0), 7, 7, 7, 8, 11, 12, 12}; + auto wideResult = URLLastPathComponent(&wideUrl); + Check(!wideResult.is8Bit && wideResult.length == 1 && ViewText(wideResult) == "b"); + auto port = FromUTF8Z(U8("http://h:80/dir/file.txt?q")); + URL withPort{port.impl, httpBits(4, 3), 7, 7, 7, 8, 16, 24, 26}; + Check(ViewText(URLLastPathComponent(&withPort)) == "file.txt"); + auto slashDot = FromUTF8Z(U8("foo:/.//a")); + URL dotted{slashDot.impl, 1U | (3U << 6), 4, 4, 4, 4, 8, 9, 9}; + Check(ViewText(URLLastPathComponent(&dotted)) == "a"); + auto beforePath = FromUTF8Z(U8("foo:/.a")); + URL guarded{beforePath.impl, 1U | (3U << 6), 4, 4, 4, 4, 5, 7, 7}; + Check(!URLLastPathComponent(&guarded).data); + auto emptyComponent = FromUTF8Z(U8("http://h//")); + URL doubled{emptyComponent.impl, httpBits(4, 0), 7, 7, 7, 8, 10, 10, 10}; + auto emptyResult = URLLastPathComponent(&doubled); + Check(emptyResult.data && emptyResult.length == 0); + URL absent{}; + Check(!URLLastPathComponent(&absent).data); + URL pastEnd = url; + pastEnd.pathEnd = 50; + Throws([&] { URLLastPathComponent(&pastEnd); }); + Throws([&] { URLLastPathComponent(nullptr); }); + + auto full = FromUTF8Z(U8("https://u:p@h:80/a?q#f")); + URL credentialed{full.impl, httpBits(5, 3), 8, 9, 11, 13, 17, 18, 20}; + auto stripped = URLStrippedForReferrer(&credentialed); + Check(Bytes(UTF8(&stripped, 1)) == "https://h:80/a?q" && stripped.impl != full.impl && stripped.impl->length == 16); + auto plain = FromUTF8Z(U8("http://h/a?q")); + URL untouched{plain.impl, httpBits(4, 0), 7, 7, 7, 8, 9, 10, 12}; + auto same = URLStrippedForReferrer(&untouched); + Check(same.impl == plain.impl && plain.impl->refCount == 4); + auto fragment = FromUTF8Z(U8("http://h/a#f")); + URL fragmentOnly{fragment.impl, httpBits(4, 0), 7, 7, 7, 8, 9, 10, 10}; + auto noFragment = URLStrippedForReferrer(&fragmentOnly); + Check(Bytes(UTF8(&noFragment, 1)) == "http://h/a"); + auto userOnly = FromUTF8Z(U8("http://u@h/a")); + URL userUrl{userOnly.impl, httpBits(4, 0), 7, 8, 8, 10, 11, 12, 12}; + auto noUser = URLStrippedForReferrer(&userUrl); + Check(Bytes(UTF8(&noUser, 1)) == "http://h/a"); + auto wideFull = Create16(u"http://u@h/a#f", 14); + URL wideCredentialed{wideFull.impl, httpBits(4, 0), 7, 8, 8, 10, 11, 12, 12}; + auto wideStripped = URLStrippedForReferrer(&wideCredentialed); + Check(!(wideStripped.impl->hashAndFlags & 4) && wideStripped.impl->length == 10 && Bytes(UTF8(&wideStripped, 1)) == "http://h/a"); + URL invalid{plain.impl, 0, 0, 0, 0, 0, 0, 0, 0}; + { + const unsigned before = plain.impl->refCount; + auto kept = URLStrippedForReferrer(&invalid); + Check(kept.impl == plain.impl && plain.impl->refCount == before + 2); + } + Check(plain.impl->refCount == 4); + URL badBounds = untouched; + badBounds.queryEnd = 99; + Throws([&] { URLStrippedForReferrer(&badBounds); }); + auto passwordOnly = FromUTF8Z(U8("http://:p@h/")); + URL passwordUrl{passwordOnly.impl, httpBits(4, 0), 7, 7, 9, 11, 12, 12, 12}; + auto noPassword = URLStrippedForReferrer(&passwordUrl); + Check(Bytes(UTF8(&noPassword, 1)) == "http://h/"); + URL reversedCredentials = userUrl; + reversedCredentials.userStart = 200; + Throws([&] { URLStrippedForReferrer(&reversedCredentials); }); + auto hostless = FromUTF8Z(U8("mailto:@x")); + URL atSign{hostless.impl, 1U | 4U | (6U << 6), 7, 7, 7, 7, 7, 9, 9}; + Check(URLStrippedForReferrer(&atSign).impl == hostless.impl); + auto allCredentials = FromUTF8Z(U8("ab")); + URL consumed{allCredentials.impl, 1U, 0, 0, 0, 0, 0, 0, 0}; + auto vanished = URLStrippedForReferrer(&consumed); + Check(vanished.impl && vanished.impl->length == 0 && vanished.impl != allCredentials.impl); + URL queryBeforeCredentials = userUrl; + queryBeforeCredentials.queryEnd = 8; + Throws([&] { URLStrippedForReferrer(&queryBeforeCredentials); }); + Throws([&] { URLStrippedForReferrer(nullptr); }); +} + } // namespace Wtf2Tests diff --git a/core/libs/prx/libSceNKWebKit/tests/WtfLanguage.cpp b/core/libs/prx/libSceNKWebKit/tests/WtfLanguage.cpp new file mode 100644 index 0000000000..b02fb70ac2 --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/tests/WtfLanguage.cpp @@ -0,0 +1,453 @@ +#include "prx/libSceNKWebKit/libSceNKWebKit.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using NKWebKit::WtfAbi::String; +using NKWebKit::WtfAbi::StringImpl; +using NKWebKit::WtfAbi::StringVector; + +namespace { + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (!condition) { + std::fprintf(stderr, "WTF language check failed at line %u\n", location.line()); + std::abort(); + } +} + +template +void RequireThrows(Function function) { + bool threw = false; + try { + function(); + } catch (const std::exception&) { + threw = true; + } + Require(threw); +} + +using AddObserver = void (APS5_VABI *)(void*, void (APS5_VABI *)(void*)); +using RemoveObserver = void (APS5_VABI *)(void*); +using LanguageDidChange = void (APS5_VABI *)(); +using DisplayName = String (APS5_VABI *)(const String*); +using OverrideLanguages = void (APS5_VABI *)(const StringVector*); +using PreferredLanguagesOverride = StringVector (APS5_VABI *)(); +using IndexOfBestMatch = std::size_t (APS5_VABI *)(const String*, const StringVector*, bool*); + +static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(std::is_same_v); + +struct Text { + const void* data; + unsigned length; + bool is8Bit; +}; + +Text Text8(const char* text) { + return {text, static_cast(std::strlen(text)), true}; +} + +Text Text16(const char16_t* text, unsigned length) { + return {text, length, false}; +} + +StringImpl Make(const Text& text) { + return StringImpl{2, text.length, text.data, text.is8Bit ? 4U : 0U}; +} + +String MakeString(StringImpl& impl) { + impl.refCount += 2; + return String(&impl); +} + +StringVector MakeVector(const std::vector& impls) { + StringVector vector; + if (impls.empty()) return vector; + vector.buffer = static_cast(_ZN3WTF10fastMallocEm(impls.size() * sizeof(String))); + vector.capacity = static_cast(impls.size()); + while (vector.size < vector.capacity) { + StringImpl* impl = impls[vector.size]; + if (impl) impl->refCount += 2; + new (&vector.buffer[vector.size]) String(impl); + ++vector.size; + } + return vector; +} + +int firstCalls; +int secondCalls; +int thirdCalls; +int replacementCalls; +int removingCalls; +int addingCalls; +int replacingCalls; +void* firstContext; +void* secondContext; +void* thirdContext; +void* contextToRemove; +void* contextToAdd; +void* contextToReplace; +StringImpl* firstStoredLanguage; +StringImpl* secondStoredLanguage; +bool observedStoredLanguages; +int overrideObserverCalls; + +void APS5_VABI First(void* context) { + ++firstCalls; + firstContext = context; +} + +void APS5_VABI Second(void* context) { + ++secondCalls; + secondContext = context; +} + +void APS5_VABI Third(void* context) { + ++thirdCalls; + thirdContext = context; +} + +void APS5_VABI Replacement(void*) { + ++replacementCalls; +} + +void APS5_VABI RemoveDuringDispatch(void*) { + ++removingCalls; + _ZN3WTF28removeLanguageChangeObserverEPv(contextToRemove); +} + +void APS5_VABI AddDuringDispatch(void*) { + ++addingCalls; + _ZN3WTF25addLanguageChangeObserverEPvPFvS0_E(contextToAdd, Third); +} + +void APS5_VABI ReplaceDuringDispatch(void*) { + ++replacingCalls; + _ZN3WTF25addLanguageChangeObserverEPvPFvS0_E(contextToReplace, Replacement); +} + +void APS5_VABI ObserveStoredOverride(void*) { + ++overrideObserverCalls; + StringVector copy = _ZN3WTF30userPreferredLanguagesOverrideEv(); + observedStoredLanguages = copy.size == 2 && copy.buffer[0].impl == firstStoredLanguage && copy.buffer[1].impl == secondStoredLanguage; +} + +void ClearCounters() { + firstCalls = secondCalls = thirdCalls = replacementCalls = removingCalls = addingCalls = replacingCalls = 0; + firstContext = secondContext = thirdContext = nullptr; +} + +void ClearOverride() { + StringVector empty; + _ZN3WTF30overrideUserPreferredLanguagesERKNS_6VectorINS_6StringELm0ENS_15CrashOnOverflowELm16ENS_10FastMallocEEE(&empty); +} + +void TestTwoObservers() { + ClearCounters(); + int a = 0; + int b = 0; + _ZN3WTF25addLanguageChangeObserverEPvPFvS0_E(&a, First); + _ZN3WTF25addLanguageChangeObserverEPvPFvS0_E(&b, Second); + _ZN3WTF17languageDidChangeEv(); + Require(firstCalls == 1 && firstContext == &a); + Require(secondCalls == 1 && secondContext == &b); + _ZN3WTF28removeLanguageChangeObserverEPv(&a); + _ZN3WTF28removeLanguageChangeObserverEPv(&b); + ClearOverride(); + std::printf("WTF language observers: two contexts called once each with their own context\n"); +} + +void TestReplacedRegistration() { + ClearCounters(); + int a = 0; + _ZN3WTF25addLanguageChangeObserverEPvPFvS0_E(&a, First); + _ZN3WTF25addLanguageChangeObserverEPvPFvS0_E(&a, Second); + _ZN3WTF17languageDidChangeEv(); + Require(firstCalls == 0); + Require(secondCalls == 1 && secondContext == &a); + _ZN3WTF28removeLanguageChangeObserverEPv(&a); + ClearOverride(); + std::printf("WTF language observers: adding a registered context replaces its function\n"); +} + +void TestRemovedRegistration() { + ClearCounters(); + int a = 0; + int b = 0; + _ZN3WTF25addLanguageChangeObserverEPvPFvS0_E(&a, First); + _ZN3WTF28removeLanguageChangeObserverEPv(&a); + _ZN3WTF17languageDidChangeEv(); + Require(firstCalls == 0); + _ZN3WTF28removeLanguageChangeObserverEPv(&b); + ClearOverride(); + std::printf("WTF language observers: removed contexts are not called and removing an absent context returns\n"); +} + +void TestRemovedDuringDispatch() { + ClearCounters(); + int a = 0; + int b = 0; + contextToRemove = &b; + _ZN3WTF25addLanguageChangeObserverEPvPFvS0_E(&a, RemoveDuringDispatch); + _ZN3WTF25addLanguageChangeObserverEPvPFvS0_E(&b, Second); + _ZN3WTF17languageDidChangeEv(); + Require(removingCalls == 1); + Require(secondCalls == 0); + _ZN3WTF28removeLanguageChangeObserverEPv(&a); + ClearOverride(); + std::printf("WTF language observers: an observer removed during dispatch is not called\n"); +} + +void TestAddedDuringDispatch() { + ClearCounters(); + int a = 0; + int c = 0; + contextToAdd = &c; + _ZN3WTF25addLanguageChangeObserverEPvPFvS0_E(&a, AddDuringDispatch); + _ZN3WTF17languageDidChangeEv(); + Require(addingCalls == 1); + Require(thirdCalls == 0); + _ZN3WTF17languageDidChangeEv(); + Require(thirdCalls == 1 && thirdContext == &c); + _ZN3WTF28removeLanguageChangeObserverEPv(&a); + _ZN3WTF28removeLanguageChangeObserverEPv(&c); + ClearOverride(); + std::printf("WTF language observers: an observer added during dispatch is called from the next dispatch\n"); +} + +void TestReplacedDuringDispatch() { + ClearCounters(); + int a = 0; + int b = 0; + contextToReplace = &b; + _ZN3WTF25addLanguageChangeObserverEPvPFvS0_E(&a, ReplaceDuringDispatch); + _ZN3WTF25addLanguageChangeObserverEPvPFvS0_E(&b, Second); + _ZN3WTF17languageDidChangeEv(); + Require(secondCalls == 1); + Require(replacementCalls == 0); + _ZN3WTF17languageDidChangeEv(); + Require(secondCalls == 1); + Require(replacementCalls == 1); + _ZN3WTF28removeLanguageChangeObserverEPv(&a); + _ZN3WTF28removeLanguageChangeObserverEPv(&b); + ClearOverride(); + std::printf("WTF language observers: a replaced observer keeps its old function for the running dispatch\n"); +} + +void TestInvalidContexts() { + ClearCounters(); + RequireThrows([] { _ZN3WTF25addLanguageChangeObserverEPvPFvS0_E(nullptr, First); }); + RequireThrows([] { _ZN3WTF25addLanguageChangeObserverEPvPFvS0_E(reinterpret_cast(-1), First); }); + RequireThrows([] { _ZN3WTF28removeLanguageChangeObserverEPv(nullptr); }); + RequireThrows([] { _ZN3WTF28removeLanguageChangeObserverEPv(reinterpret_cast(-1)); }); + _ZN3WTF17languageDidChangeEv(); + Require(firstCalls == 0); + ClearOverride(); + std::printf("WTF language observers: null and -1 contexts throw on add and remove and register nothing\n"); +} + +void TestNullObserver() { + ClearCounters(); + int a = 0; + _ZN3WTF25addLanguageChangeObserverEPvPFvS0_E(&a, nullptr); + RequireThrows([] { _ZN3WTF17languageDidChangeEv(); }); + _ZN3WTF28removeLanguageChangeObserverEPv(&a); + _ZN3WTF17languageDidChangeEv(); + Require(firstCalls == 0); + ClearOverride(); + std::printf("WTF language observers: a null function throws when dispatched\n"); +} + +void TestOverrideNotifies() { + ClearCounters(); + int a = 0; + _ZN3WTF25addLanguageChangeObserverEPvPFvS0_E(&a, First); + ClearOverride(); + Require(firstCalls == 1 && firstContext == &a); + _ZN3WTF28removeLanguageChangeObserverEPv(&a); + ClearOverride(); + std::printf("WTF language observers: overriding the preferred languages runs observers once\n"); +} + +void TestOverrideNotifiesAfterStore() { + ClearCounters(); + overrideObserverCalls = 0; + observedStoredLanguages = false; + StringImpl en = Make(Text8("en-US")); + StringImpl fr = Make(Text8("fr-FR")); + firstStoredLanguage = &en; + secondStoredLanguage = &fr; + int a = 0; + _ZN3WTF25addLanguageChangeObserverEPvPFvS0_E(&a, ObserveStoredOverride); + { + std::vector impls{&en, &fr}; + StringVector list = MakeVector(impls); + _ZN3WTF30overrideUserPreferredLanguagesERKNS_6VectorINS_6StringELm0ENS_15CrashOnOverflowELm16ENS_10FastMallocEEE(&list); + Require(overrideObserverCalls == 1); + Require(observedStoredLanguages); + } + _ZN3WTF28removeLanguageChangeObserverEPv(&a); + ClearOverride(); + firstStoredLanguage = nullptr; + secondStoredLanguage = nullptr; + std::printf("WTF language observers: the override is stored before observers run, outside the lock\n"); +} + +void TestInitialOverride() { + StringVector copy = _ZN3WTF30userPreferredLanguagesOverrideEv(); + Require(copy.buffer == nullptr); + Require(copy.capacity == 0); + Require(copy.size == 0); + std::printf("WTF language override: no override is stored before the first call\n"); +} + +void TestOverride() { + StringImpl en = Make(Text8("en-US")); + StringImpl fr = Make(Text8("fr-FR")); + { + std::vector impls{&en, &fr}; + StringVector list = MakeVector(impls); + Require(en.refCount == 4 && fr.refCount == 4); + _ZN3WTF30overrideUserPreferredLanguagesERKNS_6VectorINS_6StringELm0ENS_15CrashOnOverflowELm16ENS_10FastMallocEEE(&list); + Require(en.refCount == 6 && fr.refCount == 6); + { + StringVector copy = _ZN3WTF30userPreferredLanguagesOverrideEv(); + Require(copy.size == 2); + Require(copy.capacity == 2); + Require(copy.buffer[0].impl == &en); + Require(copy.buffer[1].impl == &fr); + Require(en.refCount == 8 && fr.refCount == 8); + } + Require(en.refCount == 6 && fr.refCount == 6); + } + Require(en.refCount == 4 && fr.refCount == 4); + + ClearOverride(); + Require(en.refCount == 2 && fr.refCount == 2); + { + StringVector copy = _ZN3WTF30userPreferredLanguagesOverrideEv(); + Require(copy.buffer == nullptr); + Require(copy.size == 0); + } + std::printf("WTF language override: copies retain and release the stored strings\n"); +} + +void CheckMatchResult(const String& language, const StringVector& list, std::size_t expectedIndex, bool expectedExact) { + bool exactMatch = !expectedExact; + std::size_t index = _ZN3WTF33indexOfBestMatchingLanguageInListERKNS_6StringERKNS_6VectorIS0_Lm0ENS_15CrashOnOverflowELm16ENS_10FastMallocEEERb(&language, &list, &exactMatch); + Require(index == expectedIndex); + Require(exactMatch == expectedExact); +} + +void CheckMatch(const Text& language, std::initializer_list list, std::size_t expectedIndex, bool expectedExact) { + StringImpl languageImpl = Make(language); + std::vector impls; + impls.reserve(list.size()); + for (const auto& entry : list) impls.push_back(Make(entry)); + std::vector pointers; + pointers.reserve(impls.size()); + for (auto& impl : impls) pointers.push_back(&impl); + StringVector vector = MakeVector(pointers); + String value = MakeString(languageImpl); + CheckMatchResult(value, vector, expectedIndex, expectedExact); +} + +void TestBestMatch() { + CheckMatch(Text8("en-US"), {Text8("en-GB"), Text8("en"), Text8("fr")}, 1, false); + CheckMatch(Text8("en-US"), {Text8("fr"), Text8("en-US")}, 1, true); + CheckMatch(Text8("EN-us"), {Text8("en_US")}, 0, true); + CheckMatch(Text8("en_US"), {Text8("en_US")}, 1, false); + CheckMatch(Text8("en"), {Text8("en-GB"), Text8("en-AU")}, 0, false); + CheckMatch(Text8("en"), {Text8("fr"), Text8("en")}, 1, true); + CheckMatch(Text8("FR"), {Text8("fr-CA")}, 0, false); + CheckMatch(Text8("en-US"), {Text8("en-GB"), Text8("fr"), Text8("en")}, 2, false); + CheckMatch(Text8("en-US"), {Text8("en"), Text8("en")}, 0, false); + CheckMatch(Text8("english"), {Text8("en")}, 1, false); + CheckMatch(Text8("en-US"), {Text8("e")}, 1, false); + CheckMatch(Text8("en-US"), {}, 0, false); + CheckMatch(Text16(u"en-US", 5), {Text8("en-us")}, 0, true); + CheckMatch(Text8("en-us"), {Text16(u"en_US", 5)}, 0, true); + CheckMatch(Text16(u"\u00C9", 1), {Text16(u"\u00E9", 1)}, 1, false); + + { + StringImpl empty = Make(Text8("")); + std::vector impls{&empty}; + StringVector list = MakeVector(impls); + String nullLanguage; + CheckMatchResult(nullLanguage, list, 1, false); + String emptyLanguage = MakeString(empty); + CheckMatchResult(emptyLanguage, list, 0, true); + { + std::vector nulls{nullptr}; + StringVector nullList = MakeVector(nulls); + CheckMatchResult(nullLanguage, nullList, 0, true); + CheckMatchResult(emptyLanguage, nullList, 1, false); + } + } + std::printf("WTF language matching: 19 language lists return the documented index and exact flag\n"); +} + +void TestDisplayName() { + StringImpl name = Make(Text8("en-US")); + String value = MakeString(name); + Require(name.refCount == 4); + { + String display = _ZN3WTF28displayNameForLanguageLocaleERKNS_6StringE(&value); + Require(display.impl == &name); + Require(name.refCount == 6); + } + Require(name.refCount == 4); + { + String none; + String display = _ZN3WTF28displayNameForLanguageLocaleERKNS_6StringE(&none); + Require(display.impl == nullptr); + } + std::printf("WTF language display name: returns the retained argument and null for null\n"); +} + +void TestNullPointers() { + StringVector list; + String language; + bool exactMatch = false; + RequireThrows([&] { _ZN3WTF30overrideUserPreferredLanguagesERKNS_6VectorINS_6StringELm0ENS_15CrashOnOverflowELm16ENS_10FastMallocEEE(nullptr); }); + RequireThrows([&] { _ZN3WTF28displayNameForLanguageLocaleERKNS_6StringE(nullptr); }); + RequireThrows([&] { _ZN3WTF33indexOfBestMatchingLanguageInListERKNS_6StringERKNS_6VectorIS0_Lm0ENS_15CrashOnOverflowELm16ENS_10FastMallocEEERb(nullptr, &list, &exactMatch); }); + RequireThrows([&] { _ZN3WTF33indexOfBestMatchingLanguageInListERKNS_6StringERKNS_6VectorIS0_Lm0ENS_15CrashOnOverflowELm16ENS_10FastMallocEEERb(&language, nullptr, &exactMatch); }); + RequireThrows([&] { _ZN3WTF33indexOfBestMatchingLanguageInListERKNS_6StringERKNS_6VectorIS0_Lm0ENS_15CrashOnOverflowELm16ENS_10FastMallocEEERb(&language, &list, nullptr); }); + std::printf("WTF language: null pointers throw for override, matching and display name\n"); +} + +} + +int main() { + TestInitialOverride(); + TestTwoObservers(); + TestReplacedRegistration(); + TestRemovedRegistration(); + TestRemovedDuringDispatch(); + TestAddedDuringDispatch(); + TestReplacedDuringDispatch(); + TestInvalidContexts(); + TestNullObserver(); + TestOverrideNotifies(); + TestOverrideNotifiesAfterStore(); + TestOverride(); + TestBestMatch(); + TestDisplayName(); + TestNullPointers(); +} diff --git a/core/libs/prx/libSceNKWebKit/tests/WtfText.cpp b/core/libs/prx/libSceNKWebKit/tests/WtfText.cpp new file mode 100644 index 0000000000..7906d56654 --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/tests/WtfText.cpp @@ -0,0 +1,452 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceNKWebKit/libSceNKWebKit.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../src/WtfTextAbi.hpp" + +namespace { + +using NKWebKit::WtfAbi::String; +using NKWebKit::WtfAbi::StringBuilder; +using NKWebKit::WtfAbi::StringImpl; +using NKWebKit::WtfAbi::TextStream; +using NKWebKit::WtfAbi::URL; + +void Check(bool condition, std::source_location location = std::source_location::current()) { + if (!condition) { + std::fprintf(stderr, "WTF text test failed at line %u\n", location.line()); + std::abort(); + } +} + +template void Throws(T action, std::source_location location = std::source_location::current()) { + bool threw = false; + try { action(); } catch (const std::exception&) { threw = true; } + Check(threw, location); +} + +const unsigned char* U8(const char* data) { + return reinterpret_cast(data); +} + +String Make8(const char* data) { + return _ZN3WTF10StringImpl6createEPKhj(U8(data), static_cast(std::strlen(data))); +} + +String Make16(const std::u16string& data) { + return _ZN3WTF10StringImpl6createEPKDsj(data.data(), static_cast(data.size())); +} + +std::u16string Units(const StringImpl* impl, unsigned length) { + std::u16string result; + for (unsigned i = 0; i < length; ++i) { + if (impl->hashAndFlags & 4) result += static_cast(impl->data)[i]; + else result += static_cast(impl->data)[i]; + } + return result; +} + +std::u16string Units(const String& string) { + Check(string.impl != nullptr); + return Units(string.impl, string.impl->length); +} + +std::u16string Contents(const StringBuilder& builder) { + if (!builder.length) return {}; + if (builder.string.impl) return Units(builder.string.impl, static_cast(builder.length)); + Check(builder.buffer != nullptr); + return Units(builder.buffer, static_cast(builder.length)); +} + +std::u16string Wide(const char* data) { + std::u16string result; + for (const char* cursor = data; *cursor; ++cursor) result += static_cast(*cursor); + return result; +} + +StringBuilder NewBuilder(bool crashOnOverflow = true) { + StringBuilder builder{String()}; + builder.shouldCrashOnOverflow = crashOnOverflow; + builder.is8Bit = true; + return builder; +} + +void Free(StringBuilder& builder) { + NKWebKit::WtfAbi::Release(builder.buffer); + builder.buffer = nullptr; +} + +TextStream NewStream(bool multiLine = true, unsigned flags = 0) { + TextStream stream{NewBuilder()}; + stream.text = NewBuilder(); + stream.formattingFlags = flags; + stream.multiLineMode = multiLine; + return stream; +} + +std::u16string Release(TextStream& stream) { + auto result = _ZN3WTF10TextStream7releaseEv(&stream); + Check(!stream.text.buffer && !stream.text.string.impl && !stream.text.length && stream.text.is8Bit && !stream.text.characters8); + return Units(result); +} + +void Append8(StringBuilder& builder, const char* data) { + _ZN3WTF13StringBuilder16appendCharactersEPKhj(&builder, U8(data), static_cast(std::strlen(data))); +} + +void TestAppendCharacters() { + auto builder = NewBuilder(); + _ZN3WTF13StringBuilder16appendCharactersEPKhj(&builder, nullptr, 0); + Check(!builder.buffer && !builder.length); + Throws([&] { _ZN3WTF13StringBuilder16appendCharactersEPKhj(&builder, nullptr, 1); }); + Append8(builder, "abc"); + Check(builder.buffer && builder.buffer->length == 16 && builder.length == 3 && builder.is8Bit); + Check(builder.characters8 == builder.buffer->data && Contents(builder) == u"abc"); + Append8(builder, "defghijklmnop"); + Check(builder.buffer->length == 16 && builder.length == 16); + Append8(builder, "q"); + Check(builder.buffer->length == 32 && Contents(builder) == u"abcdefghijklmnopq"); + const char16_t latin = u'\xe9'; + _ZN3WTF13StringBuilder16appendCharactersEPKDsj(&builder, &latin, 1); + Check(builder.is8Bit && builder.length == 18); + const char16_t wide[] = u"\x263a!"; + _ZN3WTF13StringBuilder16appendCharactersEPKDsj(&builder, wide, 2); + Check(!builder.is8Bit && !(builder.buffer->hashAndFlags & 4) && builder.buffer->length == 64 && builder.length == 20); + Check(builder.characters16 == builder.buffer->data && Contents(builder) == u"abcdefghijklmnopq\xe9\x263a!"); + Append8(builder, "\xff"); + Check(Contents(builder) == u"abcdefghijklmnopq\xe9\x263a!\xff"); + Free(builder); + + auto widened = NewBuilder(); + _ZN3WTF13StringBuilder16appendCharactersEPKDsj(&widened, wide, 1); + Check(!widened.is8Bit && widened.buffer->length == 16 && Contents(widened) == u"\x263a"); + Free(widened); +} + +void TestOverflow() { + auto recording = NewBuilder(false); + recording.length = 0x7FFFFFF0; + Append8(recording, "0123456789abcdefg"); + Check(recording.overflowed && !recording.buffer && recording.length == 0x7FFFFFF0); + Append8(recording, "a"); + _ZN3WTF13StringBuilder15reserveCapacityEj(&recording, 4); + Check(recording.overflowed && !recording.buffer); + TextStream stream = NewStream(); + stream.text.shouldCrashOnOverflow = false; + stream.text.overflowed = true; + Throws([&] { _ZN3WTF10TextStream7releaseEv(&stream); }); + + auto crashing = NewBuilder(); + crashing.length = 0x7FFFFFF0; + Throws([&] { Append8(crashing, "0123456789abcdefg"); }); + Check(crashing.overflowed); + + auto wrapping = NewBuilder(false); + wrapping.length = 0x7FFFFFFF; + const char16_t unit = u'x'; + Throws([&] { _ZN3WTF13StringBuilder16appendCharactersEPKDsj(&wrapping, &unit, 0xFFFFFFFF); }); + Check(!wrapping.overflowed); +} + +void TestReserveCapacity() { + auto builder = NewBuilder(); + _ZN3WTF13StringBuilder15reserveCapacityEj(&builder, 0); + Check(!builder.buffer); + _ZN3WTF13StringBuilder15reserveCapacityEj(&builder, 5); + Check(builder.buffer && builder.buffer->length == 5 && builder.length == 0 && builder.characters8 == builder.buffer->data); + Append8(builder, "hello"); + auto* first = builder.buffer; + _ZN3WTF13StringBuilder15reserveCapacityEj(&builder, 3); + Check(builder.buffer == first); + _ZN3WTF13StringBuilder15reserveCapacityEj(&builder, 40); + Check(builder.buffer->length == 40 && builder.buffer->refCount == 2 && Contents(builder) == u"hello"); + Free(builder); + + auto text = Make16(u"\x3b1\x3b2"); + TextStream stream = NewStream(); + _ZN3WTF10TextStreamlsERKNS_6StringE(&stream, text); + Check(stream.text.string.impl == text.impl && text.impl->refCount == 4 && !stream.text.is8Bit && stream.text.length == 2); + _ZN3WTF13StringBuilder15reserveCapacityEj(&stream.text, 10); + Check(!stream.text.string.impl && stream.text.buffer->length == 10 && !(stream.text.buffer->hashAndFlags & 4)); + Check(Contents(stream.text) == u"\x3b1\x3b2" && text.impl->refCount == 2); + Check(Release(stream) == u"\x3b1\x3b2"); + + auto shared = NewBuilder(); + Append8(shared, "abc"); + _ZN3WTF13StringBuilder11shrinkToFitEv(&shared); + _ZN3WTF13StringBuilder15reserveCapacityEj(&shared, 8); + Check(shared.buffer->length == 8 && !shared.string.impl && Contents(shared) == u"abc"); + Free(shared); +} + +void TestReifyAndShrink() { + auto builder = NewBuilder(); + _ZNK3WTF13StringBuilder11reifyStringEv(&builder); + Check(builder.string.impl && builder.string.impl->length == 0 && !builder.buffer); + + auto shortText = NewBuilder(); + Append8(shortText, "abc"); + _ZNK3WTF13StringBuilder11reifyStringEv(&shortText); + Check(shortText.string.impl != shortText.buffer && (shortText.string.impl->hashAndFlags & 3) == 0); + Check(Units(shortText.string) == u"abc" && shortText.buffer->refCount == 2); + auto* reified = shortText.string.impl; + _ZNK3WTF13StringBuilder11reifyStringEv(&shortText); + Check(shortText.string.impl == reified); + Append8(shortText, "d"); + Check(!shortText.string.impl && Contents(shortText) == u"abcd"); + Free(shortText); + + auto longText = NewBuilder(); + Append8(longText, "0123456789abcd"); + _ZNK3WTF13StringBuilder11reifyStringEv(&longText); + Check((longText.string.impl->hashAndFlags & 3) == 2 && longText.string.impl->data == longText.buffer->data); + Check(longText.buffer->refCount == 4 && Units(longText.string) == u"0123456789abcd"); + Free(longText); + + auto full = NewBuilder(); + _ZN3WTF13StringBuilder15reserveCapacityEj(&full, 4); + Append8(full, "wxyz"); + _ZNK3WTF13StringBuilder11reifyStringEv(&full); + Check(full.string.impl == full.buffer && full.buffer->refCount == 4); + Free(full); + + auto shrink = NewBuilder(); + Append8(shrink, "abc"); + _ZN3WTF13StringBuilder11shrinkToFitEv(&shrink); + Check(!shrink.buffer && shrink.string.impl && shrink.string.impl->length == 3 && shrink.string.impl->refCount == 2); + Check(shrink.characters8 == shrink.string.impl->data && Units(shrink.string) == u"abc"); + _ZN3WTF13StringBuilder11shrinkToFitEv(&shrink); + Check(shrink.string.impl->length == 3); + + auto nearlyFull = NewBuilder(); + Append8(nearlyFull, "0123456789abc"); + _ZN3WTF13StringBuilder11shrinkToFitEv(&nearlyFull); + Check(nearlyFull.buffer && nearlyFull.buffer->length == 16 && !nearlyFull.string.impl); + Free(nearlyFull); + + auto wide = NewBuilder(); + const char16_t text[] = u"\x263a\x263b"; + _ZN3WTF13StringBuilder16appendCharactersEPKDsj(&wide, text, 2); + _ZN3WTF13StringBuilder11shrinkToFitEv(&wide); + Check(!wide.buffer && wide.string.impl->length == 2 && !(wide.string.impl->hashAndFlags & 4) && Units(wide.string) == u"\x263a\x263b"); +} + +void TestQuotedJSON() { + auto builder = NewBuilder(); + auto text = Make8("a\"b\\\n\x01\x7f\xe9/"); + _ZN3WTF13StringBuilder22appendQuotedJSONStringERKNS_6StringE(&builder, text); + Check(builder.is8Bit && builder.buffer->length == 64); + Check(Contents(builder) == Wide("\"a\\\"b\\\\\\n\\u0001\x7f\xe9/\"")); + Free(builder); + + auto controls = NewBuilder(); + auto escapes = Make8("\b\t\f\r\x1f"); + _ZN3WTF13StringBuilder22appendQuotedJSONStringERKNS_6StringE(&controls, escapes); + Check(Contents(controls) == Wide("\"\\b\\t\\f\\r\\u001f\"")); + Free(controls); + + auto wide = NewBuilder(); + Append8(wide, "x"); + auto surrogates = Make16(u"\x263a\xd83d\xde00\xd800-\xdc00\xdbff"); + _ZN3WTF13StringBuilder22appendQuotedJSONStringERKNS_6StringE(&wide, surrogates); + Check(!wide.is8Bit && wide.buffer->length == 64); + Check(Contents(wide) == u"x\"\x263a\xd83d\xde00\\ud800-\\udc00\\udbff\""); + Free(wide); + + auto empty = NewBuilder(); + String null; + _ZN3WTF13StringBuilder22appendQuotedJSONStringERKNS_6StringE(&empty, null); + Check(Contents(empty) == u"\"\"" && empty.buffer->length == 2); + Free(empty); + + TextStream stream = NewStream(); + auto ascii = Make8("abc"); + _ZN3WTF10TextStreamlsERKNS_6StringE(&stream, ascii); + auto quoted = Make8("q"); + _ZN3WTF13StringBuilder22appendQuotedJSONStringERKNS_6StringE(&stream.text, quoted); + Check(Contents(stream.text) == u"abc\"q\"" && stream.text.buffer->length == 16); + Check(Release(stream) == u"abc\"q\""); + _ZN3WTF10TextStreamlsERKNS_6StringE(&stream, ascii); + Throws([&] { _ZN3WTF13StringBuilder22appendQuotedJSONStringERKNS_6StringE(&stream.text, surrogates); }); + + auto recording = NewBuilder(false); + recording.length = 0x7FFFFFF0; + _ZN3WTF13StringBuilder22appendQuotedJSONStringERKNS_6StringE(&recording, quoted); + Check(recording.overflowed && !recording.buffer); +} + +std::u16string Float(float value) { + auto builder = NewBuilder(); + _ZN3WTF13StringBuilder12appendNumberEf(&builder, value); + auto result = Contents(builder); + Free(builder); + return result; +} + +void TestAppendNumber() { + Check(Float(0.1f) == u"0.1"); + Check(Float(100.0f) == u"100"); + Check(Float(-2.5f) == u"-2.5"); + Check(Float(123456789.0f) == u"123456790"); + Check(Float(1e20f) == u"100000000000000000000"); + Check(Float(1e21f) == u"1e+21"); + Check(Float(0.000001f) == u"0.000001"); + Check(Float(1.5e-7f) == u"1.5e-7"); + Check(Float(3.4028235e38f) == u"3.4028235e+38"); + Check(Float(1.17549435e-38f) == u"1.1754944e-38"); + Check(Float(1.4e-45f) == u"1e-45"); + Check(Float(0.0f) == u"0" && Float(-0.0f) == u"0"); + Check(Float(std::numeric_limits::quiet_NaN()) == u"NaN"); + Check(Float(std::numeric_limits::infinity()) == u"Infinity"); + Check(Float(-std::numeric_limits::infinity()) == u"-Infinity"); +} + +void TestTextStreamValues() { + auto stream = NewStream(); + auto* self = &stream; + Check(&_ZN3WTF10TextStreamlsEb(self, true) == self); + _ZN3WTF10TextStreamlsEb(self, false); + _ZN3WTF10TextStreamlsEc(self, ','); + _ZN3WTF10TextStreamlsEi(self, std::numeric_limits::min()); + _ZN3WTF10TextStreamlsEc(self, ','); + _ZN3WTF10TextStreamlsEj(self, std::numeric_limits::max()); + _ZN3WTF10TextStreamlsEc(self, ','); + _ZN3WTF10TextStreamlsEl(self, std::numeric_limits::min()); + _ZN3WTF10TextStreamlsEc(self, ','); + _ZN3WTF10TextStreamlsEm(self, std::numeric_limits::max()); + _ZN3WTF10TextStreamlsEc(self, ','); + _ZN3WTF10TextStreamlsEx(self, -42); + _ZN3WTF10TextStreamlsEc(self, ','); + _ZN3WTF10TextStreamlsEy(self, 42); + Check(Release(stream) == u"10,-2147483648,4294967295,-9223372036854775808,18446744073709551615,-42,42"); + + _ZN3WTF10TextStreamlsEf(self, 1.5f); + _ZN3WTF10TextStreamlsEPKc(self, " "); + _ZN3WTF10TextStreamlsEd(self, 3.14159); + _ZN3WTF10TextStreamlsEPKc(self, " "); + _ZN3WTF10TextStreamlsEd(self, 0.125); + _ZN3WTF10TextStreamlsEPKc(self, " "); + _ZN3WTF10TextStreamlsEd(self, 0.996); + _ZN3WTF10TextStreamlsEPKc(self, " "); + _ZN3WTF10TextStreamlsEd(self, 9.999); + _ZN3WTF10TextStreamlsEPKc(self, " "); + _ZN3WTF10TextStreamlsEd(self, -0.001); + _ZN3WTF10TextStreamlsEPKc(self, " "); + _ZN3WTF10TextStreamlsEd(self, -0.0); + _ZN3WTF10TextStreamlsEPKc(self, " "); + _ZN3WTF10TextStreamlsEd(self, 1e20); + _ZN3WTF10TextStreamlsEPKc(self, " "); + _ZN3WTF10TextStreamlsEd(self, std::numeric_limits::quiet_NaN()); + _ZN3WTF10TextStreamlsEPKc(self, " "); + _ZN3WTF10TextStreamlsEd(self, -std::numeric_limits::infinity()); + _ZN3WTF10TextStreamlsEPKc(self, "|"); + _ZN3WTF10TextStreamlsEd(self, 1e21); + _ZN3WTF10TextStreamlsEPKc(self, "|"); + Check(Release(stream) == u"1.50 3.14 0.13 1.00 10.00 -0.00 0.00 100000000000000000000.00 NaN -Infinity||"); + + auto empty = NewStream(); + _ZN3WTF10TextStreamlsEd(&empty, 1e21); + Check(empty.text.buffer && empty.text.buffer->length == 16 && !empty.text.length); + Check(Release(empty).empty()); + + auto integers = NewStream(true, 2); + _ZN3WTF10TextStreamlsEd(&integers, 3.0); + _ZN3WTF10TextStreamlsEc(&integers, ' '); + _ZN3WTF10TextStreamlsEd(&integers, 3.5); + _ZN3WTF10TextStreamlsEc(&integers, ' '); + _ZN3WTF10TextStreamlsEf(&integers, -7.00001f); + _ZN3WTF10TextStreamlsEc(&integers, ' '); + _ZN3WTF10TextStreamlsEd(&integers, 1e10); + _ZN3WTF10TextStreamlsEc(&integers, ' '); + _ZN3WTF10TextStreamlsEd(&integers, std::numeric_limits::quiet_NaN()); + Check(Release(integers) == u"3 3.50 -7 10000000000.00 -2147483648"); + + _ZN3WTF10TextStreamlsEPKv(self, reinterpret_cast(std::uintptr_t{0x1234abcd})); + _ZN3WTF10TextStreamlsEc(self, ' '); + _ZN3WTF10TextStreamlsEPKv(self, nullptr); + _ZN3WTF10TextStreamlsEPKc(self, nullptr); + Check(Release(stream) == u"1234abcd 0"); + + Check(Release(stream).empty()); + Throws([] { _ZN3WTF10TextStreamlsEi(nullptr, 1); }); +} + +void TestTextStreamStrings() { + auto stream = NewStream(); + String null; + _ZN3WTF10TextStreamlsERKNS_6StringE(&stream, null); + Check(!stream.text.string.impl && !stream.text.length); + auto first = Make8("first"); + _ZN3WTF10TextStreamlsERKNS_6StringE(&stream, first); + Check(stream.text.string.impl == first.impl && !stream.text.buffer); + auto result = _ZN3WTF10TextStream7releaseEv(&stream); + Check(result.impl == first.impl && first.impl->refCount == 4); + + auto wide = Make16(u"\x3a9"); + _ZN3WTF10TextStreamlsEc(&stream, '<'); + _ZN3WTF10TextStreamlsERKNS_6StringE(&stream, wide); + _ZN3WTF10TextStreamlsEc(&stream, '>'); + Check(!stream.text.is8Bit && Release(stream) == u"<\x3a9>"); + + URL url{}; + auto address = Make8("https://example.com/"); + url.impl = address.impl; + _ZN3WTFlsERNS_10TextStreamERKNS_3URLE(stream, url); + _ZN3WTFlsERNS_10TextStreamENS_7SecondsE(stream, WTF::Seconds{2.5}); + _ZN3WTFlsERNS_10TextStreamENS_7SecondsE(stream, WTF::Seconds{-0.25}); + Check(Release(stream) == u"https://example.com/2.50s-0.25s"); +} + +void TestTextStreamGroups() { + auto stream = NewStream(); + _ZN3WTF10TextStreamlsEPKc(&stream, "root"); + _ZN3WTF10TextStream10startGroupEv(&stream); + Check(stream.indent == 1); + _ZN3WTF10TextStreamlsEPKc(&stream, "a"); + _ZN3WTF10TextStream10startGroupEv(&stream); + _ZN3WTF10TextStreamlsEPKc(&stream, "b"); + _ZN3WTF10TextStream8nextLineEv(&stream); + _ZN3WTF10TextStreamlsEPKc(&stream, "c"); + _ZN3WTF10TextStream8endGroupEv(&stream); + _ZN3WTF10TextStream8endGroupEv(&stream); + Check(stream.indent == 0); + _ZN3WTF10TextStream11writeIndentEv(&stream); + stream.indent = 2; + _ZN3WTF10TextStream11writeIndentEv(&stream); + Check(Release(stream) == u"root\n(a\n (b\n c)) "); + + auto single = NewStream(false); + _ZN3WTF10TextStreamlsEPKc(&single, "root"); + _ZN3WTF10TextStream10startGroupEv(&single); + _ZN3WTF10TextStreamlsEPKc(&single, "a"); + _ZN3WTF10TextStream8nextLineEv(&single); + _ZN3WTF10TextStreamlsEPKc(&single, "b"); + _ZN3WTF10TextStream8endGroupEv(&single); + Check(single.indent == 0); + single.indent = 3; + _ZN3WTF10TextStream11writeIndentEv(&single); + Check(single.indent == 3 && Release(single) == u"root (a b)"); +} + +} // namespace + +int main() { + TestAppendCharacters(); + TestOverflow(); + TestReserveCapacity(); + TestReifyAndShrink(); + TestQuotedJSON(); + TestAppendNumber(); + TestTextStreamValues(); + TestTextStreamStrings(); + TestTextStreamGroups(); + return 0; +} diff --git a/core/libs/prx/libSceNKWebKit/tests/allocation/WtfTextAllocation.cpp b/core/libs/prx/libSceNKWebKit/tests/allocation/WtfTextAllocation.cpp new file mode 100644 index 0000000000..ecb8600b5f --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/tests/allocation/WtfTextAllocation.cpp @@ -0,0 +1,215 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceNKWebKit/include/WtfExports.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "../../src/WtfTextAbi.hpp" + +namespace { + +constexpr std::size_t GuardSize = 16; +constexpr std::size_t LargestExpectedAllocation = std::size_t(1) << 24; +constexpr unsigned char GuardByte = 0xA5; + +std::unordered_map liveAllocations; +int allocationsBeforeFailure = -1; + +void Check(bool condition, std::source_location location = std::source_location::current()) { + if (!condition) { + std::fprintf(stderr, "WTF text allocation test failed at line %u\n", location.line()); + std::abort(); + } +} + +void* Place(std::size_t size) { + Check(size <= LargestExpectedAllocation); + if (allocationsBeforeFailure == 0) return nullptr; + if (allocationsBeforeFailure > 0) --allocationsBeforeFailure; + auto* allocation = static_cast(std::malloc(size + GuardSize)); + Check(allocation != nullptr); + std::memset(allocation + size, GuardByte, GuardSize); + liveAllocations.emplace(allocation, size); + return allocation; +} + +} // namespace + +namespace NKWebKit { + +void* Allocate(std::size_t size, std::size_t) { + void* allocation = Place(size); + if (!allocation) { + std::fprintf(stderr, "WTF text allocation test: fatal allocation failed\n"); + std::abort(); + } + return allocation; +} + +void* TryAllocate(std::size_t size, std::size_t) { + return Place(size); +} + +void Deallocate(void* ptr) { + if (!ptr) return; + auto allocation = liveAllocations.find(ptr); + Check(allocation != liveAllocations.end()); + const auto* guard = static_cast(ptr) + allocation->second; + for (std::size_t i = 0; i < GuardSize; ++i) Check(guard[i] == GuardByte); + liveAllocations.erase(allocation); + std::free(ptr); +} + +} // namespace NKWebKit + +namespace { + +using NKWebKit::WtfAbi::String; +using NKWebKit::WtfAbi::StringBuilder; + +template void Throws(T action, std::source_location location = std::source_location::current()) { + bool threw = false; + try { action(); } catch (const std::length_error&) { threw = true; } + Check(threw, location); +} + +StringBuilder NewBuilder(bool crashOnOverflow) { + StringBuilder builder{String()}; + builder.shouldCrashOnOverflow = crashOnOverflow; + builder.is8Bit = true; + return builder; +} + +void Free(StringBuilder& builder) { + NKWebKit::WtfAbi::Release(builder.buffer); + builder.buffer = nullptr; + builder.string = String(); +} + +void Append8(StringBuilder& builder, const char* data) { + _ZN3WTF13StringBuilder16appendCharactersEPKhj(&builder, reinterpret_cast(data), static_cast(std::strlen(data))); +} + +void Append16(StringBuilder& builder, const char16_t* data) { + _ZN3WTF13StringBuilder16appendCharactersEPKDsj(&builder, data, static_cast(std::char_traits::length(data))); +} + +void Reserve(StringBuilder& builder, unsigned capacity) { + _ZN3WTF13StringBuilder15reserveCapacityEj(&builder, capacity); +} + +void FailFrom(int successfulAllocations) { + allocationsBeforeFailure = successfulAllocations; +} + +void Restore() { + allocationsBeforeFailure = -1; +} + +template void Case(TSetup setup, TFailing failing, TRecorded recorded, String* kept = nullptr) { + const auto baseline = liveAllocations.size(); + auto recording = NewBuilder(false); + setup(recording); + FailFrom(0); + failing(recording); + Restore(); + Check(recording.overflowed); + recorded(recording); + Free(recording); + if (kept) *kept = String(); + Check(liveAllocations.size() == baseline); + + auto crashing = NewBuilder(true); + setup(crashing); + FailFrom(0); + Throws([&] { failing(crashing); }); + Restore(); + Check(crashing.overflowed); + Free(crashing); + if (kept) *kept = String(); + Check(liveAllocations.size() == baseline); +} + +void TestInitialAllocation() { + Case([](StringBuilder&) {}, [](StringBuilder& builder) { Append8(builder, "abc"); }, + [](StringBuilder& builder) { Check(!builder.buffer && !builder.length && builder.is8Bit && !builder.characters8); }); +} + +void TestGrowth() { + Case([](StringBuilder& builder) { Append8(builder, "abcdefghijklmnop"); }, [](StringBuilder& builder) { Append8(builder, "q"); }, + [](StringBuilder& builder) { Check(!builder.buffer && builder.length == 16); }); +} + +void TestSharedGrowth() { + String shared; + Case([&](StringBuilder& builder) { + Append8(builder, "abcdefghijklmnop"); + shared = NKWebKit::WtfAbi::ToString(builder); + Check(shared.impl == builder.buffer); + }, + [](StringBuilder& builder) { Append8(builder, "q"); }, + [&](StringBuilder& builder) { + Check(builder.buffer == shared.impl && builder.length == 16 && !builder.string.impl && !builder.characters8); + Check(shared.impl->length == 16 && std::memcmp(shared.impl->data, "abcdefghijklmnop", 16) == 0); + }, &shared); +} + +void TestWideSharedGrowth() { + String shared; + Case([&](StringBuilder& builder) { + Append16(builder, u"\x263a\x263b\x263c\x263d\x263e\x263f\x2640\x2641\x2642\x2643\x2644\x2645\x2646\x2647\x2648\x2649"); + shared = NKWebKit::WtfAbi::ToString(builder); + Check(!builder.is8Bit && shared.impl == builder.buffer && builder.length == 16); + }, + [](StringBuilder& builder) { Append16(builder, u"\x264a"); }, + [&](StringBuilder& builder) { + Check(builder.buffer == shared.impl && builder.length == 16 && !builder.string.impl && !builder.characters16); + Check(static_cast(shared.impl->data)[15] == u'\x2649'); + }, &shared); +} + +void TestLengthLimit() { + const auto baseline = liveAllocations.size(); + auto recording = NewBuilder(false); + Append16(recording, u"\x263a"); + Reserve(recording, 0x7FFFFFFF); + Check(recording.overflowed); + Free(recording); + auto crashing = NewBuilder(true); + Append16(crashing, u"\x263a"); + Throws([&] { Reserve(crashing, 0x7FFFFFFF); }); + Check(crashing.overflowed); + Free(crashing); + Check(liveAllocations.size() == baseline); +} + +void TestReserveCapacity() { + Case([](StringBuilder&) {}, [](StringBuilder& builder) { Reserve(builder, 100); }, + [](StringBuilder& builder) { Check(!builder.buffer && !builder.length && !builder.characters8); }); + Case([](StringBuilder& builder) { Append8(builder, "abc"); }, [](StringBuilder& builder) { Reserve(builder, 100); }, + [](StringBuilder& builder) { Check(!builder.buffer && builder.length == 3); }); +} + +void TestUpConversion() { + Case([](StringBuilder& builder) { Append8(builder, "abc"); }, [](StringBuilder& builder) { Append16(builder, u"\x263a"); }, + [](StringBuilder& builder) { Check(builder.is8Bit && builder.buffer && builder.buffer->length == 16 && builder.length == 3 && !builder.characters16); }); +} + +} // namespace + +int main() { + TestInitialAllocation(); + TestGrowth(); + TestSharedGrowth(); + TestWideSharedGrowth(); + TestReserveCapacity(); + TestLengthLimit(); + TestUpConversion(); + Check(liveAllocations.empty()); + return 0; +} diff --git a/core/libs/prx/libSceNKWebKitRequirements/CMakeLists.txt b/core/libs/prx/libSceNKWebKitRequirements/CMakeLists.txt new file mode 100644 index 0000000000..250909b8a9 --- /dev/null +++ b/core/libs/prx/libSceNKWebKitRequirements/CMakeLists.txt @@ -0,0 +1,21 @@ +add_library(libSceNKWebKitRequirements SHARED EXCLUDE_FROM_ALL Export.cpp src/Sqlite.cpp) +target_include_directories(libSceNKWebKitRequirements PRIVATE ${LIBS_INCLUDE_DIR}) +set_target_properties(libSceNKWebKitRequirements PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(libSceNKWebKitRequirements) +target_link_libraries(libSceNKWebKitRequirements PRIVATE sqlite3) +if(MINGW OR CMAKE_SYSTEM_NAME STREQUAL "Linux") + target_link_options(libSceNKWebKitRequirements PRIVATE LINKER:--exclude-libs,ALL) +endif() + +if(BUILD_TESTING) + add_test_executable(guest_nk_req_sqlite_tests ${LIBS_INCLUDE_DIR}/tests/GuestNKWebKitSqlite.cpp) + target_include_directories(guest_nk_req_sqlite_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_nk_req_sqlite_tests PRIVATE libSceNKWebKitRequirements libc) + configure_windows_unwind(guest_nk_req_sqlite_tests) + add_test(NAME guest_nk_req_sqlite COMMAND guest_nk_req_sqlite_tests) + set_tests_properties(guest_nk_req_sqlite PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_nk_req_sqlite PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNKWebKitRequirements/Export.cpp b/core/libs/prx/libSceNKWebKitRequirements/Export.cpp index a395929a02..9cc9e9ee50 100644 --- a/core/libs/prx/libSceNKWebKitRequirements/Export.cpp +++ b/core/libs/prx/libSceNKWebKitRequirements/Export.cpp @@ -201,206 +201,6 @@ int APS5_VABI psl_unregistrable_domain_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI sqlite3_bind_blob_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_bind_double_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_bind_int_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_bind_int64_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_bind_null_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_bind_parameter_count_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_bind_text16_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_busy_handler_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_busy_timeout_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_clear_bindings_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_close_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_column_blob_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_column_bytes_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_column_bytes16_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_column_decltype_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_column_double_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_column_int_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_column_int64_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_column_name16_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_column_text16_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_column_type_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_column_value_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_create_collation_v2_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_create_function_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_data_count_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_errcode_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_errmsg_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_errstr_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_extended_result_codes_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_finalize_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_get_autocommit_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_initialize_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_interrupt_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_last_insert_rowid_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_next_stmt_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_open_v2_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_prepare_v2_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_reset_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_result_error_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_set_authorizer_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_sql_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_step_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_stmt_busy_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_total_changes_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_user_data_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_value_double_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_value_text16_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_value_type_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_wal_checkpoint_v2_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sqlite3_wal_hook_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI uncompress_nid_postfix() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libSceNKWebKitRequirements/include/Identified.hpp b/core/libs/prx/libSceNKWebKitRequirements/include/Identified.hpp index 6a2a2dc188..5184f54665 100644 --- a/core/libs/prx/libSceNKWebKitRequirements/include/Identified.hpp +++ b/core/libs/prx/libSceNKWebKitRequirements/include/Identified.hpp @@ -50,56 +50,6 @@ int APS5_VABI psl_suffix_count_nid_postfix(); int APS5_VABI psl_suffix_exception_count_nid_postfix(); int APS5_VABI psl_suffix_wildcard_count_nid_postfix(); int APS5_VABI psl_unregistrable_domain_nid_postfix(); -int APS5_VABI sqlite3_bind_blob_nid_postfix(); -int APS5_VABI sqlite3_bind_double_nid_postfix(); -int APS5_VABI sqlite3_bind_int_nid_postfix(); -int APS5_VABI sqlite3_bind_int64_nid_postfix(); -int APS5_VABI sqlite3_bind_null_nid_postfix(); -int APS5_VABI sqlite3_bind_parameter_count_nid_postfix(); -int APS5_VABI sqlite3_bind_text16_nid_postfix(); -int APS5_VABI sqlite3_busy_handler_nid_postfix(); -int APS5_VABI sqlite3_busy_timeout_nid_postfix(); -int APS5_VABI sqlite3_clear_bindings_nid_postfix(); -int APS5_VABI sqlite3_close_nid_postfix(); -int APS5_VABI sqlite3_column_blob_nid_postfix(); -int APS5_VABI sqlite3_column_bytes_nid_postfix(); -int APS5_VABI sqlite3_column_bytes16_nid_postfix(); -int APS5_VABI sqlite3_column_decltype_nid_postfix(); -int APS5_VABI sqlite3_column_double_nid_postfix(); -int APS5_VABI sqlite3_column_int_nid_postfix(); -int APS5_VABI sqlite3_column_int64_nid_postfix(); -int APS5_VABI sqlite3_column_name16_nid_postfix(); -int APS5_VABI sqlite3_column_text16_nid_postfix(); -int APS5_VABI sqlite3_column_type_nid_postfix(); -int APS5_VABI sqlite3_column_value_nid_postfix(); -int APS5_VABI sqlite3_create_collation_v2_nid_postfix(); -int APS5_VABI sqlite3_create_function_nid_postfix(); -int APS5_VABI sqlite3_data_count_nid_postfix(); -int APS5_VABI sqlite3_errcode_nid_postfix(); -int APS5_VABI sqlite3_errmsg_nid_postfix(); -int APS5_VABI sqlite3_errstr_nid_postfix(); -int APS5_VABI sqlite3_extended_result_codes_nid_postfix(); -int APS5_VABI sqlite3_finalize_nid_postfix(); -int APS5_VABI sqlite3_get_autocommit_nid_postfix(); -int APS5_VABI sqlite3_initialize_nid_postfix(); -int APS5_VABI sqlite3_interrupt_nid_postfix(); -int APS5_VABI sqlite3_last_insert_rowid_nid_postfix(); -int APS5_VABI sqlite3_next_stmt_nid_postfix(); -int APS5_VABI sqlite3_open_v2_nid_postfix(); -int APS5_VABI sqlite3_prepare_v2_nid_postfix(); -int APS5_VABI sqlite3_reset_nid_postfix(); -int APS5_VABI sqlite3_result_error_nid_postfix(); -int APS5_VABI sqlite3_set_authorizer_nid_postfix(); -int APS5_VABI sqlite3_sql_nid_postfix(); -int APS5_VABI sqlite3_step_nid_postfix(); -int APS5_VABI sqlite3_stmt_busy_nid_postfix(); -int APS5_VABI sqlite3_total_changes_nid_postfix(); -int APS5_VABI sqlite3_user_data_nid_postfix(); -int APS5_VABI sqlite3_value_double_nid_postfix(); -int APS5_VABI sqlite3_value_text16_nid_postfix(); -int APS5_VABI sqlite3_value_type_nid_postfix(); -int APS5_VABI sqlite3_wal_checkpoint_v2_nid_postfix(); -int APS5_VABI sqlite3_wal_hook_nid_postfix(); int APS5_VABI uncompress_nid_postfix(); int APS5_VABI xmlByteConsumed_nid_postfix(); int APS5_VABI xmlCreateMemoryParserCtxt_nid_postfix(); diff --git a/core/libs/prx/libSceNKWebKitRequirements/include/Sqlite.hpp b/core/libs/prx/libSceNKWebKitRequirements/include/Sqlite.hpp new file mode 100644 index 0000000000..1f4dd1bbaf --- /dev/null +++ b/core/libs/prx/libSceNKWebKitRequirements/include/Sqlite.hpp @@ -0,0 +1,79 @@ +#pragma once +#include "prx/libc/include/general/VabiMacros.hpp" +#include + +struct sqlite3; +struct sqlite3_stmt; +struct sqlite3_value; +struct sqlite3_context; + +namespace NKWebKit { +using GuestSqliteDestructor = void (APS5_VABI *)(void*); +using GuestSqliteBusy = int (APS5_VABI *)(void*, int); +using GuestSqliteCompare = int (APS5_VABI *)(void*, int, const void*, int, const void*); +using GuestSqliteFunction = void (APS5_VABI *)(sqlite3_context*, int, sqlite3_value**); +using GuestSqliteFinal = void (APS5_VABI *)(sqlite3_context*); +using GuestSqliteAuthorizer = int (APS5_VABI *)(void*, int, const char*, const char*, const char*, const char*); +using GuestSqliteWalHook = int (APS5_VABI *)(void*, sqlite3*, const char*, int); +} + +extern "C" { +int APS5_VABI sqlite3_bind_blob_nid_postfix(sqlite3_stmt* statement, int index, const void* data, int size, + NKWebKit::GuestSqliteDestructor destructor); +int APS5_VABI sqlite3_bind_double_nid_postfix(sqlite3_stmt* statement, int index, double value); +int APS5_VABI sqlite3_bind_int_nid_postfix(sqlite3_stmt* statement, int index, int value); +int APS5_VABI sqlite3_bind_int64_nid_postfix(sqlite3_stmt* statement, int index, std::int64_t value); +int APS5_VABI sqlite3_bind_null_nid_postfix(sqlite3_stmt* statement, int index); +int APS5_VABI sqlite3_bind_parameter_count_nid_postfix(sqlite3_stmt* statement); +int APS5_VABI sqlite3_bind_text16_nid_postfix(sqlite3_stmt* statement, int index, const void* text, int size, + NKWebKit::GuestSqliteDestructor destructor); +int APS5_VABI sqlite3_busy_handler_nid_postfix(sqlite3* db, NKWebKit::GuestSqliteBusy handler, void* argument); +int APS5_VABI sqlite3_busy_timeout_nid_postfix(sqlite3* db, int milliseconds); +int APS5_VABI sqlite3_clear_bindings_nid_postfix(sqlite3_stmt* statement); +int APS5_VABI sqlite3_close_nid_postfix(sqlite3* db); +const void* APS5_VABI sqlite3_column_blob_nid_postfix(sqlite3_stmt* statement, int column); +int APS5_VABI sqlite3_column_bytes_nid_postfix(sqlite3_stmt* statement, int column); +int APS5_VABI sqlite3_column_bytes16_nid_postfix(sqlite3_stmt* statement, int column); +const char* APS5_VABI sqlite3_column_decltype_nid_postfix(sqlite3_stmt* statement, int column); +double APS5_VABI sqlite3_column_double_nid_postfix(sqlite3_stmt* statement, int column); +int APS5_VABI sqlite3_column_int_nid_postfix(sqlite3_stmt* statement, int column); +std::int64_t APS5_VABI sqlite3_column_int64_nid_postfix(sqlite3_stmt* statement, int column); +const void* APS5_VABI sqlite3_column_name16_nid_postfix(sqlite3_stmt* statement, int column); +const void* APS5_VABI sqlite3_column_text16_nid_postfix(sqlite3_stmt* statement, int column); +int APS5_VABI sqlite3_column_type_nid_postfix(sqlite3_stmt* statement, int column); +sqlite3_value* APS5_VABI sqlite3_column_value_nid_postfix(sqlite3_stmt* statement, int column); +int APS5_VABI sqlite3_create_collation_v2_nid_postfix(sqlite3* db, const char* name, int encoding, void* argument, + NKWebKit::GuestSqliteCompare compare, NKWebKit::GuestSqliteDestructor destroy); +int APS5_VABI sqlite3_create_function_nid_postfix(sqlite3* db, const char* name, int arguments, int encoding, + void* application, NKWebKit::GuestSqliteFunction function, NKWebKit::GuestSqliteFunction step, + NKWebKit::GuestSqliteFinal final); +int APS5_VABI sqlite3_data_count_nid_postfix(sqlite3_stmt* statement); +int APS5_VABI sqlite3_errcode_nid_postfix(sqlite3* db); +const char* APS5_VABI sqlite3_errmsg_nid_postfix(sqlite3* db); +const char* APS5_VABI sqlite3_errstr_nid_postfix(int code); +int APS5_VABI sqlite3_extended_result_codes_nid_postfix(sqlite3* db, int enabled); +int APS5_VABI sqlite3_finalize_nid_postfix(sqlite3_stmt* statement); +int APS5_VABI sqlite3_get_autocommit_nid_postfix(sqlite3* db); +int APS5_VABI sqlite3_initialize_nid_postfix(); +void APS5_VABI sqlite3_interrupt_nid_postfix(sqlite3* db); +std::int64_t APS5_VABI sqlite3_last_insert_rowid_nid_postfix(sqlite3* db); +sqlite3_stmt* APS5_VABI sqlite3_next_stmt_nid_postfix(sqlite3* db, sqlite3_stmt* statement); +int APS5_VABI sqlite3_open_v2_nid_postfix(const char* filename, sqlite3** db, int flags, const char* vfs); +int APS5_VABI sqlite3_prepare_v2_nid_postfix(sqlite3* db, const char* sql, int size, sqlite3_stmt** statement, + const char** tail); +int APS5_VABI sqlite3_reset_nid_postfix(sqlite3_stmt* statement); +void APS5_VABI sqlite3_result_error_nid_postfix(sqlite3_context* context, const char* message, int size); +int APS5_VABI sqlite3_set_authorizer_nid_postfix(sqlite3* db, NKWebKit::GuestSqliteAuthorizer authorizer, + void* argument); +const char* APS5_VABI sqlite3_sql_nid_postfix(sqlite3_stmt* statement); +int APS5_VABI sqlite3_step_nid_postfix(sqlite3_stmt* statement); +int APS5_VABI sqlite3_stmt_busy_nid_postfix(sqlite3_stmt* statement); +int APS5_VABI sqlite3_total_changes_nid_postfix(sqlite3* db); +void* APS5_VABI sqlite3_user_data_nid_postfix(sqlite3_context* context); +double APS5_VABI sqlite3_value_double_nid_postfix(sqlite3_value* value); +const void* APS5_VABI sqlite3_value_text16_nid_postfix(sqlite3_value* value); +int APS5_VABI sqlite3_value_type_nid_postfix(sqlite3_value* value); +int APS5_VABI sqlite3_wal_checkpoint_v2_nid_postfix(sqlite3* db, const char* schema, int mode, int* logFrames, + int* checkpointedFrames); +void* APS5_VABI sqlite3_wal_hook_nid_postfix(sqlite3* db, NKWebKit::GuestSqliteWalHook hook, void* argument); +} diff --git a/core/libs/prx/libSceNKWebKitRequirements/libSceNKWebKitRequirements.hpp b/core/libs/prx/libSceNKWebKitRequirements/libSceNKWebKitRequirements.hpp index f487bd43a8..91dd8b8a5f 100644 --- a/core/libs/prx/libSceNKWebKitRequirements/libSceNKWebKitRequirements.hpp +++ b/core/libs/prx/libSceNKWebKitRequirements/libSceNKWebKitRequirements.hpp @@ -1,3 +1,4 @@ #pragma once #include "prx/libSceNKWebKitRequirements/include/InventoryExports.hpp" #include "prx/libSceNKWebKitRequirements/include/Identified.hpp" +#include "prx/libSceNKWebKitRequirements/include/Sqlite.hpp" diff --git a/core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp b/core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp new file mode 100644 index 0000000000..48f7a629ad --- /dev/null +++ b/core/libs/prx/libSceNKWebKitRequirements/src/Sqlite.cpp @@ -0,0 +1,424 @@ +#include "prx/libSceNKWebKitRequirements/include/Sqlite.hpp" +#include "prx/libc/include/General.hpp" + +#include + +#include +#include +#include +#include +#include + +namespace { + +constexpr const char* GuestHooksKey = "anyps5.guest"; + +struct GuestHooks { + NKWebKit::GuestSqliteBusy busy = nullptr; + void* busyArgument = nullptr; + NKWebKit::GuestSqliteAuthorizer authorizer = nullptr; + void* authorizerArgument = nullptr; + NKWebKit::GuestSqliteWalHook walHook = nullptr; + void* walArgument = nullptr; +}; + +struct GuestCollation { + NKWebKit::GuestSqliteCompare compare; + NKWebKit::GuestSqliteDestructor destroy; + void* argument; +}; + +struct GuestFunction { + NKWebKit::GuestSqliteFunction function; + NKWebKit::GuestSqliteFunction step; + NKWebKit::GuestSqliteFinal final; + void* application; +}; + +void FreeHooks(void* hooks) { + delete static_cast(hooks); +} + +GuestHooks* Hooks(sqlite3* db) { + if (auto* hooks = static_cast(sqlite3_get_clientdata(db, GuestHooksKey))) { + return hooks; + } + auto* hooks = new (std::nothrow) GuestHooks(); + if (hooks && sqlite3_set_clientdata(db, GuestHooksKey, hooks, FreeHooks) != SQLITE_OK) { + return nullptr; + } + return hooks; +} + +int CallBusy(void* hooks, int count) { + auto* guest = static_cast(hooks); + return guest->busy(guest->busyArgument, count); +} + +int CallAuthorizer(void* hooks, int action, const char* first, const char* second, const char* schema, + const char* trigger) { + if (action == SQLITE_ATTACH && first != nullptr && *first != '\0') return SQLITE_DENY; + auto* guest = static_cast(hooks); + if (guest->authorizer == nullptr) return SQLITE_OK; + return guest->authorizer(guest->authorizerArgument, action, first, second, schema, trigger); +} + +int CallWalHook(void* hooks, sqlite3* db, const char* schema, int frames) { + auto* guest = static_cast(hooks); + return guest->walHook(guest->walArgument, db, schema, frames); +} + +int CallCompare(void* collation, int leftSize, const void* left, int rightSize, const void* right) { + auto* guest = static_cast(collation); + return guest->compare(guest->argument, leftSize, left, rightSize, right); +} + +void DestroyCollation(void* collation) { + auto* guest = static_cast(collation); + if (guest->destroy) { + guest->destroy(guest->argument); + } + delete guest; +} + +void CallFunction(sqlite3_context* context, int count, sqlite3_value** values) { + static_cast(sqlite3_user_data(context))->function(context, count, values); +} + +void CallStep(sqlite3_context* context, int count, sqlite3_value** values) { + static_cast(sqlite3_user_data(context))->step(context, count, values); +} + +void CallFinal(sqlite3_context* context) { + static_cast(sqlite3_user_data(context))->final(context); +} + +void DestroyFunction(void* function) { + delete static_cast(function); +} + +bool SpecialDestructor(NKWebKit::GuestSqliteDestructor destructor) { + const auto value = reinterpret_cast(destructor); + return value == 0 || value == -1; +} + +} + +extern "C" { + +int APS5_VABI sqlite3_bind_blob_nid_postfix(sqlite3_stmt* statement, int index, const void* data, int size, + NKWebKit::GuestSqliteDestructor destructor) { + const bool owned = !SpecialDestructor(destructor); + const int result = sqlite3_bind_blob(statement, index, data, size, + owned || destructor ? SQLITE_TRANSIENT : SQLITE_STATIC); + if (owned && data) { + destructor(const_cast(data)); + } + return result; +} + +int APS5_VABI sqlite3_bind_double_nid_postfix(sqlite3_stmt* statement, int index, double value) { + return sqlite3_bind_double(statement, index, value); +} + +int APS5_VABI sqlite3_bind_int_nid_postfix(sqlite3_stmt* statement, int index, int value) { + return sqlite3_bind_int(statement, index, value); +} + +int APS5_VABI sqlite3_bind_int64_nid_postfix(sqlite3_stmt* statement, int index, std::int64_t value) { + return sqlite3_bind_int64(statement, index, value); +} + +int APS5_VABI sqlite3_bind_null_nid_postfix(sqlite3_stmt* statement, int index) { + return sqlite3_bind_null(statement, index); +} + +int APS5_VABI sqlite3_bind_parameter_count_nid_postfix(sqlite3_stmt* statement) { + return sqlite3_bind_parameter_count(statement); +} + +int APS5_VABI sqlite3_bind_text16_nid_postfix(sqlite3_stmt* statement, int index, const void* text, int size, + NKWebKit::GuestSqliteDestructor destructor) { + const bool owned = !SpecialDestructor(destructor); + const int result = sqlite3_bind_text16(statement, index, text, size, + owned || destructor ? SQLITE_TRANSIENT : SQLITE_STATIC); + if (owned && text) { + destructor(const_cast(text)); + } + return result; +} + +int APS5_VABI sqlite3_busy_handler_nid_postfix(sqlite3* db, NKWebKit::GuestSqliteBusy handler, void* argument) { + if (!db || !handler) { + return sqlite3_busy_handler(db, nullptr, nullptr); + } + sqlite3_mutex_enter(sqlite3_db_mutex(db)); + GuestHooks* hooks = Hooks(db); + int result = SQLITE_NOMEM; + if (hooks) { + hooks->busy = handler; + hooks->busyArgument = argument; + result = sqlite3_busy_handler(db, CallBusy, hooks); + } + sqlite3_mutex_leave(sqlite3_db_mutex(db)); + return result; +} + +int APS5_VABI sqlite3_busy_timeout_nid_postfix(sqlite3* db, int milliseconds) { + return sqlite3_busy_timeout(db, milliseconds); +} + +int APS5_VABI sqlite3_clear_bindings_nid_postfix(sqlite3_stmt* statement) { + return sqlite3_clear_bindings(statement); +} + +int APS5_VABI sqlite3_close_nid_postfix(sqlite3* db) { + return sqlite3_close(db); +} + +const void* APS5_VABI sqlite3_column_blob_nid_postfix(sqlite3_stmt* statement, int column) { + return sqlite3_column_blob(statement, column); +} + +int APS5_VABI sqlite3_column_bytes_nid_postfix(sqlite3_stmt* statement, int column) { + return sqlite3_column_bytes(statement, column); +} + +int APS5_VABI sqlite3_column_bytes16_nid_postfix(sqlite3_stmt* statement, int column) { + return sqlite3_column_bytes16(statement, column); +} + +const char* APS5_VABI sqlite3_column_decltype_nid_postfix(sqlite3_stmt* statement, int column) { + return sqlite3_column_decltype(statement, column); +} + +double APS5_VABI sqlite3_column_double_nid_postfix(sqlite3_stmt* statement, int column) { + return sqlite3_column_double(statement, column); +} + +int APS5_VABI sqlite3_column_int_nid_postfix(sqlite3_stmt* statement, int column) { + return sqlite3_column_int(statement, column); +} + +std::int64_t APS5_VABI sqlite3_column_int64_nid_postfix(sqlite3_stmt* statement, int column) { + return sqlite3_column_int64(statement, column); +} + +const void* APS5_VABI sqlite3_column_name16_nid_postfix(sqlite3_stmt* statement, int column) { + return sqlite3_column_name16(statement, column); +} + +const void* APS5_VABI sqlite3_column_text16_nid_postfix(sqlite3_stmt* statement, int column) { + return sqlite3_column_text16(statement, column); +} + +int APS5_VABI sqlite3_column_type_nid_postfix(sqlite3_stmt* statement, int column) { + return sqlite3_column_type(statement, column); +} + +sqlite3_value* APS5_VABI sqlite3_column_value_nid_postfix(sqlite3_stmt* statement, int column) { + return sqlite3_column_value(statement, column); +} + +int APS5_VABI sqlite3_create_collation_v2_nid_postfix(sqlite3* db, const char* name, int encoding, void* argument, + NKWebKit::GuestSqliteCompare compare, NKWebKit::GuestSqliteDestructor destroy) { + auto* collation = new (std::nothrow) GuestCollation{compare, destroy, argument}; + if (!collation) { + return SQLITE_NOMEM; + } + const int result = sqlite3_create_collation_v2(db, name, encoding, collation, compare ? CallCompare : nullptr, + DestroyCollation); + if (result != SQLITE_OK) { + delete collation; + } + return result; +} + +int APS5_VABI sqlite3_create_function_nid_postfix(sqlite3* db, const char* name, int arguments, int encoding, + void* application, NKWebKit::GuestSqliteFunction function, NKWebKit::GuestSqliteFunction step, + NKWebKit::GuestSqliteFinal final) { + auto* guest = new (std::nothrow) GuestFunction{function, step, final, application}; + if (!guest) { + return SQLITE_NOMEM; + } + return sqlite3_create_function_v2(db, name, arguments, encoding, guest, function ? CallFunction : nullptr, + step ? CallStep : nullptr, final ? CallFinal : nullptr, DestroyFunction); +} + +int APS5_VABI sqlite3_data_count_nid_postfix(sqlite3_stmt* statement) { + return sqlite3_data_count(statement); +} + +int APS5_VABI sqlite3_errcode_nid_postfix(sqlite3* db) { + return sqlite3_errcode(db); +} + +const char* APS5_VABI sqlite3_errmsg_nid_postfix(sqlite3* db) { + return sqlite3_errmsg(db); +} + +const char* APS5_VABI sqlite3_errstr_nid_postfix(int code) { + return sqlite3_errstr(code); +} + +int APS5_VABI sqlite3_extended_result_codes_nid_postfix(sqlite3* db, int enabled) { + return sqlite3_extended_result_codes(db, enabled); +} + +int APS5_VABI sqlite3_finalize_nid_postfix(sqlite3_stmt* statement) { + return sqlite3_finalize(statement); +} + +int APS5_VABI sqlite3_get_autocommit_nid_postfix(sqlite3* db) { + return sqlite3_get_autocommit(db); +} + +int APS5_VABI sqlite3_initialize_nid_postfix() { + return sqlite3_initialize(); +} + +void APS5_VABI sqlite3_interrupt_nid_postfix(sqlite3* db) { + sqlite3_interrupt(db); +} + +std::int64_t APS5_VABI sqlite3_last_insert_rowid_nid_postfix(sqlite3* db) { + return sqlite3_last_insert_rowid(db); +} + +sqlite3_stmt* APS5_VABI sqlite3_next_stmt_nid_postfix(sqlite3* db, sqlite3_stmt* statement) { + return sqlite3_next_stmt(db, statement); +} + +static bool MemoryUri(const char* filename) { + const char* path = filename + 5; + if (std::strchr(path, '%') != nullptr) return false; + const char* end = path + std::strcspn(path, "?#"); + if (std::string_view(path, static_cast(end - path)) == ":memory:") return true; + if (*end != '?') return false; + bool memory = false; + for (const char* parameter = end + 1; *parameter != '\0' && *parameter != '#';) { + const char* stop = parameter + std::strcspn(parameter, "&#"); + const std::string_view text(parameter, static_cast(stop - parameter)); + if (text.substr(0, 5) == "mode=") memory = text.substr(5) == "memory"; + parameter = *stop == '&' ? stop + 1 : stop; + } + return memory; +} + +static int GuardAttach(int result, sqlite3** db) { + if (result != SQLITE_OK || db == nullptr || *db == nullptr) return result; + sqlite3_mutex_enter(sqlite3_db_mutex(*db)); + GuestHooks* hooks = Hooks(*db); + const int installed = hooks ? sqlite3_set_authorizer(*db, CallAuthorizer, hooks) : SQLITE_NOMEM; + sqlite3_mutex_leave(sqlite3_db_mutex(*db)); + if (installed == SQLITE_OK) return SQLITE_OK; + sqlite3_close(*db); + *db = nullptr; + return installed; +} + +int APS5_VABI sqlite3_open_v2_nid_postfix(const char* filename, sqlite3** db, int flags, const char* vfs) { + static_cast(vfs); + const bool uri = filename && (flags & SQLITE_OPEN_URI) && std::strncmp(filename, "file:", 5) == 0; + const bool memory = uri ? MemoryUri(filename) : + (!filename || !*filename || std::strcmp(filename, ":memory:") == 0 || (flags & SQLITE_OPEN_MEMORY)); + if (memory) { + return GuardAttach(sqlite3_open_v2(filename, db, flags, nullptr), db); + } + if (uri) { + NotImplemented_nid_no_patch("sqlite3_open_v2 file URI"); + } + const std::filesystem::path host = ResolvePath_nid_no_patch(filename); + const int result = GuardAttach(sqlite3_open_v2(reinterpret_cast(host.u8string().c_str()), db, flags, nullptr), db); + if (result == SQLITE_OK && (flags & SQLITE_OPEN_READWRITE)) { + for (const char* suffix : {"", "-wal", "-shm", "-journal"}) { + std::filesystem::path file = host; + file += suffix; + RecordWrittenPath_nid_no_patch(file); + } + } + return result; +} + +int APS5_VABI sqlite3_prepare_v2_nid_postfix(sqlite3* db, const char* sql, int size, sqlite3_stmt** statement, + const char** tail) { + return sqlite3_prepare_v2(db, sql, size, statement, tail); +} + +int APS5_VABI sqlite3_reset_nid_postfix(sqlite3_stmt* statement) { + return sqlite3_reset(statement); +} + +void APS5_VABI sqlite3_result_error_nid_postfix(sqlite3_context* context, const char* message, int size) { + sqlite3_result_error(context, message, size); +} + +int APS5_VABI sqlite3_set_authorizer_nid_postfix(sqlite3* db, NKWebKit::GuestSqliteAuthorizer authorizer, + void* argument) { + if (!db) { + return sqlite3_set_authorizer(db, nullptr, nullptr); + } + sqlite3_mutex_enter(sqlite3_db_mutex(db)); + GuestHooks* hooks = Hooks(db); + int result = SQLITE_NOMEM; + if (hooks) { + hooks->authorizer = authorizer; + hooks->authorizerArgument = argument; + result = sqlite3_set_authorizer(db, CallAuthorizer, hooks); + } + sqlite3_mutex_leave(sqlite3_db_mutex(db)); + return result; +} + +const char* APS5_VABI sqlite3_sql_nid_postfix(sqlite3_stmt* statement) { + return sqlite3_sql(statement); +} + +int APS5_VABI sqlite3_step_nid_postfix(sqlite3_stmt* statement) { + return sqlite3_step(statement); +} + +int APS5_VABI sqlite3_stmt_busy_nid_postfix(sqlite3_stmt* statement) { + return sqlite3_stmt_busy(statement); +} + +int APS5_VABI sqlite3_total_changes_nid_postfix(sqlite3* db) { + return sqlite3_total_changes(db); +} + +void* APS5_VABI sqlite3_user_data_nid_postfix(sqlite3_context* context) { + return static_cast(sqlite3_user_data(context))->application; +} + +double APS5_VABI sqlite3_value_double_nid_postfix(sqlite3_value* value) { + return sqlite3_value_double(value); +} + +const void* APS5_VABI sqlite3_value_text16_nid_postfix(sqlite3_value* value) { + return sqlite3_value_text16(value); +} + +int APS5_VABI sqlite3_value_type_nid_postfix(sqlite3_value* value) { + return sqlite3_value_type(value); +} + +int APS5_VABI sqlite3_wal_checkpoint_v2_nid_postfix(sqlite3* db, const char* schema, int mode, int* logFrames, + int* checkpointedFrames) { + return sqlite3_wal_checkpoint_v2(db, schema, mode, logFrames, checkpointedFrames); +} + +void* APS5_VABI sqlite3_wal_hook_nid_postfix(sqlite3* db, NKWebKit::GuestSqliteWalHook hook, void* argument) { + sqlite3_mutex_enter(sqlite3_db_mutex(db)); + GuestHooks* hooks = Hooks(db); + void* previous = nullptr; + if (hooks) { + void* host = sqlite3_wal_hook(db, hook ? CallWalHook : nullptr, hook ? hooks : nullptr); + previous = host == hooks ? hooks->walArgument : host; + hooks->walHook = hook; + hooks->walArgument = argument; + } + sqlite3_mutex_leave(sqlite3_db_mutex(db)); + return previous; +} + +} diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index 9e14d46d5e..c070fbdf17 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -9,7 +9,6 @@ #include #else #include -#include #include #include #include @@ -25,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -153,6 +153,10 @@ constexpr int NET_SOCK_RAW = 3; constexpr int NET_EPOLL_CTL_ADD = 1; constexpr int NET_EPOLL_CTL_MOD = 2; constexpr int NET_EPOLL_CTL_DEL = 3; +constexpr std::uint32_t NET_EPOLLIN = 0x1; +constexpr std::uint32_t NET_EPOLLHUP = 0x10; +constexpr std::uint32_t NET_EPOLLDESCID = 0x10000; +constexpr int NET_RESOLVER_ASYNC = 0x1; constexpr int NET_SOL_SOCKET = 0xFFFF; constexpr int NET_IPPROTO_TCP = 6; constexpr int NET_IPPROTO_IPV6 = 41; @@ -383,12 +387,16 @@ struct Resolver { std::uint64_t aborts = 0; bool abort_ntoa = false; bool abort_aton = false; + std::uint64_t completions = 0; + bool hangup = false; }; std::map g_resolvers; +std::map> g_epoll_resolvers_reported; int g_next_sock = 32; int g_next_epoll = 0x4000; int g_next_pool = 1; -int g_next_resolver = 0x100; +constexpr int NET_RESOLVER_ID_FIRST = 1024; +constexpr int NET_RESOLVER_ID_END = 0x4000; bool g_net_inited = false; bool g_net_init_param_set = false; std::array g_net_init_param{}; @@ -540,6 +548,17 @@ int end_lookup(int rid, std::uint64_t generation, int error) { return error; } +int complete_async_lookup(int rid, int error) { + std::lock_guard lk(g_mutex); + const auto resolver = g_resolvers.find(rid); + if (resolver == g_resolvers.end()) return error; + resolver->second.error = error; + resolver->second.hangup = error == (NET_ERROR_BASE | NET_EINTR); + ++resolver->second.completions; + g_cv.NotifyAll(); + return 0; +} + void log_soft(const char* func, const char* what) { static std::mutex mtx; static std::map hits; @@ -1036,6 +1055,17 @@ bool ControlSocketStatusForKernel(int descriptor, int command, int flags, int* r const bool g_socketStatusControlRegistered = (KernelSetSocketStatusControl_nid_no_patch(&ControlSocketStatusForKernel), true); +bool ControlSocketIoctlForKernel(int descriptor, std::uint32_t request, void* argument, int* error) { + { + std::lock_guard lk(g_mutex); + if (g_socks.find(descriptor) == g_socks.end()) return false; + } + *error = sceNetIoctl(descriptor, request, argument) == 0 ? 0 : *errno_slot(); + return true; +} + +const bool g_socketIoctlControlRegistered = (KernelSetSocketIoctlControl_nid_no_patch(&ControlSocketIoctlForKernel), true); + bool FindSocketNativeForKernel(int descriptor, std::shared_ptr* owner, std::uintptr_t* native) { std::lock_guard lk(g_mutex); const auto it = g_socks.find(descriptor); @@ -2293,6 +2323,7 @@ int APS5_VABI sceNetEpollDestroy(int eid) { } g_epoll_names.erase(eid); g_epoll_socks.erase(eid); + g_epoll_resolvers_reported.erase(eid); g_epoll_aborted.erase(eid); g_cv.NotifyAll(); return 0; @@ -2322,13 +2353,16 @@ int APS5_VABI sceNetEpollControl(int eid, int op, int id, const NetEpollEvent* e } if (op != NET_EPOLL_CTL_ADD && op != NET_EPOLL_CTL_MOD && op != NET_EPOLL_CTL_DEL) return fail(NET_EINVAL); if (op != NET_EPOLL_CTL_DEL && !event) return fail(NET_EINVAL); - if (!kernel && g_socks.count(id) == 0) return fail(NET_EBADF); + const bool resolver = g_resolvers.count(id) != 0; + if (!kernel && !resolver && g_socks.count(id) == 0) return fail(NET_EBADF); auto& registrations = g_epoll_socks[eid]; if (op == NET_EPOLL_CTL_ADD) { if (registrations.count(id) != 0) return fail(NET_EADDRINUSE); registrations.emplace(id, std::make_shared(*event)); + if (resolver) g_epoll_resolvers_reported[eid][id] = 0; } else if (op == NET_EPOLL_CTL_DEL) { if (registrations.erase(id) == 0) return fail(NET_ENOENT); + g_epoll_resolvers_reported[eid].erase(id); } else { auto registration = registrations.find(id); if (registration == registrations.end()) return fail(NET_ENOENT); @@ -2359,7 +2393,22 @@ int APS5_VABI sceNetEpollWait(int eid, NetEpollEvent* events, int maxevents, int if (g_epoll_aborted.erase(eid) != 0) return fail(NET_ECONNABORTED); const auto registration = g_epoll_socks.find(eid); if (registration != g_epoll_socks.end()) { + int count = 0; + auto& reported = g_epoll_resolvers_reported[eid]; for (const auto& entry : registration->second) { + const auto resolver = g_resolvers.find(entry.first); + if (resolver == g_resolvers.end() || count >= maxevents) continue; + if ((entry.second->events & NET_EPOLLIN) == 0 || reported[entry.first] == resolver->second.completions) continue; + reported[entry.first] = resolver->second.completions; + events[count] = *entry.second; + events[count].ident = static_cast(entry.first); + events[count].reserved = 0; + events[count].events = NET_EPOLLIN | NET_EPOLLDESCID | (resolver->second.hangup ? NET_EPOLLHUP : 0u); + ++count; + } + if (count > 0) return count; + for (const auto& entry : registration->second) { + if (g_resolvers.count(entry.first) != 0) continue; const auto socket = g_socks.find(entry.first); if (socket == g_socks.end()) kernelWatches.push_back({entry.first, entry.second, Sock{}, nullptr, INVALID_NATIVE_SOCKET}); else watches.push_back({entry.first, entry.second, socket->second, nullptr, socket->second.native->value}); @@ -2586,14 +2635,19 @@ int APS5_VABI sceNetResolverCreate(const char* name, int memid, int flags) { (void)memid; if (flags != 0) return fail(NET_EINVAL); std::lock_guard lk(g_mutex); - const int id = g_next_resolver++; + int id = NET_RESOLVER_ID_FIRST; + for (auto it = g_resolvers.lower_bound(id); it != g_resolvers.end() && it->first == id; ++it) ++id; + if (id >= NET_RESOLVER_ID_END) throw std::runtime_error("sceNetResolverCreate: more than " + std::to_string(NET_RESOLVER_ID_END - NET_RESOLVER_ID_FIRST) + " live resolvers are not implemented"); g_resolvers[id] = {}; return id; } int APS5_VABI sceNetResolverDestroy(int rid) { std::lock_guard lk(g_mutex); - return g_resolvers.erase(rid) != 0 ? 0 : fail(NET_EBADF); + if (g_resolvers.erase(rid) == 0) return fail(NET_EBADF); + for (auto& epoll : g_epoll_socks) epoll.second.erase(rid); + for (auto& epoll : g_epoll_resolvers_reported) epoll.second.erase(rid); + return 0; } int lookup_host(int rid, const char* hostname, const char* func, int family, std::vector>& addresses) { @@ -2649,9 +2703,9 @@ int APS5_VABI sceNetResolverStartNtoa(int rid, const char* hostname, void* addr, return fail(NET_EINVAL); if (!hostname || !addr) return fail(NET_EINVAL); std::vector addresses; - if (const int error = lookup_ipv4(rid, hostname, __func__, addresses)) return error; - std::memcpy(addr, addresses.data(), sizeof(addresses[0])); - return 0; + int error = lookup_ipv4(rid, hostname, __func__, addresses); + if (error == 0) std::memcpy(addr, addresses.data(), sizeof(addresses[0])); + return (flags & NET_RESOLVER_ASYNC) != 0 ? complete_async_lookup(rid, error) : error; } int APS5_VABI sceNetResolverStartNtoa6(int rid, const char* hostname, void* addr, int timeout, int retry, int flags) { @@ -2808,7 +2862,11 @@ int APS5_VABI sceNetClearDnsCache(int flags) { int APS5_VABI sceNetGetStatisticsInfo(NetStatisticsInfo* info, std::uint32_t flags) { if (info == nullptr) return fail(NET_EFAULT); constexpr std::int32_t FREE_BYTES = 16 * 1024 * 1024; - *info = {FREE_BYTES, FREE_BYTES, 0, 0, FREE_BYTES, FREE_BYTES}; + const NetStatisticsInfo stats{FREE_BYTES, FREE_BYTES, 0, 0, FREE_BYTES, FREE_BYTES}; + const int savedErrno = errno; + const bool copied = GuestCopyOut_nid_no_patch(info, &stats, sizeof(stats)); + errno = savedErrno; + if (!copied) return fail(NET_EFAULT); return 0; } @@ -3003,10 +3061,17 @@ int APS5_VABI sceNetGetNameToIndex(std::uint32_t interfaceId) { int APS5_VABI sceNetGetRandom(std::uint32_t* output) { if (!g_net_inited) return fail(NET_ENOTINIT); if (output == nullptr) return fail(NET_EINVAL); - static std::mutex mutex; - static std::random_device device; - const std::lock_guard lock(mutex); - *output = static_cast(device()); + const int savedErrno = errno; + std::uint32_t value{}; + { + static std::mutex mutex; + static std::random_device device; + const std::lock_guard lock(mutex); + value = static_cast(device()); + } + const bool copied = GuestCopyOut_nid_no_patch(output, &value, sizeof(value)); + errno = savedErrno; + if (!copied) return fail(NET_EFAULT); return 0; } @@ -3774,13 +3839,15 @@ int APS5_VABI sceNetDumpRead(std::int32_t dumpId, void* buffer, std::int32_t buf } int APS5_VABI sceNetEmulationDebugSettingsSet(const NetEmulationParameter* parameter, std::uint32_t flags) { - NotImplemented_nid_no_patch(__func__); - return 0; + (void)parameter; + (void)flags; + return fail(NET_EOPNOTSUPP); } int APS5_VABI sceNetEmulationDebugSettingsGet(NetEmulationParameter* parameter, std::uint32_t flags) { - NotImplemented_nid_no_patch(__func__); - return 0; + (void)parameter; + (void)flags; + return fail(NET_EOPNOTSUPP); } int APS5_VABI sceNetEmulationSet(const NetEmulationParameter*, std::uint32_t) { diff --git a/core/libs/prx/libSceNgs2.native/src/Custom.cpp b/core/libs/prx/libSceNgs2.native/src/Custom.cpp index 68aaa0efdc..d578d63b03 100644 --- a/core/libs/prx/libSceNgs2.native/src/Custom.cpp +++ b/core/libs/prx/libSceNgs2.native/src/Custom.cpp @@ -79,7 +79,9 @@ int Ngs2CheckCustomRack(std::uint32_t rackId, const Ngs2CustomRackOption& option } else if (info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_FILTER) { if (info.option != nullptr || info.state_size != 0 || info.state_offset != 0) APS5_INVALID_ARG_EX; } else if (info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_USER_FX) { - if (rackId != SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) throw std::runtime_error("NGS2: the custom UserFx module on a custom sampler rack is not implemented"); + if (rackId != SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER && rackId != SCE_NGS2_RACK_ID_CUSTOM_SAMPLER) { + throw std::runtime_error("NGS2: the custom UserFx module on this rack is not implemented"); + } if (info.option != nullptr) throw std::runtime_error("NGS2: a custom UserFx module option is not implemented"); } else if (info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_DELAY) { CheckDelayModule(rackId, info); @@ -421,8 +423,15 @@ void Ngs2ApplyCustomParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param) { return; } if ((param.id & ~0x1fu) == SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX) { - if (rackId != SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) throw std::invalid_argument("NGS2: voice param " + Ngs2Hex(param.id) + " belongs to another rack"); - SetCustomUserFxParam(voice, param.id & 0x1f, ParamAs(param)); + if (rackId != SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER && rackId != SCE_NGS2_RACK_ID_CUSTOM_SAMPLER) { + throw std::invalid_argument("NGS2: voice param " + Ngs2Hex(param.id) + " belongs to another rack"); + } + auto index = param.id & 0x1f; + if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SAMPLER) { + if (index == 0 || index - 1 >= voice.customUserFx.size()) throw Ngs2ReturnCode{SCE_NGS2_ERROR_INVALID_PARAM_ID}; + index--; + } + SetCustomUserFxParam(voice, index, ParamAs(param)); return; } if (rackId != SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) throw std::invalid_argument("NGS2: voice param " + Ngs2Hex(param.id) + " belongs to another rack"); diff --git a/core/libs/prx/libSceNgs2.native/src/Voice.cpp b/core/libs/prx/libSceNgs2.native/src/Voice.cpp index 92ce82b597..fb88eb92e6 100644 --- a/core/libs/prx/libSceNgs2.native/src/Voice.cpp +++ b/core/libs/prx/libSceNgs2.native/src/Voice.cpp @@ -1102,6 +1102,11 @@ int APS5_VABI sceNgs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamHe if (voice.rack->system == nullptr && !voice.pendingControls.empty()) voice.ApplyPendingControls(); try { for (const auto* param = param_list;; param = reinterpret_cast(reinterpret_cast(param) + param->next)) { + if (!PS4_LIBRARY && voice.rack->rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER && (param->id & 0xffff00e0u) == 0x40000000u) { + const auto& modules = voice.rack->customModuleIds; + const auto index = param->id & 0x1fu; + if (index >= modules.size() || modules[index] != ((param->id >> 8) & 0xffu)) return SCE_NGS2_ERROR_INVALID_PARAM_ID; + } if ((param->id & ~0xffu) == SCE_NGS2_CUSTOM_VOICE_PARAM_PITCH_SHIFT) { const auto index = param->id & 0x1fu; if (index >= voice.customPitchShift.size() || voice.rack->customModuleIds[index] != SCE_NGS2_CUSTOM_MODULE_ID_PITCH_SHIFT) { diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp index fc8fd635f4..1877596a0d 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp @@ -137,6 +137,17 @@ static bool ControlThrows(uintptr_t voice, std::uint32_t id, TParam param) { return false; } +template +static int ControlResult(uintptr_t voice, std::uint32_t id, TParam param) { + param.header = {static_cast(sizeof(TParam)), 0, id}; + return sceNgs2VoiceControl(voice, ¶m.header); +} + +struct AnyModuleParam { + Ngs2VoiceParamHeader header; + std::uint8_t body[0x100]; +}; + static uintptr_t CustomRack(uintptr_t system, const Ngs2CustomSubmixerRackOption& option) { Ngs2ContextBufferInfo query{}; Require(sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER, &option.custom_rack_option.rack_option, &query) == SCE_NGS2_OK); @@ -195,7 +206,10 @@ static void TestUserFxChain() { Require(calls.processFlags == (std::vector{3, 2, 0})); Require(ControlThrows(custom, SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX2 | 0, Ngs2CustomVoiceUserFx2Param{{}, &gain, sizeof(gain) - 1})); - Require(ControlThrows(custom, SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX2 | 2, Ngs2CustomVoiceUserFx2Param{{}, &gain, sizeof(gain)})); + for (const std::uint32_t id : {SCE_NGS2_CUSTOM_VOICE_PARAM_REVERB, SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX, SCE_NGS2_CUSTOM_VOICE_PARAM_FILTER, SCE_NGS2_CUSTOM_VOICE_PARAM_GAIN, + SCE_NGS2_CUSTOM_VOICE_PARAM_DELAY | 1, SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX2 | 2}) { + Require(ControlResult(custom, id, AnyModuleParam{}) == SCE_NGS2_ERROR_INVALID_PARAM_ID); + } Require(ControlThrows(custom, SCE_NGS2_CUSTOM_SUBMIXER_VOICE_PARAM_SETUP, Ngs2CustomSubmixerVoiceSetupParam{{}, 2, 1, 0, 0})); Require(ControlThrows(custom, SCE_NGS2_CUSTOM_SUBMIXER_VOICE_PARAM_SETUP, Ngs2CustomSubmixerVoiceSetupParam{{}, 2, 2, 1, 0})); Require(ControlThrows(custom, SCE_NGS2_CUSTOM_SUBMIXER_VOICE_PARAM_SETUP, Ngs2CustomSubmixerVoiceSetupParam{{}, 3, 3, 0, 0})); @@ -651,24 +665,13 @@ static void TestCustomRackChecks() { for (std::uint32_t index = 0; index < count; ++index) { sampler.custom_rack_option.module[index].module_id = SCE_NGS2_CUSTOM_MODULE_ID_FILTER; } - sampler.custom_rack_option.module[count - 1].module_id = 0x19; - Require(CreateSamplerThrows(sampler)); - for (const bool useAllocator : {false, true}) { - rack = 0; - rejected = false; - try { - if (useAllocator) { - sceNgs2RackCreateWithAllocator(system, SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &sampler.custom_rack_option.rack_option, &allocator, &rack); - - } else { - sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &sampler.custom_rack_option.rack_option, &info, &rack); - - } - } catch (const std::runtime_error&) { - rejected = true; - } - Require(rejected && rack == 0 && allocCalls == 0); - } + sampler.custom_rack_option.module[count - 1].module_id = SCE_NGS2_CUSTOM_MODULE_ID_USER_FX; + Ngs2ContextBufferInfo counted{}; + Require(sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &sampler.custom_rack_option.rack_option, &counted) == SCE_NGS2_OK); + const auto countedInfo = Buffer(counted); + rack = 0; + Require(sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &sampler.custom_rack_option.rack_option, &countedInfo, &rack) == SCE_NGS2_OK); + Require(sceNgs2RackDestroy(rack, nullptr) == SCE_NGS2_OK); } Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); } @@ -741,11 +744,11 @@ static void TestCustomLegacyUserFx(std::uint32_t moduleIndex) { std::uint8_t dummy[10]; }; Require(ControlThrows(custom, SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX | 0, BadSizeParam{})); - Require(ControlThrows(custom, SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX | 31, Ngs2CustomVoiceUserFxParam{{}, ProcessLegacy, 0, 0, 0})); + Require(ControlResult(custom, SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX | 31, Ngs2CustomVoiceUserFxParam{{}, ProcessLegacy, 0, 0, 0}) == SCE_NGS2_ERROR_INVALID_PARAM_ID); Require(CreateThrows(LegacyRackOption(&header, moduleIndex))); - Require(ControlThrows(custom, 0x40001d00, - Ngs2CustomVoiceUserFxParam{{}, ProcessLegacy, reinterpret_cast(&state), 123, 456})); + Require(ControlResult(custom, 0x40001d00, Ngs2CustomVoiceUserFxParam{{}, ProcessLegacy, reinterpret_cast(&state), 123, 456}) == + SCE_NGS2_ERROR_INVALID_PARAM_ID); state.result = -1; bool failed = false; @@ -778,6 +781,64 @@ static void TestCustomLegacyUserFx(std::uint32_t moduleIndex) { Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); } +static Ngs2CustomSamplerRackOption LegacySamplerRackOption(const Ngs2CustomModuleOption* option, std::uint32_t moduleIndex) { + Ngs2CustomSamplerRackOption rack{}; + auto& common = rack.custom_rack_option.rack_option; + common.size = sizeof(rack); + common.max_grain_samples = 512; + common.max_voices = 1; + common.max_matrices = 1; + common.max_ports = 1; + rack.custom_rack_option.num_buffers = 1; + rack.custom_rack_option.num_modules = moduleIndex + 1; + for (std::uint32_t index = 0; index <= moduleIndex; ++index) { + rack.custom_rack_option.module[index].option = option; + rack.custom_rack_option.module[index].module_id = SCE_NGS2_CUSTOM_MODULE_ID_USER_FX; + } + rack.max_channel_works = 1; + rack.max_waveform_blocks = 1; + return rack; +} + +static void TestCustomSamplerLegacyUserFx(std::uint32_t moduleIndex) { + const auto system = CreateSystem(); + const auto master = Mastering(system, 2); + auto option = LegacySamplerRackOption(nullptr, moduleIndex); + Ngs2ContextBufferInfo query{}; + Require(sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &option.custom_rack_option.rack_option, &query) == SCE_NGS2_OK); + const auto info = Buffer(query); + uintptr_t rack = 0; + Require(sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &option.custom_rack_option.rack_option, &info, &rack) == SCE_NGS2_OK); + + const auto voice = Voice(rack); + Control(voice, SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_SETUP, + Ngs2SamplerVoiceSetupParam{{}, {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 2, 48000, 0, 0, 0}}); + std::vector pcm(Grain * 2 * 8, 16384); + const Ngs2WaveformBlock block{0, pcm.size() * sizeof(std::int16_t), 1, 0, static_cast(pcm.size() / 2), 0, 0}; + Control(voice, SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS, + Ngs2SamplerVoiceWaveformBlocksParam{{}, pcm.data(), 0, 1, &block}); + Patch(voice, master); + Event(voice, SCE_NGS2_VOICE_EVENT_PLAY); + + LegacyUserFxState state{}; + const auto param = Ngs2CustomVoiceUserFxParam{{}, ProcessLegacy, reinterpret_cast(&state), 123, 456}; + Control(voice, SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX | (moduleIndex + 1), param); + auto out = Render(system); + Require(state.calls == 1 && state.flags == 3); + for (std::uint32_t i = 0; i < Grain; i++) Require(out[i * 2] == 0.25f && out[i * 2 + 1] == 0.5f); + out = Render(system); + Require(state.calls == 2 && state.flags == 0); + for (std::uint32_t i = 0; i < Grain; i++) Require(out[i * 2] == 0.25f && out[i * 2 + 1] == 0.5f); + + Ngs2CustomVoiceUserFxParam invalid{}; + invalid.header = {sizeof(invalid), 0, SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX | 0}; + Require(sceNgs2VoiceControl(voice, &invalid.header) == SCE_NGS2_ERROR_INVALID_PARAM_ID); + invalid.header.id = SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX | (moduleIndex + 2); + Require(sceNgs2VoiceControl(voice, &invalid.header) == SCE_NGS2_ERROR_INVALID_PARAM_ID); + Require(sceNgs2RackDestroy(rack, nullptr) == SCE_NGS2_OK); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} + struct DelayOption { std::uint32_t size = 20; std::uint32_t type = 0; @@ -828,6 +889,10 @@ static void TestCustomDelay(std::uint32_t count, const DelayOption& delay) { for (std::uint32_t index = 0; index < 256; ++index) { Ngs2CustomVoiceUserFxParam param{{sizeof(Ngs2CustomVoiceUserFxParam), 0, 0x40001d00 | index}, ProcessLegacy, reinterpret_cast(&state), 123, 456}; + if ((index & 0xe0u) == 0 && index >= count) { + Require(sceNgs2VoiceControl(custom, ¶m.header) == SCE_NGS2_ERROR_INVALID_PARAM_ID); + continue; + } bool rejected = false; try { sceNgs2VoiceControl(custom, ¶m.header); @@ -1188,6 +1253,7 @@ int main() { TestCustomSampler(); TestCustomFilter(); for (const auto moduleIndex : {0u, 1u, 23u}) TestCustomLegacyUserFx(moduleIndex); + for (const auto moduleIndex : {0u, 1u}) TestCustomSamplerLegacyUserFx(moduleIndex); TestCustomSamplerGain(); TestCustomGain(); TestCustomUserFxBuffers(); diff --git a/core/libs/prx/libSceNpAuth/Export.cpp b/core/libs/prx/libSceNpAuth/Export.cpp index 026144f14c..f25f1a4cff 100644 --- a/core/libs/prx/libSceNpAuth/Export.cpp +++ b/core/libs/prx/libSceNpAuth/Export.cpp @@ -122,15 +122,19 @@ int APS5_VABI sceNpAuthGetIdToken(int req_id, const void* param, void* id_token) int APS5_VABI sceNpAuthPollAsync(int req_id, int* result) { if (!result) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT; - std::lock_guard lock(g_requestMutex); - auto it = g_requests.find(req_id); - if (it == g_requests.end()) return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND; - if (!it->second.isAsync || !it->second.processed) return ORBIS_NP_AUTH_ERROR_INVALID_ID; - if (!it->second.polled) { - it->second.polled = true; - return NP_POLL_ASYNC_RUNNING; + int res = 0; + { + std::lock_guard lock(g_requestMutex); + auto it = g_requests.find(req_id); + if (it == g_requests.end()) return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND; + if (!it->second.isAsync || !it->second.processed) return ORBIS_NP_AUTH_ERROR_INVALID_ID; + if (!it->second.polled) { + it->second.polled = true; + return NP_POLL_ASYNC_RUNNING; + } + res = it->second.result; } - *result = it->second.result; + if (!GuestCopyOut_nid_no_patch(result, &res, sizeof(res))) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT; return NP_POLL_ASYNC_FINISHED; } @@ -151,11 +155,15 @@ int APS5_VABI sceNpAuthSetTimeout(std::int32_t requestId, std::int32_t resolveRe int APS5_VABI sceNpAuthWaitAsync(int req_id, int* result) { if (!result) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT; - std::lock_guard lock(g_requestMutex); - auto it = g_requests.find(req_id); - if (it == g_requests.end()) return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND; - if (!it->second.isAsync || !it->second.processed) return ORBIS_NP_AUTH_ERROR_INVALID_ID; - *result = it->second.result; + int res = 0; + { + std::lock_guard lock(g_requestMutex); + auto it = g_requests.find(req_id); + if (it == g_requests.end()) return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND; + if (!it->second.isAsync || !it->second.processed) return ORBIS_NP_AUTH_ERROR_INVALID_ID; + res = it->second.result; + } + if (!GuestCopyOut_nid_no_patch(result, &res, sizeof(res))) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT; return NP_POLL_ASYNC_FINISHED; } diff --git a/core/libs/prx/libSceNpCommerce/Export.cpp b/core/libs/prx/libSceNpCommerce/Export.cpp index e964c0be97..41d52abe7d 100644 --- a/core/libs/prx/libSceNpCommerce/Export.cpp +++ b/core/libs/prx/libSceNpCommerce/Export.cpp @@ -1,5 +1,6 @@ #include "prx/libSceNpCommerce/include/Api.hpp" #include "prx/libSceUserService/UserService.hpp" +#include #include #include #include @@ -126,11 +127,25 @@ int APS5_VABI sceNpCommerceDialogGetResult(NpCommerceDialogResult* result) { if (result == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; if (g_status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; if (g_status != COMMON_DIALOG_STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED; + const std::int32_t cancelResult = COMMON_DIALOG_RESULT_USER_CANCELED; + const std::uint8_t authorized = 0; + const std::uint64_t userData = g_userData; + const int savedErrno = errno; + if (!GuestCopyOut_nid_no_patch(&result->result, &cancelResult, sizeof(cancelResult)) || + !GuestCopyOut_nid_no_patch(&result->authorized, &authorized, sizeof(authorized)) || + !GuestCopyOut_nid_no_patch(&result->userData, &userData, sizeof(userData))) { + errno = savedErrno; + return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + } const auto sdkVersion = compiledSdkVersion(); - result->result = COMMON_DIALOG_RESULT_USER_CANCELED; - result->authorized = 0; - result->userData = g_userData; - if (sdkVersion >= 0x01700000) std::fill(std::begin(result->reserved), std::end(result->reserved), 0); + if (sdkVersion >= 0x01700000) { + const std::uint8_t zeroReserved[sizeof(result->reserved)] = {0}; + if (!GuestCopyOut_nid_no_patch(result->reserved, zeroReserved, sizeof(zeroReserved))) { + errno = savedErrno; + return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + } + } + errno = savedErrno; return COMMON_DIALOG_RESULT_USER_CANCELED; } diff --git a/core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp b/core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp index 3644658316..bc7abe75c0 100644 --- a/core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp +++ b/core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp @@ -39,11 +39,6 @@ int APS5_VABI _ZN3sce2np10CancelableC2Ev(void) { return 0; } -int APS5_VABI _ZN3sce2np4CondD0Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN3sce2np8WorkItem6CancelEi(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -74,11 +69,6 @@ int APS5_VABI _ZN3sce2np10EventQueueC2EP16SceNpAllocatorEx(void) { return 0; } -int APS5_VABI _ZN3sce2np4Cond4ctorEv(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN3sce2np16StreamReadBufferD1Ev(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -189,11 +179,6 @@ int APS5_VABI _ZN3sce2np3ipc10IpmiClient7ConnectEPKvm(void) { return 0; } -int APS5_VABI _ZN3sce2np4Cond4InitEPKcPNS0_5MutexE(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient10DisconnectEv(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -416,11 +401,6 @@ int APS5_VABI _ZN3sce2np14CalloutContextD1Ev(void) { return 0; } -int APS5_VABI _ZN3sce2np5Mutex7DestroyEv(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN3sce2np14JsonDocBuilderC1ERKNS0_9JsonValueE(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -516,11 +496,6 @@ int APS5_VABI _ZN3sce2np4PathD2Ev(void) { return 0; } -int APS5_VABI _ZN3sce2np4CondD1Ev(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN3sce2np9EventFlag7DestroyEv(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceNpCommon/Runtime.cpp b/core/libs/prx/libSceNpCommon/Runtime.cpp index f957531659..4eba08f1bd 100644 --- a/core/libs/prx/libSceNpCommon/Runtime.cpp +++ b/core/libs/prx/libSceNpCommon/Runtime.cpp @@ -281,6 +281,26 @@ NpAllocator MemoryHeapAllocator(NpMemoryHeap* heap) { constexpr std::size_t NP_OBJECT_HEADER_SIZE = 16; +constexpr int NP_ERROR_OBJECT_ALREADY_INITIALIZED = static_cast(0x80559E03); + +void* const NpMutexVtable[] = { + nullptr, nullptr, + reinterpret_cast(&_ZN3sce2np5MutexD1Ev), reinterpret_cast(&_ZN3sce2np5MutexD0Ev), +}; + +void* const NpCondVtable[] = { + nullptr, nullptr, + reinterpret_cast(&_ZN3sce2np4CondD1Ev), reinterpret_cast(&_ZN3sce2np4CondD0Ev), +}; + +void RequireMutexInit(const NpMutexObject* self, const char* function) { + if (!self->isInit) throw std::invalid_argument(std::string(function) + ": the mutex is not initialized"); +} + +void RequireMutexResult(int result, const char* function) { + if (result != 0) throw std::runtime_error(std::string(function) + ": the kernel mutex call failed"); +} + constexpr int NP_ERROR_STREAM_BUFFER_TOO_SMALL = static_cast(0x80559E0D); constexpr std::size_t NP_MEMORY_STREAM_SIZE = 0x20; @@ -1244,6 +1264,137 @@ void APS5_VABI sceNpDbgDumpBinary(const void* data, std::size_t size) { std::putchar('\n'); } +void APS5_VABI _ZN3sce2np5MutexC1Ev(NpMutexObject* self) { + self->vtable = &NpMutexVtable[2]; + self->mutex = nullptr; + self->isInit = false; +} + +void APS5_VABI _ZN3sce2np5MutexC2Ev(NpMutexObject* self) { + _ZN3sce2np5MutexC1Ev(self); +} + +void APS5_VABI _ZN3sce2np5Mutex4dtorEv(NpMutexObject* self) { + if (self->isInit) scePthreadMutexDestroy(&self->mutex); +} + +void APS5_VABI _ZN3sce2np5MutexD1Ev(NpMutexObject* self) { + self->vtable = &NpMutexVtable[2]; + _ZN3sce2np5Mutex4dtorEv(self); +} + +void APS5_VABI _ZN3sce2np5MutexD2Ev(NpMutexObject* self) { + _ZN3sce2np5MutexD1Ev(self); +} + +void APS5_VABI _ZN3sce2np5MutexD0Ev(NpMutexObject* self) { + self->vtable = &NpMutexVtable[2]; + if (self->isInit) sceNpMutexDestroy(&self->mutex); + _ZN3sce2np6ObjectdlEPv(self); +} + +void APS5_VABI _ZN3sce2np5Mutex4ctorEv(NpMutexObject* self) { + self->isInit = false; +} + +int APS5_VABI _ZN3sce2np5Mutex4InitEPKcj(NpMutexObject* self, const char* name, std::uint32_t flags) { + if (self->isInit) return NP_ERROR_OBJECT_ALREADY_INITIALIZED; + const int result = sceNpMutexInit(&self->mutex, name, flags & 1); + if (result < 0) return result; + self->isInit = true; + return 0; +} + +void APS5_VABI _ZN3sce2np5Mutex7DestroyEv(NpMutexObject* self) { + if (self->isInit) sceNpMutexDestroy(&self->mutex); + self->isInit = false; +} + +bool APS5_VABI _ZN3sce2np5Mutex7TryLockEv(NpMutexObject* self) { + RequireMutexInit(self, "sce::np::Mutex::TryLock"); + const int result = sceNpMutexTryLock(&self->mutex); + if (result == NP_ERROR_MUTEX_BUSY) return false; + RequireMutexResult(result, "sce::np::Mutex::TryLock"); + return true; +} + +void APS5_VABI _ZN3sce2np5Mutex4LockEv(NpMutexObject* self) { + RequireMutexInit(self, "sce::np::Mutex::Lock"); + RequireMutexResult(sceNpMutexLock(&self->mutex), "sce::np::Mutex::Lock"); +} + +void APS5_VABI _ZN3sce2np5Mutex6UnlockEv(NpMutexObject* self) { + RequireMutexInit(self, "sce::np::Mutex::Unlock"); + RequireMutexResult(sceNpMutexUnlock(&self->mutex), "sce::np::Mutex::Unlock"); +} + +void APS5_VABI _ZN3sce2np4CondC1Ev(NpCondObject* self) { + self->vtable = &NpCondVtable[2]; + self->cond = nullptr; + self->mutex = nullptr; +} + +void APS5_VABI _ZN3sce2np4CondC2Ev(NpCondObject* self) { + _ZN3sce2np4CondC1Ev(self); +} + +void APS5_VABI _ZN3sce2np4Cond4dtorEv(NpCondObject* self) { + if (self->mutex != nullptr) scePthreadCondDestroy(&self->cond); +} + +void APS5_VABI _ZN3sce2np4CondD1Ev(NpCondObject* self) { + self->vtable = &NpCondVtable[2]; + _ZN3sce2np4Cond4dtorEv(self); +} + +void APS5_VABI _ZN3sce2np4CondD2Ev(NpCondObject* self) { + _ZN3sce2np4CondD1Ev(self); +} + +void APS5_VABI _ZN3sce2np4CondD0Ev(NpCondObject* self) { + self->vtable = &NpCondVtable[2]; + if (self->mutex != nullptr) sceNpCondDestroy(&self->cond); + _ZN3sce2np6ObjectdlEPv(self); +} + +void APS5_VABI _ZN3sce2np4Cond4ctorEv(NpCondObject* self) { + self->cond = nullptr; + self->mutex = nullptr; +} + +bool APS5_VABI _ZNK3sce2np4Cond6IsInitEv(const NpCondObject* self) { + return self->mutex != nullptr; +} + +int APS5_VABI _ZN3sce2np4Cond4InitEPKcPNS0_5MutexE(NpCondObject* self, const char* name, NpMutexObject* mutex) { + if (self->mutex != nullptr) return NP_ERROR_OBJECT_ALREADY_INITIALIZED; + const int result = sceNpCondInit(&self->cond, name, 0); + if (result < 0) return result; + self->mutex = reinterpret_cast(reinterpret_cast(mutex) + offsetof(NpMutexObject, mutex)); + return 0; +} + +void APS5_VABI _ZN3sce2np4Cond7DestroyEv(NpCondObject* self) { + if (self->mutex != nullptr) sceNpCondDestroy(&self->cond); + self->cond = nullptr; + self->mutex = nullptr; +} + +int APS5_VABI _ZN3sce2np4Cond4WaitEj(NpCondObject* self, std::uint32_t usec) { + return std::min(sceNpCondTimedwait(&self->cond, self->mutex, usec), 0); +} + +int APS5_VABI _ZN3sce2np4Cond6SignalEv(NpCondObject* self) { + return std::min(sceNpCondSignal(&self->cond), 0); +} + +int APS5_VABI _ZN3sce2np4Cond9SignalAllEv(NpCondObject* self) { + return std::min(sceNpCondSignalAll(&self->cond), 0); +} + +int APS5_VABI _ZN3sce2np4Cond8SignalToEP7pthread(NpCondObject* self, Pthread thread) { + return std::min(sceNpCondSignalTo(&self->cond, thread), 0); +} void APS5_VABI _ZN3sce2np18MemoryStreamReaderC1EPKvm(NpMemoryStreamReader* self, const void* data, std::size_t size) { self->vtable = &NpMemoryStreamReaderVtable[2]; self->data = data; diff --git a/core/libs/prx/libSceNpCommon/Strings.cpp b/core/libs/prx/libSceNpCommon/Strings.cpp index 8364be0989..5899507358 100644 --- a/core/libs/prx/libSceNpCommon/Strings.cpp +++ b/core/libs/prx/libSceNpCommon/Strings.cpp @@ -40,7 +40,7 @@ struct Base64Block { int DecodeLastBlock(const char* src, std::size_t length, const char* alphabet, Base64Block* block) { char c[4] = {BASE64_PAD, BASE64_PAD, BASE64_PAD, BASE64_PAD}; - std::memcpy(c, src, length); + if (length != 0) std::memcpy(c, src, length); const int v0 = Base64Value(c[0], alphabet); const int v1 = Base64Value(c[1], alphabet); if (v0 < 0 || v1 < 0) return NP_ERROR_INVALID_CHARACTER; diff --git a/core/libs/prx/libSceNpCommon/include/Api.hpp b/core/libs/prx/libSceNpCommon/include/Api.hpp index faaaa92b8d..eb93799ce8 100644 --- a/core/libs/prx/libSceNpCommon/include/Api.hpp +++ b/core/libs/prx/libSceNpCommon/include/Api.hpp @@ -92,6 +92,18 @@ struct NpTitleIdParts { std::uint8_t index; }; +struct NpMutexObject { + const void* const* vtable; + PthreadMutex mutex; + bool isInit; +}; + +struct NpCondObject { + const void* const* vtable; + PthreadCond cond; + PthreadMutex* mutex; +}; + struct NpRingBuf { const void* const* vtable; std::uint8_t* buf; @@ -402,14 +414,12 @@ int APS5_VABI sceNpUtilHttpUrlEncode(char* dst, std::size_t dstSize, const char* int APS5_VABI _ZN3sce2np10CancelableC2Ev(void); int APS5_VABI sceNpUtilXmlEscape(char* dst, std::size_t dstSize, const char* src, std::size_t srcLength, std::size_t* required); -int APS5_VABI _ZN3sce2np4CondD0Ev(void); int APS5_VABI _ZN3sce2np8WorkItem6CancelEi(void); int APS5_VABI _ZN3sce2np9WorkQueue11GetItemByIdEi(void); int APS5_VABI _ZN3sce2np14JsonDocBuilderC2ERKNS0_9JsonValueE(void); int APS5_VABI _ZThn16_N3sce2np9HttpTransD0Ev(void); int APS5_VABI _ZN3sce2np10MemoryFileD0Ev(void); int APS5_VABI _ZN3sce2np10EventQueueC2EP16SceNpAllocatorEx(void); -int APS5_VABI _ZN3sce2np4Cond4ctorEv(void); int APS5_VABI _ZN3sce2np16StreamReadBufferD1Ev(void); bool APS5_VABI _ZNK3sce2np9EventFlag6IsInitEv(const void* self); int APS5_VABI _ZN3sce2np10CancelLockC2Ev(void); @@ -432,7 +442,6 @@ int APS5_VABI _ZThn16_N3sce2np9HttpTrans5WriteEPNS0_6HandleEPKvmPm(void); int APS5_VABI _ZN3sce2np10EventQueue4ctorEv(void); int APS5_VABI _ZN3sce2np20BufferedStreamReaderD1Ev(void); int APS5_VABI _ZN3sce2np3ipc10IpmiClient7ConnectEPKvm(void); -int APS5_VABI _ZN3sce2np4Cond4InitEPKcPNS0_5MutexE(void); int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient10DisconnectEv(void); bool APS5_VABI _ZN3sce2np3ipc10IpmiClient11IsConnectedEv(void* self); int APS5_VABI _ZN3sce2np12HttpTemplate19SetAuthInfoCallbackEPFii15SceHttpAuthTypePKcPcS5_iPPhPmPiPvESA_(void); @@ -479,7 +488,6 @@ int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient7ConnectEPKvm(void); int APS5_VABI _ZN3sce2np10MemoryFileD1Ev(void); int APS5_VABI _ZN3sce2np9HttpTrans21GetResponseStatusCodeEPNS0_6HandleEPi(void); int APS5_VABI _ZN3sce2np14CalloutContextD1Ev(void); -int APS5_VABI _ZN3sce2np5Mutex7DestroyEv(void); int APS5_VABI _ZN3sce2np14JsonDocBuilderC1ERKNS0_9JsonValueE(void); int APS5_VABI _ZN3sce2np9HttpTransD2Ev(void); int APS5_VABI _ZN3sce2np12HttpTemplateC2Ev(void); @@ -499,7 +507,6 @@ int APS5_VABI _ZN3sce2np9LocalFile8TruncateEl(void); int APS5_VABI _ZNK3sce2np10JsonNumber6GetNumEPcm(void); int APS5_VABI _ZN3sce2np9LocalFileC1Ev(void); int APS5_VABI _ZN3sce2np4PathD2Ev(void); -int APS5_VABI _ZN3sce2np4CondD1Ev(void); int APS5_VABI _ZN3sce2np9EventFlag7DestroyEv(void); int APS5_VABI _ZN3sce2np9HttpTransC1EP16SceNpAllocatorEx(void); int APS5_VABI _ZN3sce2np6Thread4dtorEv(void); @@ -511,6 +518,32 @@ void APS5_VABI _ZN3sce2np8JsonNull5ClearEv(void* self); int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient12AbortRequestEii(void); bool APS5_VABI _ZNK3sce2np5Mutex6IsInitEv(const void* self); +void APS5_VABI _ZN3sce2np5MutexC1Ev(NpMutexObject* self); +void APS5_VABI _ZN3sce2np5MutexC2Ev(NpMutexObject* self); +void APS5_VABI _ZN3sce2np5MutexD1Ev(NpMutexObject* self); +void APS5_VABI _ZN3sce2np5MutexD2Ev(NpMutexObject* self); +void APS5_VABI _ZN3sce2np5MutexD0Ev(NpMutexObject* self); +void APS5_VABI _ZN3sce2np5Mutex4ctorEv(NpMutexObject* self); +void APS5_VABI _ZN3sce2np5Mutex4dtorEv(NpMutexObject* self); +int APS5_VABI _ZN3sce2np5Mutex4InitEPKcj(NpMutexObject* self, const char* name, std::uint32_t flags); +void APS5_VABI _ZN3sce2np5Mutex7DestroyEv(NpMutexObject* self); +bool APS5_VABI _ZN3sce2np5Mutex7TryLockEv(NpMutexObject* self); +void APS5_VABI _ZN3sce2np5Mutex4LockEv(NpMutexObject* self); +void APS5_VABI _ZN3sce2np5Mutex6UnlockEv(NpMutexObject* self); +void APS5_VABI _ZN3sce2np4CondC1Ev(NpCondObject* self); +void APS5_VABI _ZN3sce2np4CondC2Ev(NpCondObject* self); +void APS5_VABI _ZN3sce2np4CondD1Ev(NpCondObject* self); +void APS5_VABI _ZN3sce2np4CondD2Ev(NpCondObject* self); +void APS5_VABI _ZN3sce2np4CondD0Ev(NpCondObject* self); +void APS5_VABI _ZN3sce2np4Cond4ctorEv(NpCondObject* self); +void APS5_VABI _ZN3sce2np4Cond4dtorEv(NpCondObject* self); +bool APS5_VABI _ZNK3sce2np4Cond6IsInitEv(const NpCondObject* self); +int APS5_VABI _ZN3sce2np4Cond4InitEPKcPNS0_5MutexE(NpCondObject* self, const char* name, NpMutexObject* mutex); +void APS5_VABI _ZN3sce2np4Cond7DestroyEv(NpCondObject* self); +int APS5_VABI _ZN3sce2np4Cond4WaitEj(NpCondObject* self, std::uint32_t usec); +int APS5_VABI _ZN3sce2np4Cond6SignalEv(NpCondObject* self); +int APS5_VABI _ZN3sce2np4Cond9SignalAllEv(NpCondObject* self); +int APS5_VABI _ZN3sce2np4Cond8SignalToEP7pthread(NpCondObject* self, Pthread thread); void APS5_VABI _ZN3sce2np18MemoryStreamReaderC1EPKvm(NpMemoryStreamReader* self, const void* data, std::size_t size); void APS5_VABI _ZN3sce2np18MemoryStreamReaderC2EPKvm(NpMemoryStreamReader* self, const void* data, std::size_t size); void APS5_VABI _ZN3sce2np18MemoryStreamReaderD1Ev(NpMemoryStreamReader* self); diff --git a/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt b/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt index cd3460bd81..158c20a840 100644 --- a/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt +++ b/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt @@ -21,6 +21,15 @@ if(MINGW) endif() if(BUILD_TESTING) + add_test_executable(guest_np_cpp_web_api_response_header_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpCppWebApiResponseHeader.cpp) + target_include_directories(guest_np_cpp_web_api_response_header_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_cpp_web_api_response_header_tests PRIVATE libSceNpCppWebApi libc) + configure_windows_unwind(guest_np_cpp_web_api_response_header_tests) + add_test(NAME guest_np_cpp_web_api_response_header COMMAND guest_np_cpp_web_api_response_header_tests) + if(WIN32) + set_tests_properties(guest_np_cpp_web_api_response_header + PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() 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) diff --git a/core/libs/prx/libSceNpCppWebApi/Export.cpp b/core/libs/prx/libSceNpCppWebApi/Export.cpp index 199895450d..09a351b3c2 100644 --- a/core/libs/prx/libSceNpCppWebApi/Export.cpp +++ b/core/libs/prx/libSceNpCppWebApi/Export.cpp @@ -347,6 +347,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi12downloadDataEi static constexpr int ParameterNotInitialized = static_cast(0x80553502); static constexpr int ParameterInvalidArgument = static_cast(0x80553503); +static constexpr int ParameterAllocationFailed = static_cast(0x80553504); static constexpr int ParameterAlreadyInitialized = static_cast(0x8055350f); static constexpr int ParameterTerminatedUninitialized = static_cast(0x80553510); static constexpr int ParameterBodyMissing = static_cast(0x80553512); @@ -1738,7 +1739,8 @@ bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_12Le static constexpr std::uint64_t VectorBodyMagic = 0x434e507056656342ULL; struct VectorBody { std::uint64_t magic; - std::uint8_t reserved[0x28]; + void* libContext; + std::uint8_t reserved[0x20]; std::int32_t referenceCount; std::int32_t elementKind; // 1 = IntrusivePtr elements, 2 = String elements void* elements; @@ -1765,6 +1767,14 @@ static void APS5_VABI DestroyVectorBody(void* object) { } static VectorBody* VectorBodyFor(IntrusivePtr* self, const std::int32_t elementKind, const char* function) { + auto* direct = static_cast(static_cast(self)); + if (direct != nullptr && direct->magic == VectorBodyMagic) { + if (direct->elementKind != elementKind) { + NotImplemented_nid_no_patch(function); + return nullptr; + } + return direct; + } auto* body = static_cast(self->object); if (body == nullptr) { body = static_cast(std::calloc(1, sizeof(VectorBody))); @@ -1772,6 +1782,7 @@ static VectorBody* VectorBodyFor(IntrusivePtr* self, const std::int32_t elementK body->magic = VectorBodyMagic; body->referenceCount = 1; body->elementKind = elementKind; + body->libContext = self->libContext; self->object = body; self->deleter = &DestroyVectorBody; return body; @@ -1794,15 +1805,22 @@ static bool VectorGrow(VectorBody* body, const std::size_t elementSize) { return true; } +static const VectorBody* ResolveVectorBody(const void* self) { + const auto* direct = static_cast(self); + if (direct == nullptr) return nullptr; + if (direct->magic == VectorBodyMagic) return direct; + return static_cast(static_cast(self)->object); +} + static std::uint64_t HostVectorSize(const void* self, const char* function) { - const auto* body = static_cast(static_cast(self)->object); + const auto* body = ResolveVectorBody(self); if (body == nullptr) return 0; if (body->magic != VectorBodyMagic) NotImplemented_nid_no_patch(function); return body->size; } static IntrusivePtr* HostVectorElement(void* self, const std::uint64_t index, const char* function) { - auto* body = static_cast(static_cast(self)->object); + auto* body = const_cast(ResolveVectorBody(self)); if (body == nullptr || body->magic != VectorBodyMagic || body->elementKind != 1) NotImplemented_nid_no_patch(function); return static_cast(body->elements) + index; } @@ -1844,6 +1862,14 @@ static int VectorPushBackString(IntrusivePtr* self, const CommonString* element) static_assert(sizeof(CommonIterator) == 0x10 && offsetof(CommonIterator, element) == 0x08); +static IntrusivePtr* IntrusiveVectorElements(const IntrusivePtr* self, const bool end, const char* function) { + const auto* body = static_cast(self->object); + if (body == nullptr) return nullptr; + if (body->magic != VectorBodyMagic || body->elementKind != 1) NotImplemented_nid_no_patch(function); + auto* elements = static_cast(body->elements); + return end && body->size != 0 ? elements + body->size : elements; +} + static CommonString* StringVectorElements(const IntrusivePtr* self, const bool end, const char* function) { const auto* body = static_cast(self->object); if (body == nullptr) return nullptr; @@ -1851,6 +1877,144 @@ static CommonString* StringVectorElements(const IntrusivePtr* self, const bool e auto* elements = static_cast(body->elements); return end && body->size != 0 ? elements + body->size : elements; } + +static const VectorBody* HostVectorBody(const void* self, const std::int32_t elementKind, const char* function) { + const auto* body = ResolveVectorBody(self); + if (body == nullptr) return nullptr; + if (body->magic != VectorBodyMagic || body->elementKind != elementKind) NotImplemented_nid_no_patch(function); + return body; +} + +static void* HostVectorBegin(void* out, const void* self, const std::int32_t elementKind, const char* function) { + const VectorBody* body = HostVectorBody(self, elementKind, function); + auto* it = static_cast(out); + it->referenceCount = 0; + it->element = body != nullptr ? body->elements : nullptr; + return out; +} + +static void* HostVectorEnd(void* out, const void* self, const std::int32_t elementKind, const std::size_t elementSize, const char* function) { + const VectorBody* body = HostVectorBody(self, elementKind, function); + auto* it = static_cast(out); + it->referenceCount = 0; + it->element = body != nullptr ? static_cast(body->elements) + body->size * elementSize : nullptr; + return out; +} + +static void* HostIteratorAdvance(void* self, const std::size_t elementSize) { + auto* it = static_cast(self); + it->element = static_cast(it->element) + elementSize; + return self; +} + +static bool HostIteratorNotEqual(const void* self, const void* other) { + return static_cast(self)->element != static_cast(other)->element; +} + +static bool VectorReserve(VectorBody* body, const std::size_t elementSize, const std::uint64_t count) { + if (body->capacity >= count) return true; + std::uint64_t capacity = body->capacity != 0 ? body->capacity : 4; + while (capacity < count) capacity *= 2; + 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 HostVectorCopyFrom(IntrusivePtr* self, const IntrusivePtr* source, const char* function) { + VectorBody* body = VectorBodyFor(self, 1, function); + if (body == nullptr) return ParameterAllocationFailed; + const auto* sourceDirect = static_cast(static_cast(source)); + const bool sourceIsBody = sourceDirect != nullptr && sourceDirect->magic == VectorBodyMagic; + const auto* sourceBody = sourceIsBody ? sourceDirect : static_cast(source->object); + void** const selfContext = body == static_cast(static_cast(self)) ? &body->libContext : &self->libContext; + void* const sourceContext = sourceIsBody ? sourceDirect->libContext : source->libContext; + if (*selfContext == nullptr) *selfContext = sourceContext; + if (body->libContext == nullptr) body->libContext = *selfContext; + if (sourceBody != nullptr && (sourceBody->magic != VectorBodyMagic || sourceBody->elementKind != 1)) { + NotImplemented_nid_no_patch(function); + return ParameterAllocationFailed; + } + if (sourceBody != nullptr && body == sourceBody) return 0; + auto* elements = static_cast(body->elements); + for (std::uint64_t index = 0; index < body->size; ++index) { + ReleaseIntrusivePtr(&elements[index], ReferenceCountOffset, function); + } + body->size = 0; + const std::uint64_t count = sourceBody != nullptr ? sourceBody->size : 0; + if (count == 0) return 0; + if (!VectorReserve(body, sizeof(IntrusivePtr), count)) return ParameterAllocationFailed; + elements = static_cast(body->elements); + const auto* sourceElements = static_cast(sourceBody->elements); + for (std::uint64_t index = 0; index < count; ++index) { + elements[index] = {}; + CopyIntrusivePtr(&elements[index], &sourceElements[index], ReferenceCountOffset); + } + body->size = count; + return 0; +} + +static void* CopyMemberIntrusivePtr(void* out, const void* self, const std::size_t memberOffset, const std::size_t referenceCountOffset) { + CopyIntrusivePtr(static_cast(out), + reinterpret_cast(static_cast(self) + memberOffset), + referenceCountOffset); + return out; +} + +static void InitHostString(void* field, void* context) { + *static_cast(field) = {}; + static_cast(field)->libContext = context; +} + +static void DestroyHostString(void* field) { + auto* str = static_cast(field); + if (str->buffer != nullptr) { + std::free(str->buffer); + str->buffer = nullptr; + str->bufferSize = 0; + } +} + +static int HostVectorCopyFromStrings(IntrusivePtr* self, const IntrusivePtr* source, const char* function) { + VectorBody* body = VectorBodyFor(self, 2, function); + if (body == nullptr) return ParameterAllocationFailed; + if (self->libContext == nullptr) self->libContext = source->libContext; + const auto* sourceBody = static_cast(source->object); + if (sourceBody != nullptr && (sourceBody->magic != VectorBodyMagic || sourceBody->elementKind != 2)) { + NotImplemented_nid_no_patch(function); + return ParameterAllocationFailed; + } + if (sourceBody != nullptr && body == sourceBody) return 0; + auto* elements = static_cast(body->elements); + for (std::uint64_t index = 0; index < body->size; ++index) DestroyHostString(&elements[index]); + body->size = 0; + const std::uint64_t count = sourceBody != nullptr ? sourceBody->size : 0; + if (count == 0) return 0; + if (!VectorReserve(body, sizeof(CommonString), count)) return ParameterAllocationFailed; + elements = static_cast(body->elements); + const auto* sourceElements = static_cast(sourceBody->elements); + for (std::uint64_t index = 0; index < count; ++index) { + CommonString copy = sourceElements[index]; + copy.referenceCount = 0; + copy.buffer = nullptr; + copy.bufferSize = 0; + if (copy.libContext == nullptr) copy.libContext = sourceElements[index].libContext; + if (sourceElements[index].buffer != nullptr) { + const std::size_t length = std::strlen(sourceElements[index].buffer) + 1; + copy.buffer = static_cast(std::malloc(length)); + if (copy.buffer == nullptr) { + body->size = index; + return ParameterAllocationFailed; + } + std::memcpy(copy.buffer, sourceElements[index].buffer, length); + copy.bufferSize = length; + } + elements[index] = copy; + } + body->size = count; + return 0; +} // -------------------------------------------------------------------------- // SessionManager2V1 and extra Common symbols, from a title dump whose modules lack // libSceNpCppWebApi.prx. @@ -2073,10 +2237,8 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V1 CopyIntrusivePtr(self, source, 0x00); } -void* APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V130PostPlayerSessionsResponseBody17getPlayerSessionsEv(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return nullptr; +void* APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V130PostPlayerSessionsResponseBody17getPlayerSessionsEv(void* out, void* self) { + return CopyMemberIntrusivePtr(out, self, 0x30, VectorReferenceCountOffset); } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi18leavePlayerSessionEiRKNS4_29ParameterToLeavePlayerSessionERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE() { @@ -2383,9 +2545,7 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V1 } const char* APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V113PlayerSession12getSessionIdEv(const void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return nullptr; + return static_cast(self) + 0x10; } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V158PostPlayerSessionsSessionIdMemberPlayersRequestBodyFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_26RequestPlayerSessionPlayerEEEEEPNS9_INS3_51PostPlayerSessionsSessionIdMemberPlayersRequestBodyEEE() { @@ -2508,22 +2668,15 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_17TitleCloudStorage } bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEEneERKS9_(const void* self, const void* other) { - (void)self; - (void)other; - NotImplemented_nid_no_patch(__func__); - return false; + return HostIteratorNotEqual(self, other); } void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEEdeEv(const void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return nullptr; + return static_cast(self)->element; } void* APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEEppEv(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return nullptr; + return HostIteratorAdvance(self, sizeof(IntrusivePtr)); } bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEEneERKS9_(const void* self, const void* other) { @@ -2552,8 +2705,7 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS2_6 int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS2_6VectorINS4_INS1_13InGameCatalog2V59ContainerEEEEEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERSB_(const void* self, void* response) { (void)self; (void)response; - NotImplemented_nid_no_patch(__func__); - return 0; + return TransactionNotStarted; } void APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS2_6VectorINS4_INS1_13InGameCatalog2V59ContainerEEEEEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { @@ -2567,8 +2719,7 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_1 int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V132GetMultiDataStatusesResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_(const void* self, void* response) { (void)self; (void)response; - NotImplemented_nid_no_patch(__func__); - return 0; + return TransactionNotStarted; } void APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V132GetMultiDataStatusesResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { @@ -2582,9 +2733,9 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS2_6VectorINS4_INS1_13InGameCatalog2V59ContainerEEEEEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(void* self, void* context) { (void)self; - (void)context; - NotImplemented_nid_no_patch(__func__); - return 0; + if (context == nullptr) return ParameterInvalidArgument; + if (*static_cast(context) == 0) return ParameterNotInitialized; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_17TitleCloudStorage2V132GetMultiDataStatusesResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv(void* self) { @@ -2607,38 +2758,32 @@ std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1 return HostVectorSize(self, __func__); } -void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEE5beginEv(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return nullptr; +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEE5beginEv(void* out, void* self) { + return HostVectorBegin(out, self, 1, __func__); } -void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEE3endEv(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return nullptr; +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEE3endEv(void* out, void* self) { + return HostVectorEnd(out, self, 1, sizeof(IntrusivePtr), __func__); } std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEE4sizeEv(const void* self) { return HostVectorSize(self, __func__); } -void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEE5beginEv(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return nullptr; +CommonIterator* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEE5beginEv(CommonIterator* iterator, const IntrusivePtr* self) { + iterator->referenceCount = 0; + iterator->element = IntrusiveVectorElements(self, false, __func__); + return iterator; } -void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEE3endEv(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return nullptr; +CommonIterator* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEE3endEv(CommonIterator* iterator, const IntrusivePtr* self) { + iterator->referenceCount = 0; + iterator->element = IntrusiveVectorElements(self, true, __func__); + return iterator; } -void* APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V59Container11getChildrenEv(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return nullptr; +void* APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V59Container11getChildrenEv(void* out, void* self) { + return CopyMemberIntrusivePtr(out, self, 0x228, VectorReferenceCountOffset); } int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainerC1Ev(void* self) { @@ -2679,22 +2824,16 @@ const char* APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product8getLabelEv return nullptr; } -void* APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V57Product7getSkusEv(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return nullptr; +void* APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V57Product7getSkusEv(void* out, void* self) { + return CopyMemberIntrusivePtr(out, self, 0x1e0, VectorReferenceCountOffset); } -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product7getTypeEv(const void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return 0; +const void* APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product7getTypeEv(const void* self) { + return static_cast(self) + 0x1c0; } bool APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product9skusIsSetEv(const void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return false; + return *reinterpret_cast(static_cast(self) + 0x1f8) != 0; } int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi23ParameterToDownloadData11setobjectIdEPKc(void* self, const char* objectId) { @@ -2723,9 +2862,8 @@ int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi37ParameterToGet return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi26getMultiDataStatusesByUserEiRKNS4_37ParameterToGetMultiDataStatusesByUserERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_32GetMultiDataStatusesResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi26getMultiDataStatusesByUserEiRKNS4_37ParameterToGetMultiDataStatusesByUserERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_32GetMultiDataStatusesResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi17TitleCloudStorage2V110DataStatus11getDataSizeEv(const void* self) { @@ -2814,9 +2952,8 @@ int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetB return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V118GetFriendsResponse10getFriendsEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V118GetFriendsResponse10getFriendsEv(void* self) { + return static_cast(self) + 0x30; } // -------------------------------------------------------------------------- @@ -2867,14 +3004,12 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120RequestPlayerSession14set return 0; } -int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead16customData2IsSetEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead16customData2IsSetEv(const void* self) { + return *reinterpret_cast(static_cast(self) + 0x200) != 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V124GetBlockingUsersResponse9getBlocksEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V124GetBlockingUsersResponse9getBlocksEv(void* self) { + return static_cast(self) + 0x30; } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionPropertiesD1Ev(void* self) { @@ -2898,14 +3033,12 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V139PatchPlayerSessionsSessio return 0; } -int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V127ResponsePlayerSessionPlayer11getPlatformEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +const void* APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V127ResponsePlayerSessionPlayer11getPlatformEv(const void* self) { + return static_cast(self) + 0x18; } -int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V125GetBasicPresencesResponse17getBasicPresencesEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V125GetBasicPresencesResponse17getBasicPresencesEv(void* out, void* self) { + return CopyMemberIntrusivePtr(out, self, 0x30, VectorReferenceCountOffset); } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessions9setfieldsENS1_6Common12IntrusivePtrINS6_6VectorINS6_6StringEEEEE() { @@ -2925,9 +3058,8 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39Parame return 0; } -int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead14getCustomData1Ev() { - NotImplemented_nid_no_patch(__func__); - return 0; +const void* APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead14getCustomData1Ev(const void* self) { + return static_cast(self) + 0x1c8; } int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi16getBlockingUsersEiRKNS4_27ParameterToGetBlockingUsersERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_24GetBlockingUsersResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE() { @@ -2960,9 +3092,8 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38addPla return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } -int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead9getLeaderEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead9getLeaderEv(void* out, const void* self) { + return CopyMemberIntrusivePtr(out, self, 0xf8, ReferenceCountOffset); } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitationsC1Ev(void* self) { @@ -2975,9 +3106,8 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28Parame return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead28getExclusiveLeaderPrivilegesEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead28getExclusiveLeaderPrivilegesEv(void* out, void* self) { + return CopyMemberIntrusivePtr(out, self, 0x188, StringVectorReferenceCountOffset); } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi26setPlayerSessionPropertiesEiRKNS4_37ParameterToSetPlayerSessionPropertiesERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE() { @@ -3018,18 +3148,16 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28Parame return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V133ResponsePlayerSessionMemberPlayer10getPlayersEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V133ResponsePlayerSessionMemberPlayer10getPlayersEv(void* out, void* self) { + return CopyMemberIntrusivePtr(out, self, 0x10, VectorReferenceCountOffset); } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V139PatchPlayerSessionsSessionIdRequestBody28setExclusiveLeaderPrivilegesERKNS1_6Common6VectorINS5_6StringEEE() { return 0; } -int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V113PlayerSession9getMemberEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V113PlayerSession9getMemberEv(void* out, const void* self) { + return CopyMemberIntrusivePtr(out, self, 0x30, ReferenceCountOffset); } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V19ToFactory6createEPNS1_6Common10LibContextERKmPNS5_12IntrusivePtrINS3_2ToEEE() { @@ -3067,9 +3195,8 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V139PatchPlayerSessionsSessio return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V129GetPlayerSessionsResponseBody17getPlayerSessionsEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V129GetPlayerSessionsResponseBody17getPlayerSessionsEv(void* out, void* self) { + return CopyMemberIntrusivePtr(out, self, 0x30, VectorReferenceCountOffset); } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsersC1Ev(void* self) { @@ -3077,14 +3204,12 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49Parame return 0; } -int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead14getCustomData2Ev() { - NotImplemented_nid_no_patch(__func__); - return 0; +const void* APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead14getCustomData2Ev(const void* self) { + return static_cast(self) + 0x1e8; } -int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V127ResponsePlayerSessionPlayer12getAccountIdEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::uint64_t APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V127ResponsePlayerSessionPlayer12getAccountIdEv(const void* self) { + return *reinterpret_cast(static_cast(self) + 0x10); } int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11hasResponseEv() { @@ -3265,9 +3390,8 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V1 *self = {}; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_14SessionManager2V120PlayerSessionForReadEEEEdeEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_14SessionManager2V120PlayerSessionForReadEEEEdeEv(const void* self) { + return static_cast(self)->element; } void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V118LeaderWithOnlineIdEEptEv(const IntrusivePtr* self) { @@ -3421,7 +3545,10 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS return TransactionNotStarted; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBase14getHeaderValueEPKcRNS2_12IntrusivePtrINS2_6StringEEE() { +int APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBase14getHeaderValueEPKcRNS2_12IntrusivePtrINS2_6StringEEE(const CommonResponseHeaderBase* self, const char* name, IntrusivePtr* value) { + (void)name; + (void)value; + if (self->headers == nullptr) return ParameterBodyMissing; NotImplemented_nid_no_patch(__func__); return 0; } @@ -3430,59 +3557,48 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS return TransactionNotStarted; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE3endEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE3endEv(void* out, void* self) { + return HostVectorEnd(out, self, 1, sizeof(IntrusivePtr), __func__); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE5beginEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE5beginEv(void* out, void* self) { + return HostVectorBegin(out, self, 1, __func__); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorImE3endEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorImE3endEv(void* out, void* self) { + return HostVectorEnd(out, self, 3, sizeof(std::uint64_t), __func__); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorImE5beginEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorImE5beginEv(void* out, void* self) { + return HostVectorBegin(out, self, 3, __func__); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEEppEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEEppEv(void* self) { + return HostIteratorAdvance(self, sizeof(IntrusivePtr)); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorImEppEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorImEppEv(void* self) { + return HostIteratorAdvance(self, sizeof(std::uint64_t)); } -int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence12getAccountIdEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::uint64_t APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence12getAccountIdEv(const void* self) { + return *reinterpret_cast(static_cast(self) + 0x10); } -int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence12getInContextEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence12getInContextEv(const void* self) { + return *reinterpret_cast(static_cast(self) + 0x21) != 0; } -int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence15getOnlineStatusEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::uint32_t APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence15getOnlineStatusEv(const void* self) { + return *reinterpret_cast(static_cast(self) + 0x1c); } -int APS5_VABI _ZNK3sce2Np9CppWebApi15ProfanityFilter2V223FilterProfanityResponse10getMessageEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +const void* APS5_VABI _ZNK3sce2Np9CppWebApi15ProfanityFilter2V223FilterProfanityResponse10getMessageEv(const void* self) { + return static_cast(self) + 0x38; } -int APS5_VABI _ZNK3sce2Np9CppWebApi30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponse13getRestrictedEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI _ZNK3sce2Np9CppWebApi30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponse13getRestrictedEv(const void* self) { + return *reinterpret_cast(static_cast(self) + 0x30) != 0; } void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEptEv(const IntrusivePtr* self) { @@ -3517,6 +3633,25 @@ void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderB return self->object; } +static_assert(sizeof(CommonResponseHeaderBase) == 0x10 && offsetof(CommonResponseHeaderBase, headers) == 0x08); + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBaseC1Ev(CommonResponseHeaderBase* self) { + self->referenceCount = 0; + self->headers = nullptr; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBaseC2Ev(CommonResponseHeaderBase* self) { + _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBaseC1Ev(self); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBaseD1Ev(CommonResponseHeaderBase* self) { + (void)self; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBaseD2Ev(CommonResponseHeaderBase* self) { + (void)self; +} + void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEE3getEv(const IntrusivePtr* self) { return self->object; } @@ -3566,14 +3701,8 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V1 ReleaseIntrusivePtr(self, 0x00, __func__); } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V120PlayerSessionForReadEEEE5emptyEv(const IntrusivePtr* self) { - const auto* body = static_cast(self->object); - if (body == nullptr) return 1; - if (body->magic != VectorBodyMagic) { - NotImplemented_nid_no_patch(__func__); - return 0; - } - return body->size == 0; +bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V120PlayerSessionForReadEEEE5emptyEv(const void* self) { + return HostVectorSize(self, __func__) == 0; } void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V150PostPlayerSessionsSessionIdInvitationsResponseBodyEEC1Ev(IntrusivePtr* self) { @@ -3661,18 +3790,16 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS return TransactionNotStarted; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorImE4sizeEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::uint64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorImE4sizeEv(const void* self) { + return HostVectorSize(self, __func__); } std::int64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv() { return 0; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEEdeEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEEdeEv(const void* self) { + return static_cast(self)->element; } int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11hasResponseEv() { @@ -3715,9 +3842,8 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS return TransactionNotStarted; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEEneERKS9_() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEEneERKS9_(const void* self, const void* other) { + return HostIteratorNotEqual(self, other); } void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V139PatchPlayerSessionsSessionIdRequestBodyEEptEv(const IntrusivePtr* self) { @@ -3774,9 +3900,8 @@ std::int64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12Intru return 0; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorImEdeEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorImEdeEv(const void* self) { + return static_cast(self)->element; } void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V118LeaderWithOnlineIdEED1Ev(IntrusivePtr* self) { @@ -3787,28 +3912,20 @@ std::int64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12Intru return 0; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorImEneERKS4_() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorImEneERKS4_(const void* self, const void* other) { + return HostIteratorNotEqual(self, other); } -std::uint64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEE4sizeEv(const IntrusivePtr* self) { - const auto* body = static_cast(self->object); - if (body == nullptr) return 0; - if (body->magic != VectorBodyMagic) { - NotImplemented_nid_no_patch(__func__); - return 0; - } - return body->size; +std::uint64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEE4sizeEv(const void* self) { + return HostVectorSize(self, __func__); } void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V120PlayerSessionForReadEEEEEED1Ev(IntrusivePtr* self) { ReleaseIntrusivePtr(self, 0x30, __func__); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V120PlayerSessionForReadEEEE5beginEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V120PlayerSessionForReadEEEE5beginEv(void* out, void* self) { + return HostVectorBegin(out, self, 1, __func__); } int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V130RequestPlayerSessionInvitationEEEE8pushBackERKS8_(IntrusivePtr* self, const void* element) { @@ -3839,9 +3956,8 @@ std::int64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12Intru return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEE8copyFromERKS9_() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEE8copyFromERKS9_(void* self, const void* source) { + return HostVectorCopyFrom(static_cast(self), static_cast(source), __func__); } void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V150PostPlayerSessionsSessionIdInvitationsResponseBodyEED1Ev(IntrusivePtr* self) { @@ -3852,9 +3968,8 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14 *self = {}; } -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku10labelIsSetEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku10labelIsSetEv(const void* self) { + return *reinterpret_cast(static_cast(self) + 0x80) != 0; } struct MatchesParameter { @@ -3938,6 +4053,76 @@ static int TerminateMatchesParameter(void* self, const bool hasText, const std:: return 0; } +static void APS5_VABI DestroyMatchesModel(void* object) { + std::free(object); +} + +static void APS5_VABI DestroyMatchesModelString16(void* object) { + DestroyMatchesString(MatchesStringAt(object, 16)); + std::free(object); +} + +static void APS5_VABI DestroyAddedPlayer(void* object) { + DestroyMatchesString(MatchesStringAt(object, 16)); + DestroyMatchesString(MatchesStringAt(object, 56)); + DestroyMatchesString(MatchesStringAt(object, 96)); + DestroyMatchesString(MatchesStringAt(object, 136)); + std::free(object); +} + +static void ReleaseMatchesMember(void* object, const std::size_t offset, const std::size_t referenceCountOffset) { + ReleaseIntrusivePtr(reinterpret_cast(static_cast(object) + offset), referenceCountOffset, __func__); +} + +static void APS5_VABI DestroyCreateMatchRequest(void* object) { + DestroyMatchesString(MatchesStringAt(object, 24)); + DestroyMatchesString(MatchesStringAt(object, 56)); + ReleaseMatchesMember(object, 152, ReferenceCountOffset); + std::free(object); +} + +static void APS5_VABI DestroyRequestMatchTeam(void* object) { + DestroyMatchesString(MatchesStringAt(object, 16)); + DestroyMatchesString(MatchesStringAt(object, 48)); + ReleaseMatchesMember(object, 88, VectorReferenceCountOffset); + std::free(object); +} + +static void APS5_VABI DestroyRequestTeamStatistic(void* object) { + DestroyMatchesString(MatchesStringAt(object, 16)); + ReleaseMatchesMember(object, 48, VectorReferenceCountOffset); + ReleaseMatchesMember(object, 80, VectorReferenceCountOffset); + std::free(object); +} + +static void APS5_VABI DestroyMatchesModelVectors16And48(void* object) { + ReleaseMatchesMember(object, 16, VectorReferenceCountOffset); + ReleaseMatchesMember(object, 48, VectorReferenceCountOffset); + std::free(object); +} + +static void APS5_VABI DestroyUpdateMatchDetailRequest(void* object) { + DestroyMatchesString(MatchesStringAt(object, 24)); + ReleaseMatchesMember(object, 112, ReferenceCountOffset); + std::free(object); +} + +static void APS5_VABI DestroyRequestMatchResults(void* object) { + ReleaseMatchesMember(object, 32, ReferenceCountOffset); + std::free(object); +} + +static int MatchesFactoryCreate(const void* context, IntrusivePtr* out, const std::size_t size, void (APS5_VABI* deleter)(void*)) { + if (context == nullptr || out == nullptr) return ParameterInvalidArgument; + void* object = std::calloc(1, size); + if (object == nullptr) return MatchesOutOfMemory; + ReferenceCount(object, ReferenceCountOffset).store(1); + out->object = object; + out->deleter = deleter; + out->libContext = const_cast(context); + return 0; +} + int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V111AddedPlayer12setAccountIdEPKc(void* self, const char* value) { const int result = AssignMatchesString(MatchesStringAt(self, 96), value); if (result < 0) return result; @@ -3991,9 +4176,16 @@ int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V117LeaveMatchRequest17setNpServiceLa return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118AddedPlayerFactory6createEPNS1_6Common10LibContextEPKcNS3_10PlayerTypeEPNS5_12IntrusivePtrINS3_11AddedPlayerEEE(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118AddedPlayerFactory6createEPNS1_6Common10LibContextEPKcNS3_10PlayerTypeEPNS5_12IntrusivePtrINS3_11AddedPlayerEEE(void* self, const char* name, std::int32_t playerType, IntrusivePtr* out) { + const int created = MatchesFactoryCreate(self, out, 0xb0, &DestroyAddedPlayer); + if (created != 0) return created; + ConstructMatchesString(MatchesStringAt(out->object, 16), out->libContext); + ConstructMatchesString(MatchesStringAt(out->object, 56), out->libContext); + ConstructMatchesString(MatchesStringAt(out->object, 96), out->libContext); + ConstructMatchesString(MatchesStringAt(out->object, 136), out->libContext); + const int assigned = AssignMatchesString(MatchesStringAt(out->object, 16), name); + if (assigned < 0) return assigned; + *reinterpret_cast(static_cast(out->object) + 0x30) = playerType; return 0; } @@ -4077,10 +4269,11 @@ int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V119RequestMatchResults20setCooperati return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RemovedPlayerFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_13RemovedPlayerEEE(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RemovedPlayerFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_13RemovedPlayerEEE(void* self, const char* name, IntrusivePtr* out) { + const int created = MatchesFactoryCreate(self, out, 0x38, &DestroyMatchesModelString16); + if (created != 0) return created; + ConstructMatchesString(MatchesStringAt(out->object, 16), out->libContext); + return AssignMatchesString(MatchesStringAt(out->object, 16), name); } int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120ReportResultsRequest17setNpServiceLabelERKi(void* self, const int* value) { @@ -4096,10 +4289,11 @@ int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120ReportResultsRequest18setMatchSta return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RequestMemberFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_13RequestMemberEEE(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RequestMemberFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_13RequestMemberEEE(void* self, const char* name, IntrusivePtr* out) { + const int created = MatchesFactoryCreate(self, out, 0x30, &DestroyMatchesModelString16); + if (created != 0) return created; + ConstructMatchesString(MatchesStringAt(out->object, 16), out->libContext); + return AssignMatchesString(MatchesStringAt(out->object, 16), name); } int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RequestPlayerResults8setScoreERKd(void* self, const double* value) { @@ -4142,10 +4336,12 @@ int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V123JoinMatchRequestFactory6createEPN return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V123RequestMatchTeamFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_16RequestMatchTeamEEE(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V123RequestMatchTeamFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_16RequestMatchTeamEEE(void* self, const char* name, IntrusivePtr* out) { + const int created = MatchesFactoryCreate(self, out, 0x78, &DestroyRequestMatchTeam); + if (created != 0) return created; + ConstructMatchesString(MatchesStringAt(out->object, 16), out->libContext); + ConstructMatchesString(MatchesStringAt(out->object, 48), out->libContext); + return AssignMatchesString(MatchesStringAt(out->object, 16), name); } int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124LeaveMatchRequestFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_13RemovedPlayerEEEEEPNS9_INS3_17LeaveMatchRequestEEE(void* self) { @@ -4200,10 +4396,12 @@ int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124UpdateMatchStatusRequest17setNpSe return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V125CreateMatchRequestFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_18CreateMatchRequestEEE(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V125CreateMatchRequestFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_18CreateMatchRequestEEE(void* self, const char* name, IntrusivePtr* out) { + const int created = MatchesFactoryCreate(self, out, 0xb8, &DestroyCreateMatchRequest); + if (created != 0) return created; + ConstructMatchesString(MatchesStringAt(out->object, 24), out->libContext); + ConstructMatchesString(MatchesStringAt(out->object, 56), out->libContext); + return AssignMatchesString(MatchesStringAt(out->object, 24), name); } int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V125RequestMatchPlayerFactory6createEPNS1_6Common10LibContextEPKcNS3_10PlayerTypeEPNS5_12IntrusivePtrINS3_18RequestMatchPlayerEEE(void* self) { @@ -4224,15 +4422,14 @@ int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V126AdditionalStatisticFactory6create return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V126RequestInGameRosterFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_19RequestInGameRosterEEE(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V126RequestInGameRosterFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_19RequestInGameRosterEEE(void* self, IntrusivePtr* out) { + return MatchesFactoryCreate(self, out, 0x50, &DestroyMatchesModelVectors16And48); } -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V126RequestMatchResultsFactory6createEPNS1_6Common10LibContextENS3_14ResultsVersionEPNS5_12IntrusivePtrINS3_19RequestMatchResultsEEE(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V126RequestMatchResultsFactory6createEPNS1_6Common10LibContextENS3_14ResultsVersionEPNS5_12IntrusivePtrINS3_19RequestMatchResultsEEE(void* self, std::int32_t version, IntrusivePtr* out) { + const int created = MatchesFactoryCreate(self, out, 0x40, &DestroyRequestMatchResults); + if (created != 0) return created; + *reinterpret_cast(static_cast(out->object) + 0x10) = version; return 0; } @@ -4248,16 +4445,15 @@ int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V127RequestPlayerResultsFactory6creat return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V127RequestTeamStatisticFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_20RequestTeamStatisticEEE(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V127RequestTeamStatisticFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_20RequestTeamStatisticEEE(void* self, const char* name, IntrusivePtr* out) { + const int created = MatchesFactoryCreate(self, out, 0x70, &DestroyRequestTeamStatistic); + if (created != 0) return created; + ConstructMatchesString(MatchesStringAt(out->object, 16), out->libContext); + return AssignMatchesString(MatchesStringAt(out->object, 16), name); } -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V129RequestMatchStatisticsFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_22RequestMatchStatisticsEEE(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V129RequestMatchStatisticsFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_22RequestMatchStatisticsEEE(void* self, IntrusivePtr* out) { + return MatchesFactoryCreate(self, out, 0x50, &DestroyMatchesModelVectors16And48); } int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V129RequestPlayerStatisticFactory6createEPNS1_6Common10LibContextEPKcRKNS5_6VectorINS5_12IntrusivePtrINS3_19AdditionalStatisticEEEEEPNSB_INS3_22RequestPlayerStatisticEEE(void* self) { @@ -4272,21 +4468,21 @@ int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V130RequestTeamMemberResultFactory6cr return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131RequestCompetitiveResultFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_24RequestCompetitiveResultEEE(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131RequestCompetitiveResultFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_24RequestCompetitiveResultEEE(void* self, IntrusivePtr* out) { + return MatchesFactoryCreate(self, out, 0x50, &DestroyMatchesModelVectors16And48); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131UpdateMatchDetailRequestFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_24UpdateMatchDetailRequestEEE(void* self, IntrusivePtr* out) { + const int created = MatchesFactoryCreate(self, out, 0xa8, &DestroyUpdateMatchDetailRequest); + if (created != 0) return created; + ConstructMatchesString(MatchesStringAt(out->object, 24), out->libContext); return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131UpdateMatchDetailRequestFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_24UpdateMatchDetailRequestEEE(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131UpdateMatchStatusRequestFactory6createEPNS1_6Common10LibContextENS3_12UpdateStatusEPNS5_12IntrusivePtrINS3_24UpdateMatchStatusRequestEEE(void* self) { - (void)self; - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131UpdateMatchStatusRequestFactory6createEPNS1_6Common10LibContextENS3_12UpdateStatusEPNS5_12IntrusivePtrINS3_24UpdateMatchStatusRequestEEE(void* self, std::int32_t status, IntrusivePtr* out) { + const int created = MatchesFactoryCreate(self, out, 0x20, &DestroyMatchesModel); + if (created != 0) return created; + *reinterpret_cast(static_cast(out->object) + 0x18) = status; return 0; } @@ -4443,72 +4639,60 @@ bool APS5_VABI _ZNK3sce2Np9CppWebApi7Matches2V119RequestInGameRoster12playersIsS } -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku10priceIsSetEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku10priceIsSetEv(const void* self) { + return *reinterpret_cast(static_cast(self) + 0xd8) != 0; } -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku8getPriceEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::uint32_t APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku8getPriceEv(const void* self) { + return *reinterpret_cast(static_cast(self) + 0xd4); } -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku15getDisplayPriceEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +const void* APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku15getDisplayPriceEv(const void* self) { + return static_cast(self) + 0xe0; } int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainer7setsortENS5_4SortE() { return 0; } -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku18originalPriceIsSetEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku18originalPriceIsSetEv(const void* self) { + return *reinterpret_cast(static_cast(self) + 0x108) != 0; } -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product14getDescriptionEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +const void* APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product14getDescriptionEv(const void* self) { + return static_cast(self) + 0x130; } -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product14getReleaseDateEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +const void* APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product14getReleaseDateEv(const void* self) { + return static_cast(self) + 0x80; } -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku23getDisplayOriginalPriceEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +const void* APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku23getDisplayOriginalPriceEv(const void* self) { + return static_cast(self) + 0x110; } -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product14getDisplayNameEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +const void* APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product14getDisplayNameEv(const void* self) { + return static_cast(self) + 0x60; } -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku16getOriginalPriceEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::uint32_t APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku16getOriginalPriceEv(const void* self) { + return *reinterpret_cast(static_cast(self) + 0x104); } -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku25displayOriginalPriceIsSetEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku25displayOriginalPriceIsSetEv(const void* self) { + return *reinterpret_cast(static_cast(self) + 0x130) != 0; } -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku8getLabelEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +const void* APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku8getLabelEv(const void* self) { + return static_cast(self) + 0x60; } int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainer9setoffsetEi() { return 0; } -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product16releaseDateIsSetEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product16releaseDateIsSetEv(const void* self) { + return *reinterpret_cast(static_cast(self) + 0xa0) != 0; } int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainer15setcontainerIdsEPKc(void* self, const char* containerIds) { @@ -4519,53 +4703,44 @@ int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToG return 0; } -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku17displayPriceIsSetEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku17displayPriceIsSetEv(const void* self) { + return *reinterpret_cast(static_cast(self) + 0x100) != 0; } -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product5getIdEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +const void* APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product5getIdEv(const void* self) { + return static_cast(self) + 0x10; } int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS2_6VectorINS4_INS1_13InGameCatalog2V59ContainerEEEEEEENS4_INS2_18ResponseHeaderBaseEEEE11hasResponseEv() { return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS2_6VectorINS4_INS1_13InGameCatalog2V59ContainerEEEEEEENS4_INS2_18ResponseHeaderBaseEEEEaSERKSE_() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS2_6VectorINS4_INS1_13InGameCatalog2V59ContainerEEEEEEENS4_INS2_18ResponseHeaderBaseEEEEaSERKSE_(void* self, const void* source) { + auto* dst = static_cast(self); + const auto* src = static_cast(source); + dst[0] = src[0]; + dst[1] = src[1]; + return self; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEEppEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEEppEv(void* self) { + return HostIteratorAdvance(self, sizeof(IntrusivePtr)); } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEE5emptyEv(const IntrusivePtr* self) { - const auto* body = static_cast(self->object); - if (body == nullptr) return 1; - if (body->magic != VectorBodyMagic) { - NotImplemented_nid_no_patch(__func__); - return 0; - } - return body->size == 0; +bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEE5emptyEv(const void* self) { + return HostVectorSize(self, __func__) == 0; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEEneERKS9_() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEEneERKS9_(const void* self, const void* other) { + return HostIteratorNotEqual(self, other); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEE5beginEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEE5beginEv(void* out, void* self) { + return HostVectorBegin(out, self, 1, __func__); } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEEneERKS9_() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEEneERKS9_(const void* self, const void* other) { + return HostIteratorNotEqual(self, other); } void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V57ProductEEEEEEdeEv(const IntrusivePtr* self) { @@ -4581,19 +4756,16 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_ return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEE3endEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEE3endEv(void* out, void* self) { + return HostVectorEnd(out, self, 1, sizeof(IntrusivePtr), __func__); } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEEdeEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEEdeEv(const void* self) { + return static_cast(self)->element; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEEdeEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEEdeEv(const void* self) { + return static_cast(self)->element; } void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V59ContainerEEEEEEC1ERKSA_(IntrusivePtr* self, const IntrusivePtr* source) { @@ -4610,33 +4782,24 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_ return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEE5beginEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEE5beginEv(void* out, void* self) { + return HostVectorBegin(out, self, 1, __func__); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEE3endEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEE3endEv(void* out, void* self) { + return HostVectorEnd(out, self, 1, sizeof(IntrusivePtr), __func__); } void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V53SkuEEEEEEdeEv(const IntrusivePtr* self) { return self->object; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEEppEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEEppEv(void* self) { + return HostIteratorAdvance(self, sizeof(IntrusivePtr)); } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEE5emptyEv(const IntrusivePtr* self) { - const auto* body = static_cast(self->object); - if (body == nullptr) return 1; - if (body->magic != VectorBodyMagic) { - NotImplemented_nid_no_patch(__func__); - return 0; - } - return body->size == 0; +bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEE5emptyEv(const void* self) { + return HostVectorSize(self, __func__) == 0; } void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V59ContainerEEEEEEaSERKSA_(IntrusivePtr* self, const IntrusivePtr* source) { @@ -4657,9 +4820,8 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_6StringEED2Ev(void* return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE8copyFromERKS5_() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE8copyFromERKS5_(void* self, const void* source) { + return HostVectorCopyFromStrings(static_cast(self), static_cast(source), __func__); } void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEE3getEv(const IntrusivePtr* self) { @@ -4717,16 +4879,26 @@ CommonString* APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_6StringEEdeEv( // libSceNpCppWebApi.prx. // -------------------------------------------------------------------------- -int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile19personalDetailIsSetEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile19personalDetailIsSetEv(const void* self) { + return *reinterpret_cast(static_cast(self) + 0x40) != 0; } int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V115BasicProfileApi28ParameterToGetPublicProfiles9terminateEv() { return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V112BasicProfileC1EPNS1_6Common10LibContextE() { +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V112BasicProfileC1EPNS1_6Common10LibContextE(void* self, void* context) { + auto* object = static_cast(self); + *reinterpret_cast(object) = 0; + *reinterpret_cast(object + 8) = context; + std::memset(object + 0x10, 0, 0x38); + InitHostString(object + 0x48, context); + object[0x68] = 0; + std::memset(object + 0x70, 0, 0x40); + *reinterpret_cast(object + 0xb0) = 0; + object[0xb8] = 0; + *reinterpret_cast(object + 0xbc) = 0; + object[0xc0] = 0; return 0; } @@ -4755,39 +4927,45 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11 return 0; } -int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile25isOfficiallyVerifiedIsSetEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile25isOfficiallyVerifiedIsSetEv(const void* self) { + return *reinterpret_cast(static_cast(self) + 0xaa) != 0; } -int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence14accountIdIsSetEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence14accountIdIsSetEv(const void* self) { + return *reinterpret_cast(static_cast(self) + 0x18) != 0; } void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V112BasicProfileEEEEEED1Ev(IntrusivePtr* self) { ReleaseIntrusivePtr(self, 0x30, __func__); } -int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V113BasicPresence12setAccountIdERKm() { +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V113BasicPresence12setAccountIdERKm(void* self, const std::uint64_t* accountId) { + auto* object = static_cast(self); + *reinterpret_cast(object + 0x10) = *accountId; + object[0x18] = 1; return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V113BasicPresenceC1EPNS1_6Common10LibContextE() { +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V113BasicPresenceC1EPNS1_6Common10LibContextE(void* self, void* context) { + auto* object = static_cast(self); + *reinterpret_cast(object) = 0; + *reinterpret_cast(object + 8) = context; + *reinterpret_cast(object + 0x10) = 0; + object[0x18] = 0; + *reinterpret_cast(object + 0x1c) = 0; + std::memset(object + 0x1f, 0, 4); return 0; } int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv() { - NotImplemented_nid_no_patch(__func__); - return 0; + return TransactionNotStarted; } void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V114PersonalDetailEED1Ev(IntrusivePtr* self) { ReleaseIntrusivePtr(self, 0x00, __func__); } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv() { - NotImplemented_nid_no_patch(__func__); +std::int64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv() { return 0; } @@ -4795,7 +4973,10 @@ int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_1 return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V113BasicPresence12setInContextERKb() { +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V113BasicPresence12setInContextERKb(void* self, const bool* inContext) { + auto* object = static_cast(self); + object[0x21] = *inContext ? 1 : 0; + object[0x22] = 1; return 0; } @@ -4823,7 +5004,11 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V118G ReleaseIntrusivePtr(self, 0x00, __func__); } -int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V112BasicProfile11setOnlineIdERK13SceNpOnlineId() { +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V112BasicProfile11setOnlineIdERK13SceNpOnlineId(void* self, const void* onlineId) { + auto* object = static_cast(self); + std::memcpy(object + 0x10, onlineId, 0x10); + *reinterpret_cast(object + 0x20) = *reinterpret_cast(static_cast(onlineId) + 0x10); + object[0x24] = 1; return 0; } @@ -4839,9 +5024,8 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V113B ReleaseIntrusivePtr(self, 0x00, __func__); } -int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile17getPersonalDetailEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile17getPersonalDetailEv(void* out, const void* self) { + return CopyMemberIntrusivePtr(out, self, 0x28, ReferenceCountOffset); } void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEC1Ev(IntrusivePtr* self) { @@ -4857,9 +5041,11 @@ void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_ return self->object; } -int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile11getOnlineIdEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile11getOnlineIdEv(void* out, const void* self) { + std::memcpy(out, static_cast(self) + 0x10, 0x10); + *reinterpret_cast(static_cast(out) + 0x10) = + *reinterpret_cast(static_cast(self) + 0x20); + return out; } void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V113BasicPresenceEEEEEEaSERKSA_(IntrusivePtr* self, const IntrusivePtr* source) { @@ -4867,24 +5053,20 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11 CopyIntrusivePtr(self, source, 0x30); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEE5beginEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEE5beginEv(void* out, void* self) { + return HostVectorBegin(out, self, 1, __func__); } -int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V114PersonalDetail14getDisplayNameEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +const void* APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V114PersonalDetail14getDisplayNameEv(const void* self) { + return static_cast(self) + 0x88; } -int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile13onlineIdIsSetEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile13onlineIdIsSetEv(const void* self) { + return *reinterpret_cast(static_cast(self) + 0x24) != 0; } -int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence14inContextIsSetEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence14inContextIsSetEv(const void* self) { + return *reinterpret_cast(static_cast(self) + 0x22) != 0; } int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetPublicProfilesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE11hasResponseEv() { @@ -4903,13 +5085,17 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V112B ReleaseIntrusivePtr(self, 0x00, __func__); } -int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V112BasicProfileD1Ev() { - return 0; +void* APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V112BasicProfileD1Ev(void* self) { + auto* object = static_cast(self); + ReleaseIntrusivePtr(reinterpret_cast(object + 0x90), StringVectorReferenceCountOffset, __func__); + ReleaseIntrusivePtr(reinterpret_cast(object + 0x70), VectorReferenceCountOffset, __func__); + DestroyHostString(object + 0x48); + ReleaseIntrusivePtr(reinterpret_cast(object + 0x28), ReferenceCountOffset, __func__); + return self; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEEneERKS9_() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEEneERKS9_(const void* self, const void* other) { + return HostIteratorNotEqual(self, other); } void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { @@ -4920,9 +5106,8 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V125G *self = {}; } -int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile23getIsOfficiallyVerifiedEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile23getIsOfficiallyVerifiedEv(const void* self) { + return *reinterpret_cast(static_cast(self) + 0xa9) != 0; } void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V112BasicProfileEEEEEE3getEv(const IntrusivePtr* self) { @@ -4974,19 +5159,16 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V112B CopyIntrusivePtr(self, source, 0x00); } -int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V125GetPublicProfilesResponse11getProfilesEv() { - NotImplemented_nid_no_patch(__func__); +void* APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V125GetPublicProfilesResponse11getProfilesEv(void* out, void* self) { + return CopyMemberIntrusivePtr(out, self, 0x30, VectorReferenceCountOffset); +} + +std::int64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv() { return 0; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEEppEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEEppEv(void* self) { + return HostIteratorAdvance(self, sizeof(IntrusivePtr)); } int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriends8setlimitEi() { @@ -4994,21 +5176,22 @@ int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFr } int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv() { - NotImplemented_nid_no_patch(__func__); - return 0; + return TransactionNotStarted; } void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEC1Ev(IntrusivePtr* self) { *self = {}; } -int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V112BasicProfile23setIsOfficiallyVerifiedERKb() { +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V112BasicProfile23setIsOfficiallyVerifiedERKb(void* self, const bool* verified) { + auto* object = static_cast(self); + object[0xa9] = *verified ? 1 : 0; + object[0xaa] = 1; return 0; } -int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence17onlineStatusIsSetEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence17onlineStatusIsSetEv(const void* self) { + return *reinterpret_cast(static_cast(self) + 0x20) != 0; } int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V115BasicProfileApi28ParameterToGetPublicProfilesD1Ev() { @@ -5024,13 +5207,12 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_30CommunicationRest CopyIntrusivePtr(self, source, 0x00); } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv() { - NotImplemented_nid_no_patch(__func__); +std::int64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv() { return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V113BasicPresenceD1Ev() { - return 0; +void* APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V113BasicPresenceD1Ev(void* self) { + return self; } int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetPublicProfilesResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { @@ -5048,16 +5230,14 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_ } int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv() { - NotImplemented_nid_no_patch(__func__); - return 0; + return TransactionNotStarted; } int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsers8setlimitEi() { return 0; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetPublicProfilesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv() { - NotImplemented_nid_no_patch(__func__); +std::int64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetPublicProfilesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv() { return 0; } @@ -5069,18 +5249,19 @@ int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBloc return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEE3endEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEE3endEv(void* out, void* self) { + return HostVectorEnd(out, self, 1, sizeof(IntrusivePtr), __func__); } -int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V113BasicPresence15setOnlineStatusERKNS3_12OnlineStatusE() { +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V113BasicPresence15setOnlineStatusERKNS3_12OnlineStatusE(void* self, const std::uint32_t* status) { + auto* object = static_cast(self); + *reinterpret_cast(object + 0x1c) = *status; + object[0x20] = 1; return 0; } int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetPublicProfilesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv() { - NotImplemented_nid_no_patch(__func__); - return 0; + return TransactionNotStarted; } int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEEdeEv() { @@ -5088,9 +5269,12 @@ int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11Use return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEEaSERKSB_() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEEaSERKSB_(void* self, const void* source) { + auto* dst = static_cast(self); + const auto* src = static_cast(source); + dst[0] = src[0]; + dst[1] = src[1]; + return self; } int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_() { diff --git a/core/libs/prx/libSceNpCppWebApi/include/Api.hpp b/core/libs/prx/libSceNpCppWebApi/include/Api.hpp index 178fdd1025..365baba687 100644 --- a/core/libs/prx/libSceNpCppWebApi/include/Api.hpp +++ b/core/libs/prx/libSceNpCppWebApi/include/Api.hpp @@ -16,6 +16,11 @@ struct CommonString { void* libContext; }; +struct CommonResponseHeaderBase { + std::int32_t referenceCount; + void* headers; +}; + struct IntrusivePtr { void* object; void (APS5_VABI* deleter)(void*); @@ -358,7 +363,7 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V1 const char* APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V136UsersPlayerSessionsInvitationForRead12getSessionIdEv(const void* self); void* APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V136UsersPlayerSessionsInvitationForRead7getFromEv(const void* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V129PostPlayerSessionsRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source); -void* APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V130PostPlayerSessionsResponseBody17getPlayerSessionsEv(void* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V130PostPlayerSessionsResponseBody17getPlayerSessionsEv(void* out, void* self); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi18leavePlayerSessionEiRKNS4_29ParameterToLeavePlayerSessionERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE(); int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V124PlayerSessionPushContextEEEE8pushBackERKS8_(IntrusivePtr* self, const void* element); int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(); @@ -474,12 +479,12 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_17TitleCloudStorage2V132GetMultiDataStatusesResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(void* self, void* context); void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEEixEm(void* self, std::uint64_t index); std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEE4sizeEv(const void* self); -void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEE5beginEv(void* self); -void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEE3endEv(void* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEE5beginEv(void* out, void* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEE3endEv(void* out, void* self); std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEE4sizeEv(const void* self); -void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEE5beginEv(void* self); -void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEE3endEv(void* self); -void* APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V59Container11getChildrenEv(void* self); +CommonIterator* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEE5beginEv(CommonIterator* iterator, const IntrusivePtr* self); +CommonIterator* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEE3endEv(CommonIterator* iterator, const IntrusivePtr* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V59Container11getChildrenEv(void* out, void* self); int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainerC1Ev(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainer10initializeEPNS1_6Common10LibContextE(void* self, const std::uint64_t* context); bool APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainer13isInitializedEv(const void* self); @@ -488,8 +493,8 @@ int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToG int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainerD1Ev(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi12getContainerEiRKNS4_23ParameterToGetContainerERNS1_6Common11TransactionINS8_12IntrusivePtrINS8_6VectorINSA_INS3_9ContainerEEEEEEENSA_INS8_18ResponseHeaderBaseEEEEE(); const char* APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product8getLabelEv(const void* self); -void* APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V57Product7getSkusEv(void* self); -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product7getTypeEv(const void* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V57Product7getSkusEv(void* out, void* self); +const void* APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product7getTypeEv(const void* self); bool APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product9skusIsSetEv(const void* self); int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi23ParameterToDownloadData11setobjectIdEPKc(void* self, const char* objectId); int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi37ParameterToGetMultiDataStatusesByUserC1Ev(void* self); @@ -550,7 +555,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetB int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresences9terminateEv(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresencesC1Ev(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V111PresenceApi28ParameterToGetBasicPresencesD1Ev(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V118GetFriendsResponse10getFriendsEv(); +void* APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V118GetFriendsResponse10getFriendsEv(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V149PostPlayerSessionsSessionIdInvitationsRequestBody14setInvitationsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_30RequestPlayerSessionInvitationEEEEE(); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28sendPlayerSessionInvitationsEiRKNS4_39ParameterToSendPlayerSessionInvitationsERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_50PostPlayerSessionsSessionIdInvitationsResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE(); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi17getPlayerSessionsEiRKNS4_28ParameterToGetPlayerSessionsERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_29GetPlayerSessionsResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE(); @@ -561,20 +566,20 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36Parame int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionPropertiesC1Ev(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsers9terminateEv(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120RequestPlayerSession14setCustomData1EPKvm(); -int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead16customData2IsSetEv(); -int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V124GetBlockingUsersResponse9getBlocksEv(); +bool APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead16customData2IsSetEv(const void* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V124GetBlockingUsersResponse9getBlocksEv(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionPropertiesD1Ev(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitations10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_49PostPlayerSessionsSessionIdInvitationsRequestBodyEEE(void* self, const std::uint64_t* context, const char* sessionId, const IntrusivePtr* body); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V146PatchPlayerSessionsSessionIdRequestBodyFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_39PatchPlayerSessionsSessionIdRequestBodyEEE(); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V137RequestPlayerSessionInvitationFactory6createEPNS1_6Common10LibContextENS5_12IntrusivePtrINS3_2ToEEEPNS8_INS3_30RequestPlayerSessionInvitationEEE(); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V139PatchPlayerSessionsSessionIdRequestBody19setJoinableUserTypeERKNS3_22CustomJoinableUserTypeE(); -int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V127ResponsePlayerSessionPlayer11getPlatformEv(); -int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V125GetBasicPresencesResponse17getBasicPresencesEv(); +const void* APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V127ResponsePlayerSessionPlayer11getPlatformEv(const void* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V125GetBasicPresencesResponse17getBasicPresencesEv(void* out, void* self); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessions9setfieldsENS1_6Common12IntrusivePtrINS6_6VectorINS6_6StringEEEEE(); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessions9terminateEv(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSession9terminateEv(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitationsD1Ev(void* self); -int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead14getCustomData1Ev(); +const void* APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead14getCustomData1Ev(const void* self); int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi16getBlockingUsersEiRKNS4_27ParameterToGetBlockingUsersERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_24GetBlockingUsersResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE(); int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsers10initializeEPNS1_6Common10LibContextE(void* self, const std::uint64_t* context); int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsers9terminateEv(void* self); @@ -582,10 +587,10 @@ int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBloc int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31ParameterToCreatePlayerSessions9terminateEv(void* self); int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead16customData1IsSetEv(); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi38addPlayerSessionJoinableSpecifiedUsersEiRKNS4_49ParameterToAddPlayerSessionJoinableSpecifiedUsersERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_61PostPlayerSessionsSessionIdJoinableSpecifiedUsersResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE(); -int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead9getLeaderEv(); +void* APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead9getLeaderEv(void* out, const void* self); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitationsC1Ev(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessionsD1Ev(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead28getExclusiveLeaderPrivilegesEv(); +void* APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead28getExclusiveLeaderPrivilegesEv(void* out, void* self); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi26setPlayerSessionPropertiesEiRKNS4_37ParameterToSetPlayerSessionPropertiesERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE(); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi25changePlayerSessionLeaderEiRKNS4_36ParameterToChangePlayerSessionLeaderERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE(); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V150PutPlayerSessionsSessionIdLeaderRequestBodyFactory6createEPNS1_6Common10LibContextERKNS_4Json5ValueEPNS5_12IntrusivePtrINS3_43PutPlayerSessionsSessionIdLeaderRequestBodyEEE(); @@ -595,9 +600,9 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V156PostPlayerSessionsSession int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitations9terminateEv(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessions10initializeEPNS1_6Common10LibContextEPKc(void* self, const std::uint64_t* context, const char* sessionIds); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessionsC1Ev(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V133ResponsePlayerSessionMemberPlayer10getPlayersEv(); +void* APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V133ResponsePlayerSessionMemberPlayer10getPlayersEv(void* out, void* self); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V139PatchPlayerSessionsSessionIdRequestBody28setExclusiveLeaderPrivilegesERKNS1_6Common6VectorINS5_6StringEEE(); -int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V113PlayerSession9getMemberEv(); +void* APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V113PlayerSession9getMemberEv(void* out, const void* self); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V19ToFactory6createEPNS1_6Common10LibContextERKmPNS5_12IntrusivePtrINS3_2ToEEE(); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsersD1Ev(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToChangePlayerSessionLeader10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_43PutPlayerSessionsSessionIdLeaderRequestBodyEEE(void* self, const std::uint64_t* context, const char* sessionId, const IntrusivePtr* body); @@ -606,10 +611,10 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36Parame int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionProperties9terminateEv(void* self); int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V118LeaderWithOnlineId12getAccountIdEv(); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V139PatchPlayerSessionsSessionIdRequestBody20setInvitableUserTypeERKNS3_23CustomInvitableUserTypeE(); -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V129GetPlayerSessionsResponseBody17getPlayerSessionsEv(); +void* APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V129GetPlayerSessionsResponseBody17getPlayerSessionsEv(void* out, void* self); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsersC1Ev(void* self); -int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead14getCustomData2Ev(); -int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V127ResponsePlayerSessionPlayer12getAccountIdEv(); +const void* APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead14getCustomData2Ev(const void* self); +std::uint64_t APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V127ResponsePlayerSessionPlayer12getAccountIdEv(const void* self); int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11hasResponseEv(); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V112JoinableUserEED1Ev(IntrusivePtr* self); int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self); @@ -650,7 +655,7 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEED1Ev(Intru void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V113BasicPresenceEEEEEED1Ev(IntrusivePtr* self); int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEED2Ev(void* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V112JoinableUserEEC1Ev(IntrusivePtr* self); -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_14SessionManager2V120PlayerSessionForReadEEEEdeEv(); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_14SessionManager2V120PlayerSessionForReadEEEEdeEv(const void* self); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V118LeaderWithOnlineIdEEptEv(const IntrusivePtr* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V112JoinableUserEEEED1Ev(IntrusivePtr* self); int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11hasResponseEv(); @@ -688,19 +693,19 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption(); int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(); int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv(); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBase14getHeaderValueEPKcRNS2_12IntrusivePtrINS2_6StringEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBase14getHeaderValueEPKcRNS2_12IntrusivePtrINS2_6StringEEE(const CommonResponseHeaderBase* self, const char* name, IntrusivePtr* value); int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V150PostPlayerSessionsSessionIdInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv(); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE3endEv(); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE5beginEv(); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorImE3endEv(); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorImE5beginEv(); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEEppEv(); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorImEppEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence12getAccountIdEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence12getInContextEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence15getOnlineStatusEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi15ProfanityFilter2V223FilterProfanityResponse10getMessageEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponse13getRestrictedEv(); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE3endEv(void* out, void* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE5beginEv(void* out, void* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorImE3endEv(void* out, void* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorImE5beginEv(void* out, void* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEEppEv(void* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorImEppEv(void* self); +std::uint64_t APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence12getAccountIdEv(const void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence12getInContextEv(const void* self); +std::uint32_t APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence15getOnlineStatusEv(const void* self); +const void* APS5_VABI _ZNK3sce2Np9CppWebApi15ProfanityFilter2V223FilterProfanityResponse10getMessageEv(const void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponse13getRestrictedEv(const void* self); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEptEv(const IntrusivePtr* self); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEptEv(const IntrusivePtr* self); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEptEv(const IntrusivePtr* self); @@ -709,6 +714,10 @@ void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEptEv(const IntrusivePtr* self); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEE3getEv(const IntrusivePtr* self); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEEptEv(const IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBaseC1Ev(CommonResponseHeaderBase* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBaseC2Ev(CommonResponseHeaderBase* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBaseD1Ev(CommonResponseHeaderBase* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBaseD2Ev(CommonResponseHeaderBase* self); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEE3getEv(const IntrusivePtr* self); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEEptEv(const IntrusivePtr* self); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V113BasicPresenceEEEEEEdeEv(const IntrusivePtr* self); @@ -721,7 +730,7 @@ int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrIN int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_15ProfanityFilter2V223FilterProfanityResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_(); int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE18getResponseHeadersERSA_(); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEED1Ev(IntrusivePtr* self); -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V120PlayerSessionForReadEEEE5emptyEv(const IntrusivePtr* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V120PlayerSessionForReadEEEE5emptyEv(const void* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V150PostPlayerSessionsSessionIdInvitationsResponseBodyEEC1Ev(IntrusivePtr* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V143PutPlayerSessionsSessionIdLeaderRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V139PatchPlayerSessionsSessionIdRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source); @@ -742,9 +751,9 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V1 int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V150PostPlayerSessionsSessionIdInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_(); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V12ToEED1Ev(IntrusivePtr* self); int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V161PostPlayerSessionsSessionIdJoinableSpecifiedUsersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorImE4sizeEv(); +std::uint64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorImE4sizeEv(const void* self); std::int64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V129GetPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEEdeEv(); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEEdeEv(const void* self); int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11hasResponseEv(); void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEEixEm(IntrusivePtr* self, unsigned long index); void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEED1Ev(IntrusivePtr* self); @@ -753,7 +762,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14 void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V143PutPlayerSessionsSessionIdLeaderRequestBodyEEC1Ev(IntrusivePtr* self); int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V161PostPlayerSessionsSessionIdJoinableSpecifiedUsersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEEneERKS9_(); +bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEEneERKS9_(const void* self, const void* other); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V139PatchPlayerSessionsSessionIdRequestBodyEEptEv(const IntrusivePtr* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V113PlayerSessionEED1Ev(IntrusivePtr* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V139PatchPlayerSessionsSessionIdRequestBodyEEC1Ev(IntrusivePtr* self); @@ -767,13 +776,13 @@ int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_1 void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V113PlayerSessionEEC1Ev(IntrusivePtr* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V130RequestPlayerSessionInvitationEEEEC1EPNS2_10LibContextE(IntrusivePtr* self, void* libContext); std::int64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V161PostPlayerSessionsSessionIdJoinableSpecifiedUsersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorImEdeEv(); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorImEdeEv(const void* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V118LeaderWithOnlineIdEED1Ev(IntrusivePtr* self); std::int64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorImEneERKS4_(); -std::uint64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEE4sizeEv(const IntrusivePtr* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorImEneERKS4_(const void* self, const void* other); +std::uint64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEE4sizeEv(const void* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V120PlayerSessionForReadEEEEEED1Ev(IntrusivePtr* self); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V120PlayerSessionForReadEEEE5beginEv(); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V120PlayerSessionForReadEEEE5beginEv(void* out, void* self); int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V130RequestPlayerSessionInvitationEEEE8pushBackERKS8_(IntrusivePtr* self, const void* element); int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V161PostPlayerSessionsSessionIdJoinableSpecifiedUsersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(); int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv(); @@ -781,54 +790,54 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V1 void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V130RequestPlayerSessionInvitationEEEED1Ev(IntrusivePtr* self); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V120PlayerSessionForReadEEptEv(const IntrusivePtr* self); std::int64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv(); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEE8copyFromERKS9_(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEE8copyFromERKS9_(void* self, const void* source); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V150PostPlayerSessionsSessionIdInvitationsResponseBodyEED1Ev(IntrusivePtr* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V113PlayerSessionEEEEEEC1Ev(IntrusivePtr* self); -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku10labelIsSetEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku10priceIsSetEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku8getPriceEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku15getDisplayPriceEv(); +bool APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku10labelIsSetEv(const void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku10priceIsSetEv(const void* self); +std::uint32_t APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku8getPriceEv(const void* self); +const void* APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku15getDisplayPriceEv(const void* self); int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainer7setsortENS5_4SortE(); -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku18originalPriceIsSetEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product14getDescriptionEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product14getReleaseDateEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku23getDisplayOriginalPriceEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product14getDisplayNameEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku16getOriginalPriceEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku25displayOriginalPriceIsSetEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku8getLabelEv(); +bool APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku18originalPriceIsSetEv(const void* self); +const void* APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product14getDescriptionEv(const void* self); +const void* APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product14getReleaseDateEv(const void* self); +const void* APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku23getDisplayOriginalPriceEv(const void* self); +const void* APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product14getDisplayNameEv(const void* self); +std::uint32_t APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku16getOriginalPriceEv(const void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku25displayOriginalPriceIsSetEv(const void* self); +const void* APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku8getLabelEv(const void* self); int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainer9setoffsetEi(); -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product16releaseDateIsSetEv(); +bool APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product16releaseDateIsSetEv(const void* self); int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainer15setcontainerIdsEPKc(void* self, const char* containerIds); int APS5_VABI _ZN3sce2Np9CppWebApi13InGameCatalog2V512ContainerApi23ParameterToGetContainer8setlimitEi(); -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku17displayPriceIsSetEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product5getIdEv(); +bool APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku17displayPriceIsSetEv(const void* self); +const void* APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V57Product5getIdEv(const void* self); int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS2_6VectorINS4_INS1_13InGameCatalog2V59ContainerEEEEEEENS4_INS2_18ResponseHeaderBaseEEEE11hasResponseEv(); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS2_6VectorINS4_INS1_13InGameCatalog2V59ContainerEEEEEEENS4_INS2_18ResponseHeaderBaseEEEEaSERKSE_(); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEEppEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEE5emptyEv(const IntrusivePtr* self); -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEEneERKS9_(); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEE5beginEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEEneERKS9_(); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS2_6VectorINS4_INS1_13InGameCatalog2V59ContainerEEEEEEENS4_INS2_18ResponseHeaderBaseEEEEaSERKSE_(void* self, const void* source); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEEppEv(void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEE5emptyEv(const void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEEneERKS9_(const void* self, const void* other); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEE5beginEv(void* out, void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEEneERKS9_(const void* self, const void* other); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V57ProductEEEEEEdeEv(const IntrusivePtr* self); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V53SkuEEptEv(const IntrusivePtr* self); int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEED2Ev(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEE3endEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEEdeEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEEdeEv(); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEE3endEv(void* out, void* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEEdeEv(const void* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEEdeEv(const void* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V59ContainerEEEEEEC1ERKSA_(IntrusivePtr* self, const IntrusivePtr* source); int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS2_6VectorINS4_INS1_13InGameCatalog2V59ContainerEEEEEEENS4_INS2_18ResponseHeaderBaseEEEEC1ERKSE_(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEED2Ev(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEE5beginEv(); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEE3endEv(); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEE5beginEv(void* out, void* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEE3endEv(void* out, void* self); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V53SkuEEEEEEdeEv(const IntrusivePtr* self); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEEppEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEE5emptyEv(const IntrusivePtr* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEEppEv(void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V59ContainerEEEE5emptyEv(const void* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V59ContainerEEEEEEaSERKSA_(IntrusivePtr* self, const IntrusivePtr* source); CommonIterator* APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_6StringEEppEv(CommonIterator* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEEC1Ev(IntrusivePtr* self); int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_6StringEED2Ev(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE8copyFromERKS5_(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE8copyFromERKS5_(void* self, const void* source); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEE3getEv(const IntrusivePtr* self); bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_6StringEEneERKS5_(const CommonIterator* self, const CommonIterator* other); int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEE11hasResponseEv(); @@ -847,16 +856,16 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V124G void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEC1ERKS7_(IntrusivePtr* self, const IntrusivePtr* source); int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriends8setlimitEi(); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V113BasicPresenceEEEEEE3getEv(const IntrusivePtr* self); -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv(); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEEaSERKSB_(); +std::int64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv(); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEEaSERKSB_(void* self, const void* source); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V112BasicProfileEED1Ev(IntrusivePtr* self); -int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V113BasicPresenceD1Ev(); +void* APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V113BasicPresenceD1Ev(void* self); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V125GetPublicProfilesResponseEEptEv(const IntrusivePtr* self); int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V115BasicProfileApi28ParameterToGetPublicProfiles9terminateEv(); -int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V125GetPublicProfilesResponse11getProfilesEv(); +void* APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V125GetPublicProfilesResponse11getProfilesEv(void* out, void* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V112BasicProfileEEEEEEaSERKSA_(IntrusivePtr* self, const IntrusivePtr* source); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEED1Ev(IntrusivePtr* self); -int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile17getPersonalDetailEv(); +void* APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile17getPersonalDetailEv(void* out, const void* self); int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_(); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V111AddedPlayer12setAccountIdEPKc(void* self, const char* value); @@ -867,7 +876,7 @@ int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V116JoinMatchRequest17setNpServiceLab int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V116RequestMatchTeam10setMembersERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_13RequestMemberEEEEE(void* self, const IntrusivePtr* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V116RequestMatchTeam11setTeamNameEPKc(void* self, const char* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V117LeaveMatchRequest17setNpServiceLabelERKi(void* self, const int* value); -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118AddedPlayerFactory6createEPNS1_6Common10LibContextEPKcNS3_10PlayerTypeEPNS5_12IntrusivePtrINS3_11AddedPlayerEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118AddedPlayerFactory6createEPNS1_6Common10LibContextEPKcNS3_10PlayerTypeEPNS5_12IntrusivePtrINS3_11AddedPlayerEEE(void* self, const char* name, std::int32_t playerType, IntrusivePtr* out); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118CreateMatchRequest15setInGameRosterERKNS1_6Common12IntrusivePtrINS3_19RequestInGameRosterEEE(void* self, const IntrusivePtr* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118CreateMatchRequest17setExpirationTimeERKi(void* self, const int* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118CreateMatchRequest17setNpServiceLabelERKi(void* self, const int* value); @@ -880,17 +889,17 @@ int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V119RequestInGameRoster10setPlayersER int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V119RequestInGameRoster8setTeamsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_16RequestMatchTeamEEEEE(void* self, const IntrusivePtr* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V119RequestMatchResults20setCompetitiveResultERKNS1_6Common12IntrusivePtrINS3_24RequestCompetitiveResultEEE(void* self, const IntrusivePtr* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V119RequestMatchResults20setCooperativeResultERKNS3_24RequestCooperativeResultE(void* self, const int* value); -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RemovedPlayerFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_13RemovedPlayerEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RemovedPlayerFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_13RemovedPlayerEEE(void* self, const char* name, IntrusivePtr* out); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120ReportResultsRequest17setNpServiceLabelERKi(void* self, const int* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120ReportResultsRequest18setMatchStatisticsERKNS1_6Common12IntrusivePtrINS3_22RequestMatchStatisticsEEE(void* self, const IntrusivePtr* value); -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RequestMemberFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_13RequestMemberEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RequestMemberFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_13RequestMemberEEE(void* self, const char* name, IntrusivePtr* out); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RequestPlayerResults8setScoreERKd(void* self, const double* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RequestTeamStatistic23setTeamMemberStatisticsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_26RequestTeamMemberStatisticEEEEE(void* self, const IntrusivePtr* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RequestTeamStatistic8setStatsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_19AdditionalStatisticEEEEE(void* self, const IntrusivePtr* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V122RequestMatchStatistics17setTeamStatisticsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_20RequestTeamStatisticEEEEE(void* self, const IntrusivePtr* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V122RequestMatchStatistics19setPlayerStatisticsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_22RequestPlayerStatisticEEEEE(void* self, const IntrusivePtr* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V123JoinMatchRequestFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_11AddedPlayerEEEEEPNS9_INS3_16JoinMatchRequestEEE(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V123RequestMatchTeamFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_16RequestMatchTeamEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V123RequestMatchTeamFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_16RequestMatchTeamEEE(void* self, const char* name, IntrusivePtr* out); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124LeaveMatchRequestFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_13RemovedPlayerEEEEEPNS9_INS3_17LeaveMatchRequestEEE(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124RequestCompetitiveResult14setTeamResultsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_18RequestTeamResultsEEEEE(void* self, const IntrusivePtr* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124RequestCompetitiveResult16setPlayerResultsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_20RequestPlayerResultsEEEEE(void* self, const IntrusivePtr* value); @@ -899,21 +908,21 @@ int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124UpdateMatchDetailRequest17setExpi int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124UpdateMatchDetailRequest17setNpServiceLabelERKi(void* self, const int* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124UpdateMatchDetailRequest9setZoneIdEPKc(void* self, const char* value); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124UpdateMatchStatusRequest17setNpServiceLabelERKi(void* self, const int* value); -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V125CreateMatchRequestFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_18CreateMatchRequestEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V125CreateMatchRequestFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_18CreateMatchRequestEEE(void* self, const char* name, IntrusivePtr* out); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V125RequestMatchPlayerFactory6createEPNS1_6Common10LibContextEPKcNS3_10PlayerTypeEPNS5_12IntrusivePtrINS3_18RequestMatchPlayerEEE(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V125RequestTeamResultsFactory6createEPNS1_6Common10LibContextEPKciPNS5_12IntrusivePtrINS3_18RequestTeamResultsEEE(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V126AdditionalStatisticFactory6createEPNS1_6Common10LibContextEPKcS9_PNS5_12IntrusivePtrINS3_19AdditionalStatisticEEE(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V126RequestInGameRosterFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_19RequestInGameRosterEEE(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V126RequestMatchResultsFactory6createEPNS1_6Common10LibContextENS3_14ResultsVersionEPNS5_12IntrusivePtrINS3_19RequestMatchResultsEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V126RequestInGameRosterFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_19RequestInGameRosterEEE(void* self, IntrusivePtr* out); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V126RequestMatchResultsFactory6createEPNS1_6Common10LibContextENS3_14ResultsVersionEPNS5_12IntrusivePtrINS3_19RequestMatchResultsEEE(void* self, std::int32_t version, IntrusivePtr* out); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V127ReportResultsRequestFactory6createEPNS1_6Common10LibContextENS5_12IntrusivePtrINS3_19RequestMatchResultsEEEPNS8_INS3_20ReportResultsRequestEEE(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V127RequestPlayerResultsFactory6createEPNS1_6Common10LibContextEPKciPNS5_12IntrusivePtrINS3_20RequestPlayerResultsEEE(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V127RequestTeamStatisticFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_20RequestTeamStatisticEEE(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V129RequestMatchStatisticsFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_22RequestMatchStatisticsEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V127RequestTeamStatisticFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_20RequestTeamStatisticEEE(void* self, const char* name, IntrusivePtr* out); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V129RequestMatchStatisticsFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_22RequestMatchStatisticsEEE(void* self, IntrusivePtr* out); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V129RequestPlayerStatisticFactory6createEPNS1_6Common10LibContextEPKcRKNS5_6VectorINS5_12IntrusivePtrINS3_19AdditionalStatisticEEEEEPNSB_INS3_22RequestPlayerStatisticEEE(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V130RequestTeamMemberResultFactory6createEPNS1_6Common10LibContextEPKcdPNS5_12IntrusivePtrINS3_23RequestTeamMemberResultEEE(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131RequestCompetitiveResultFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_24RequestCompetitiveResultEEE(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131UpdateMatchDetailRequestFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_24UpdateMatchDetailRequestEEE(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131UpdateMatchStatusRequestFactory6createEPNS1_6Common10LibContextENS3_12UpdateStatusEPNS5_12IntrusivePtrINS3_24UpdateMatchStatusRequestEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131RequestCompetitiveResultFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_24RequestCompetitiveResultEEE(void* self, IntrusivePtr* out); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131UpdateMatchDetailRequestFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_24UpdateMatchDetailRequestEEE(void* self, IntrusivePtr* out); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131UpdateMatchStatusRequestFactory6createEPNS1_6Common10LibContextENS3_12UpdateStatusEPNS5_12IntrusivePtrINS3_24UpdateMatchStatusRequestEEE(void* self, std::int32_t status, IntrusivePtr* out); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V133RequestTeamMemberStatisticFactory6createEPNS1_6Common10LibContextEPKcRKNS5_6VectorINS5_12IntrusivePtrINS3_19AdditionalStatisticEEEEEPNSB_INS3_26RequestTeamMemberStatisticEEE(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi10leaveMatchEiRKNS4_21ParameterToLeaveMatchERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi11createMatchEiRKNS4_22ParameterToCreateMatchERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_19CreateMatchResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE(void* self); @@ -949,51 +958,51 @@ bool APS5_VABI _ZNK3sce2Np9CppWebApi7Matches2V119RequestInGameRoster10teamsIsSet bool APS5_VABI _ZNK3sce2Np9CppWebApi7Matches2V119RequestInGameRoster12playersIsSetEv(const void* self); int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetPublicProfilesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(); int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V115BasicProfileApi17getPublicProfilesEiRKNS4_28ParameterToGetPublicProfilesERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_25GetPublicProfilesResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE(); -int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V113BasicPresence12setInContextERKb(); -int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile13onlineIdIsSetEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V113BasicPresence12setInContextERKb(void* self, const bool* inContext); +bool APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile13onlineIdIsSetEv(const void* self); int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_(); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEaSERKS7_(IntrusivePtr* self, const IntrusivePtr* source); -int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V113BasicPresence15setOnlineStatusERKNS3_12OnlineStatusE(); -int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V112BasicProfile23setIsOfficiallyVerifiedERKb(); -int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V112BasicProfileC1EPNS1_6Common10LibContextE(); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V113BasicPresence15setOnlineStatusERKNS3_12OnlineStatusE(void* self, const std::uint32_t* status); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V112BasicProfile23setIsOfficiallyVerifiedERKb(void* self, const bool* verified); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V112BasicProfileC1EPNS1_6Common10LibContextE(void* self, void* context); int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEED2Ev(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V115BasicProfileApi28ParameterToGetPublicProfiles10initializeEPNS1_6Common10LibContextEPKc(); -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetPublicProfilesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv(); +std::int64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetPublicProfilesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv(); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEC1Ev(IntrusivePtr* self); -int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile23getIsOfficiallyVerifiedEv(); +bool APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile23getIsOfficiallyVerifiedEv(const void* self); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V114PersonalDetailEEptEv(const IntrusivePtr* self); int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetPublicProfilesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_(); int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE11hasResponseEv(); int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_(); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V113BasicPresenceEEEEEEC1Ev(IntrusivePtr* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEED1Ev(IntrusivePtr* self); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEE3endEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence17onlineStatusIsSetEv(); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEE3endEv(void* out, void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence17onlineStatusIsSetEv(const void* self); int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_(); int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetPublicProfilesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv(); -int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V113BasicPresenceC1EPNS1_6Common10LibContextE(); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V113BasicPresenceC1EPNS1_6Common10LibContextE(void* self, void* context); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V113BasicPresenceEEEEEEaSERKSA_(IntrusivePtr* self, const IntrusivePtr* source); -int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile25isOfficiallyVerifiedIsSetEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence14inContextIsSetEv(); +bool APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile25isOfficiallyVerifiedIsSetEv(const void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence14inContextIsSetEv(const void* self); int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv(); -int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V112BasicProfileD1Ev(); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEEppEv(); +void* APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V112BasicProfileD1Ev(void* self); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEEppEv(void* self); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V112BasicProfileEEEEEE3getEv(const IntrusivePtr* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEC1Ev(IntrusivePtr* self); -int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V112BasicProfile11setOnlineIdERK13SceNpOnlineId(); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V112BasicProfile11setOnlineIdERK13SceNpOnlineId(void* self, const void* onlineId); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEC1Ev(IntrusivePtr* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V125GetPublicProfilesResponseEED1Ev(IntrusivePtr* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V112BasicProfileEEEEEEC1Ev(IntrusivePtr* self); int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE11hasResponseEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence14accountIdIsSetEv(); +bool APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence14accountIdIsSetEv(const void* self); int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V110FriendsApi21ParameterToGetFriends9setoffsetEi(); int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile11getOnlineIdEv(); +void* APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile11getOnlineIdEv(void* out, const void* self); int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1ERKSB_(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V113BasicPresence12setAccountIdERKm(); -int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile19personalDetailIsSetEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEEneERKS9_(); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V113BasicPresence12setAccountIdERKm(void* self, const std::uint64_t* accountId); +bool APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile19personalDetailIsSetEv(const void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEEneERKS9_(const void* self, const void* other); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V114PersonalDetailEED1Ev(IntrusivePtr* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source); int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetPublicProfilesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE11hasResponseEv(); @@ -1002,16 +1011,16 @@ void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_ int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE11hasResponseEv(); int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetPublicProfilesResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEC1Ev(IntrusivePtr* self); -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv(); +std::int64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv(); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V125GetPublicProfilesResponseEEC1Ev(IntrusivePtr* self); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEE5beginEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V114PersonalDetail14getDisplayNameEv(); +void* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEE5beginEv(void* out, void* self); +const void* APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V114PersonalDetail14getDisplayNameEv(const void* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEED1Ev(IntrusivePtr* self); int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V115BasicProfileApi28ParameterToGetPublicProfilesC1Ev(); int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetPublicProfilesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv(); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEED1Ev(IntrusivePtr* self); int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv(); -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv(); +std::int64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv(); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEptEv(const IntrusivePtr* self); int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetPublicProfilesResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsers8setlimitEi(); @@ -1358,9 +1367,9 @@ int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrIN int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_17TitleCloudStorage2V122UploadDataResponseBodyEEENS4_INS6_7DataApi25UploadDataResponseHeadersEEEE18getResponseHeadersERSB_(); int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEE18getResponseHeadersERS7_(); std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_12Leaderboards2V15EntryEEEE4sizeEv(const void* self); -std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEE4sizeEv(const void* self); +std::uint64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V57ProductEEEE4sizeEv(const void* self); std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V118GameSessionForReadEEEE4sizeEv(const void* self); -std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V120PlayerSessionForReadEEEE4sizeEv(const void* self); +std::uint64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V120PlayerSessionForReadEEEE4sizeEv(const void* self); std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_7Matches2V113RequestMemberEEEE4sizeEv(const void* self); std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_7Matches2V116RequestMatchTeamEEEE4sizeEv(const void* self); std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_7Matches2V118RequestMatchPlayerEEEE4sizeEv(const void* self); diff --git a/core/libs/prx/libSceNpSessionSignaling/Export.cpp b/core/libs/prx/libSceNpSessionSignaling/Export.cpp index a325235968..7ffb337a7f 100644 --- a/core/libs/prx/libSceNpSessionSignaling/Export.cpp +++ b/core/libs/prx/libSceNpSessionSignaling/Export.cpp @@ -20,10 +20,14 @@ struct SessionSignalingContext { std::uint32_t id; std::int32_t userId; bool active; + std::uint32_t customProperty; + void* internalCallback; + void* internalCallbackUserData; }; static std::mutex g_mutex; static bool g_initialized = false; +static std::uint64_t g_poolSize = 0; static std::array g_contexts{}; static_assert(sizeof(SessionSignalingMemoryInfo) == 24); @@ -75,6 +79,9 @@ static int CreateContext(const SessionSignalingContextParam for (auto& context : g_contexts) { if (!context.active) { context.userId = param->userId; + context.customProperty = 0; + context.internalCallback = nullptr; + context.internalCallbackUserData = nullptr; context.active = true; *contextId = context.id; return 0; @@ -85,17 +92,28 @@ static int CreateContext(const SessionSignalingContextParam extern "C" { -int APS5_VABI sceNpSessionSignalingInitialize(const SessionSignalingInitParam* param) { +static int InitializePort(const SessionSignalingInitParam* param, std::uint32_t port) { std::lock_guard lock(g_mutex); if (g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_ALREADY_INITIALIZED; if (!param || !param->poolSize) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; for (std::uint32_t index = 0; index < g_contexts.size(); ++index) { - g_contexts[index] = {1000 + index, 0, false}; + g_contexts[index] = {1000 + index, 0, false, 0, nullptr, nullptr}; } + (void)port; + g_poolSize = param->poolSize; g_initialized = true; return 0; } +int APS5_VABI sceNpSessionSignalingInitialize(const SessionSignalingInitParam* param) { + return InitializePort(param, 0x217b); +} + +int APS5_VABI sceNpSessionSignalingInitializeWithPort(const SessionSignalingInitParam* param, std::uint32_t port, std::uint32_t reserved) { + (void)reserved; + return InitializePort(param, port); +} + int APS5_VABI sceNpSessionSignalingActivateSession(std::uint32_t contextId, const char* sessionId, std::uint32_t sessionType, const SessionSignalingSessionParam* param, std::uint32_t* groupId) { std::lock_guard lock(g_mutex); if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; @@ -125,6 +143,9 @@ int APS5_VABI sceNpSessionSignalingDestroyContext(std::uint32_t contextId) { if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; auto* context = FindContext(contextId); if (!context) return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_NOT_FOUND; + context->customProperty = 0; + context->internalCallback = nullptr; + context->internalCallbackUserData = nullptr; context->active = false; const auto nextId = context->id + static_cast(g_contexts.size()); context->id = nextId < 100001 ? nextId : 1000 + contextId % static_cast(g_contexts.size()); @@ -159,6 +180,7 @@ int APS5_VABI sceNpSessionSignalingTerminate(void) { std::lock_guard lock(g_mutex); if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; g_initialized = false; + g_poolSize = 0; for (auto& context : g_contexts) context.active = false; return 0; } @@ -191,12 +213,18 @@ int APS5_VABI sceNpSessionSignalingRequestPrepare(uint32_t contextId, uint32_t* } int APS5_VABI sceNpSessionSignalingGetMemoryInfo(SessionSignalingMemoryInfo* memoryInfo) { - NotImplemented_nid_no_patch(__func__); + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; + if (!memoryInfo) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + *memoryInfo = {g_poolSize, 0, 0}; return 0; } int APS5_VABI sceNpSessionSignalingGetConnectionStatistics(SessionSignalingConnectionStatistics* statistics) { - NotImplemented_nid_no_patch(__func__); + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; + if (!statistics) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + *statistics = {}; return 0; } @@ -251,19 +279,21 @@ int APS5_VABI sceNpSessionSignalingGetConnectionFromNetAddress(std::uint32_t con return SCE_NP_SESSION_SIGNALING_ERROR_CONN_NOT_FOUND; } -int APS5_VABI sceNpSessionSignalingGetCustomProperty() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpSessionSignalingGetCustomProperty(std::uint32_t contextId, std::uint32_t* property) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; + auto* context = FindContext(contextId); + if (!context) return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_NOT_FOUND; + if (property) *property = context->customProperty; return 0; } -int APS5_VABI sceNpSessionSignalingGetGroupFromPeerAddress() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpSessionSignalingInitializeWithPort() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpSessionSignalingGetGroupFromPeerAddress(std::uint32_t contextId, const SessionSignalingPeerAddress* peerAddress, std::uint32_t* groupId) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; + if (!peerAddress || !groupId) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + if (!FindContext(contextId)) return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_NOT_FOUND; + return SCE_NP_SESSION_SIGNALING_ERROR_GROUP_NOT_FOUND; } int APS5_VABI sceNpSessionSignalingJoinActivate() { @@ -271,9 +301,13 @@ int APS5_VABI sceNpSessionSignalingJoinActivate() { return 0; } -int APS5_VABI sceNpSessionSignalingSetCustomProperty() { +int APS5_VABI sceNpSessionSignalingSetCustomProperty(std::uint32_t contextId, const std::uint32_t* property, std::uint32_t* previous) { + (void)previous; + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; + if (!property) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + if (!FindContext(contextId)) return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_NOT_FOUND; NotImplemented_nid_no_patch(__func__); - return 0; } int APS5_VABI sceNpSessionSignalingManualUdpAbortConnection() { @@ -292,8 +326,14 @@ int APS5_VABI sceNpSessionSignalingManualUdpJoin() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI sceNpSessionSignalingRegisterInternalCallback() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpSessionSignalingRegisterInternalCallback(std::uint32_t contextId, void* callback, void* userData) { + std::lock_guard lock(g_mutex); + if (!g_initialized) return SCE_NP_SESSION_SIGNALING_ERROR_NOT_INITIALIZED; + auto* context = FindContext(contextId); + if (!context) return SCE_NP_SESSION_SIGNALING_ERROR_CONTEXT_NOT_FOUND; + context->internalCallback = callback; + context->internalCallbackUserData = userData; + return 0; } } diff --git a/core/libs/prx/libSceNpSessionSignaling/include/Api.hpp b/core/libs/prx/libSceNpSessionSignaling/include/Api.hpp index ad73c1a595..6f72608aff 100644 --- a/core/libs/prx/libSceNpSessionSignaling/include/Api.hpp +++ b/core/libs/prx/libSceNpSessionSignaling/include/Api.hpp @@ -105,15 +105,15 @@ int APS5_VABI sceNpSessionSignalingGetGroupInfo(std::uint32_t contextId, std::ui int APS5_VABI sceNpSessionSignalingGetConnectionFromNetAddress2(std::uint32_t contextId, std::uint32_t groupId, std::uint32_t address, std::uint16_t port, std::uint32_t* connectionId); int APS5_VABI sceNpSessionSignalingCreateJoinActivate(); int APS5_VABI sceNpSessionSignalingGetConnectionFromNetAddress(std::uint32_t contextId, std::uint32_t address, std::uint16_t port, std::uint32_t* connectionId); -int APS5_VABI sceNpSessionSignalingGetCustomProperty(); -int APS5_VABI sceNpSessionSignalingGetGroupFromPeerAddress(); -int APS5_VABI sceNpSessionSignalingInitializeWithPort(); +int APS5_VABI sceNpSessionSignalingGetCustomProperty(std::uint32_t contextId, std::uint32_t* property); +int APS5_VABI sceNpSessionSignalingGetGroupFromPeerAddress(std::uint32_t contextId, const SessionSignalingPeerAddress* peerAddress, std::uint32_t* groupId); +int APS5_VABI sceNpSessionSignalingInitializeWithPort(const SessionSignalingInitParam* param, std::uint32_t port, std::uint32_t reserved); int APS5_VABI sceNpSessionSignalingJoinActivate(); -int APS5_VABI sceNpSessionSignalingSetCustomProperty(); +int APS5_VABI sceNpSessionSignalingSetCustomProperty(std::uint32_t contextId, const std::uint32_t* property, std::uint32_t* previous); int APS5_VABI sceNpSessionSignalingManualUdpAbortConnection(); int APS5_VABI sceNpSessionSignalingManualUdpCreateJoin(); int APS5_VABI sceNpSessionSignalingManualUdpEstablishConnection(); int APS5_VABI sceNpSessionSignalingManualUdpJoin(); -int APS5_VABI sceNpSessionSignalingRegisterInternalCallback(); +int APS5_VABI sceNpSessionSignalingRegisterInternalCallback(std::uint32_t contextId, void* callback, void* userData); } diff --git a/core/libs/prx/libSceNpUtility/Export.cpp b/core/libs/prx/libSceNpUtility/Export.cpp index 226842b45f..b07eb6bf28 100644 --- a/core/libs/prx/libSceNpUtility/Export.cpp +++ b/core/libs/prx/libSceNpUtility/Export.cpp @@ -20,10 +20,16 @@ static constexpr int SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT = static_cast< static constexpr int SCE_NP_COMMUNITY_ERROR_INVALID_ID = static_cast(0x8055070E); static constexpr int SCE_NP_COMMUNITY_ERROR_INVALID_TYPE = static_cast(0x80550711); static constexpr int SCE_NP_LOOKUP_ERROR_SYNCHRONOUS_REQUEST = static_cast(0x80550307); +static constexpr int SCE_NP_SERVICE_CHECKER2_ERROR_ALREADY_INITIALIZED = static_cast(0x8055B301); +static constexpr int SCE_NP_SERVICE_CHECKER2_ERROR_NOT_INITIALIZED = static_cast(0x8055B302); +static constexpr int SCE_NP_SERVICE_CHECKER2_ERROR_INVALID_ARGUMENT = static_cast(0x8055B305); +static constexpr int SCE_NP_SERVICE_CHECKER2_ERROR_REQUEST_NOT_FOUND = static_cast(0x8055B304); 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 = 32; +static constexpr std::size_t MAX_SERVICE_CHECKER2_REQUESTS = 0x1000; +static constexpr std::uint32_t MAX_SERVICE_CHECKER2_REQUEST_ID = 0x7FFFFFFF; static constexpr int WORD_FILTER_TITLE_CTX_ID_OFFSET = 0x01000000; static constexpr int WORD_FILTER_REQUEST_ID_OFFSET = 0x02000000; static constexpr std::size_t WORD_FILTER_COMMENT_MAX_LENGTH = 0x400; @@ -54,6 +60,14 @@ struct Service { Service lookup(1, 1); Service wordFilter(WORD_FILTER_TITLE_CTX_ID_OFFSET + 1, WORD_FILTER_REQUEST_ID_OFFSET + 1); +struct ServiceChecker2 { + std::mutex mutex; + bool initialized = false; + std::unordered_set requests; + std::uint32_t nextRequest = 1; +}; +ServiceChecker2 serviceChecker2; + int AddTitleContext(Service& service) { std::lock_guard lock(service.mutex); if (service.titleContexts.size() >= MAX_TITLE_CONTEXTS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS; @@ -388,37 +402,62 @@ int APS5_VABI sceNpLookupNetTerm() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNpServiceChecker2IntAbortRequest() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpServiceChecker2IntAbortRequest(int requestId) { + std::lock_guard lock(serviceChecker2.mutex); + if (!serviceChecker2.initialized) return SCE_NP_SERVICE_CHECKER2_ERROR_NOT_INITIALIZED; + if (!serviceChecker2.requests.contains(requestId)) return SCE_NP_SERVICE_CHECKER2_ERROR_REQUEST_NOT_FOUND; + serviceChecker2.requests.erase(requestId); return 0; } -int APS5_VABI sceNpServiceChecker2IntCreateRequest() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpServiceChecker2IntCreateRequest(int* requestId) { + std::lock_guard lock(serviceChecker2.mutex); + if (!serviceChecker2.initialized) return SCE_NP_SERVICE_CHECKER2_ERROR_NOT_INITIALIZED; + if (!requestId) return SCE_NP_SERVICE_CHECKER2_ERROR_INVALID_ARGUMENT; + if (serviceChecker2.requests.size() >= MAX_SERVICE_CHECKER2_REQUESTS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS; + std::uint32_t id = serviceChecker2.nextRequest; + while (serviceChecker2.requests.contains(static_cast(id))) id = id >= MAX_SERVICE_CHECKER2_REQUEST_ID ? 1 : id + 1; + serviceChecker2.nextRequest = id >= MAX_SERVICE_CHECKER2_REQUEST_ID ? 1 : id + 1; + serviceChecker2.requests.insert(static_cast(id)); + *requestId = static_cast(id); return 0; } -int APS5_VABI sceNpServiceChecker2IntDestroyRequest() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpServiceChecker2IntDestroyRequest(int requestId) { + std::lock_guard lock(serviceChecker2.mutex); + if (!serviceChecker2.initialized) return SCE_NP_SERVICE_CHECKER2_ERROR_NOT_INITIALIZED; + serviceChecker2.requests.erase(requestId); return 0; } int APS5_VABI sceNpServiceChecker2IntFinalize() { - NotImplemented_nid_no_patch(__func__); + std::lock_guard lock(serviceChecker2.mutex); + serviceChecker2.requests.clear(); + serviceChecker2.initialized = false; return 0; } -int APS5_VABI sceNpServiceChecker2IntGetServiceAvailability() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpServiceChecker2IntGetServiceAvailability(int requestId, const void* npId, void* result) { + std::lock_guard lock(serviceChecker2.mutex); + if (!serviceChecker2.initialized) return SCE_NP_SERVICE_CHECKER2_ERROR_NOT_INITIALIZED; + if (!npId || !result) return SCE_NP_SERVICE_CHECKER2_ERROR_INVALID_ARGUMENT; + if (!serviceChecker2.requests.contains(requestId)) return SCE_NP_SERVICE_CHECKER2_ERROR_REQUEST_NOT_FOUND; + return SCE_NP_ERROR_SIGNED_OUT; } -int APS5_VABI sceNpServiceChecker2IntGetServiceAvailabilityA() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpServiceChecker2IntGetServiceAvailabilityA(int requestId, int userId, void* result) { + std::lock_guard lock(serviceChecker2.mutex); + if (!serviceChecker2.initialized) return SCE_NP_SERVICE_CHECKER2_ERROR_NOT_INITIALIZED; + if (userId == -1 || !result) return SCE_NP_SERVICE_CHECKER2_ERROR_INVALID_ARGUMENT; + if (!serviceChecker2.requests.contains(requestId)) return SCE_NP_SERVICE_CHECKER2_ERROR_REQUEST_NOT_FOUND; + return SCE_NP_ERROR_SIGNED_OUT; } int APS5_VABI sceNpServiceChecker2IntInitialize() { - NotImplemented_nid_no_patch(__func__); + std::lock_guard lock(serviceChecker2.mutex); + if (serviceChecker2.initialized) return SCE_NP_SERVICE_CHECKER2_ERROR_ALREADY_INITIALIZED; + serviceChecker2.requests.clear(); + serviceChecker2.nextRequest = 1; + serviceChecker2.initialized = true; return 0; } -int APS5_VABI sceNpServiceChecker2IntIsSetServiceType() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpServiceChecker2IntIsSetServiceType(const std::uint32_t* serviceTypes, std::uint32_t serviceType) { + if (!serviceTypes || serviceType > 9) return 0; + return static_cast((serviceTypes[serviceType >> 5] >> serviceType) & 1); } int APS5_VABI sceNpServiceCheckerIntAbortRequest() { NotImplemented_nid_no_patch(__func__); diff --git a/core/libs/prx/libSceNpUtility/include/Api.hpp b/core/libs/prx/libSceNpUtility/include/Api.hpp index 469ecd9005..cb1e95553d 100644 --- a/core/libs/prx/libSceNpUtility/include/Api.hpp +++ b/core/libs/prx/libSceNpUtility/include/Api.hpp @@ -77,14 +77,14 @@ int APS5_VABI sceNpLookupNetNpId(); int APS5_VABI sceNpLookupNetSanitizeComment(); int APS5_VABI sceNpLookupNetSetTimeout(); int APS5_VABI sceNpLookupNetTerm(); -int APS5_VABI sceNpServiceChecker2IntAbortRequest(); -int APS5_VABI sceNpServiceChecker2IntCreateRequest(); -int APS5_VABI sceNpServiceChecker2IntDestroyRequest(); +int APS5_VABI sceNpServiceChecker2IntAbortRequest(int requestId); +int APS5_VABI sceNpServiceChecker2IntCreateRequest(int* requestId); +int APS5_VABI sceNpServiceChecker2IntDestroyRequest(int requestId); int APS5_VABI sceNpServiceChecker2IntFinalize(); -int APS5_VABI sceNpServiceChecker2IntGetServiceAvailability(); -int APS5_VABI sceNpServiceChecker2IntGetServiceAvailabilityA(); +int APS5_VABI sceNpServiceChecker2IntGetServiceAvailability(int requestId, const void* npId, void* result); +int APS5_VABI sceNpServiceChecker2IntGetServiceAvailabilityA(int requestId, int userId, void* result); int APS5_VABI sceNpServiceChecker2IntInitialize(); -int APS5_VABI sceNpServiceChecker2IntIsSetServiceType(); +int APS5_VABI sceNpServiceChecker2IntIsSetServiceType(const std::uint32_t* serviceTypes, std::uint32_t serviceType); int APS5_VABI sceNpServiceCheckerIntAbortRequest(); int APS5_VABI sceNpServiceCheckerIntCreateRequest(); int APS5_VABI sceNpServiceCheckerIntDestroyRequest(); diff --git a/core/libs/prx/libScePadTracker/Export.cpp b/core/libs/prx/libScePadTracker/Export.cpp index 2ddae2fad6..5fc489e90f 100644 --- a/core/libs/prx/libScePadTracker/Export.cpp +++ b/core/libs/prx/libScePadTracker/Export.cpp @@ -1,4 +1,5 @@ #include "prx/libScePadTracker/include/Api.hpp" +#include #include #include #include "SceTypes.hpp" @@ -13,8 +14,17 @@ int APS5_VABI scePadTrackerCalibrate() { int APS5_VABI scePadTrackerGetWorkingMemorySize(std::uint32_t* first, std::uint32_t* second) { if (first == nullptr || second == nullptr) return static_cast(0x80c40003); - *first = 0x800000; - *second = 0x800000; + std::uint32_t originalFirst = 0; + const int savedErrno = errno; + const bool readableFirst = GuestCopyIn_nid_no_patch(&originalFirst, first, sizeof(originalFirst)); + const std::uint32_t memorySize = 0x800000; + const bool copiedFirst = GuestCopyOut_nid_no_patch(first, &memorySize, sizeof(memorySize)); + const bool copiedSecond = copiedFirst ? GuestCopyOut_nid_no_patch(second, &memorySize, sizeof(memorySize)) : false; + if (copiedFirst && !copiedSecond && readableFirst) { + GuestCopyOut_nid_no_patch(first, &originalFirst, sizeof(originalFirst)); + } + errno = savedErrno; + if (!copiedFirst || !copiedSecond) return static_cast(0x80c40003); return 0; } diff --git a/core/libs/prx/libSceRandom/Export.cpp b/core/libs/prx/libSceRandom/Export.cpp index 2fcda9cfb5..45c7949e54 100644 --- a/core/libs/prx/libSceRandom/Export.cpp +++ b/core/libs/prx/libSceRandom/Export.cpp @@ -4,6 +4,7 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/HostEntropy.hpp" +#include namespace { @@ -16,7 +17,14 @@ extern "C" { int APS5_VABI sceRandomGetRandomNumber(void* buf, size_t size) { if (!buf || size > SCE_RANDOM_MAX_SIZE) return SCE_RANDOM_ERROR_INVALID; - HostEntropy_nid_no_patch(buf, size); + std::uint8_t temp[SCE_RANDOM_MAX_SIZE]; + HostEntropy_nid_no_patch(temp, size); + const auto preserved = errno; + if (!GuestCopyOut_nid_no_patch(buf, temp, size)) { + errno = preserved; + return SCE_RANDOM_ERROR_INVALID; + } + errno = preserved; return 0; } diff --git a/core/libs/prx/libSceS3da/CMakeLists.txt b/core/libs/prx/libSceS3da/CMakeLists.txt new file mode 100644 index 0000000000..23bb8a49e0 --- /dev/null +++ b/core/libs/prx/libSceS3da/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(libSceS3da SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceS3da PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceS3da) +if(BUILD_TESTING) + add_test_executable(guest_s3da_tests ${LIBS_INCLUDE_DIR}/tests/GuestS3da.cpp) + target_include_directories(guest_s3da_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_s3da_tests PRIVATE libSceS3da libc) + configure_windows_unwind(guest_s3da_tests) + add_test(NAME guest_s3da COMMAND guest_s3da_tests) + if(WIN32) + set_tests_properties(guest_s3da PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceS3da/Export.cpp b/core/libs/prx/libSceS3da/Export.cpp index fcf0f1f9af..459cfd71dd 100644 --- a/core/libs/prx/libSceS3da/Export.cpp +++ b/core/libs/prx/libSceS3da/Export.cpp @@ -1,27 +1,48 @@ #include "prx/libSceS3da/include/Api.hpp" +#include #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr int ERROR_INVALID_ARGUMENT = static_cast(0x81600002u); +constexpr int ERROR_ALREADY_OPEN = static_cast(0x81600006u); +constexpr std::uint32_t MAX_SIZE = 0xA000; + +std::atomic g_slots[2]{false, false}; + +} + extern "C" { int APS5_VABI sceS3daClose(std::uint32_t port) { - NotImplemented_nid_no_patch(__func__); + if (port > 1) return ERROR_INVALID_ARGUMENT; + bool expected = true; + if (!g_slots[port].compare_exchange_strong(expected, false)) { + return ERROR_INVALID_ARGUMENT; + } return 0; } int APS5_VABI sceS3daGetInfo(std::uint32_t port, S3daInfo* info) { + if (port > 1) return ERROR_INVALID_ARGUMENT; NotImplemented_nid_no_patch(__func__); return 0; } int APS5_VABI sceS3daOpen(std::uint32_t port) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (port > 1) return ERROR_INVALID_ARGUMENT; + bool expected = false; + if (!g_slots[port].compare_exchange_strong(expected, true)) { + return ERROR_ALREADY_OPEN; + } + return static_cast(port); } int APS5_VABI sceS3daSendData(std::uint32_t port, const void* data, std::uint32_t size) { + if (port > 1 || data == nullptr || size == 0 || size > MAX_SIZE) return ERROR_INVALID_ARGUMENT; NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceSysUtil/CMakeLists.txt b/core/libs/prx/libSceSysUtil/CMakeLists.txt new file mode 100644 index 0000000000..dd8bcbf9d6 --- /dev/null +++ b/core/libs/prx/libSceSysUtil/CMakeLists.txt @@ -0,0 +1,15 @@ +add_library(libSceSysUtil SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceSysUtil PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceSysUtil) + +if(BUILD_TESTING) + add_test_executable(guest_sys_util_notification_tests ${LIBS_INCLUDE_DIR}/tests/GuestSysUtilNotification.cpp) + target_include_directories(guest_sys_util_notification_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_sys_util_notification_tests PRIVATE libSceSysUtil libkernel libc) + configure_windows_unwind(guest_sys_util_notification_tests) + add_test(NAME guest_sys_util_notification COMMAND guest_sys_util_notification_tests) + if(WIN32) + set_tests_properties(guest_sys_util_notification PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceUsbStorageDialog/Export.cpp b/core/libs/prx/libSceUsbStorageDialog/Export.cpp index 290ab47eca..ba8378debe 100644 --- a/core/libs/prx/libSceUsbStorageDialog/Export.cpp +++ b/core/libs/prx/libSceUsbStorageDialog/Export.cpp @@ -1,6 +1,7 @@ #include "prx/libSceUsbStorageDialog/include/Api.hpp" #include "prx/libSceUserService/UserService.hpp" #include +#include #include #include #include @@ -73,13 +74,37 @@ int APS5_VABI sceUsbStorageDialogGetResult(UsbStorageDialogResult* result) { const int status = g_status.load(); if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; if (g_closed.load()) { - result->result = COMMON_DIALOG_ERROR_NOT_FINISHED; - result->status = 0; + const std::int32_t notFinished = COMMON_DIALOG_ERROR_NOT_FINISHED; + const std::int32_t zeroStatus = 0; + const int savedErrno = errno; + if (!GuestCopyOut_nid_no_patch(&result->result, ¬Finished, sizeof(notFinished)) || + !GuestCopyOut_nid_no_patch(&result->status, &zeroStatus, sizeof(zeroStatus))) { + errno = savedErrno; + return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + } + errno = savedErrno; return 0; } if (status != COMMON_DIALOG_STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED; - if (g_sdkVersion >= 0x04500000 && (result->size != sizeof(*result) || result->result != COMMON_DIALOG_ERROR_BUSY)) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; - result->result = COMMON_DIALOG_RESULT_USER_CANCELED; + if (g_sdkVersion >= 0x04500000) { + std::uint64_t size = 0; + std::int32_t currentResult = 0; + const int savedErrno = errno; + if (!GuestCopyIn_nid_no_patch(&size, &result->size, sizeof(size)) || + !GuestCopyIn_nid_no_patch(¤tResult, &result->result, sizeof(currentResult))) { + errno = savedErrno; + return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + } + errno = savedErrno; + if (size != sizeof(*result) || currentResult != COMMON_DIALOG_ERROR_BUSY) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + } + const std::int32_t cancelResult = COMMON_DIALOG_RESULT_USER_CANCELED; + const int savedErrno = errno; + if (!GuestCopyOut_nid_no_patch(&result->result, &cancelResult, sizeof(cancelResult))) { + errno = savedErrno; + return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; + } + errno = savedErrno; return COMMON_DIALOG_RESULT_USER_CANCELED; } diff --git a/core/libs/prx/libSceUserService/CMakeLists.txt b/core/libs/prx/libSceUserService/CMakeLists.txt index c72c148a68..184ebc4366 100644 --- a/core/libs/prx/libSceUserService/CMakeLists.txt +++ b/core/libs/prx/libSceUserService/CMakeLists.txt @@ -5,6 +5,17 @@ configure_windows_unwind(libSceUserService) target_sources(libSceUserService PRIVATE LegacyExports.cpp) if(BUILD_TESTING) + add_test_executable(guest_user_service_group_io_tests ${LIBS_INCLUDE_DIR}/tests/GuestUserServiceGroupIo.cpp Export.cpp) + target_include_directories(guest_user_service_group_io_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_user_service_group_io_tests PRIVATE libSceSystemService libc libkernel) + configure_windows_unwind(guest_user_service_group_io_tests) + add_test(NAME guest_user_service_group_io COMMAND guest_user_service_group_io_tests) + set_tests_properties(guest_user_service_group_io PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_user_service_group_io PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_user_service_tests ${LIBS_INCLUDE_DIR}/tests/GuestUserService.cpp) target_include_directories(guest_user_service_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_user_service_tests PRIVATE libSceUserService libSceSystemService libc) diff --git a/core/libs/prx/libSceUserService/Export.cpp b/core/libs/prx/libSceUserService/Export.cpp index 7601922a34..6dc6dbe6dd 100644 --- a/core/libs/prx/libSceUserService/Export.cpp +++ b/core/libs/prx/libSceUserService/Export.cpp @@ -1,3 +1,7 @@ +#include +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libkernel/include/Api.hpp" #include "prx/libSceSystemService/include/SystemService.hpp" #include #include @@ -95,6 +99,15 @@ static int ReportKeyboardSetting(int user_id, int32_t* type, int32_t (*value)()) *type = value(); return USER_SERVICE_OK; } +bool UserGroupSlotExists(int index) { + if (index < 0 || index > 128) { + return false; + } + char path[64]; + std::snprintf(path, sizeof(path), "/system_data/priv/home/user_list.dat.%d", index); + FileStat status{}; + return sceKernelStat(path, &status) == 0; +} } extern "C" { @@ -428,9 +441,31 @@ int APS5_VABI sceUserServiceGetIsHrtfEnabled(void) { return 0; } -int APS5_VABI sceUserServiceGetCurrentUserGroupIndex(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceUserServiceGetCurrentUserGroupIndex(std::int32_t* index) { + if (index == nullptr) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + *index = 0; + const int descriptor = sceKernelOpen("/system_data/priv/home/user_list.dat", 0, 0); + if (descriptor < 0) { + return descriptor; + } + const std::int64_t seekResult = sceKernelLseek(descriptor, 0x44, 0); + if (seekResult != 0x44) { + sceKernelClose(descriptor); + return static_cast(seekResult); + } + std::int32_t storedIndex{}; + const std::int64_t bytesRead = sceKernelRead(descriptor, &storedIndex, sizeof(storedIndex)); + sceKernelClose(descriptor); + if (bytesRead < 0) { + return static_cast(bytesRead); + } + if (bytesRead != sizeof(storedIndex)) { + return SCE_KERNEL_ERROR_EIO; + } + *index = storedIndex; + return USER_SERVICE_OK; } int APS5_VABI sceUserServiceGetPbtcThursdayHoursEnd(void) { @@ -448,9 +483,23 @@ int APS5_VABI sceUserServiceSetSystemLoggerHashedAccountIdTtl(void) { return 0; } -int APS5_VABI sceUserServiceGetRegisteredHomeUserIdList(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceUserServiceGetRegisteredHomeUserIdList(std::int32_t* userIds, std::int32_t* count) { + if (userIds == nullptr || count == nullptr) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + UserServiceRegisteredUserIdList registeredUsers{}; + const int result = sceUserServiceGetRegisteredUserIdList(®isteredUsers); + if (result != USER_SERVICE_OK) { + return result; + } + *count = 0; + for (std::size_t index = 0; index < sizeof(registeredUsers.user_id) / sizeof(registeredUsers.user_id[0]); ++index) { + userIds[index] = registeredUsers.user_id[index]; + if (userIds[index] != USER_SERVICE_USER_ID_INVALID) { + ++*count; + } + } + return USER_SERVICE_OK; } int APS5_VABI sceUserServiceSetVolumeForGenericUSB(void) { @@ -618,9 +667,35 @@ int APS5_VABI sceUserServiceSetKeyRepeatSpeed(void) { return 0; } -int APS5_VABI sceUserServiceSetUserGroupName(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceUserServiceSetUserGroupName(int groupIndex, const char* name) { + if (name == nullptr) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + if (!UserGroupSlotExists(groupIndex)) { + int nextGroupIndex = 0; + while (nextGroupIndex < 128 && UserGroupSlotExists(nextGroupIndex)) { + ++nextGroupIndex; + } + if (groupIndex != nextGroupIndex) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + } + const std::size_t nameSize = std::strlen(name); + if (nameSize > 128) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + char path[64]; + std::snprintf(path, sizeof(path), "/system_data/priv/home/user_group_name.dat.%d", groupIndex); + const int descriptor = sceKernelOpen(path, 0x601, 0x180); + if (descriptor < 0) { + return descriptor; + } + const std::int64_t bytesWritten = sceKernelWrite(descriptor, name, nameSize); + sceKernelClose(descriptor); + if (bytesWritten < 0) { + return static_cast(bytesWritten); + } + return bytesWritten == static_cast(nameSize) ? USER_SERVICE_OK : SCE_KERNEL_ERROR_EIO; } int APS5_VABI sceUserServiceGetParentalDvdRegion(void) { @@ -753,9 +828,32 @@ int APS5_VABI sceUserServiceSetHearingAssistUsePreset(void) { return 0; } -int APS5_VABI sceUserServiceGetUserGroupName(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceUserServiceGetUserGroupName(int groupIndex, char* name, std::size_t size) { + if (name == nullptr) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + if (size < 129) { + return USER_SERVICE_ERROR_BUFFER_TOO_SHORT; + } + if (size != 129) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + if (!UserGroupSlotExists(groupIndex)) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + std::memset(name, 0, size); + char path[64]; + std::snprintf(path, sizeof(path), "/system_data/priv/home/user_group_name.dat.%d", groupIndex); + const int descriptor = sceKernelOpen(path, 0, 0); + if (descriptor >= 0) { + const std::int64_t bytesRead = sceKernelRead(descriptor, name, 128); + sceKernelClose(descriptor); + if (bytesRead > 0) { + return USER_SERVICE_OK; + } + } + std::snprintf(name, size, "User Group %d", groupIndex + 1); + return USER_SERVICE_OK; } int APS5_VABI sceUserServiceSetAutoLoginEnabled(void) { @@ -815,7 +913,6 @@ int APS5_VABI sceUserServiceGetVoiceAgentEnableFeedback(void) { int APS5_VABI sceUserServiceIsKratosUser(void) { NotImplemented_nid_no_patch(__func__); - return 0; } int APS5_VABI sceUserServiceSetCreatedVersion(void) { @@ -953,9 +1050,12 @@ int APS5_VABI sceUserServiceSetNotificationSettings_2(void) { return 0; } -int APS5_VABI sceUserServiceGetHomeDirectory(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceUserServiceGetHomeDirectory(std::uint32_t user_id, char* path, std::size_t size) { + if (path == nullptr) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + const int result = std::snprintf(path, size, "/user/home/%08x", user_id); + return result >= 0 && static_cast(result) < size ? USER_SERVICE_OK : USER_SERVICE_ERROR_BUFFER_TOO_SHORT; } int APS5_VABI sceUserServiceSetPbtcSundayDuration(void) { @@ -968,9 +1068,8 @@ int APS5_VABI sceUserServiceSetIPDLeft(void) { return 0; } -int APS5_VABI sceUserServiceGetKratosPrimaryUser(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceUserServiceGetKratosPrimaryUser(int*) { + NotImplemented_nid_no_patch(__func__); } int APS5_VABI sceUserServiceLogout(void) { @@ -1295,7 +1394,6 @@ int APS5_VABI sceUserServiceSetHearingAssistSeparateLR(void) { int APS5_VABI sceUserServiceIsUserStorageAccountBound(void) { NotImplemented_nid_no_patch(__func__); - return 0; } int APS5_VABI sceUserServiceSetHmd2Brightness(void) { @@ -1403,9 +1501,11 @@ int APS5_VABI sceUserServiceGetHmd2Vibration(void) { return 0; } -int APS5_VABI sceUserServiceIsLoggedIn(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceUserServiceIsLoggedIn(int userId) { + if (!initialized.load()) { + return USER_SERVICE_ERROR_NOT_INITIALIZED; + } + return IsUserLoggedIn(userId) ? 1 : 0; } int APS5_VABI sceUserServiceGetPbtcMondayHoursStart(void) { @@ -1428,9 +1528,8 @@ int APS5_VABI sceUserServiceGetIsQuickSignup(void) { return 0; } -int APS5_VABI sceUserServiceIsKratosPrimaryUser(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI sceUserServiceIsKratosPrimaryUser(int) { + NotImplemented_nid_no_patch(__func__); } int APS5_VABI sceUserServiceSetVoiceAgentEnableWup(void) { @@ -1453,9 +1552,11 @@ int APS5_VABI sceUserServiceSetGlsHintFlag(void) { return 0; } -int APS5_VABI sceUserServiceIsLoggedInWithoutLock(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceUserServiceIsLoggedInWithoutLock(int userId) { + if (!initialized.load()) { + return USER_SERVICE_ERROR_NOT_INITIALIZED; + } + return IsUserLoggedIn(userId) ? 1 : 0; } int APS5_VABI sceUserServiceSetShareStatus3(void) { @@ -1958,9 +2059,21 @@ int APS5_VABI sceUserServiceSetPsCloudFeedbackTime(void) { return 0; } -int APS5_VABI sceUserServiceSetUserGroupIndex(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceUserServiceSetUserGroupIndex(int index) { + if (index != -1 && (index < 0 || index > 128 || !UserGroupSlotExists(index))) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + const int descriptor = sceKernelOpen("/system_data/priv/home/user_list.dat", 2, 0); + if (descriptor < 0) { + return descriptor; + } + const std::int32_t storedIndex = index == -1 ? 0x7fffffff : index; + const std::int64_t bytesWritten = sceKernelPwrite(descriptor, &storedIndex, sizeof(storedIndex), 0x44); + sceKernelClose(descriptor); + if (bytesWritten < 0) { + return static_cast(bytesWritten); + } + return bytesWritten == sizeof(storedIndex) ? USER_SERVICE_OK : SCE_KERNEL_ERROR_EIO; } int APS5_VABI sceUserServiceSetPartyMuteListA(void) { @@ -1978,7 +2091,7 @@ int APS5_VABI sceUserServiceSetShareStatus2(void) { return 0; } -int APS5_VABI sceUserServiceSetForegroundUser(void) { +int APS5_VABI sceUserServiceSetForegroundUser(int userId) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -2087,7 +2200,7 @@ int APS5_VABI sceUserServiceGetPbtcTuesdayDuration(void) { return 0; } -int APS5_VABI sceUserServiceGetUserWithHearingAssist(void) { +int APS5_VABI sceUserServiceGetUserWithHearingAssist(std::int32_t* userId) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -2227,9 +2340,19 @@ int APS5_VABI sceUserServiceGetSaveDataAutoSync(void) { return 0; } -int APS5_VABI sceUserServiceGetDefaultNewUserGroupName(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceUserServiceGetDefaultNewUserGroupName(char* name, std::size_t size) { + if (name == nullptr) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + if (size < 129) { + return USER_SERVICE_ERROR_BUFFER_TOO_SHORT; + } + int groupIndex = 0; + while (groupIndex < 128 && UserGroupSlotExists(groupIndex)) { + ++groupIndex; + } + std::snprintf(name, size, "User Group %d", groupIndex + 1); + return USER_SERVICE_OK; } int APS5_VABI sceUserServiceGetHmd2Brightness(void) { @@ -2342,9 +2465,12 @@ int APS5_VABI sceUserServiceGetDeletedUserInfo(void) { return 0; } -int APS5_VABI sceUserServiceGetSecureHomeDirectory(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceUserServiceGetSecureHomeDirectory(std::uint32_t user_id, char* path, std::size_t size) { + if (path == nullptr) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + const int result = std::snprintf(path, size, "/%s/priv/home/%08x", sceKernelGetFsSandboxRandomWord(), user_id); + return result >= 0 && static_cast(result) < size ? USER_SERVICE_OK : USER_SERVICE_ERROR_BUFFER_TOO_SHORT; } int APS5_VABI sceUserServiceGetHearingAssistAdjustmentLevelRight(void) { @@ -2556,9 +2682,46 @@ int APS5_VABI sceUserServiceSetGlsMessageFilterLevel(void) { return 0; } -int APS5_VABI sceUserServiceGetUserGroupNameList(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceUserServiceGetUserGroupNameList(int groupIndex, char* nameList, std::size_t size, + std::int32_t* count) { + if (nameList == nullptr || count == nullptr) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + if (size < 0x110) { + return USER_SERVICE_ERROR_BUFFER_TOO_SHORT; + } + if (size != 0x110 || groupIndex < -1 || groupIndex > 128 || + (groupIndex >= 0 && !UserGroupSlotExists(groupIndex))) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + char path[64]; + if (groupIndex < 0) { + std::snprintf(path, sizeof(path), "/system_data/priv/home/user_list.dat"); + } else { + std::snprintf(path, sizeof(path), "/system_data/priv/home/user_list.dat.%d", groupIndex); + } + std::array userIds; + userIds.fill(USER_SERVICE_USER_ID_INVALID); + const int descriptor = sceKernelOpen(path, 0, 0); + if (descriptor < 0) { + return descriptor; + } + const std::int64_t bytesRead = sceKernelRead(descriptor, userIds.data(), 0x40); + sceKernelClose(descriptor); + if (bytesRead < 0) { + return static_cast(bytesRead); + } + *count = 0; + for (std::size_t index = 0; index < userIds.size() && userIds[index] != USER_SERVICE_USER_ID_INVALID; ++index) { + char userName[USER_SERVICE_MAX_USER_NAME_LENGTH + 1]{}; + const int result = sceUserServiceGetUserName(userIds[index], userName, sizeof(userName)); + if (result != USER_SERVICE_OK) { + return result; + } + std::memcpy(nameList + index * sizeof(userName), userName, sizeof(userName)); + ++*count; + } + return USER_SERVICE_OK; } int APS5_VABI sceUserServiceSetVoiceAgentEnableFeedback(void) { @@ -2911,9 +3074,16 @@ int APS5_VABI sceUserServiceSetNotificationBehavior(void) { return 0; } -int APS5_VABI sceUserServiceGetUserGroupNum(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceUserServiceGetUserGroupNum(std::int32_t* count) { + if (count == nullptr) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + int groupCount = 0; + while (groupCount < 128 && UserGroupSlotExists(groupCount)) { + ++groupCount; + } + *count = groupCount; + return USER_SERVICE_OK; } int APS5_VABI sceUserServiceSetPartySettingFlags(void) { diff --git a/core/libs/prx/libSceUserService/InventoryExports.cpp b/core/libs/prx/libSceUserService/InventoryExports.cpp index 0a45386094..4e91a3c0d1 100644 --- a/core/libs/prx/libSceUserService/InventoryExports.cpp +++ b/core/libs/prx/libSceUserService/InventoryExports.cpp @@ -1,4 +1,6 @@ #include "SceTypes.hpp" +#include "prx/libSceUserService/UserService.hpp" +#include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/general/ExportMacros.hpp" @@ -84,8 +86,15 @@ int APS5_VABI libSceUserServiceUnknown_DL6cKVeISqs_nid_no_patch() { } APS5_EXPORT("DL6cKVeISqs", libSceUserServiceUnknown_DL6cKVeISqs_nid_no_patch); -int APS5_VABI libSceUserServiceUnknown_ErTMkmSho4E_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI libSceUserServiceUnknown_ErTMkmSho4E_nid_no_patch(std::uint32_t userId, char* path, std::size_t size) { + if (path == nullptr) { + return USER_SERVICE_ERROR_INVALID_ARGUMENT; + } + if (size <= 0x25) { + return USER_SERVICE_ERROR_BUFFER_TOO_SHORT; + } + std::snprintf(path, size, "/%s/eap/rodata/home/%08x", sceKernelGetFsSandboxRandomWord(), userId); + return USER_SERVICE_OK; } APS5_EXPORT("ErTMkmSho4E", libSceUserServiceUnknown_ErTMkmSho4E_nid_no_patch); diff --git a/core/libs/prx/libSceUserService/UserService.hpp b/core/libs/prx/libSceUserService/UserService.hpp index 4d0bf9de3c..0dda852e12 100644 --- a/core/libs/prx/libSceUserService/UserService.hpp +++ b/core/libs/prx/libSceUserService/UserService.hpp @@ -23,6 +23,7 @@ constexpr int USER_SERVICE_THREAD_PRIORITY_MIN = 0x100; constexpr int USER_SERVICE_THREAD_PRIORITY_MAX = 0x2FF; constexpr int USER_SERVICE_ERROR_BUFFER_TOO_SHORT = -2137653238; constexpr int USER_SERVICE_ERROR_UNKNOWN_USER = static_cast(0x80960105u); +constexpr int USER_SERVICE_ERROR_NOT_SUPPORTED = static_cast(0x80960006u); constexpr std::size_t USER_SERVICE_MAX_USER_NAME_LENGTH = 16; constexpr const char* USER_SERVICE_INITIAL_USER_NAME = "Player"; constexpr int USER_SERVICE_MAX_USERS = 4; @@ -74,11 +75,11 @@ int APS5_VABI sceUserServiceSetJapaneseInputType(void); int APS5_VABI sceUserServiceSetGlsBroadcastersComment(void); int APS5_VABI sceUserServiceSetGlsCameraTransparency(void); int APS5_VABI sceUserServiceGetIsHrtfEnabled(void); -int APS5_VABI sceUserServiceGetCurrentUserGroupIndex(void); +int APS5_VABI sceUserServiceGetCurrentUserGroupIndex(std::int32_t* index); int APS5_VABI sceUserServiceGetPbtcThursdayHoursEnd(void); int APS5_VABI sceUserServiceGetPbtcFridayHoursStart(void); int APS5_VABI sceUserServiceSetSystemLoggerHashedAccountIdTtl(void); -int APS5_VABI sceUserServiceGetRegisteredHomeUserIdList(void); +int APS5_VABI sceUserServiceGetRegisteredHomeUserIdList(std::int32_t* userIds, std::int32_t* count); int APS5_VABI sceUserServiceSetVolumeForGenericUSB(void); int APS5_VABI sceUserServiceSetControlCenterFunctionControlSettings(void); int APS5_VABI sceUserServiceGetAudioFocus(void); @@ -112,7 +113,7 @@ int APS5_VABI sceUserServiceGetGlsHintFlag(void); int APS5_VABI sceUserServiceSetIsFakePremium(void); int APS5_VABI sceUserServiceGetAccessibilityKeyremapDataAston(void); int APS5_VABI sceUserServiceSetKeyRepeatSpeed(void); -int APS5_VABI sceUserServiceSetUserGroupName(void); +int APS5_VABI sceUserServiceSetUserGroupName(int groupIndex, const char* name); int APS5_VABI sceUserServiceGetParentalDvdRegion(void); int APS5_VABI sceUserServiceSetGlsCameraDepthLevel(void); int APS5_VABI sceUserServiceGetParentalContentControl(void); @@ -139,7 +140,7 @@ int APS5_VABI sceUserServiceGetEventCalendarType(void); int APS5_VABI sceUserServiceSetPbtcSaturdayHoursEnd(void); int APS5_VABI sceUserServiceSetSystemLoggerHashedAccountIdParam(void); int APS5_VABI sceUserServiceSetHearingAssistUsePreset(void); -int APS5_VABI sceUserServiceGetUserGroupName(void); +int APS5_VABI sceUserServiceGetUserGroupName(int groupIndex, char* name, std::size_t size); int APS5_VABI sceUserServiceSetAutoLoginEnabled(void); int APS5_VABI sceUserServiceGetIPDLeft(void); int APS5_VABI sceUserServiceSetHintShowFlag(void); @@ -179,10 +180,10 @@ int APS5_VABI sceUserServiceSetHmd2Vibration(void); int APS5_VABI sceUserServiceGetGlsLiveQuality2(void); int APS5_VABI sceUserServiceSetIsQuickSignup(void); int APS5_VABI sceUserServiceSetNotificationSettings_2(void); -int APS5_VABI sceUserServiceGetHomeDirectory(void); +int APS5_VABI sceUserServiceGetHomeDirectory(std::uint32_t userId, char* path, std::size_t size); int APS5_VABI sceUserServiceSetPbtcSundayDuration(void); int APS5_VABI sceUserServiceSetIPDLeft(void); -int APS5_VABI sceUserServiceGetKratosPrimaryUser(void); +int APS5_VABI sceUserServiceGetKratosPrimaryUser(int* userId); int APS5_VABI sceUserServiceLogout(void); int APS5_VABI sceUserServiceSetNpDateOfBirth(void); int APS5_VABI sceUserServiceSetPbtcWednesdayHoursStart(void); @@ -269,17 +270,17 @@ int APS5_VABI sceUserServiceGetGlsIsRecievedMessageHidden(void); int APS5_VABI sceUserServiceSetVoiceAgentSessionTimeout(void); int APS5_VABI sceUserServiceSetGlsLanguageFilter(void); int APS5_VABI sceUserServiceGetHmd2Vibration(void); -int APS5_VABI sceUserServiceIsLoggedIn(void); +int APS5_VABI sceUserServiceIsLoggedIn(int userId); int APS5_VABI sceUserServiceGetPbtcMondayHoursStart(void); int APS5_VABI sceUserServiceSetAppSortOrder(void); int APS5_VABI sceUserServiceSetMouseHandType(void); int APS5_VABI sceUserServiceGetIsQuickSignup(void); -int APS5_VABI sceUserServiceIsKratosPrimaryUser(void); +bool APS5_VABI sceUserServiceIsKratosPrimaryUser(int userId); int APS5_VABI sceUserServiceSetVoiceAgentEnableWup(void); int APS5_VABI sceUserServiceGetGlsCameraDepthLevel(void); int APS5_VABI sceUserServiceGetHmuBrightness(void); int APS5_VABI sceUserServiceSetGlsHintFlag(void); -int APS5_VABI sceUserServiceIsLoggedInWithoutLock(void); +int APS5_VABI sceUserServiceIsLoggedInWithoutLock(int userId); int APS5_VABI sceUserServiceSetShareStatus3(void); int APS5_VABI sceUserServiceGetGlsTtsVolume(void); int APS5_VABI sceUserServiceGetMicLevel(void); @@ -380,11 +381,11 @@ int APS5_VABI sceUserServiceSetGlsIsCameraHidden(void); int APS5_VABI sceUserServiceSetUserStatus(void); int APS5_VABI sceUserServiceGetEventFilterTeamEvent(void); int APS5_VABI sceUserServiceSetPsCloudFeedbackTime(void); -int APS5_VABI sceUserServiceSetUserGroupIndex(void); +int APS5_VABI sceUserServiceSetUserGroupIndex(int index); int APS5_VABI sceUserServiceSetPartyMuteListA(void); int APS5_VABI sceUserServiceSetNpLanguageCode2(void); int APS5_VABI sceUserServiceSetShareStatus2(void); -int APS5_VABI sceUserServiceSetForegroundUser(void); +int APS5_VABI sceUserServiceSetForegroundUser(int userId); int APS5_VABI sceUserServiceSetGlsAccessTokenTwitch(void); int APS5_VABI sceUserServiceGetNpOfflineAccountAdult(void); int APS5_VABI sceUserServiceSetGamePresets(void); @@ -406,7 +407,7 @@ int APS5_VABI sceUserServiceSetGlsCameraEdgeLevel(void); int APS5_VABI sceUserServiceGetPartyChatTranscriptionLanguageType(void); int APS5_VABI sceUserServiceSetVoiceAgentTtsVolume(void); int APS5_VABI sceUserServiceGetPbtcTuesdayDuration(void); -int APS5_VABI sceUserServiceGetUserWithHearingAssist(void); +int APS5_VABI sceUserServiceGetUserWithHearingAssist(std::int32_t* userId); int APS5_VABI sceUserServiceGetNpOnlineId(void); int APS5_VABI sceUserServiceGetHintShowFlag3(void); int APS5_VABI sceUserServiceSetNotificationSettings_3(void); @@ -434,7 +435,7 @@ int APS5_VABI sceUserServiceSetEventSortTitle(void); int APS5_VABI sceUserServiceSetNpOnlineId(void); int APS5_VABI sceUserServiceSetA11yControllerBName(void); int APS5_VABI sceUserServiceGetSaveDataAutoSync(void); -int APS5_VABI sceUserServiceGetDefaultNewUserGroupName(void); +int APS5_VABI sceUserServiceGetDefaultNewUserGroupName(char* name, std::size_t size); int APS5_VABI sceUserServiceGetHmd2Brightness(void); int APS5_VABI sceUserServiceGetHoldAudioOutDevice(void); int APS5_VABI sceUserServiceSetVibrationStrength(void); @@ -457,7 +458,7 @@ int APS5_VABI sceUserServiceSetGlsCameraEliminationLevel(void); int APS5_VABI sceUserServiceGetVibrationStrength(void); int APS5_VABI sceUserServiceGetNpAccountUpgradeFlag(void); int APS5_VABI sceUserServiceGetDeletedUserInfo(void); -int APS5_VABI sceUserServiceGetSecureHomeDirectory(void); +int APS5_VABI sceUserServiceGetSecureHomeDirectory(std::uint32_t userId, char* path, std::size_t size); int APS5_VABI sceUserServiceGetHearingAssistAdjustmentLevelRight(void); int APS5_VABI sceUserServiceGetPlatformPrivacyWs1Internal(void); int APS5_VABI sceUserServiceGetGlsIsRecDisabled(void); @@ -500,7 +501,8 @@ int APS5_VABI sceUserServiceSetGlsLiveQuality2(void); int APS5_VABI sceUserServiceGetHmd2GazeActivate(void); int APS5_VABI sceUserServiceSetGlsCameraPosition(void); int APS5_VABI sceUserServiceSetGlsMessageFilterLevel(void); -int APS5_VABI sceUserServiceGetUserGroupNameList(void); +int APS5_VABI sceUserServiceGetUserGroupNameList(int groupIndex, char* nameList, std::size_t size, + std::int32_t* count); int APS5_VABI sceUserServiceSetVoiceAgentEnableFeedback(void); int APS5_VABI sceUserServiceGetGazeActivate(void); int APS5_VABI sceUserServiceGetGlsIsTwitterEnabled(void); @@ -571,11 +573,12 @@ int APS5_VABI sceUserServiceSetPbtcThursdayHoursStart(void); int APS5_VABI sceUserServiceSetIsHrtfEnabled(void); int APS5_VABI sceUserServiceGetParentalGameAgeLevel(void); int APS5_VABI sceUserServiceSetNotificationBehavior(void); -int APS5_VABI sceUserServiceGetUserGroupNum(void); +int APS5_VABI sceUserServiceGetUserGroupNum(std::int32_t* count); int APS5_VABI sceUserServiceSetPartySettingFlags(void); int APS5_VABI sceUserServiceGetGlsCameraEffect(void); int APS5_VABI sceUserServiceGetPbtcSundayHoursEnd(void); int APS5_VABI sceUserServiceGetIsSpartacusEnabled(std::int32_t userId, bool* enabled); +int APS5_VABI libSceUserServiceUnknown_ErTMkmSho4E_nid_no_patch(std::uint32_t userId, char* path, std::size_t size); } #endif diff --git a/core/libs/prx/libSceVideoOut/CMakeLists.txt b/core/libs/prx/libSceVideoOut/CMakeLists.txt index 96ceacccbe..bcaf2def20 100644 --- a/core/libs/prx/libSceVideoOut/CMakeLists.txt +++ b/core/libs/prx/libSceVideoOut/CMakeLists.txt @@ -81,6 +81,17 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_video_out_firmware_answers_tests ${LIBS_INCLUDE_DIR}/tests/GuestVideoOutFirmwareAnswers.cpp) + target_include_directories(guest_video_out_firmware_answers_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_video_out_firmware_answers_tests PRIVATE libSceVideoOut libkernel libc) + configure_windows_unwind(guest_video_out_firmware_answers_tests) + add_test(NAME guest_video_out_firmware_answers COMMAND guest_video_out_firmware_answers_tests) + set_tests_properties(guest_video_out_firmware_answers PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_video_out_firmware_answers PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_video_out_high_frame_rate_tests ${LIBS_INCLUDE_DIR}/tests/GuestVideoOutHighFrameRate.cpp) target_include_directories(guest_video_out_high_frame_rate_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_video_out_high_frame_rate_tests PRIVATE libSceVideoOut libSceAgcDriver libkernel libc) diff --git a/core/libs/prx/libSceVideoOut/InventoryExports.cpp b/core/libs/prx/libSceVideoOut/InventoryExports.cpp index 77d4bde7ed..0bbcff8e99 100644 --- a/core/libs/prx/libSceVideoOut/InventoryExports.cpp +++ b/core/libs/prx/libSceVideoOut/InventoryExports.cpp @@ -3,6 +3,19 @@ #include "prx/libc/include/general/ExportMacros.hpp" #include "prx/libSceVideoOut/include/InventoryExports.hpp" #include "prx/libc/include/General.hpp" +#include +#include + +namespace { +constexpr int VideoOutErrorCurveSamplePoints = static_cast(0x8029001Cu); +constexpr int VideoOutErrorLatestEopHandle = static_cast(0x802900FFu); + +std::uint32_t ModeFlags(const void* mode) { + std::uint32_t flags; + std::memcpy(&flags, static_cast(mode) + 0x20, sizeof(flags)); + return flags; +} +} extern "C" { @@ -19,7 +32,7 @@ int APS5_VABI sceVideoOutAddBufferAnisoPrivilege() { } int APS5_VABI sceVideoOutAddBufferHdrPrivilege() { - NotImplemented_nid_no_patch(__func__); + return 0; } int APS5_VABI sceVideoOutAddBufferYccPrivilege() { @@ -91,7 +104,7 @@ int APS5_VABI sceVideoOutCursorSetPositionStereo() { } int APS5_VABI sceVideoOutCurveSamplePointsCalc_() { - NotImplemented_nid_no_patch(__func__); + return VideoOutErrorCurveSamplePoints; } int APS5_VABI sceVideoOutDebugAddVrrPrivilege() { @@ -155,7 +168,7 @@ int APS5_VABI sceVideoOutDriverGetFinishedEopHandle() { } int APS5_VABI sceVideoOutDriverGetLatestEopHandle() { - NotImplemented_nid_no_patch(__func__); + return VideoOutErrorLatestEopHandle; } int APS5_VABI sceVideoOutDriverIncrementBufferLabel() { @@ -255,7 +268,7 @@ int APS5_VABI sceVideoOutRazorAddCallback() { } int APS5_VABI sceVideoOutRazorRecallCallbacks() { - NotImplemented_nid_no_patch(__func__); + return 0; } int APS5_VABI sceVideoOutRazorRemoveCallback() { @@ -383,7 +396,7 @@ int APS5_VABI sceVideoOutSysGetKsvList() { } int APS5_VABI sceVideoOutSysGetMonitorInfo_() { - NotImplemented_nid_no_patch(__func__); + return -1; } int APS5_VABI sceVideoOutSysGetOutputStatus() { @@ -418,8 +431,10 @@ int APS5_VABI sceVideoOutSysHdmiMonitorInfoIsMorpheus() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI sceVideoOutSysInitMode() { - NotImplemented_nid_no_patch(__func__); +void APS5_VABI sceVideoOutSysInitMode(void* mode) { + std::memset(mode, 0xFF, 0x20); + const std::uint32_t size = 0x20; + std::memcpy(mode, &size, sizeof(size)); } int APS5_VABI sceVideoOutSysIsModeS3d() { @@ -446,24 +461,25 @@ int APS5_VABI sceVideoOutSysIsUserStatusVr() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI sceVideoOutSysModeIsHdr() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVideoOutSysModeIsHdr(const void* mode) { + const auto value = static_cast(mode)[6]; + return static_cast(value - 12u) < 6u ? 1 : 0; } -int APS5_VABI sceVideoOutSysModeIsSystemDefault() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVideoOutSysModeIsSystemDefault(const void* mode) { + return static_cast(~ModeFlags(mode) & 1u); } -int APS5_VABI sceVideoOutSysModeIsVrLegacy() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVideoOutSysModeIsVrLegacy(const void* mode) { + return (ModeFlags(mode) & 6u) == 6u ? 1 : 0; } -int APS5_VABI sceVideoOutSysModeIsVrNative() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVideoOutSysModeIsVrNative(const void* mode) { + return (ModeFlags(mode) & 6u) == 2u ? 1 : 0; } -int APS5_VABI sceVideoOutSysModeIsVr() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVideoOutSysModeIsVr(const void* mode) { + return static_cast((ModeFlags(mode) >> 1) & 1u); } int APS5_VABI sceVideoOutSysOpenInternal() { @@ -523,7 +539,7 @@ int APS5_VABI sceVideoOutSysUpdatePrivilege() { } int APS5_VABI sceVideoOutSysUpdateRenderingMode() { - NotImplemented_nid_no_patch(__func__); + return 0; } int APS5_VABI sceVideoOutUnlockBuffer() { diff --git a/core/libs/prx/libSceVideoOut/include/InventoryExports.hpp b/core/libs/prx/libSceVideoOut/include/InventoryExports.hpp index 5c65f81866..40c9079b46 100644 --- a/core/libs/prx/libSceVideoOut/include/InventoryExports.hpp +++ b/core/libs/prx/libSceVideoOut/include/InventoryExports.hpp @@ -107,18 +107,18 @@ int APS5_VABI sceVideoOutSysGetVblankStatus2(); int APS5_VABI sceVideoOutSysGetVblankStatus(); int APS5_VABI sceVideoOutSysGetVideoOutModeByBusSpecifier(); int APS5_VABI sceVideoOutSysHdmiMonitorInfoIsMorpheus(); -int APS5_VABI sceVideoOutSysInitMode(); +void APS5_VABI sceVideoOutSysInitMode(void* mode); int APS5_VABI sceVideoOutSysIsModeS3d(); int APS5_VABI sceVideoOutSysIsOutputSupported(); int APS5_VABI sceVideoOutSysIsSupportedByMonitorInfo_(); int APS5_VABI sceVideoOutSysIsUserStatusHdr(); int APS5_VABI sceVideoOutSysIsUserStatusSystemDefault(); int APS5_VABI sceVideoOutSysIsUserStatusVr(); -int APS5_VABI sceVideoOutSysModeIsHdr(); -int APS5_VABI sceVideoOutSysModeIsSystemDefault(); -int APS5_VABI sceVideoOutSysModeIsVrLegacy(); -int APS5_VABI sceVideoOutSysModeIsVrNative(); -int APS5_VABI sceVideoOutSysModeIsVr(); +int APS5_VABI sceVideoOutSysModeIsHdr(const void* mode); +int APS5_VABI sceVideoOutSysModeIsSystemDefault(const void* mode); +int APS5_VABI sceVideoOutSysModeIsVrLegacy(const void* mode); +int APS5_VABI sceVideoOutSysModeIsVrNative(const void* mode); +int APS5_VABI sceVideoOutSysModeIsVr(const void* mode); int APS5_VABI sceVideoOutSysOpenInternal(); int APS5_VABI sceVideoOutSysOpenVrNative(); int APS5_VABI sceVideoOutSysReadCrc64(); diff --git a/core/libs/prx/libSceVideodec/Export.cpp b/core/libs/prx/libSceVideodec/Export.cpp index b8a4b3cbc8..f8f4fcbd88 100644 --- a/core/libs/prx/libSceVideodec/Export.cpp +++ b/core/libs/prx/libSceVideodec/Export.cpp @@ -118,12 +118,18 @@ int CopyPicture(const SceVideodec2::OutputInfo& output, const Geometry& geometry const auto sourcePitch = static_cast(output.framePitchInBytes); const auto* source = static_cast(output.frameBuffer); auto* destination = static_cast(frameBuffer->frameBuffer); - for (std::uint32_t row = 0; row < geometry.height; ++row) - std::memcpy(destination + static_cast(row) * geometry.pitch, source + static_cast(row) * sourcePitch, geometry.width); + for (std::uint32_t row = 0; row < geometry.height; ++row) { + if (!GuestCopyOut_nid_no_patch(destination + static_cast(row) * geometry.pitch, source + static_cast(row) * sourcePitch, geometry.width)) { + return VideodecErrorFrameBufferPointer; + } + } const auto* sourceChroma = source + sourcePitch * geometry.height; auto* destinationChroma = destination + static_cast(geometry.pitch) * geometry.height; - for (std::uint32_t row = 0; row < geometry.height / 2; ++row) - std::memcpy(destinationChroma + static_cast(row) * geometry.pitch, sourceChroma + static_cast(row) * sourcePitch, geometry.width); + for (std::uint32_t row = 0; row < geometry.height / 2; ++row) { + if (!GuestCopyOut_nid_no_patch(destinationChroma + static_cast(row) * geometry.pitch, sourceChroma + static_cast(row) * sourcePitch, geometry.width)) { + return VideodecErrorFrameBufferPointer; + } + } return 0; } @@ -133,16 +139,28 @@ extern "C" { int APS5_VABI sceVideodecCreateDecoder(const ConfigInfo* config, const ResourceInfo* resource, Ctrl* ctrl) { if (config == nullptr || resource == nullptr || ctrl == nullptr) return VideodecErrorArgumentPointer; - if (config->thisSize < sizeof(ConfigInfo) || resource->thisSize < sizeof(ResourceInfo)) return VideodecErrorStructSize; - const auto decoderConfig = MapConfig(*config); + std::uint64_t configSize = 0; + if (!GuestCopyIn_nid_no_patch(&configSize, config, sizeof(configSize))) return VideodecErrorArgumentPointer; + std::uint64_t resourceSize = 0; + if (!GuestCopyIn_nid_no_patch(&resourceSize, resource, sizeof(resourceSize))) return VideodecErrorArgumentPointer; + if (configSize < sizeof(ConfigInfo) || resourceSize < sizeof(ResourceInfo)) return VideodecErrorStructSize; + ConfigInfo localConfig{}; + if (!GuestCopyIn_nid_no_patch(&localConfig, config, sizeof(localConfig))) return VideodecErrorArgumentPointer; + ResourceInfo localResource{}; + if (!GuestCopyIn_nid_no_patch(&localResource, resource, sizeof(localResource))) return VideodecErrorArgumentPointer; + Ctrl localCtrl{}; + localCtrl.thisSize = sizeof(Ctrl); + localCtrl.version = 1; + localCtrl.handle = nullptr; + const auto decoderConfig = MapConfig(localConfig); SceVideodec2::DecoderMemoryInfo decoderMemory{}; decoderMemory.thisSize = sizeof(SceVideodec2::DecoderMemoryInfo); - decoderMemory.cpuMemorySize = resource->cpuMemorySize; - decoderMemory.cpuMemory = resource->cpuMemory; - decoderMemory.cpuGpuMemorySize = resource->cpuGpuMemorySize; - decoderMemory.cpuGpuMemory = resource->cpuGpuMemory; - decoderMemory.maxFrameBufferSize = resource->maxFrameBufferSize; - decoderMemory.frameBufferAlignment = resource->frameBufferAlignment; + decoderMemory.cpuMemorySize = localResource.cpuMemorySize; + decoderMemory.cpuMemory = localResource.cpuMemory; + decoderMemory.cpuGpuMemorySize = localResource.cpuGpuMemorySize; + decoderMemory.cpuGpuMemory = localResource.cpuGpuMemory; + decoderMemory.maxFrameBufferSize = localResource.maxFrameBufferSize; + decoderMemory.frameBufferAlignment = localResource.frameBufferAlignment; try { sceVideodec2QueryDecoderMemoryInfo_nid_postfix(&decoderConfig, &decoderMemory); @@ -158,31 +176,54 @@ int APS5_VABI sceVideodecCreateDecoder(const ConfigInfo* config, const ResourceI std::lock_guard lock(decodersMutex); decoders.emplace(decoder, decoder); } - ctrl->thisSize = sizeof(Ctrl); - ctrl->handle = decoder; - ctrl->version = 1; + localCtrl.handle = decoder; + if (!GuestCopyOut_nid_no_patch(ctrl, &localCtrl, sizeof(localCtrl))) { + { + std::lock_guard lock(decodersMutex); + decoders.erase(decoder); + } + delete decoder; + sceVideodec2DeleteDecoder_nid_postfix(handle); + return VideodecErrorArgumentPointer; + } return 0; } int APS5_VABI sceVideodecDecode(Ctrl* ctrl, const SceVideodec2::InputData* input, FrameBuffer* frameBuffer, PictureInfo* picture) { if (ctrl == nullptr || input == nullptr || picture == nullptr) return VideodecErrorArgumentPointer; if (frameBuffer == nullptr) return VideodecErrorFrameBufferPointer; - if (ctrl->thisSize < sizeof(Ctrl)) return VideodecErrorStructSize; - if (input->thisSize < sizeof(SceVideodec2::InputData)) return VideodecErrorStructSize; - if (picture->thisSize < sizeof(PictureInfo)) return VideodecErrorStructSize; - if (frameBuffer->thisSize < sizeof(FrameBuffer)) return VideodecErrorStructSize; - auto* decoder = FindDecoder(ctrl->handle); + std::uint64_t ctrlSize = 0; + if (!GuestCopyIn_nid_no_patch(&ctrlSize, ctrl, sizeof(ctrlSize))) return VideodecErrorArgumentPointer; + std::uint64_t inputSize = 0; + if (!GuestCopyIn_nid_no_patch(&inputSize, input, sizeof(inputSize))) return VideodecErrorArgumentPointer; + std::uint64_t pictureSize = 0; + if (!GuestCopyIn_nid_no_patch(&pictureSize, picture, sizeof(pictureSize))) return VideodecErrorArgumentPointer; + std::uint64_t frameBufferInfoSize = 0; + if (!GuestCopyIn_nid_no_patch(&frameBufferInfoSize, frameBuffer, sizeof(frameBufferInfoSize))) return VideodecErrorFrameBufferPointer; + if (ctrlSize < sizeof(Ctrl)) return VideodecErrorStructSize; + if (inputSize < sizeof(SceVideodec2::InputData)) return VideodecErrorStructSize; + if (pictureSize < sizeof(PictureInfo)) return VideodecErrorStructSize; + if (frameBufferInfoSize < sizeof(FrameBuffer)) return VideodecErrorStructSize; + Ctrl localCtrl{}; + if (!GuestCopyIn_nid_no_patch(&localCtrl, ctrl, sizeof(localCtrl))) return VideodecErrorArgumentPointer; + SceVideodec2::InputData localInput{}; + if (!GuestCopyIn_nid_no_patch(&localInput, input, sizeof(localInput))) return VideodecErrorArgumentPointer; + PictureInfo localPicture{}; + if (!GuestCopyIn_nid_no_patch(&localPicture, picture, sizeof(localPicture))) return VideodecErrorArgumentPointer; + FrameBuffer localFrameBuffer{}; + if (!GuestCopyIn_nid_no_patch(&localFrameBuffer, frameBuffer, sizeof(localFrameBuffer))) return VideodecErrorFrameBufferPointer; + auto* decoder = FindDecoder(localCtrl.handle); if (decoder == nullptr) return VideodecErrorHandle; - if (input->auData == nullptr) return VideodecErrorAuPointer; - if (input->auSize == 0) return VideodecErrorAuSize; - if (frameBuffer->frameBuffer == nullptr) return VideodecErrorFrameBufferPointer; + if (localInput.auData == nullptr) return VideodecErrorAuPointer; + if (localInput.auSize == 0) return VideodecErrorAuSize; + if (localFrameBuffer.frameBuffer == nullptr) return VideodecErrorFrameBufferPointer; SceVideodec2::FrameBuffer target{}; target.thisSize = sizeof(SceVideodec2::FrameBuffer); target.frameBuffer = decoder->picture.data(); target.frameBufferSize = decoder->picture.size(); SceVideodec2::OutputInfo output{sizeof(SceVideodec2::OutputInfo)}; try { - sceVideodec2Decode_nid_postfix(decoder->handle, input, &target, &output); + sceVideodec2Decode_nid_postfix(decoder->handle, &localInput, &target, &output); } catch (const std::exception&) { return VideodecErrorDecodeAu; @@ -190,20 +231,25 @@ int APS5_VABI sceVideodecDecode(Ctrl* ctrl, const SceVideodec2::InputData* input SceVideodec2::Internal::PictureHeader header{}; if (output.isValid) Videodec2PictureHeader_nid_no_patch(decoder->handle, output.frameBuffer, &header); const auto geometry = Measure(output, header); - FillPictureInfo(geometry, output, picture); - picture->ptsData = header.pts; - picture->attachedData = header.attached; + FillPictureInfo(geometry, output, &localPicture); + localPicture.ptsData = header.pts; + localPicture.attachedData = header.attached; + if (!GuestCopyOut_nid_no_patch(picture, &localPicture, sizeof(localPicture))) return VideodecErrorArgumentPointer; if (!output.isValid) return 0; - return CopyPicture(output, geometry, frameBuffer); + return CopyPicture(output, geometry, &localFrameBuffer); } int APS5_VABI sceVideodecDeleteDecoder(Ctrl* ctrl) { if (ctrl == nullptr) return VideodecErrorArgumentPointer; - if (ctrl->thisSize < sizeof(Ctrl)) return VideodecErrorStructSize; + std::uint64_t ctrlSize = 0; + if (!GuestCopyIn_nid_no_patch(&ctrlSize, ctrl, sizeof(ctrlSize))) return VideodecErrorArgumentPointer; + if (ctrlSize < sizeof(Ctrl)) return VideodecErrorStructSize; + Ctrl localCtrl{}; + if (!GuestCopyIn_nid_no_patch(&localCtrl, ctrl, sizeof(localCtrl))) return VideodecErrorArgumentPointer; Decoder* decoder = nullptr; { std::lock_guard lock(decodersMutex); - const auto found = decoders.find(ctrl->handle); + const auto found = decoders.find(localCtrl.handle); if (found == decoders.end()) return VideodecErrorHandle; decoder = found->second; decoders.erase(found); @@ -222,12 +268,24 @@ int APS5_VABI sceVideodecDeleteDecoder(Ctrl* ctrl) { int APS5_VABI sceVideodecFlush(Ctrl* ctrl, FrameBuffer* frameBuffer, PictureInfo* picture) { if (ctrl == nullptr || picture == nullptr) return VideodecErrorArgumentPointer; if (frameBuffer == nullptr) return VideodecErrorFrameBufferPointer; - if (ctrl->thisSize < sizeof(Ctrl)) return VideodecErrorStructSize; - if (picture->thisSize < sizeof(PictureInfo)) return VideodecErrorStructSize; - if (frameBuffer->thisSize < sizeof(FrameBuffer)) return VideodecErrorStructSize; - auto* decoder = FindDecoder(ctrl->handle); + std::uint64_t ctrlSize = 0; + if (!GuestCopyIn_nid_no_patch(&ctrlSize, ctrl, sizeof(ctrlSize))) return VideodecErrorArgumentPointer; + std::uint64_t pictureSize = 0; + if (!GuestCopyIn_nid_no_patch(&pictureSize, picture, sizeof(pictureSize))) return VideodecErrorArgumentPointer; + std::uint64_t frameBufferInfoSize = 0; + if (!GuestCopyIn_nid_no_patch(&frameBufferInfoSize, frameBuffer, sizeof(frameBufferInfoSize))) return VideodecErrorFrameBufferPointer; + if (ctrlSize < sizeof(Ctrl)) return VideodecErrorStructSize; + if (pictureSize < sizeof(PictureInfo)) return VideodecErrorStructSize; + if (frameBufferInfoSize < sizeof(FrameBuffer)) return VideodecErrorStructSize; + Ctrl localCtrl{}; + if (!GuestCopyIn_nid_no_patch(&localCtrl, ctrl, sizeof(localCtrl))) return VideodecErrorArgumentPointer; + PictureInfo localPicture{}; + if (!GuestCopyIn_nid_no_patch(&localPicture, picture, sizeof(localPicture))) return VideodecErrorArgumentPointer; + FrameBuffer localFrameBuffer{}; + if (!GuestCopyIn_nid_no_patch(&localFrameBuffer, frameBuffer, sizeof(localFrameBuffer))) return VideodecErrorFrameBufferPointer; + auto* decoder = FindDecoder(localCtrl.handle); if (decoder == nullptr) return VideodecErrorHandle; - if (frameBuffer->frameBuffer == nullptr) return VideodecErrorFrameBufferPointer; + if (localFrameBuffer.frameBuffer == nullptr) return VideodecErrorFrameBufferPointer; SceVideodec2::FrameBuffer target{}; target.thisSize = sizeof(SceVideodec2::FrameBuffer); target.frameBuffer = decoder->picture.data(); @@ -242,17 +300,22 @@ int APS5_VABI sceVideodecFlush(Ctrl* ctrl, FrameBuffer* frameBuffer, PictureInfo SceVideodec2::Internal::PictureHeader header{}; if (output.isValid) Videodec2PictureHeader_nid_no_patch(decoder->handle, output.frameBuffer, &header); const auto geometry = Measure(output, header); - FillPictureInfo(geometry, output, picture); - picture->ptsData = header.pts; - picture->attachedData = header.attached; + FillPictureInfo(geometry, output, &localPicture); + localPicture.ptsData = header.pts; + localPicture.attachedData = header.attached; + if (!GuestCopyOut_nid_no_patch(picture, &localPicture, sizeof(localPicture))) return VideodecErrorArgumentPointer; if (!output.isValid) return 0; - return CopyPicture(output, geometry, frameBuffer); + return CopyPicture(output, geometry, &localFrameBuffer); } int APS5_VABI sceVideodecReset(Ctrl* ctrl) { if (ctrl == nullptr) return VideodecErrorArgumentPointer; - if (ctrl->thisSize < sizeof(Ctrl)) return VideodecErrorStructSize; - auto* decoder = FindDecoder(ctrl->handle); + std::uint64_t ctrlSize = 0; + if (!GuestCopyIn_nid_no_patch(&ctrlSize, ctrl, sizeof(ctrlSize))) return VideodecErrorArgumentPointer; + if (ctrlSize < sizeof(Ctrl)) return VideodecErrorStructSize; + Ctrl localCtrl{}; + if (!GuestCopyIn_nid_no_patch(&localCtrl, ctrl, sizeof(localCtrl))) return VideodecErrorArgumentPointer; + auto* decoder = FindDecoder(localCtrl.handle); if (decoder == nullptr) return VideodecErrorHandle; try { sceVideodec2Reset_nid_postfix(decoder->handle); @@ -269,8 +332,16 @@ int APS5_VABI sceVideodecMapMemory(void) { int APS5_VABI sceVideodecQueryResourceInfo(const ConfigInfo* config, ResourceInfo* resource) { if (config == nullptr || resource == nullptr) return VideodecErrorArgumentPointer; - if (config->thisSize < sizeof(ConfigInfo) || resource->thisSize < sizeof(ResourceInfo)) return VideodecErrorStructSize; - const auto decoderConfig = MapConfig(*config); + std::uint64_t configSize = 0; + if (!GuestCopyIn_nid_no_patch(&configSize, config, sizeof(configSize))) return VideodecErrorArgumentPointer; + std::uint64_t resourceSize = 0; + if (!GuestCopyIn_nid_no_patch(&resourceSize, resource, sizeof(resourceSize))) return VideodecErrorArgumentPointer; + if (configSize < sizeof(ConfigInfo) || resourceSize < sizeof(ResourceInfo)) return VideodecErrorStructSize; + ConfigInfo localConfig{}; + if (!GuestCopyIn_nid_no_patch(&localConfig, config, sizeof(localConfig))) return VideodecErrorArgumentPointer; + ResourceInfo localResource{}; + if (!GuestCopyIn_nid_no_patch(&localResource, resource, sizeof(localResource))) return VideodecErrorArgumentPointer; + const auto decoderConfig = MapConfig(localConfig); SceVideodec2::DecoderMemoryInfo decoderMemory{}; decoderMemory.thisSize = sizeof(SceVideodec2::DecoderMemoryInfo); try { @@ -279,10 +350,13 @@ int APS5_VABI sceVideodecQueryResourceInfo(const ConfigInfo* config, ResourceInf } catch (const std::exception&) { return VideodecErrorConfigInfo; } - resource->cpuMemorySize = decoderMemory.cpuMemorySize; - resource->cpuGpuMemorySize = decoderMemory.cpuGpuMemorySize; - resource->maxFrameBufferSize = decoderMemory.maxFrameBufferSize; - resource->frameBufferAlignment = decoderMemory.frameBufferAlignment; + localResource.cpuMemorySize = decoderMemory.cpuMemorySize; + localResource.cpuGpuMemorySize = decoderMemory.cpuGpuMemorySize; + localResource.maxFrameBufferSize = decoderMemory.maxFrameBufferSize; + localResource.frameBufferAlignment = decoderMemory.frameBufferAlignment; + if (!GuestCopyOut_nid_no_patch(resource, &localResource, sizeof(localResource))) { + return VideodecErrorArgumentPointer; + } return 0; } diff --git a/core/libs/prx/libSceVrHand/Export.cpp b/core/libs/prx/libSceVrHand/Export.cpp index 5d646b9218..d4bd80f8ac 100644 --- a/core/libs/prx/libSceVrHand/Export.cpp +++ b/core/libs/prx/libSceVrHand/Export.cpp @@ -1,4 +1,5 @@ #include "prx/libSceVrHand/include/Api.hpp" +#include #include #include #include @@ -35,17 +36,20 @@ int APS5_VABI sceVrHandInitialize() { int APS5_VABI sceVrHandQueryMemory(const void* param, void* result) { constexpr int errInvalidArg = static_cast(0x8a870006); if (param == nullptr || result == nullptr) return errInvalidArg; - const auto* input = static_cast(param); std::uint32_t size = 0; - std::memcpy(&size, input, sizeof(size)); + if (!GuestCopyIn_nid_no_patch(&size, param, sizeof(size))) return errInvalidArg; if (size != 0x20) return errInvalidArg; + std::array input{}; + if (!GuestCopyIn_nid_no_patch(input.data(), param, sizeof(input))) return errInvalidArg; for (std::size_t index = 4; index < 0x20; ++index) if (input[index] != 0) return errInvalidArg; - auto* output = static_cast(result); - const std::uint32_t at0 = 0x3c00000; - const std::uint32_t at4 = 0x10000; - std::memcpy(output, &at0, sizeof(at0)); - std::memcpy(output + 4, &at4, sizeof(at4)); - std::memset(output + 8, 0, 0x14); + struct MemoryInfo { + std::uint32_t memorySize{0x3c00000}; + std::uint32_t alignment{0x10000}; + std::uint8_t reserved[0x14]{}; + }; + static_assert(sizeof(MemoryInfo) == 28); + const MemoryInfo info{}; + if (!GuestCopyOut_nid_no_patch(result, &info, sizeof(info))) return errInvalidArg; return 0; } diff --git a/core/libs/prx/libSceVrTracker2/Export.cpp b/core/libs/prx/libSceVrTracker2/Export.cpp index 7a58f2152c..86264794fc 100644 --- a/core/libs/prx/libSceVrTracker2/Export.cpp +++ b/core/libs/prx/libSceVrTracker2/Export.cpp @@ -653,8 +653,7 @@ std::int32_t APS5_VABI VrTracker2Unknown01(VrTracker2CalibrationResult* result, } std::int32_t APS5_VABI sceVrTracker2GetRawCameraMeshSize(VrTracker2MeshRequirement* requirement, std::uint32_t format) { - NotImplemented_nid_no_patch(__func__); - return 0; + return VR_TRACKER2_ERROR_NOT_INITIALIZED; } APS5_EXPORT("9R6W33Au4B4", VrTracker2Unknown03); diff --git a/core/libs/prx/libSceVrTracker2/tests/UninitializedNamedAnswers.cpp b/core/libs/prx/libSceVrTracker2/tests/UninitializedNamedAnswers.cpp index 552b1c4c02..02c553af23 100644 --- a/core/libs/prx/libSceVrTracker2/tests/UninitializedNamedAnswers.cpp +++ b/core/libs/prx/libSceVrTracker2/tests/UninitializedNamedAnswers.cpp @@ -63,5 +63,8 @@ int main() { Require(sceVrTracker2UnregisterDevice(2) == NotInitialized, "unregister device before initialization"); Require(sceVrTracker2SetCoordinateSystem(1) == NotInitialized, "set coordinate system before initialization"); Require(sceVrTracker2ResetLocalCoordinate(2) == NotInitialized, "reset local coordinate before initialization"); + Require(sceVrTracker2GetRawCameraMeshSize(reinterpret_cast(output.bytes), 0) == NotInitialized, + "get raw camera mesh size before initialization"); + Require(output.Untouched(), "get raw camera mesh size wrote nothing"); return 0; } diff --git a/core/libs/prx/libSceXml/Export.cpp b/core/libs/prx/libSceXml/Export.cpp index d83d703932..db4d9fcfe0 100644 --- a/core/libs/prx/libSceXml/Export.cpp +++ b/core/libs/prx/libSceXml/Export.cpp @@ -14,6 +14,10 @@ void AssignNodeId(XmlNodeId* self, const XmlNodeId& value) { self->flag = value.flag; } +bool NodeIdIsNull(const XmlNodeId* self) { + return self->first == nullptr && self->second == nullptr && self->flag == 0; +} + void CopyNode(XmlNode* self, const XmlNode* other) { AssignNodeId(&self->id, other->id); self->document = other->document; @@ -259,20 +263,20 @@ bool APS5_VABI _ZN3sce3Xml3Dom6NodeIdeqES2_(const XmlNodeId* self, XmlNodeId oth return other.first == self->first && other.second == self->second && other.flag == self->flag; } -int APS5_VABI _ZN3sce3Xml3Dom6NodeIdeqEi() { - NotImplemented_nid_no_patch(__func__); +bool APS5_VABI _ZN3sce3Xml3Dom6NodeIdeqEi(const XmlNodeId* self, int value) { + return value == 0 && NodeIdIsNull(self); } bool APS5_VABI _ZN3sce3Xml3Dom6NodeIdneES2_(const XmlNodeId* self, XmlNodeId other) { return other.first != self->first || other.second != self->second || other.flag != self->flag; } -int APS5_VABI _ZN3sce3Xml3Dom6NodeIdneEi() { - NotImplemented_nid_no_patch(__func__); +bool APS5_VABI _ZN3sce3Xml3Dom6NodeIdneEi(const XmlNodeId* self, int value) { + return value != 0 || !NodeIdIsNull(self); } -int APS5_VABI _ZN3sce3Xml3Dom6NodeIdntEv() { - NotImplemented_nid_no_patch(__func__); +bool APS5_VABI _ZN3sce3Xml3Dom6NodeIdntEv(const XmlNodeId* self) { + return NodeIdIsNull(self); } int APS5_VABI _ZN3sce3Xml3Dom8Document10importNodeENS1_6NodeIdES3_PKS2_S3_() { diff --git a/core/libs/prx/libSceXml/include/Api.hpp b/core/libs/prx/libSceXml/include/Api.hpp index 777a60e2ac..945a1f8adf 100644 --- a/core/libs/prx/libSceXml/include/Api.hpp +++ b/core/libs/prx/libSceXml/include/Api.hpp @@ -96,10 +96,10 @@ void APS5_VABI _ZN3sce3Xml3Dom6NodeIdC2Ev(XmlNodeId* self); XmlNodeId APS5_VABI _ZN3sce3Xml3Dom6NodeIdaSES2_(XmlNodeId* self, XmlNodeId value); void* APS5_VABI _ZN3sce3Xml3Dom6NodeIdcvPvEv(XmlNodeId*); bool APS5_VABI _ZN3sce3Xml3Dom6NodeIdeqES2_(const XmlNodeId* self, XmlNodeId other); -int APS5_VABI _ZN3sce3Xml3Dom6NodeIdeqEi(); +bool APS5_VABI _ZN3sce3Xml3Dom6NodeIdeqEi(const XmlNodeId* self, int value); bool APS5_VABI _ZN3sce3Xml3Dom6NodeIdneES2_(const XmlNodeId* self, XmlNodeId other); -int APS5_VABI _ZN3sce3Xml3Dom6NodeIdneEi(); -int APS5_VABI _ZN3sce3Xml3Dom6NodeIdntEv(); +bool APS5_VABI _ZN3sce3Xml3Dom6NodeIdneEi(const XmlNodeId* self, int value); +bool APS5_VABI _ZN3sce3Xml3Dom6NodeIdntEv(const XmlNodeId* self); int APS5_VABI _ZN3sce3Xml3Dom8Document10importNodeENS1_6NodeIdES3_PKS2_S3_(); int APS5_VABI _ZN3sce3Xml3Dom8Document10initializeEPKNS0_11InitializerE(); int APS5_VABI _ZN3sce3Xml3Dom8Document10insertNodeENS1_6NodeIdES3_S3_(); diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 0c826bc915..45f8564ca7 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -377,6 +377,18 @@ if(BUILD_TESTING) set_tests_properties(guest_sce_v2_exceptions PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_sce_v2_locale_basics_tests + ${LIBS_INCLUDE_DIR}/tests/GuestSceV2LocaleBasics.cpp + ${LIBS_INCLUDE_DIR}/tests/GuestSceV2Exceptions.S) + target_include_directories(guest_sce_v2_locale_basics_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_sce_v2_locale_basics_tests PRIVATE libc) + configure_windows_unwind(guest_sce_v2_locale_basics_tests) + add_test(NAME guest_sce_v2_locale_basics COMMAND guest_sce_v2_locale_basics_tests) + set_tests_properties(guest_sce_v2_locale_basics PROPERTIES TIMEOUT 10) + if(WIN32) + set_tests_properties(guest_sce_v2_locale_basics PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_filesystem_error_tests ${LIBS_INCLUDE_DIR}/tests/GuestFilesystemError.cpp ${LIBS_INCLUDE_DIR}/tests/GuestSceV2Exceptions.S) @@ -1513,7 +1525,11 @@ if(BUILD_TESTING) endif() add_test_executable(guest_freopen_host_standard_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreopenHostStandard.cpp) target_include_directories(guest_freopen_host_standard_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_freopen_host_standard_tests PRIVATE libc) + if(WIN32) + target_link_libraries(guest_freopen_host_standard_tests PRIVATE libkernel libc) + else() + target_link_libraries(guest_freopen_host_standard_tests PRIVATE libc) + endif() configure_windows_unwind(guest_freopen_host_standard_tests) add_test(NAME guest_freopen_host_standard COMMAND guest_freopen_host_standard_tests) set_tests_properties(guest_freopen_host_standard PROPERTIES TIMEOUT 20) diff --git a/core/libs/prx/libc/include/Api.hpp b/core/libs/prx/libc/include/Api.hpp index 929bc87d42..1d9f1f987e 100644 --- a/core/libs/prx/libc/include/Api.hpp +++ b/core/libs/prx/libc/include/Api.hpp @@ -632,6 +632,14 @@ void APS5_VABI _ZNSt8__sce_v28ios_base14_Ios_base_dtorEPS0__nid_postfix(GuestLoc void APS5_VABI _ZNSt8__sce_v215_Facet_RegisterEPNS_11_Facet_baseE_nid_postfix(GuestLocale::Facet* facet); GuestLocale::Implementation* APS5_VABI _ZNSt8__sce_v26locale7_Locimp11_New_LocimpERKS1__nid_postfix(const GuestLocale::Implementation* source); void APS5_VABI _ZNSt8__sce_v26locale7_Locimp14_Locimp_AddfacEPS1_PNS0_5facetEm_nid_postfix(GuestLocale::Implementation* self, GuestLocale::Facet* facet, std::size_t id); +GuestLocaleFacade* APS5_VABI _ZNSt8__sce_v26locale7classicEv_nid_postfix(); +GuestLocaleFacade* APS5_VABI _ZNSt8__sce_v26locale5emptyEv_nid_postfix(GuestLocaleFacade* result); +GuestLocale::Implementation* APS5_VABI _ZNSt8__sce_v26locale7_Locimp11_New_LocimpEb_nid_postfix(bool transparent); +void APS5_VABI _ZNSt8__sce_v26locale16_SetgloballocaleEPv_nid_postfix(void* locale); +GuestLocaleFacade* APS5_VABI _ZNSt8__sce_v26locale6globalERKS0__nid_postfix(GuestLocaleFacade* result, const GuestLocaleFacade* locale); +void APS5_VABI _ZNSt8__sce_v215_sceLibcLocinfoEPKc_nid_postfix(const char* localeName); +void APS5_VABI _ZNSt8__sce_v28_Locinfo13_Locinfo_ctorEPS0_iPKc_nid_postfix(GuestLocale::SceV2Locinfo* self, int category, const char* localeName); +GuestLocale::SceV2Locinfo* APS5_VABI _ZNSt8__sce_v28_Locinfo16_Locinfo_AddcatsEPS0_iPKc_nid_postfix(GuestLocale::SceV2Locinfo* self, int category, const char* localeName); void APS5_VABI _init_env_nid_postfix(); void APS5_VABI init_env_nid_postfix(const InitEnvParams* params); SonyLconv* APS5_VABI localeconv_nid_postfix(void); @@ -1207,7 +1215,7 @@ int APS5_VABI _WStopfx_nid_postfix(const char16_t** cursor, char16_t** end); int APS5_VABI wcscoll_nid_postfix(const char16_t* first, const char16_t* second); size_t APS5_VABI wcsxfrm_nid_postfix(char16_t* destination, const char16_t* source, size_t count); size_t APS5_VABI strxfrm_nid_postfix(char* destination, const char* source, size_t count); -char* APS5_VABI strtok_s_nid_postfix(char* str, const char* delim, char** context); +char* APS5_VABI strtok_s_nid_postfix(char* str, std::size_t* remaining, const char* delimiters, char** context); int APS5_VABI wmemcpy_s_nid_postfix(char16_t* dest, size_t destsz, const char16_t* src, size_t count); int APS5_VABI wmemmove_s_nid_postfix(char16_t* dest, size_t destsz, const char16_t* src, size_t count); int APS5_VABI wcsncpy_s_nid_postfix(char16_t* dest, size_t destsz, const char16_t* src, size_t count); diff --git a/core/libs/prx/libc/include/FileStream.hpp b/core/libs/prx/libc/include/FileStream.hpp index a2f9bedde0..acec4c89ce 100644 --- a/core/libs/prx/libc/include/FileStream.hpp +++ b/core/libs/prx/libc/include/FileStream.hpp @@ -25,8 +25,9 @@ struct UcrtStreamState { int count; long flags; }; -extern "C" void LibcRegisterDirectoryStreams_nid_no_patch(int (*open)(const wchar_t* path), void (*forget)(int descriptor)); +extern "C" void LibcRegisterDirectoryStreams_nid_no_patch(int (*open)(const wchar_t* path), void (*forget)(int descriptor), void (*duplicate)(int from, int to)); extern "C" void ForgetDirectoryStream_nid_no_patch(int descriptor); +extern "C" void DuplicateDirectoryStream_nid_no_patch(int from, int to); #else #include #endif @@ -208,7 +209,8 @@ public: } bool Reopen(const std::filesystem::path& filename, const char* mode, - [[maybe_unused]] std::FILE* (*open)(const std::filesystem::path&, const std::string&) = nullptr) { + [[maybe_unused]] std::FILE* (*open)(const std::filesystem::path&, const std::string&) = nullptr, + [[maybe_unused]] bool directoryStream = false) { if (!_handle) { if (!open || filename.empty()) { errno = ENOENT; @@ -248,9 +250,12 @@ public: std::FILE* const replacement = open(filename, mode); if (replacement) { if (::_wfreopen(L"NUL", resetMode.c_str(), previous) == previous) { - const int moved = ::_dup2(_fileno(replacement), replaced); + const int source = ::_fileno(replacement); + const int moved = ::_dup2(source, replaced); const int error = errno; + if (moved == 0 && directoryStream) DuplicateDirectoryStream_nid_no_patch(source, replaced); std::fclose(replacement); + if (moved == 0 && directoryStream) ForgetDirectoryStream_nid_no_patch(source); if (moved == 0) _handle = previous; else errno = error; } else { diff --git a/core/libs/prx/libc/include/GuestAllocations.hpp b/core/libs/prx/libc/include/GuestAllocations.hpp index 4ea19487df..cb6931bdcb 100644 --- a/core/libs/prx/libc/include/GuestAllocations.hpp +++ b/core/libs/prx/libc/include/GuestAllocations.hpp @@ -75,6 +75,7 @@ Lease GuestAllocationsAcquireMapped_nid_no_patch(const Mapped& mapped); Lease GuestAllocationsAcquireAll_nid_postfix(); Lease GuestAllocationsAcquireRegistered_nid_postfix(); std::shared_ptr GuestAllocationsLookup_nid_postfix(std::uintptr_t address, bool next); +void GuestAllocationsLookupEach_nid_postfix(const std::uintptr_t* addresses, std::size_t count, std::shared_ptr* ranges); // Changes whenever a mutation ends, so callers can cache facts about guest mappings between changes. std::uint64_t GuestAllocationsGeneration_nid_postfix(); // Precise invalidation for such caches: the callback runs, after the generation changed, for every diff --git a/core/libs/prx/libc/include/GuestLocale.hpp b/core/libs/prx/libc/include/GuestLocale.hpp index bd1176e893..f66cbf68dd 100644 --- a/core/libs/prx/libc/include/GuestLocale.hpp +++ b/core/libs/prx/libc/include/GuestLocale.hpp @@ -270,5 +270,11 @@ extern "C" std::uintptr_t _ZTVSt7num_putIcSt19ostreambuf_iteratorIcSt11char_trai inline constexpr std::size_t kCodecvtCharVtableSlots = 13; static_assert(kCodecvtCharVtableSlots * sizeof(std::uintptr_t) == 104); extern "C" std::uintptr_t _ZTVSt7codecvtIcc9_MbstatetE_nid_postfix[kCodecvtCharVtableSlots]; +inline constexpr std::size_t kTimePutCharVtableSlots = 7; +static_assert(kTimePutCharVtableSlots * sizeof(std::uintptr_t) == 56); +extern "C" std::uintptr_t _ZTVSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[kTimePutCharVtableSlots]; +inline constexpr std::size_t kMoneyGetCharVtableSlots = 8; +static_assert(kMoneyGetCharVtableSlots * sizeof(std::uintptr_t) == 64); +extern "C" std::uintptr_t _ZTVSt9money_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[kMoneyGetCharVtableSlots]; #endif diff --git a/core/libs/prx/libc/include/PreciseWait.hpp b/core/libs/prx/libc/include/PreciseWait.hpp index e9f11a91da..1f59838458 100644 --- a/core/libs/prx/libc/include/PreciseWait.hpp +++ b/core/libs/prx/libc/include/PreciseWait.hpp @@ -142,12 +142,28 @@ inline std::uint64_t PreciseMonotonicNanos() { } #ifdef __linux__ -inline bool PreciseLinuxLowTimerSlack() { - static thread_local const bool lowSlack = [] { +inline unsigned long long PreciseLinuxSpinTailNanos_nid_no_patch() { + static thread_local unsigned long long cachedSpinTailNanos = 0; + unsigned long long spinTailNanos = cachedSpinTailNanos; + if (spinTailNanos == 0) [[unlikely]] { const long current = prctl(PR_GET_TIMERSLACK, 0UL, 0UL, 0UL, 0UL); - return current == 1L || prctl(PR_SET_TIMERSLACK, 1UL, 0UL, 0UL, 0UL) == 0; - }(); - return lowSlack; + const bool lowSlack = current == 1L || prctl(PR_SET_TIMERSLACK, 1UL, 0UL, 0UL, 0UL) == 0; + spinTailNanos = lowSlack ? 15000ULL : 100000ULL; + cachedSpinTailNanos = spinTailNanos; + } + return spinTailNanos; +} + +inline bool PreciseLinuxLowTimerSlack() { + return PreciseLinuxSpinTailNanos_nid_no_patch() == 15000ULL; +} + +[[gnu::noinline]] inline void PreciseSleepLinuxTimer_nid_no_patch(std::uint64_t wakeNanos) { + struct timespec req; + req.tv_sec = static_cast(wakeNanos / 1000000000ULL); + req.tv_nsec = static_cast(wakeNanos % 1000000000ULL); + while (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &req, nullptr) == EINTR) { + } } #endif @@ -157,14 +173,10 @@ inline void PreciseSleepFromNanosWithSlack(std::uint64_t startNanos, unsigned lo } const std::uint64_t deadlineNanos = startNanos + nanos; #ifdef __linux__ - const bool lowSlack = allowLowSlack && PreciseLinuxLowTimerSlack(); - const unsigned long long spinTailNanos = lowSlack ? 15000ULL : 100000ULL; - if (nanos > spinTailNanos) [[unlikely]] { - const std::uint64_t wakeNanos = deadlineNanos - spinTailNanos; - struct timespec req; - req.tv_sec = static_cast(wakeNanos / 1000000000ULL); - req.tv_nsec = static_cast(wakeNanos % 1000000000ULL); - while (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &req, nullptr) == EINTR) { + if (nanos > 15000ULL) [[unlikely]] { + const unsigned long long spinTailNanos = allowLowSlack ? PreciseLinuxSpinTailNanos_nid_no_patch() : 100000ULL; + if (nanos > spinTailNanos) { + PreciseSleepLinuxTimer_nid_no_patch(deadlineNanos - spinTailNanos); } } #elif defined(__APPLE__) diff --git a/core/libs/prx/libc/include/SonyCrt.hpp b/core/libs/prx/libc/include/SonyCrt.hpp index 5b3d833dc3..0dd00af973 100644 --- a/core/libs/prx/libc/include/SonyCrt.hpp +++ b/core/libs/prx/libc/include/SonyCrt.hpp @@ -68,23 +68,50 @@ inline void SonyInvokeConstraintHandler(const char* message, void* pointer, int if (handler) handler(message, pointer, error); } -inline char* SonyStrtokS(char* str, const char* delim, char** context) { - if (!delim || !context) return nullptr; - char* cursor = str ? str : *context; - if (!cursor) return nullptr; - cursor += std::strspn(cursor, delim); - if (*cursor == '\0') { - *context = cursor; +inline char* SonyStrtokS(char* str, std::size_t* remaining, const char* delimiters, char** context) { + if (!remaining || !context || !delimiters || *remaining > (SIZE_MAX >> 3) || (!str && !*context)) { + SonyInvokeConstraintHandler("strtok_s: bad argument", nullptr, EINVAL); return nullptr; } - char* token = cursor; - cursor += std::strcspn(cursor, delim); - if (*cursor != '\0') { - *cursor = '\0'; - *context = cursor + 1; - } else { - *context = cursor; + if (!str) str = *context; + const auto isDelimiter = [delimiters](char value) { + for (const char* delimiter = delimiters; *delimiter != '\0'; ++delimiter) + if (*delimiter == value) return true; + return false; + }; + const std::size_t available = *remaining; + std::size_t skipped = 0; + if (available == 0) { + SonyInvokeConstraintHandler("strtok_s: missing token", nullptr, EINVAL); + return nullptr; } + while (str[skipped] != '\0' && isDelimiter(str[skipped])) { + if (++skipped == available) { + SonyInvokeConstraintHandler("strtok_s: missing token", nullptr, EINVAL); + return nullptr; + } + } + char* const token = str + skipped; + const std::size_t left = available - skipped; + *remaining = left; + if (*token == '\0') { + *context = token; + return nullptr; + } + std::size_t length = 0; + while (token[length] != '\0' && !isDelimiter(token[length])) { + if (++length == left) { + SonyInvokeConstraintHandler("strtok_s: missing delimiter", nullptr, EINVAL); + return nullptr; + } + } + char* next = token + length; + *remaining = left - length; + if (*next != '\0') { + *next++ = '\0'; + *remaining = left - length - 1; + } + *context = next; return token; } diff --git a/core/libs/prx/libc/include/WindowsFormatting.hpp b/core/libs/prx/libc/include/WindowsFormatting.hpp index 6e59910a44..849b3faddd 100644 --- a/core/libs/prx/libc/include/WindowsFormatting.hpp +++ b/core/libs/prx/libc/include/WindowsFormatting.hpp @@ -182,7 +182,7 @@ inline std::string ApplicableLength(const std::string& length, char conversion) if (std::strchr("diouxXn", conversion)) return length == "L" ? std::string() : length; if (std::strchr("aAeEfFgG", conversion)) return length == "L" ? length : std::string(); if (conversion == 'c' || conversion == 's') return length == "l" ? length : std::string(); - if (conversion == 'p') return std::string(); + if (conversion == 'p' || conversion == 'S') return std::string(); return length; } @@ -510,7 +510,7 @@ inline std::string FormatPointer(const std::string& spec, const void* pointer) { return std::string(padding, ' ') + digits; } -inline constexpr char KnownConversions[] = "diouxXeEfFgGaAcCsSpn"; +inline constexpr char KnownConversions[] = "diouxXeEfFgGaAcsSpn"; constexpr size_t maximumArgumentPosition = 65535; inline size_t ArgumentPosition(const char*& format) { @@ -526,7 +526,7 @@ inline size_t ArgumentPosition(const char*& format) { return position; } -inline std::vector NumberedArgumentClasses(const char* format) { +inline std::vector NumberedArgumentClasses(const char* format, bool wide = false) { if (!std::strchr(format, '$')) return {}; std::vector classes; bool numbered = false; @@ -557,7 +557,7 @@ inline std::vector NumberedArgumentClasses(const char* format) { if (!conversion) break; ++format; if (std::strchr("aAeEfFgG", conversion)) take(next++, longDouble ? ArgumentClass::LongDouble : ArgumentClass::Double); - else if (std::strchr("diouxXcCsSpn", conversion)) take(next++, ArgumentClass::Integer); + else if (std::strchr(wide ? "diouxXcspn" : "diouxXcsSpn", conversion)) take(next++, ArgumentClass::Integer); } if (!numbered) classes.clear(); return classes; @@ -685,11 +685,10 @@ inline int FormatWindows(char* buffer, size_t size, const char* format, const vo } } else if (conversion == 'c' && (length.empty() || length == "h")) { output.Value(spec + conversion, args.Next()); - } else if ((conversion == 'c' && length == "l") || (conversion == 'C' && length.empty())) { + } else if (conversion == 'c' && length == "l") { std::string utf8; AppendUtf8(utf8, static_cast(args.Next())); - if (utf8[0] == 0) output.Value(spec + 'c', 0); - else output.Value(spec + 's', utf8.c_str()); + if (utf8[0] != 0) output.Value(spec.substr(0, spec.find('.')) + 's', utf8.c_str()); } else if (conversion == 's' && (length.empty() || length == "h")) { const char* value = args.Next(); if (!value && secure) throw FormatConstraintViolation("Null string argument"); diff --git a/core/libs/prx/libc/src/FileAndHeap.cpp b/core/libs/prx/libc/src/FileAndHeap.cpp index bb1792d85b..0ee23a83ad 100644 --- a/core/libs/prx/libc/src/FileAndHeap.cpp +++ b/core/libs/prx/libc/src/FileAndHeap.cpp @@ -501,10 +501,12 @@ void EndClosingDescriptor_nid_no_patch(void* closing) { #ifdef _WIN32 static int (*g_openDirectoryDescriptor)(const wchar_t*) = nullptr; static void (*g_forgetDirectoryDescriptor)(int) = nullptr; +static void (*g_duplicateDirectoryDescriptor)(int, int) = nullptr; -void LibcRegisterDirectoryStreams_nid_no_patch(int (*open)(const wchar_t*), void (*forget)(int)) { +void LibcRegisterDirectoryStreams_nid_no_patch(int (*open)(const wchar_t*), void (*forget)(int), void (*duplicate)(int, int)) { g_openDirectoryDescriptor = open; g_forgetDirectoryDescriptor = forget; + g_duplicateDirectoryDescriptor = duplicate; } void ForgetDirectoryStream_nid_no_patch(int descriptor) { @@ -512,6 +514,11 @@ void ForgetDirectoryStream_nid_no_patch(int descriptor) { g_forgetDirectoryDescriptor(descriptor); } +void DuplicateDirectoryStream_nid_no_patch(int from, int to) { + if (!g_duplicateDirectoryDescriptor) throw std::runtime_error("DuplicateDirectoryStream: libkernel has not registered its directory descriptors"); + g_duplicateDirectoryDescriptor(from, to); +} + static std::FILE* OpenDirectoryHandle(const std::filesystem::path& path, const std::string& = {}) { if (!g_openDirectoryDescriptor) throw std::runtime_error("OpenDirectoryHandle: libkernel has not registered its directory descriptors"); const auto filename = HostOpenName(path); @@ -572,8 +579,7 @@ FileStream* APS5_VABI freopen_nid_postfix(const char* filename, const char* mode } #ifdef _WIN32 if (std::error_code status; !parsed->writes && std::filesystem::is_directory(path, status)) { - if (stream->IsHostStandard()) throw std::runtime_error("freopen: reopening a standard stream onto a directory is not implemented"); - if (!stream->Reopen(path, parsed->native.c_str(), OpenDirectoryHandle)) { + if (!stream->Reopen(path, parsed->native.c_str(), OpenDirectoryHandle, true)) { const int error = errno; if (stream->IsDynamic()) delete stream; errno = error; diff --git a/core/libs/prx/libc/src/FormattingWide.cpp b/core/libs/prx/libc/src/FormattingWide.cpp index 7b5b4a05c0..158bd33878 100644 --- a/core/libs/prx/libc/src/FormattingWide.cpp +++ b/core/libs/prx/libc/src/FormattingWide.cpp @@ -122,7 +122,7 @@ std::u16string FormatWide(const char16_t* format, VaList* source, bool secure = const char16_t* const start = format; std::string ascii; for (const char16_t* unit = format; *unit != 0; ++unit) ascii.push_back(*unit < 0x80 ? static_cast(*unit) : '?'); - args.Number(LibcDetail::NumberedArgumentClasses(ascii.c_str())); + args.Number(LibcDetail::NumberedArgumentClasses(ascii.c_str(), true)); const auto position = [&] { const char* cursor = ascii.c_str() + (format - start); const std::size_t found = LibcDetail::ArgumentPosition(cursor); @@ -230,10 +230,10 @@ std::u16string FormatWide(const char16_t* format, VaList* source, bool secure = } else if (conversion == u'p' && (length.empty() || length == "l")) { const std::string text = LibcDetail::FormatPointer(spec, args.Next()); out.append(text.begin(), text.end()); - } else if (conversion == u'c' || conversion == u'C') { + } else if (conversion == u'c') { const int value = args.Next(); std::u16string text; - if (conversion == u'C' || length == "l") { + if (length == "l") { text.push_back(static_cast(value)); } else { const auto byte = static_cast(value); @@ -241,10 +241,10 @@ std::u16string FormatWide(const char16_t* format, VaList* source, bool secure = text.push_back(static_cast(byte)); } AppendPadded(out, text, left, width); - } else if (conversion == u's' || conversion == u'S') { + } else if (conversion == u's') { std::u16string text; const std::size_t limit = precision < 0 ? SIZE_MAX : static_cast(precision); - if (conversion == u'S' || length == "l") { + if (length == "l") { const char16_t* value = args.Next(); if (value == nullptr && secure) throw std::invalid_argument("Null string argument"); if (value == nullptr) value = u"(null)"; @@ -264,7 +264,7 @@ std::u16string FormatWide(const char16_t* format, VaList* source, bool secure = else if (length == "h") *static_cast(pointer) = static_cast(count); else if (length.empty()) *static_cast(pointer) = count; else *static_cast(pointer) = count; - } else if (conversion < 0x80 && !std::strchr(LibcDetail::KnownConversions, static_cast(conversion))) { + } else if (conversion == u'S' || (conversion < 0x80 && !std::strchr(LibcDetail::KnownConversions, static_cast(conversion)))) { std::u16string text = u"%"; if (conversion != u'%') text.push_back(conversion); AppendPadded(out, text, left, width); diff --git a/core/libs/prx/libc/src/GuestAllocations.cpp b/core/libs/prx/libc/src/GuestAllocations.cpp index cb518ad307..a7fb66830e 100644 --- a/core/libs/prx/libc/src/GuestAllocations.cpp +++ b/core/libs/prx/libc/src/GuestAllocations.cpp @@ -71,6 +71,21 @@ void renewBacking(std::uintptr_t address, std::size_t bytes) { } } +std::shared_ptr lookupLocked(std::uintptr_t address, bool next) { + const auto& ranges = registry().ranges; + auto it = ranges.upper_bound(address); + if (it != ranges.begin()) { + const auto& previous = std::prev(it)->second; + if (previous->bytes != 0 && address < previous->address + previous->bytes) return previous; + } + if (next) { + for (; it != ranges.end(); ++it) { + if (it->second->bytes != 0) return it->second; + } + } + return nullptr; +} + bool isRetiring(const Range& range) { for (const auto& [begin, end] : registry().retiring) { if (range.address < end && begin < range.address + range.bytes) return true; @@ -837,20 +852,14 @@ Lease GuestAllocationsAcquireRegistered_nid_postfix() { return Lease(std::move(ranges)); } +void GuestAllocationsLookupEach_nid_postfix(const std::uintptr_t* addresses, std::size_t count, std::shared_ptr* ranges) { + std::lock_guard lock(registry().mutex); + for (std::size_t index = 0; index < count; ++index) ranges[index] = lookupLocked(addresses[index], false); +} + std::shared_ptr GuestAllocationsLookup_nid_postfix(std::uintptr_t address, bool next) { std::lock_guard lock(registry().mutex); - const auto& ranges = registry().ranges; - auto it = ranges.upper_bound(address); - if (it != ranges.begin()) { - const auto& previous = std::prev(it)->second; - if (previous->bytes != 0 && address < previous->address + previous->bytes) return previous; - } - if (next) { - for (; it != ranges.end(); ++it) { - if (it->second->bytes != 0) return it->second; - } - } - return nullptr; + return lookupLocked(address, next); } Lease GuestAllocationsAcquireAll_nid_postfix() { diff --git a/core/libs/prx/libc/src/LocaleSupport.cpp b/core/libs/prx/libc/src/LocaleSupport.cpp index 1c79844a1c..44a4afad9d 100644 --- a/core/libs/prx/libc/src/LocaleSupport.cpp +++ b/core/libs/prx/libc/src/LocaleSupport.cpp @@ -670,6 +670,8 @@ std::uintptr_t _ZTVSt7collateIcE_nid_postfix[9] { reinterpret_cast(&CollateTransform), reinterpret_cast(&CollateHash), }; +std::uintptr_t _ZTVSt8numpunctIcE_nid_postfix[11] {}; +std::uintptr_t _ZTVSt8messagesIcE_nid_postfix[9] {}; std::uint64_t _ZNSt7codecvtIcc9_MbstatetE2idE_nid_postfix = 0; std::uintptr_t _ZTVSt7codecvtIwc9_MbstatetE_nid_postfix[13] {}; std::uintptr_t _ZTVSt7codecvtIcc9_MbstatetE_nid_postfix[kCodecvtCharVtableSlots] {}; @@ -683,9 +685,11 @@ std::uintptr_t _ZTVSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE_nid std::uint64_t _ZNSt8numpunctIcE2idE_nid_postfix = 0; std::uint64_t _ZNSt8numpunctIwE2idE_nid_postfix = 0; std::uint64_t _ZNSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 0; +std::uintptr_t _ZTVSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[kTimePutCharVtableSlots] {}; std::uintptr_t _ZTVSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[13] {}; std::uintptr_t _ZTVSt8time_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE_nid_postfix[13] {}; std::uintptr_t _ZTVSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE_nid_postfix[17] {}; +std::uintptr_t _ZTVSt9money_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[kMoneyGetCharVtableSlots] {}; std::streamoff _ZSt7_BADOFF_nid_postfix = -1; std::fpos_t _ZSt4_Fpz_nid_postfix {}; @@ -782,6 +786,21 @@ static_assert(sizeof(SceV2StreamNode) == 0x20); static_assert(sizeof(SceV2CallbackNode) == 0x18); std::vector g_sceV2RegisteredFacets; +std::atomic g_sceV2GlobalLocale{&g_classicLocale}; +GuestLocaleFacade g_sceV2ClassicLocaleFacade{&g_classicLocale}; +constexpr std::uint32_t SceV2AllLocaleCategories = 0x3f; +constexpr int SceV2LocaleCategoryCount = 6; + +GuestLocale::Implementation* NewSceV2Locimp(bool transparent) { + auto* self = static_cast(_Znwm_nid_postfix(sizeof(GuestLocale::Implementation))); + *self = {{&g_copiedLocaleVtable, 1, 0}, nullptr, 0, 0, transparent, "*"}; + return self; +} + +void CheckSceV2LocaleName(const char* localeName) { + if (localeName == nullptr || (*localeName != 0 && std::strcmp(localeName, "C") != 0)) + _ZNSt8__sce_v215_Xruntime_errorEPKc_nid_postfix("bad locale name"); +} } @@ -801,12 +820,13 @@ alignas(16) unsigned char _ZNSt8__sce_v24wcinE_nid_postfix[0x400] {}; GuestLocale::Implementation* APS5_VABI _ZNSt8__sce_v26locale5_InitEb_nid_postfix(bool doIncref) { std::lock_guard lock(g_localeInitMutex); - if (doIncref) RetainLocale(&g_classicLocale.base); - return &g_classicLocale; + auto* global = g_sceV2GlobalLocale.load(); + if (doIncref) global->base.vtable->retain(&global->base); + return global; } GuestLocale::Implementation* APS5_VABI _ZNSt8__sce_v26locale16_GetgloballocaleEv_nid_postfix() { - return _ZNSt8__sce_v221_sceLibcClassicLocaleE_nid_postfix; + return g_sceV2GlobalLocale.load(); } void APS5_VABI _ZNSt8__sce_v28ios_base7_AddstdEPS0__nid_postfix(GuestLocale::SceV2IosBase* self) { @@ -871,6 +891,66 @@ void APS5_VABI _ZNSt8__sce_v26locale7_Locimp14_Locimp_AddfacEPS1_PNS0_5facetEm_n _ZNSt6locale7_Locimp7_AddfacEPNS_5facetEm_nid_postfix(self, facet, id); } +GuestLocale::Implementation* _ZNSt8__sce_v26locale7_Locimp8_ClocptrE_nid_postfix = &g_classicLocale; + +GuestLocaleFacade* APS5_VABI _ZNSt8__sce_v26locale7classicEv_nid_postfix() { + return &g_sceV2ClassicLocaleFacade; +} + +GuestLocaleFacade* APS5_VABI _ZNSt8__sce_v26locale5emptyEv_nid_postfix(GuestLocaleFacade* result) { + if (result == nullptr) throw std::invalid_argument("locale::empty: null result"); + result->implementation = NewSceV2Locimp(true); + return result; +} + +GuestLocale::Implementation* APS5_VABI _ZNSt8__sce_v26locale7_Locimp11_New_LocimpEb_nid_postfix(bool transparent) { + return NewSceV2Locimp(transparent); +} + +void APS5_VABI _ZNSt8__sce_v26locale16_SetgloballocaleEPv_nid_postfix(void* locale) { + if (locale == nullptr) throw std::invalid_argument("locale::_Setgloballocale: null locale"); + g_sceV2GlobalLocale.store(static_cast(locale)); +} + +GuestLocaleFacade* APS5_VABI _ZNSt8__sce_v26locale6globalERKS0__nid_postfix(GuestLocaleFacade* result, const GuestLocaleFacade* locale) { + if (result == nullptr || locale == nullptr || locale->implementation == nullptr) throw std::invalid_argument("locale::global: null locale"); + result->implementation = _ZNSt8__sce_v26locale5_InitEb_nid_postfix(true); + std::lock_guard lock(g_localeInitMutex); + auto* current = g_sceV2GlobalLocale.load(); + auto* next = locale->implementation; + if (current == next) return result; + if (auto* released = current->base.vtable->release(¤t->base)) released->vtable->deleteObject(released); + _ZNSt8__sce_v26locale16_SetgloballocaleEPv_nid_postfix(next); + next->base.vtable->retain(&next->base); + if ((next->category & SceV2AllLocaleCategories) == SceV2AllLocaleCategories) { + setlocale_nid_postfix(SceV2AllLocaleCategories, next->name); + return result; + } + for (int category = 0; category < SceV2LocaleCategoryCount; ++category) { + if ((next->category & (1u << category)) != 0) setlocale_nid_postfix(category + 1, next->name); + } + return result; +} + +void APS5_VABI _ZNSt8__sce_v215_sceLibcLocinfoEPKc_nid_postfix(const char* localeName) { + CheckSceV2LocaleName(localeName); +} + +void APS5_VABI _ZNSt8__sce_v28_Locinfo13_Locinfo_ctorEPS0_iPKc_nid_postfix(GuestLocale::SceV2Locinfo* self, int, const char* localeName) { + if (self == nullptr) throw std::invalid_argument("_Locinfo: null object"); + const char* current = setlocale_nid_postfix(SceV2AllLocaleCategories, nullptr); + self->name = current != nullptr ? current : ""; + CheckSceV2LocaleName(localeName); + self->classicName = setlocale_nid_postfix(SceV2AllLocaleCategories, nullptr); +} + +GuestLocale::SceV2Locinfo* APS5_VABI _ZNSt8__sce_v28_Locinfo16_Locinfo_AddcatsEPS0_iPKc_nid_postfix(GuestLocale::SceV2Locinfo* self, int, const char* localeName) { + CheckSceV2LocaleName(localeName); + if (self == nullptr) throw std::invalid_argument("_Locinfo::_Addcats: null object"); + self->classicName = setlocale_nid_postfix(SceV2AllLocaleCategories, nullptr); + return self; +} + namespace { GuestLocaleFacade g_classicLocaleFacade{&g_classicLocale}; diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index 6cdc25ebbc..bd7b048aa9 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -481,6 +481,18 @@ std::int64_t APS5_VABI imaxabs_nid_postfix(std::int64_t x) { } float APS5_VABI fmodf_nid_postfix(float x, float y) { + const std::uint32_t ix = std::bit_cast(x) & 0x7fffffffu; + const std::uint32_t iy = std::bit_cast(y) & 0x7fffffffu; + if (iy == 0 || ix >= 0x7f800000u || iy > 0x7f800000u) { + _Feraise_nid_postfix(0x01); + if (iy > 0x7f800000u) { + return y + y; + } + if (ix > 0x7f800000u) { + return x + x; + } + return (x * y) / (x * y); + } const int saved_errno = errno; const float result = std::fmod(x, y); errno = saved_errno; @@ -541,7 +553,85 @@ float APS5_VABI acosf_nid_postfix(float x) { const float correction = (z - high * high) / (s + high); return 2.0f * (high + (AcosfRational(z) * s + correction)); } -float APS5_VABI hypotf_nid_postfix(float x, float y) { return static_cast(std::hypot(static_cast(x), static_cast(y))); } +/* Adapted from FreeBSD release/11.4.0 lib/msun/src/e_hypotf.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_hypotf.c */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunPro, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ +float APS5_VABI hypotf_nid_postfix(float x, float y) { + const auto word = [](float value) { return static_cast(std::bit_cast(value)); }; + const auto fromWord = [](std::int32_t value) { return std::bit_cast(static_cast(value)); }; + std::int32_t ha = word(x) & 0x7fffffff; + std::int32_t hb = word(y) & 0x7fffffff; + float a = x; + float b = y; + if (hb > ha) { + std::swap(a, b); + std::swap(ha, hb); + } + a = std::fabs(a); + b = std::fabs(b); + if (ha - hb > 0xf000000) { + return a + b; + } + int k = 0; + if (ha > 0x58800000) { + if (ha >= 0x7f800000) { + float w = std::fabs(x + 0.0f) - std::fabs(y + 0.0f); + if (ha == 0x7f800000) { + w = a; + } + if (hb == 0x7f800000) { + w = b; + } + return w; + } + ha -= 0x22000000; + hb -= 0x22000000; + k += 68; + a = fromWord(ha); + b = fromWord(hb); + } + if (hb < 0x26800000) { + if (hb <= 0x007fffff) { + if (hb == 0) { + return a; + } + b *= 0x1p126f; + a *= 0x1p126f; + k -= 126; + } else { + ha += 0x22000000; + hb += 0x22000000; + k -= 68; + a = fromWord(ha); + b = fromWord(hb); + } + } + float w = a - b; + if (w > b) { + const float t1 = fromWord(ha & static_cast(0xfffff000)); + const float t2 = a - t1; + w = std::sqrt(t1 * t1 - (b * (-b) - t2 * (a + t1))); + } else { + a = a + a; + const float y1 = fromWord(hb & static_cast(0xfffff000)); + const float y2 = b - y1; + const float t1 = fromWord((ha + 0x00800000) & static_cast(0xfffff000)); + const float t2 = a - t1; + w = std::sqrt(t1 * y1 - (w * (-w) - (t1 * y2 + t2 * b))); + } + if (k != 0) { + return fromWord((127 + k) << 23) * w; + } + return w; +} /* Adapted from FreeBSD release/11.4.0 lib/msun/src/e_hypot.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_hypot.c */ /* * ==================================================== @@ -1160,8 +1250,93 @@ double APS5_VABI atan_nid_postfix(double x) { z = atanhi[id] - ((x * (s1 + s2) - atanlo[id]) - x); return hx < 0 ? -z : z; } -double APS5_VABI tan_nid_postfix(double x) { return std::tan(x); } -double APS5_VABI log_nid_postfix(double x) { return std::log(x); } +/* Adapted from FreeBSD release/11.4.0 lib/msun/src/e_log.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_log.c */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ +double APS5_VABI log_nid_postfix(double x) { + const double ln2_hi = 6.93147180369123816490e-01; + const double ln2_lo = 1.90821492927058770002e-10; + const double two54 = 1.80143985094819840000e+16; + const double Lg1 = 6.666666666666735130e-01; + const double Lg2 = 3.999999999940941908e-01; + const double Lg3 = 2.857142874366239149e-01; + const double Lg4 = 2.222219843214978396e-01; + const double Lg5 = 1.818357216161805012e-01; + const double Lg6 = 1.531383769920937332e-01; + const double Lg7 = 1.479819860511658591e-01; + static volatile double vzero = 0.0; + std::uint64_t bits = std::bit_cast(x); + std::int32_t hx = static_cast(bits >> 32); + const std::uint32_t lx = static_cast(bits); + std::int32_t k = 0; + if (hx < 0x00100000) { + if (((hx & 0x7fffffff) | lx) == 0) { + errno = ERANGE; + return -two54 / vzero; + } + if (hx < 0) { + _Feraise_nid_postfix(0x01); + return (x - x) / 0.0; + } + k -= 54; + x *= two54; + bits = std::bit_cast(x); + hx = static_cast(bits >> 32); + } + if (hx >= 0x7ff00000) { + return x + x; + } + k += (hx >> 20) - 1023; + hx &= 0x000fffff; + std::int32_t i = (hx + 0x95f64) & 0x100000; + x = std::bit_cast((static_cast(static_cast(hx | (i ^ 0x3ff00000))) << 32) | (bits & 0xffffffffu)); + k += i >> 20; + const double f = x - 1.0; + if ((0x000fffff & (2 + hx)) < 3) { + if (f == 0.0) { + if (k == 0) { + return 0.0; + } + const double dk = k; + return dk * ln2_hi + dk * ln2_lo; + } + const double R = f * f * (0.5 - 0.33333333333333333 * f); + if (k == 0) { + return f - R; + } + const double dk = k; + return dk * ln2_hi - ((R - dk * ln2_lo) - f); + } + const double s = f / (2.0 + f); + const double dk = k; + const double z = s * s; + i = hx - 0x6147a; + const double w = z * z; + const std::int32_t j = 0x6b851 - hx; + const double t1 = w * (Lg2 + w * (Lg4 + w * Lg6)); + const double t2 = z * (Lg1 + w * (Lg3 + w * (Lg5 + w * Lg7))); + i |= j; + const double R = t2 + t1; + if (i > 0) { + const double hfsq = 0.5 * f * f; + if (k == 0) { + return f - (hfsq - s * (hfsq + R)); + } + return dk * ln2_hi - ((hfsq - (s * (hfsq + R) + dk * ln2_lo)) - f); + } + if (k == 0) { + return f - s * (f - R); + } + return dk * ln2_hi - ((s * (f - R) - dk * ln2_lo) - f); +} double APS5_VABI log1p_nid_postfix(double x) { const double ln2_hi = 6.93147180369123816490e-01; const double ln2_lo = 1.90821492927058770002e-10; @@ -1444,6 +1619,138 @@ double APS5_VABI lgamma_nid_postfix(double x) { return std::lgamma(x); } +static const std::int32_t RemPio2Ipio2[] = { + 0xA2F983, 0x6E4E44, 0x1529FC, 0x2757D1, 0xF534DD, 0xC0DB62, 0x95993C, 0x439041, 0xFE5163, 0xABDEBB, 0xC561B7, 0x246E3A, + 0x424DD2, 0xE00649, 0x2EEA09, 0xD1921C, 0xFE1DEB, 0x1CB129, 0xA73EE8, 0x8235F5, 0x2EBB44, 0x84E99C, 0x7026B4, 0x5F7E41, + 0x3991D6, 0x398353, 0x39F49C, 0x845F8B, 0xBDF928, 0x3B1FF8, 0x97FFDE, 0x05980F, 0xEF2F11, 0x8B5A0A, 0x6D1F6D, 0x367ECF, + 0x27CB09, 0xB74F46, 0x3F669E, 0x5FEA2D, 0x7527BA, 0xC7EBE5, 0xF17B3D, 0x0739F7, 0x8A5292, 0xEA6BFB, 0x5FB11F, 0x8D5D08, + 0x560330, 0x46FC7B, 0x6BABF0, 0xCFBC20, 0x9AF436, 0x1DA9E3, 0x91615E, 0xE61B08, 0x659985, 0x5F14A0, 0x68408D, 0xFFD880, + 0x4D7327, 0x310606, 0x1556CA, 0x73A8C9, 0x60E27B, 0xC08C6B, +}; + +static const double RemPio2Parts[] = { + 1.57079625129699707031e+00, 7.54978941586159635335e-08, 5.39030252995776476554e-15, 3.28200341580791294123e-22, + 1.27065575308067607349e-29, 1.22933308981111328932e-36, 2.73370053816464559624e-44, 2.16741683877804819444e-51, +}; + +static int KernelRemPio2(const double* x, double* y, int e0, int nx, int prec) { + const double two24 = 1.67772160000000000000e+07, twon24 = 5.96046447753906250000e-08; + std::int32_t jz, jv, jp, jk, carry, n, iq[20], i, j, k, m, q0, ih; + double z, fw, f[20], fq[20], q[20]; + const std::int32_t jx = nx - 1; + jk = prec == 0 ? 3 : 4; + jp = jk; + jv = (e0 - 3) / 24; + if (jv < 0) jv = 0; + q0 = e0 - 24 * (jv + 1); + j = jv - jx; + m = jx + jk; + for (i = 0; i <= m; i++, j++) f[i] = j < 0 ? 0.0 : static_cast(RemPio2Ipio2[j]); + for (i = 0; i <= jk; i++) { + for (j = 0, fw = 0.0; j <= jx; j++) fw += x[j] * f[jx + i - j]; + q[i] = fw; + } + jz = jk; +recompute: + for (i = 0, j = jz, z = q[jz]; j > 0; i++, j--) { + fw = static_cast(static_cast(twon24 * z)); + iq[i] = static_cast(z - two24 * fw); + z = q[j - 1] + fw; + } + z = std::scalbn(z, q0); + z -= 8.0 * std::floor(z * 0.125); + n = static_cast(z); + z -= static_cast(n); + ih = 0; + if (q0 > 0) { + i = iq[jz - 1] >> (24 - q0); + n += i; + iq[jz - 1] -= i << (24 - q0); + ih = iq[jz - 1] >> (23 - q0); + } else if (q0 == 0) { + ih = iq[jz - 1] >> 23; + } else if (z >= 0.5) { + ih = 2; + } + if (ih > 0) { + n += 1; + carry = 0; + for (i = 0; i < jz; i++) { + j = iq[i]; + if (carry == 0) { + if (j != 0) { + carry = 1; + iq[i] = 0x1000000 - j; + } + } else { + iq[i] = 0xffffff - j; + } + } + if (q0 > 0) { + switch (q0) { + case 1: iq[jz - 1] &= 0x7fffff; break; + case 2: iq[jz - 1] &= 0x3fffff; break; + } + } + if (ih == 2) { + z = 1.0 - z; + if (carry != 0) z -= std::scalbn(1.0, q0); + } + } + if (z == 0.0) { + j = 0; + for (i = jz - 1; i >= jk; i--) j |= iq[i]; + if (j == 0) { + for (k = 1; iq[jk - k] == 0; k++) { + } + for (i = jz + 1; i <= jz + k; i++) { + f[jx + i] = static_cast(RemPio2Ipio2[jv + i]); + for (j = 0, fw = 0.0; j <= jx; j++) fw += x[j] * f[jx + i - j]; + q[i] = fw; + } + jz += k; + goto recompute; + } + } + if (z == 0.0) { + jz -= 1; + q0 -= 24; + while (iq[jz] == 0) { + jz--; + q0 -= 24; + } + } else { + z = std::scalbn(z, -q0); + if (z >= two24) { + fw = static_cast(static_cast(twon24 * z)); + iq[jz] = static_cast(z - two24 * fw); + jz += 1; + q0 += 24; + iq[jz] = static_cast(fw); + } else { + iq[jz] = static_cast(z); + } + } + fw = std::scalbn(1.0, q0); + for (i = jz; i >= 0; i--) { + q[i] = fw * static_cast(iq[i]); + fw *= twon24; + } + for (i = jz; i >= 0; i--) { + for (fw = 0.0, k = 0; k <= jp && k <= jz - i; k++) fw += RemPio2Parts[k] * q[i + k]; + fq[jz - i] = fw; + } + fw = 0.0; + for (i = jz; i >= 0; i--) fw += fq[i]; + y[0] = ih == 0 ? fw : -fw; + if (prec != 0) { + fw = fq[0] - fw; + for (i = 1; i <= jz; i++) fw += fq[i]; + y[1] = ih == 0 ? fw : -fw; + } + return n & 7; +} + static const std::int32_t TrigfIpio2[] = { 0xA2F983, 0x6E4E44, 0x1529FC, 0x2757D1, 0xF534DD, 0xC0DB62, 0x95993C, 0x439041, 0xFE5163, 0xABDEBB, 0xC561B7, 0x246E3A, 0x424DD2, 0xE00649, 0x2EEA09, 0xD1921C, 0xFE1DEB, 0x1CB129, 0xA73EE8, 0x8235F5, 0x2EBB44, 0x84E99C, 0x7026B4, 0x5F7E41, @@ -1722,8 +2029,298 @@ void APS5_VABI sincosf_nid_postfix(float x, float* sinp, float* cosp) { *cosp = cosf_nid_postfix(x); } -double APS5_VABI sin_nid_postfix(double x) { return std::sin(x); } -double APS5_VABI cos_nid_postfix(double x) { return std::cos(x); } +static std::int32_t HighWord(double x) { + return static_cast(std::bit_cast(x) >> 32); +} + +static double ClearLowWord(double x) { + return std::bit_cast(std::bit_cast(x) & 0xffffffff00000000ull); +} + +/* Adapted from FreeBSD release/11.4.0 lib/msun/src/e_rem_pio2.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_rem_pio2.c */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + * + * Optimized by Bruce D. Evans. + */ +static int RemPio2Near(double x, double* y, std::int32_t hx, int k) { + const double pio2_1 = 1.57079632673412561417e+00; + const double pio2_1t = 6.07710050650619224932e-11; + if (hx > 0) { + const double z = x - k * pio2_1; + y[0] = z - k * pio2_1t; + y[1] = (z - y[0]) - k * pio2_1t; + return k; + } + const double z = x + k * pio2_1; + y[0] = z + k * pio2_1t; + y[1] = (z - y[0]) + k * pio2_1t; + return -k; +} + +static int RemPio2(double x, double* y) { + const double two24 = 1.67772160000000000000e+07; + const double invpio2 = 6.36619772367581382433e-01; + const double pio2_1 = 1.57079632673412561417e+00; + const double pio2_1t = 6.07710050650619224932e-11; + const double pio2_2 = 6.07710050630396597660e-11; + const double pio2_2t = 2.02226624879595063154e-21; + const double pio2_3 = 2.02226624871116645580e-21; + const double pio2_3t = 8.47842766036889956997e-32; + const std::int32_t hx = HighWord(x); + const std::int32_t ix = hx & 0x7fffffff; + bool medium = false; + if (ix <= 0x400f6a7a) { + if ((ix & 0xfffff) != 0x921fb) { + return RemPio2Near(x, y, hx, ix <= 0x4002d97c ? 1 : 2); + } + medium = true; + } else if (ix <= 0x401c463b) { + if (ix <= 0x4015fdbc) { + if (ix != 0x4012d97c) { + return RemPio2Near(x, y, hx, 3); + } + } else if (ix != 0x401921fb) { + return RemPio2Near(x, y, hx, 4); + } + medium = true; + } + if (medium || ix < 0x413921fb) { + double fn = x * invpio2 + 0x1.8p52; + fn = fn - 0x1.8p52; + const std::int32_t n = static_cast(fn); + double r = x - fn * pio2_1; + double w = fn * pio2_1t; + const std::int32_t j = ix >> 20; + y[0] = r - w; + std::int32_t i = j - ((HighWord(y[0]) >> 20) & 0x7ff); + if (i > 16) { + double t = r; + w = fn * pio2_2; + r = t - w; + w = fn * pio2_2t - ((t - r) - w); + y[0] = r - w; + i = j - ((HighWord(y[0]) >> 20) & 0x7ff); + if (i > 49) { + t = r; + w = fn * pio2_3; + r = t - w; + w = fn * pio2_3t - ((t - r) - w); + y[0] = r - w; + } + } + y[1] = (r - y[0]) - w; + return n; + } + if (ix >= 0x7ff00000) { + y[0] = y[1] = x - x; + return 0; + } + const std::uint32_t low = static_cast(std::bit_cast(x)); + const std::int32_t e0 = (ix >> 20) - 1046; + double z = std::bit_cast((static_cast(static_cast(ix - (e0 << 20))) << 32) | low); + double tx[3]; + double ty[2]; + for (int i = 0; i < 2; i++) { + tx[i] = static_cast(static_cast(z)); + z = (z - tx[i]) * two24; + } + tx[2] = z; + int nx = 3; + while (tx[nx - 1] == 0.0) nx--; + const int n = KernelRemPio2(tx, ty, e0, nx, 1); + if (hx < 0) { + y[0] = -ty[0]; + y[1] = -ty[1]; + return -n; + } + y[0] = ty[0]; + y[1] = ty[1]; + return n; +} + +/* Adapted from FreeBSD release/11.4.0 lib/msun/src/k_sin.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/k_sin.c */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ +static double KernelSin(double x, double y, int iy) { + const double S1 = -1.66666666666666324348e-01; + const double S2 = 8.33333333332248946124e-03; + const double S3 = -1.98412698298579493134e-04; + const double S4 = 2.75573137070700676789e-06; + const double S5 = -2.50507602534068634195e-08; + const double S6 = 1.58969099521155010221e-10; + const double z = x * x; + const double w = z * z; + const double r = S2 + z * (S3 + z * S4) + z * w * (S5 + z * S6); + const double v = z * x; + if (iy == 0) { + return x + v * (S1 + z * r); + } + return x - ((z * (0.5 * y - v * r) - y) - v * S1); +} + +/* Adapted from FreeBSD release/11.4.0 lib/msun/src/k_cos.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/k_cos.c */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ +static double KernelCos(double x, double y) { + const double C1 = 4.16666666666666019037e-02; + const double C2 = -1.38888888888741095749e-03; + const double C3 = 2.48015872894767294178e-05; + const double C4 = -2.75573143513906633035e-07; + const double C5 = 2.08757232129817482790e-09; + const double C6 = -1.13596475577881948265e-11; + const double z = x * x; + double w = z * z; + const double r = z * (C1 + z * (C2 + z * C3)) + w * w * (C4 + z * (C5 + z * C6)); + const double hz = 0.5 * z; + w = 1.0 - hz; + return w + (((1.0 - w) - hz) + (z * r - x * y)); +} + +/* Adapted from FreeBSD release/11.4.0 lib/msun/src/k_tan.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/k_tan.c */ +/* + * ==================================================== + * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved. + * + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ +static double KernelTan(double x, double y, int iy) { + static constexpr double T[] = { + 3.33333333333334091986e-01, 1.33333333333201242699e-01, 5.39682539762260521377e-02, 2.18694882948595424599e-02, + 8.86323982359930005737e-03, 3.59207910759131235356e-03, 1.45620945432529025516e-03, 5.88041240820264096874e-04, + 2.46463134818469906812e-04, 7.81794442939557092300e-05, 7.14072491382608190305e-05, -1.85586374855275456654e-05, + 2.59073051863633712884e-05, + }; + const double pio4 = 7.85398163397448278999e-01; + const double pio4lo = 3.06161699786838301793e-17; + const std::int32_t hx = HighWord(x); + const std::int32_t ix = hx & 0x7fffffff; + if (ix >= 0x3fe59428) { + if (hx < 0) { + x = -x; + y = -y; + } + const double z = pio4 - x; + const double w = pio4lo - y; + x = z + w; + y = 0.0; + } + const double z = x * x; + double w = z * z; + double r = T[1] + w * (T[3] + w * (T[5] + w * (T[7] + w * (T[9] + w * T[11])))); + double v = z * (T[2] + w * (T[4] + w * (T[6] + w * (T[8] + w * (T[10] + w * T[12]))))); + double s = z * x; + r = y + z * (s * (r + v) + y); + r += T[0] * s; + w = x + r; + if (ix >= 0x3fe59428) { + v = static_cast(iy); + return static_cast(1 - ((hx >> 30) & 2)) * (v - 2.0 * (x - (w * w / (w + v) - r))); + } + if (iy == 1) { + return w; + } + const double head = ClearLowWord(w); + v = r - (head - x); + const double a = -1.0 / w; + const double t = ClearLowWord(a); + s = 1.0 + t * head; + return t + a * (s + t * v); +} + +/* Adapted from FreeBSD release/11.4.0 lib/msun/src/s_sin.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_sin.c */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunPro, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ +double APS5_VABI sin_nid_postfix(double x) { + const std::int32_t ix = HighWord(x) & 0x7fffffff; + if (ix <= 0x3fe921fb) { + if (ix < 0x3e500000 && static_cast(x) == 0) { + return x; + } + return KernelSin(x, 0.0, 0); + } + if (ix >= 0x7ff00000) { + if (ix == 0x7ff00000) { + errno = EDOM; + } + return x - x; + } + double y[2]; + switch (RemPio2(x, y) & 3) { + case 0: return KernelSin(y[0], y[1], 1); + case 1: return KernelCos(y[0], y[1]); + case 2: return -KernelSin(y[0], y[1], 1); + default: return -KernelCos(y[0], y[1]); + } +} + +/* Adapted from FreeBSD release/11.4.0 lib/msun/src/s_cos.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_cos.c */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunPro, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ +double APS5_VABI cos_nid_postfix(double x) { + const std::int32_t ix = HighWord(x) & 0x7fffffff; + if (ix <= 0x3fe921fb) { + if (ix < 0x3e46a09e && static_cast(x) == 0) { + return 1.0; + } + return KernelCos(x, 0.0); + } + if (ix >= 0x7ff00000) { + if (ix == 0x7ff00000) { + errno = EDOM; + } + return x - x; + } + double y[2]; + switch (RemPio2(x, y) & 3) { + case 0: return KernelCos(y[0], y[1]); + case 1: return -KernelSin(y[0], y[1], 1); + case 2: return -KernelCos(y[0], y[1]); + default: return KernelSin(y[0], y[1], 1); + } +} /*- * Copyright (c) 2011 David Schultz * All rights reserved. @@ -1772,6 +2369,36 @@ static double SinhLdexpExpHalf(double x) { * ==================================================== */ /* Adapted from FreeBSD release/11.4.0 lib/msun/src/e_sinh.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_sinh.c */ + +/* Adapted from FreeBSD release/11.4.0 lib/msun/src/s_tan.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_tan.c */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunPro, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ +double APS5_VABI tan_nid_postfix(double x) { + const std::int32_t ix = HighWord(x) & 0x7fffffff; + if (ix <= 0x3fe921fb) { + if (ix < 0x3e400000 && static_cast(x) == 0) { + return x; + } + return KernelTan(x, 0.0, 1); + } + if (ix >= 0x7ff00000) { + if (ix == 0x7ff00000) { + errno = EDOM; + } + return x - x; + } + double y[2]; + const int n = RemPio2(x, y); + return KernelTan(y[0], y[1], 1 - ((n & 1) << 1)); +} double APS5_VABI sinh_nid_postfix(double x) { const double one = 1.0; const double shuge = 1.0e307; @@ -1924,7 +2551,48 @@ float APS5_VABI acoshf_nid_postfix(float x) { const float t = x - 1.0f; return AtanhfLog1p(t + std::sqrt(2.0f * t + t * t)); } -double APS5_VABI atanh_nid_postfix(double x) { return std::atanh(x); } +/* Adapted from FreeBSD release/11.4.0 lib/msun/src/e_atanh.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_atanh.c */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + * + */ +double APS5_VABI atanh_nid_postfix(double x) { + const double huge = 1e300; + static volatile double zero = 0.0; + const std::uint64_t bits = std::bit_cast(x); + const std::int32_t hx = static_cast(bits >> 32); + const std::uint32_t lx = static_cast(bits); + const std::int32_t ix = hx & 0x7fffffff; + if ((static_cast(ix) | ((lx | (0u - lx)) >> 31)) > 0x3ff00000u) { + _Feraise_nid_postfix(0x01); + errno = EDOM; + return (x - x) / (x - x); + } + if (ix == 0x3ff00000) { + _Feraise_nid_postfix(0x04); + errno = ERANGE; + return x / zero; + } + if (ix < 0x3e300000 && huge + x > 0.0) { + return x; + } + x = std::bit_cast((static_cast(static_cast(ix)) << 32) | lx); + double t; + if (ix < 0x3fe00000) { + t = x + x; + t = 0.5 * log1p_nid_postfix(t + t * x / (1.0 - x)); + } else { + t = 0.5 * log1p_nid_postfix((x + x) / (1.0 - x)); + } + return hx >= 0 ? t : -t; +} float APS5_VABI atanhf_nid_postfix(float x) { const std::uint32_t magnitude = std::bit_cast(x) & 0x7fffffffu; @@ -2737,9 +3405,32 @@ float APS5_VABI ldexpf_nid_postfix(float x, int exp) { errno = saved_errno; return result; } +/* Adapted from FreeBSD release/11.4.0 lib/msun/src/e_fmod.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_fmod.c */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ double APS5_VABI fmod_nid_postfix(double x, double y) { - int saved_errno = errno; - double result = std::fmod(x, y); + const std::uint64_t ix = std::bit_cast(x) & 0x7fffffffffffffffull; + const std::uint64_t iy = std::bit_cast(y) & 0x7fffffffffffffffull; + if (iy == 0 || ix >= 0x7ff0000000000000ull || iy > 0x7ff0000000000000ull) { + _Feraise_nid_postfix(0x01); + if (iy > 0x7ff0000000000000ull) { + return y + y; + } + if (ix > 0x7ff0000000000000ull) { + return x + x; + } + return (x * y) / (x * y); + } + const int saved_errno = errno; + const double result = std::fmod(x, y); errno = saved_errno; return result; } diff --git a/core/libs/prx/libc/src/Strings.cpp b/core/libs/prx/libc/src/Strings.cpp index b3e0a5ec5a..e2234f64ea 100644 --- a/core/libs/prx/libc/src/Strings.cpp +++ b/core/libs/prx/libc/src/Strings.cpp @@ -964,8 +964,8 @@ size_t APS5_VABI strxfrm_nid_postfix(char* destination, const char* source, size return length; } -char* APS5_VABI strtok_s_nid_postfix(char* str, const char* delim, char** context) { - return SonyStrtokS(str, delim, context); +char* APS5_VABI strtok_s_nid_postfix(char* str, std::size_t* remaining, const char* delimiters, char** context) { + return SonyStrtokS(str, remaining, delimiters, context); } int APS5_VABI wmemcpy_s_nid_postfix(char16_t* dest, size_t destsz, const char16_t* src, size_t count) { diff --git a/core/libs/prx/libcairo/CMakeLists.txt b/core/libs/prx/libcairo/CMakeLists.txt index f2cbd123cc..ce0285bbd0 100644 --- a/core/libs/prx/libcairo/CMakeLists.txt +++ b/core/libs/prx/libcairo/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libcairo SHARED EXCLUDE_FROM_ALL Export.cpp Matrix.cpp Surface.cpp) +add_library(libcairo SHARED EXCLUDE_FROM_ALL Export.cpp Matrix.cpp Surface.cpp FontOptions.cpp) target_include_directories(libcairo PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(libcairo) @@ -23,4 +23,14 @@ if(BUILD_TESTING) set_tests_properties(guest_cairo_surface PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_cairo_font_options_tests ${LIBS_INCLUDE_DIR}/tests/GuestCairoFontOptions.cpp) + target_include_directories(guest_cairo_font_options_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_cairo_font_options_tests PRIVATE libcairo libc) + configure_windows_unwind(guest_cairo_font_options_tests) + add_test(NAME guest_cairo_font_options COMMAND guest_cairo_font_options_tests) + set_tests_properties(guest_cairo_font_options PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_cairo_font_options PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libcairo/Export.cpp b/core/libs/prx/libcairo/Export.cpp index e0de462395..538f6ab7e0 100644 --- a/core/libs/prx/libcairo/Export.cpp +++ b/core/libs/prx/libcairo/Export.cpp @@ -12,10 +12,6 @@ int APS5_VABI cairo_paint() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI cairo_font_options_set_hint_metrics() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI cairo_set_font_options() { NotImplemented_nid_no_patch(__func__); } @@ -180,10 +176,6 @@ int APS5_VABI cairo_region_get_rectangle() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI cairo_font_options_set_hint_style() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI cairo_set_miter_limit() { NotImplemented_nid_no_patch(__func__); } @@ -244,10 +236,6 @@ int APS5_VABI cairo_mesh_pattern_set_corner_color_rgba() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI cairo_font_options_copy() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI cairo_identity_matrix() { NotImplemented_nid_no_patch(__func__); } @@ -280,10 +268,6 @@ int APS5_VABI cairo_ft_scaled_font_unlock_face() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI cairo_font_options_get_antialias() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI cairo_mesh_pattern_move_to() { NotImplemented_nid_no_patch(__func__); } @@ -408,26 +392,14 @@ int APS5_VABI cairo_push_group() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI cairo_font_options_destroy() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI cairo_ft_font_options_substitute() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI cairo_font_options_create() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI cairo_device_to_user() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI cairo_font_options_set_antialias() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI cairo_glyph_free() { NotImplemented_nid_no_patch(__func__); } @@ -464,10 +436,6 @@ int APS5_VABI cairo_scaled_font_glyph_extents() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI cairo_font_options_set_subpixel_order() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI cairo_show_text() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libcairo/FontOptions.cpp b/core/libs/prx/libcairo/FontOptions.cpp new file mode 100644 index 0000000000..825808f949 --- /dev/null +++ b/core/libs/prx/libcairo/FontOptions.cpp @@ -0,0 +1,71 @@ +#include "prx/libcairo/include/InventoryExports.hpp" +#include + +namespace { + +struct CairoFontOptionsStorage { + cairo_antialias_t antialias; + cairo_subpixel_order_t subpixel_order; + cairo_hint_style_t hint_style; + cairo_hint_metrics_t hint_metrics; +}; + +const CairoFontOptionsStorage nilOptions{}; + +CairoFontOptionsStorage* Storage(cairo_font_options_t* options) { + return reinterpret_cast(options); +} + +const CairoFontOptionsStorage* Storage(const cairo_font_options_t* options) { + return reinterpret_cast(options); +} + +bool Invalid(const cairo_font_options_t* options) { + return options == nullptr || Storage(options) == &nilOptions; +} + +cairo_font_options_t* Nil() { + return reinterpret_cast(const_cast(&nilOptions)); +} + +} + +extern "C" { + +cairo_font_options_t* APS5_VABI cairo_font_options_create() { + auto* options = std::calloc(1, sizeof(CairoFontOptionsStorage)); + return options != nullptr ? static_cast(options) : Nil(); +} + +cairo_font_options_t* APS5_VABI cairo_font_options_copy(const cairo_font_options_t* original) { + if (Invalid(original)) return Nil(); + auto* options = cairo_font_options_create(); + if (options != Nil()) *Storage(options) = *Storage(original); + return options; +} + +void APS5_VABI cairo_font_options_destroy(cairo_font_options_t* options) { + if (!Invalid(options)) std::free(options); +} + +void APS5_VABI cairo_font_options_set_antialias(cairo_font_options_t* options, cairo_antialias_t antialias) { + if (!Invalid(options)) Storage(options)->antialias = antialias; +} + +cairo_antialias_t APS5_VABI cairo_font_options_get_antialias(const cairo_font_options_t* options) { + return Invalid(options) ? CAIRO_ANTIALIAS_DEFAULT : Storage(options)->antialias; +} + +void APS5_VABI cairo_font_options_set_hint_style(cairo_font_options_t* options, cairo_hint_style_t hint_style) { + if (!Invalid(options)) Storage(options)->hint_style = hint_style; +} + +void APS5_VABI cairo_font_options_set_hint_metrics(cairo_font_options_t* options, cairo_hint_metrics_t hint_metrics) { + if (!Invalid(options)) Storage(options)->hint_metrics = hint_metrics; +} + +void APS5_VABI cairo_font_options_set_subpixel_order(cairo_font_options_t* options, cairo_subpixel_order_t subpixel_order) { + if (!Invalid(options)) Storage(options)->subpixel_order = subpixel_order; +} + +} diff --git a/core/libs/prx/libcairo/include/InventoryExports.hpp b/core/libs/prx/libcairo/include/InventoryExports.hpp index 5492916b18..565051ab86 100644 --- a/core/libs/prx/libcairo/include/InventoryExports.hpp +++ b/core/libs/prx/libcairo/include/InventoryExports.hpp @@ -2,11 +2,44 @@ #include "SceTypes.hpp" +struct cairo_font_options_t; + +enum cairo_antialias_t : int { + CAIRO_ANTIALIAS_DEFAULT, + CAIRO_ANTIALIAS_NONE, + CAIRO_ANTIALIAS_GRAY, + CAIRO_ANTIALIAS_SUBPIXEL, + CAIRO_ANTIALIAS_FAST, + CAIRO_ANTIALIAS_GOOD, + CAIRO_ANTIALIAS_BEST, +}; + +enum cairo_subpixel_order_t : int { + CAIRO_SUBPIXEL_ORDER_DEFAULT, + CAIRO_SUBPIXEL_ORDER_RGB, + CAIRO_SUBPIXEL_ORDER_BGR, + CAIRO_SUBPIXEL_ORDER_VRGB, + CAIRO_SUBPIXEL_ORDER_VBGR, +}; + +enum cairo_hint_style_t : int { + CAIRO_HINT_STYLE_DEFAULT, + CAIRO_HINT_STYLE_NONE, + CAIRO_HINT_STYLE_SLIGHT, + CAIRO_HINT_STYLE_MEDIUM, + CAIRO_HINT_STYLE_FULL, +}; + +enum cairo_hint_metrics_t : int { + CAIRO_HINT_METRICS_DEFAULT, + CAIRO_HINT_METRICS_OFF, + CAIRO_HINT_METRICS_ON, +}; + extern "C" { int APS5_VABI cairo_scaled_font_get_font_face(); int APS5_VABI cairo_paint(); -int APS5_VABI cairo_font_options_set_hint_metrics(); int APS5_VABI cairo_set_font_options(); int APS5_VABI cairo_device_to_user_distance(); int APS5_VABI cairo_font_face_set_user_data(); @@ -48,7 +81,6 @@ int APS5_VABI cairo_scaled_font_get_font_matrix(); int APS5_VABI cairo_mesh_pattern_line_to(); int APS5_VABI cairo_get_line_cap(); int APS5_VABI cairo_region_get_rectangle(); -int APS5_VABI cairo_font_options_set_hint_style(); int APS5_VABI cairo_set_miter_limit(); int APS5_VABI cairo_translate(); int APS5_VABI cairo_font_face_destroy(); @@ -64,7 +96,6 @@ int APS5_VABI cairo_transform(); int APS5_VABI cairo_set_line_join(); int APS5_VABI cairo_pop_group_to_source(); int APS5_VABI cairo_mesh_pattern_set_corner_color_rgba(); -int APS5_VABI cairo_font_options_copy(); int APS5_VABI cairo_identity_matrix(); int APS5_VABI cairo_save(); int APS5_VABI cairo_clip(); @@ -73,7 +104,6 @@ int APS5_VABI cairo_arc(); int APS5_VABI cairo_stroke_preserve(); int APS5_VABI cairo_path_extents(); int APS5_VABI cairo_ft_scaled_font_unlock_face(); -int APS5_VABI cairo_font_options_get_antialias(); int APS5_VABI cairo_mesh_pattern_move_to(); int APS5_VABI cairo_paint_with_alpha(); int APS5_VABI cairo_set_fill_rule(); @@ -105,11 +135,8 @@ int APS5_VABI cairo_set_dash(); int APS5_VABI cairo_pattern_set_filter(); int APS5_VABI cairo_new_path(); int APS5_VABI cairo_push_group(); -int APS5_VABI cairo_font_options_destroy(); int APS5_VABI cairo_ft_font_options_substitute(); -int APS5_VABI cairo_font_options_create(); int APS5_VABI cairo_device_to_user(); -int APS5_VABI cairo_font_options_set_antialias(); int APS5_VABI cairo_glyph_free(); int APS5_VABI cairo_select_font_face(); int APS5_VABI cairo_line_to(); @@ -119,7 +146,6 @@ int APS5_VABI cairo_font_face_reference(); int APS5_VABI cairo_set_line_cap(); int APS5_VABI cairo_scaled_font_text_to_glyphs(); int APS5_VABI cairo_scaled_font_glyph_extents(); -int APS5_VABI cairo_font_options_set_subpixel_order(); int APS5_VABI cairo_show_text(); int APS5_VABI cairo_set_font_face(); int APS5_VABI cairo_close_path(); @@ -137,4 +163,13 @@ int APS5_VABI cairo_pattern_add_color_stop_rgba(); int APS5_VABI cairo_region_num_rectangles(); int APS5_VABI cairo_set_line_width(); +cairo_font_options_t* APS5_VABI cairo_font_options_create(); +cairo_font_options_t* APS5_VABI cairo_font_options_copy(const cairo_font_options_t* original); +void APS5_VABI cairo_font_options_destroy(cairo_font_options_t* options); +void APS5_VABI cairo_font_options_set_antialias(cairo_font_options_t* options, cairo_antialias_t antialias); +cairo_antialias_t APS5_VABI cairo_font_options_get_antialias(const cairo_font_options_t* options); +void APS5_VABI cairo_font_options_set_hint_style(cairo_font_options_t* options, cairo_hint_style_t hint_style); +void APS5_VABI cairo_font_options_set_hint_metrics(cairo_font_options_t* options, cairo_hint_metrics_t hint_metrics); +void APS5_VABI cairo_font_options_set_subpixel_order(cairo_font_options_t* options, cairo_subpixel_order_t subpixel_order); + } diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 8b32c6ddb4..bea23f2242 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -74,7 +74,7 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/System/src/HostPerformance.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/HostPerformanceProcess.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/HostPerformanceStartup.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/System/src/MimallocMacOs.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/System/src/MimallocRestart.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/SystemConfiguration.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/Login.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/Getlogin.cpp @@ -154,7 +154,7 @@ if(BUILD_TESTING) endforeach() add_library(guest_exec_host SHARED ${LIBS_INCLUDE_DIR}/tests/GuestExecHost.cpp) target_include_directories(guest_exec_host PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_exec_host PRIVATE libkernel libc libSceAgcDriver) + target_link_libraries(guest_exec_host PRIVATE libkernel libkernel_sys libc libSceAgcDriver) set_target_properties(guest_exec_host PROPERTIES PREFIX "" SUFFIX ".prx" BUILD_RPATH "$ORIGIN") configure_windows_unwind(guest_exec_host) find_package(Python3 COMPONENTS Interpreter QUIET) @@ -162,9 +162,17 @@ if(BUILD_TESTING) add_test(NAME exec_arguments COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/core/relinker/relinker/tests/test_exec_arguments.py $ $ - $ $ $ ${CMAKE_CXX_COMPILER}) + $ $ $ $ ${CMAKE_CXX_COMPILER}) set_tests_properties(exec_arguments PROPERTIES TIMEOUT 30) add_asan_test_options(exec_arguments verify_asan_link_order=0) + foreach(mode IN ITEMS ve vpe) + add_test(NAME exec_sys_${mode}_arguments COMMAND ${Python3_EXECUTABLE} + ${CMAKE_SOURCE_DIR}/core/relinker/relinker/tests/test_exec_arguments.py + $ $ + $ $ $ $ ${CMAKE_CXX_COMPILER}) + set_tests_properties(exec_sys_${mode}_arguments PROPERTIES TIMEOUT 30 ENVIRONMENT "ANYPS5_KERNEL_SYS_EXEC_MODE=${mode}") + add_asan_test_options(exec_sys_${mode}_arguments verify_asan_link_order=0) + endforeach() endif() if(NOT WIN32) add_library(guest_memory_lifetime_module MODULE ${LIBS_INCLUDE_DIR}/tests/GuestMemoryLifetimeModule.cpp) @@ -579,6 +587,14 @@ if(BUILD_TESTING) file(MAKE_DIRECTORY ${sandboxCertificatesRun}) add_test(NAME guest_sandbox_certificates COMMAND guest_sandbox_certificates_tests WORKING_DIRECTORY ${sandboxCertificatesRun}) set_tests_properties(guest_sandbox_certificates PROPERTIES TIMEOUT 20) + add_test_executable(guest_sandbox_certificate_failure_tests ${LIBS_INCLUDE_DIR}/tests/GuestSandboxCertificateFailure.cpp) + target_include_directories(guest_sandbox_certificate_failure_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_sandbox_certificate_failure_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_sandbox_certificate_failure_tests) + set(sandboxCertificateFailureRun ${CMAKE_CURRENT_BINARY_DIR}/guest_sandbox_certificate_failure) + file(MAKE_DIRECTORY ${sandboxCertificateFailureRun}) + add_test(NAME guest_sandbox_certificate_failure COMMAND guest_sandbox_certificate_failure_tests WORKING_DIRECTORY ${sandboxCertificateFailureRun}) + set_tests_properties(guest_sandbox_certificate_failure PROPERTIES TIMEOUT 20) if(WIN32) add_test_executable(guest_sandbox_certificate_trust_tests ${LIBS_INCLUDE_DIR}/tests/GuestSandboxCertificateTrust.cpp) target_include_directories(guest_sandbox_certificate_trust_tests PRIVATE ${LIBS_INCLUDE_DIR}) @@ -587,7 +603,7 @@ if(BUILD_TESTING) add_test(NAME guest_sandbox_certificate_trust COMMAND guest_sandbox_certificate_trust_tests) set_tests_properties(guest_sandbox_certificate_trust PROPERTIES TIMEOUT 10 ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") - set_tests_properties(guest_sandbox_certificates PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(guest_sandbox_certificates guest_sandbox_certificate_failure PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() add_test_executable(guest_kernel_rename_tests ${LIBS_INCLUDE_DIR}/tests/GuestKernelRename.cpp) @@ -1800,17 +1816,21 @@ if(BUILD_TESTING) set_tests_properties(guest_system_configuration PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() - if(APPLE AND TARGET mimalloc) + if(NOT WIN32 AND TARGET mimalloc) add_test_executable(mimalloc_restart_tests ${LIBS_INCLUDE_DIR}/tests/MimallocRestart.cpp) target_link_libraries(mimalloc_restart_tests PRIVATE libkernel libc) add_custom_command(TARGET mimalloc_restart_tests POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy $ $/libmimalloc.dylib) + COMMAND ${CMAKE_COMMAND} -E copy $ $/${CMAKE_SHARED_LIBRARY_PREFIX}mimalloc${CMAKE_SHARED_LIBRARY_SUFFIX}) add_test(NAME mimalloc_restart_off COMMAND mimalloc_restart_tests off) set_tests_properties(mimalloc_restart_off PROPERTIES ENVIRONMENT_MODIFICATION "APS5_MIMALLOC=unset:") add_test(NAME mimalloc_restart_on COMMAND mimalloc_restart_tests on) set_tests_properties(mimalloc_restart_on PROPERTIES ENVIRONMENT "APS5_MIMALLOC=1") add_test(NAME mimalloc_restart_once COMMAND mimalloc_restart_tests once) set_tests_properties(mimalloc_restart_once PROPERTIES ENVIRONMENT "APS5_MIMALLOC=1;APS5_MIMALLOC_RESTARTED=1") + add_test(NAME mimalloc_restart_log COMMAND mimalloc_restart_tests log) + set_tests_properties(mimalloc_restart_log PROPERTIES + ENVIRONMENT "APS5_MIMALLOC=1;ANYPS5_LOG_FILE=${CMAKE_CURRENT_BINARY_DIR}/mimalloc_restart_log.txt" + PASS_REGULAR_EXPRESSION "mimalloc_restart_tests: the restarted process writes to its standard output") endif() add_test_executable(guest_software_version_tests ${LIBS_INCLUDE_DIR}/tests/GuestSoftwareVersion.cpp) target_include_directories(guest_software_version_tests PRIVATE ${LIBS_INCLUDE_DIR}) diff --git a/core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp b/core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp index bb392c8d70..1a096f69b4 100644 --- a/core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp +++ b/core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp @@ -260,8 +260,8 @@ int OpenStreamDirectory(const wchar_t* path) { } struct StreamRegistration { - StreamRegistration() { LibcRegisterDirectoryStreams_nid_no_patch(&OpenStreamDirectory, &File::ForgetDirectoryDescriptor); } - ~StreamRegistration() { LibcRegisterDirectoryStreams_nid_no_patch(nullptr, nullptr); } + StreamRegistration() { LibcRegisterDirectoryStreams_nid_no_patch(&OpenStreamDirectory, &File::ForgetDirectoryDescriptor, &File::DuplicateDirectoryDescriptor); } + ~StreamRegistration() { LibcRegisterDirectoryStreams_nid_no_patch(nullptr, nullptr, nullptr); } StreamRegistration(const StreamRegistration&) = delete; StreamRegistration& operator=(const StreamRegistration&) = delete; }; diff --git a/core/libs/prx/libkernel/File/src/SandboxCertificates.cpp b/core/libs/prx/libkernel/File/src/SandboxCertificates.cpp index 14437699aa..31a1de835e 100644 --- a/core/libs/prx/libkernel/File/src/SandboxCertificates.cpp +++ b/core/libs/prx/libkernel/File/src/SandboxCertificates.cpp @@ -5,9 +5,8 @@ #include #include #include -#include +#include #include -#include #include #include #include @@ -54,10 +53,14 @@ std::string CertificateBlocksOnly(const std::string& bundle) { } std::optional ReadFile(const std::filesystem::path& path) { + std::error_code error; + if (!std::filesystem::is_regular_file(path, error)) return std::nullopt; std::ifstream file(path, std::ios::binary); if (!file) return std::nullopt; - std::string text((std::istreambuf_iterator(file)), std::istreambuf_iterator()); - if (file.bad()) throw std::runtime_error("Reading " + path.string() + " failed"); + std::string text; + char chunk[4096]; + while (file.read(chunk, sizeof(chunk)) || file.gcount() > 0) text.append(chunk, static_cast(file.gcount())); + if (file.bad()) return std::nullopt; return text; } @@ -86,7 +89,9 @@ std::filesystem::path WriteCertificateList(const std::filesystem::path& root) { const auto bundle = CertificateBlocksOnly(HostCertificates()); if (bundle.empty()) return {}; const auto directory = root / "cert_cache"; - std::filesystem::create_directories(directory); + std::error_code error; + std::filesystem::create_directories(directory, error); + if (error) return {}; const auto target = directory / "CA_LIST.cer"; if (ReadFile(target) == bundle) return target; auto temporary = target; @@ -94,18 +99,33 @@ std::filesystem::path WriteCertificateList(const std::filesystem::path& root) { { std::ofstream file(temporary, std::ios::binary | std::ios::trunc); file.write(bundle.data(), static_cast(bundle.size())); - if (!file.flush()) throw std::runtime_error("Writing " + temporary.string() + " failed"); + if (!file.flush()) { + file.close(); + std::filesystem::remove(temporary, error); + return {}; + } } using std::filesystem::perms; - std::filesystem::permissions(temporary, perms::owner_read | perms::group_read | perms::others_read); - std::error_code error; - if (std::filesystem::exists(target, error)) std::filesystem::permissions(target, perms::owner_write, std::filesystem::perm_options::add); - std::filesystem::rename(temporary, target); + std::filesystem::permissions(temporary, perms::owner_read | perms::group_read | perms::others_read, error); + if (error) { + std::filesystem::remove(temporary, error); + return {}; + } + if (std::filesystem::exists(target, error)) std::filesystem::permissions(target, perms::owner_write, std::filesystem::perm_options::add, error); + std::filesystem::rename(temporary, target, error); + if (error) { + std::filesystem::permissions(temporary, perms::owner_write, std::filesystem::perm_options::add, error); + std::filesystem::remove(temporary, error); + return ReadFile(target) == bundle ? target : std::filesystem::path{}; + } return target; } std::filesystem::path CertificateList(const std::filesystem::path& root) { - static const std::filesystem::path list = WriteCertificateList(root); + static std::mutex lock; + static std::filesystem::path list; + std::lock_guard guard(lock); + if (list.empty()) list = WriteCertificateList(root); return list; } diff --git a/core/libs/prx/libkernel/Pthread/src/MainThreadMacOs.cpp b/core/libs/prx/libkernel/Pthread/src/MainThreadMacOs.cpp index f1ae7e7fdf..a968fa655f 100644 --- a/core/libs/prx/libkernel/Pthread/src/MainThreadMacOs.cpp +++ b/core/libs/prx/libkernel/Pthread/src/MainThreadMacOs.cpp @@ -35,6 +35,7 @@ constexpr std::size_t MovedStackBytes = std::size_t {16} << 20; std::atomic mainLoopRunning {false}; GuestStart guestStart; HostStack oldStack; +std::uintptr_t keptLow = 0; std::size_t guestStackBytes = 0; void* RunGuest(void* argument) { @@ -49,11 +50,14 @@ void KeepAlive(CFRunLoopTimerRef, void*) {} [[noreturn]] void ServeMainQueue(void*) { if (oldStack.high != oldStack.low) { - if (mach_vm_deallocate(mach_task_self(), oldStack.low, oldStack.high - oldStack.low) != KERN_SUCCESS) { - std::fprintf(stderr, "cannot release the host stack\n"); - std::abort(); + const std::uintptr_t end = keptLow > oldStack.low && keptLow < oldStack.high ? keptLow : oldStack.high; + if (end > oldStack.low) { + if (mach_vm_deallocate(mach_task_self(), oldStack.low, end - oldStack.low) != KERN_SUCCESS) { + std::fprintf(stderr, "cannot release the host stack\n"); + std::abort(); + } + GuestArena::GuestArenaReleaseHostRegion_nid_no_patch(reinterpret_cast(oldStack.low), end - oldStack.low); } - GuestArena::GuestArenaReleaseHostRegion_nid_no_patch(reinterpret_cast(oldStack.low), oldStack.high - oldStack.low); } pthread_attr_t attributes; if (pthread_attr_init(&attributes) != 0) std::abort(); @@ -124,6 +128,13 @@ void* CopyBlock(void* block) { return copy; } +std::uintptr_t ProcessStartBlockLow() { + const auto argv = reinterpret_cast(*_NSGetArgv()); + if (argv <= oldStack.low || argv >= oldStack.high) return 0; + const auto page = static_cast(getpagesize()); + return (argv - sizeof(char*)) & ~(page - 1); +} + [[noreturn]] void RunOnStack(void* top, void (*function)(void*)) { asm volatile("mov %0, %%rsp\n\txor %%edi, %%edi\n\tcall *%1\n\tud2" : : "r"(top), "r"(function) : "rdi", "memory"); __builtin_unreachable(); @@ -146,6 +157,7 @@ void MoveHostStack() { oldStack = {}; return; } + keptLow = ProcessStartBlockLow(); guestStart.block = CopyBlock(guestStart.block); *_NSGetEnviron() = CopyStrings(*_NSGetEnviron()); *_NSGetArgv() = CopyStrings(*_NSGetArgv()); diff --git a/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp b/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp index 6d816ac6f5..f5e50cf55c 100644 --- a/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp +++ b/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp @@ -22,6 +22,7 @@ using Closer = bool (*)(int descriptor, int* error); using OptionSetter = bool (*)(int descriptor, int level, int option, const void* value, std::uint32_t length, int* error); using OptionGetter = bool (*)(int descriptor, int level, int option, void* value, std::uint32_t* length, int* error); using StatusControl = bool (*)(int descriptor, int command, int flags, int* result, int* error); +using IoctlControl = bool (*)(int descriptor, std::uint32_t request, void* argument, int* error); struct Forwards { int (*family)(int descriptor); int (*bind)(int descriptor, const void* address, std::uint32_t length); @@ -54,6 +55,8 @@ extern "C" void KernelSetSocketOptionGetter_nid_no_patch(KernelSocketPoll::Optio extern "C" bool KernelGetSocketOption_nid_no_patch(int descriptor, int level, int option, void* value, std::uint32_t* length, int* error); extern "C" void KernelSetSocketStatusControl_nid_no_patch(KernelSocketPoll::StatusControl control); extern "C" bool KernelControlSocketStatus_nid_no_patch(int descriptor, int command, int flags, int* result, int* error); +extern "C" void KernelSetSocketIoctlControl_nid_no_patch(KernelSocketPoll::IoctlControl control); +extern "C" bool KernelControlSocketIoctl_nid_no_patch(int descriptor, std::uint32_t request, void* argument, int* error); extern "C" void KernelSetSocketNativeLookup_nid_no_patch(KernelSocketPoll::NativeLookup lookup); extern "C" bool KernelFindSocketNative_nid_no_patch(int descriptor, std::shared_ptr* owner, std::uintptr_t* native); extern "C" bool KernelLookupSocketNative_nid_no_patch(int descriptor, std::shared_ptr* owner, std::uintptr_t* native); diff --git a/core/libs/prx/libkernel/Socket/src/Socket.cpp b/core/libs/prx/libkernel/Socket/src/Socket.cpp index b8f8ab3a47..30c0f421a1 100644 --- a/core/libs/prx/libkernel/Socket/src/Socket.cpp +++ b/core/libs/prx/libkernel/Socket/src/Socket.cpp @@ -42,6 +42,7 @@ std::atomic g_socketCloser{nullptr}; std::atomic g_socketOptionSetter{nullptr}; std::atomic g_socketOptionGetter{nullptr}; std::atomic g_socketStatusControl{nullptr}; +std::atomic g_socketIoctlControl{nullptr}; std::atomic g_socketNativeLookup{nullptr}; bool DescriptorSet(const void* set, int descriptor) { @@ -190,6 +191,15 @@ bool KernelControlSocketStatus_nid_no_patch(int descriptor, int command, int fla return control != nullptr && control(descriptor, command, flags, result, error); } +void KernelSetSocketIoctlControl_nid_no_patch(KernelSocketPoll::IoctlControl control) { + g_socketIoctlControl.store(control); +} + +bool KernelControlSocketIoctl_nid_no_patch(int descriptor, std::uint32_t request, void* argument, int* error) { + const auto control = g_socketIoctlControl.load(); + return control != nullptr && control(descriptor, request, argument, error); +} + void KernelSetSocketNativeLookup_nid_no_patch(KernelSocketPoll::NativeLookup lookup) { g_socketNativeLookup.store(lookup); } diff --git a/core/libs/prx/libkernel/Socket/src/Udp.cpp b/core/libs/prx/libkernel/Socket/src/Udp.cpp index 11d3088b20..52d0288eca 100644 --- a/core/libs/prx/libkernel/Socket/src/Udp.cpp +++ b/core/libs/prx/libkernel/Socket/src/Udp.cpp @@ -686,6 +686,7 @@ struct PollFlag { }; constexpr short GuestPollPriority = 0x2; constexpr short GuestPollInvalid = 0x20; +constexpr int SocketCloseCheckMilliseconds = 10; constexpr short GuestPollAccepted = 0x1 | 0x2 | 0x4 | 0x8 | 0x10 | 0x20 | 0x40 | 0x80 | 0x100; #ifdef _WIN32 constexpr short NativePollIn = POLLRDNORM; @@ -944,7 +945,15 @@ int SocketSendFlags(Socket& socket, int flags) { std::lock_guard lock(socket.modeMutex); return NativeSendFlags(socket.options.noSignal ? flags | GuestNoSignal : flags); } -int WaitForTransfer(Socket& socket, bool send, int flags) { +#ifdef _WIN32 +bool ReceiveWouldBlock(const Socket& socket) { + if (socket.type == 1) return PeerConnected(socket); + sockaddr_storage name{}; + socklen_t size = sizeof(name); + return ::getsockname(socket.value, reinterpret_cast(&name), &size) == 0; +} +#endif +int WaitForTransfer(Socket& socket, bool send, int flags, int receiver = -1) { if ((flags & GuestDontWait) != 0) { #ifdef _WIN32 NativePollDescriptor entry{}; @@ -961,22 +970,38 @@ int WaitForTransfer(Socket& socket, bool send, int flags) { if (socket.options.nonblocking) return 0; timeout = send ? socket.options.sendTimeout : socket.options.receiveTimeout; } - if (timeout == 0) return 0; +#ifdef _WIN32 + const bool watched = receiver >= 0 && ReceiveWouldBlock(socket); +#else + static_cast(receiver); + constexpr bool watched = false; +#endif + if (timeout == 0 && !watched) return 0; + const ReadWatch watch(watched ? std::vector{receiver} : std::vector{}); const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(timeout >> 32) + std::chrono::nanoseconds((static_cast(static_cast(timeout)) * 1000000000) >> 32); for (;;) { - const auto left = deadline - std::chrono::steady_clock::now(); - if (left <= std::chrono::steady_clock::duration::zero()) return 35; - const int milliseconds = static_cast(std::min(std::chrono::ceil(left).count(), INT_MAX)); - NativePollDescriptor entry{}; - entry.fd = socket.value; + if (watched && ReceiveShut(socket)) return 0; + int milliseconds = -1; + if (timeout != 0) { + const auto left = deadline - std::chrono::steady_clock::now(); + if (left <= std::chrono::steady_clock::duration::zero()) return 35; + milliseconds = static_cast(std::min(std::chrono::ceil(left).count(), INT_MAX)); + } + NativePollDescriptor entries[2]{}; + entries[0].fd = socket.value; #ifdef _WIN32 - entry.events = static_cast(send ? POLLWRNORM : POLLRDNORM); - const int ready = WSAPoll(&entry, 1, milliseconds); - if (ready != 0) return 0; + entries[0].events = static_cast(send ? POLLWRNORM : POLLRDNORM); + if (watched) { + entries[1].fd = ThreadWaker().value; + entries[1].events = POLLRDNORM; + } + const int ready = WSAPoll(entries, watched ? 2 : 1, milliseconds); + if (ready < 0 || entries[0].revents != 0) return 0; + if (entries[1].revents != 0) DrainWaker(entries[1].fd); #else - entry.events = static_cast(send ? POLLOUT : POLLIN); - const int ready = ::poll(&entry, 1, milliseconds); + entries[0].events = static_cast(send ? POLLOUT : POLLIN); + const int ready = ::poll(entries, 1, milliseconds); if (ready > 0 || (ready < 0 && errno != EINTR)) return 0; #endif } @@ -1259,12 +1284,28 @@ int GuestSockets::Poll(KernelSocketPoll::Entry* entries, int count, int timeoutM } for (int index = 0; index < count; ++index) if (shutReader(index)) timeoutMilliseconds = 0; + const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeoutMilliseconds < 0 ? 0 : timeoutMilliseconds); + int result = 0; + for (;;) { + for (std::size_t index = 0; index < held.size(); ++index) { + if (Find(entries[index].descriptor) != held[index]) return -9; + } + int slice = SocketCloseCheckMilliseconds; + if (timeoutMilliseconds >= 0) { + const auto left = std::chrono::duration_cast(deadline - std::chrono::steady_clock::now()).count(); + slice = static_cast(std::clamp(left, 0, SocketCloseCheckMilliseconds)); + } #ifdef _WIN32 - const int result = WSAPoll(native.data(), static_cast(native.size()), timeoutMilliseconds); + result = WSAPoll(native.data(), static_cast(native.size()), slice); #else - const int result = ::poll(native.data(), static_cast(native.size()), timeoutMilliseconds); + result = ::poll(native.data(), static_cast(native.size()), slice); #endif - if (result < 0) return -NativeError(); + if (result < 0) return -NativeError(); + for (std::size_t index = 0; index < held.size(); ++index) { + if (Find(entries[index].descriptor) != held[index]) return -9; + } + if (result != 0 || (timeoutMilliseconds >= 0 && std::chrono::steady_clock::now() >= deadline)) break; + } if (!readers.empty() && native[static_cast(count)].revents != 0) DrainWaker(native[static_cast(count)].fd); int ready = 0; for (int index = 0; index < count; ++index) { @@ -1621,7 +1662,8 @@ std::int64_t APS5_VABI recv_nid_postfix(int descriptor, void* buffer, std::uint6 const GuestArena::HostWrite destination(length == 0 ? nullptr : buffer, static_cast(length)); if (!destination.Open()) return Fail(14); if (const int answered = ReceiveAnsweredHere(*socket, length); answered >= 0) return answered == 0 ? 0 : Fail(answered); - if (const int waited = WaitForTransfer(*socket, false, flags)) return Fail(waited); + if (const int waited = WaitForTransfer(*socket, false, flags, descriptor)) return Fail(waited); + if (ReceiveShut(*socket)) return 0; const auto result = ::recv(socket->value, static_cast(buffer), static_cast(length), NativeReceiveFlags(flags, socket->type)); #ifdef _WIN32 @@ -1678,9 +1720,9 @@ int APS5_VABI shutdown_nid_postfix(int descriptor, int how) { sockaddr_storage peer{}; socklen_t size = sizeof(peer); const bool unconnected = socket->type == 2 && ::getpeername(socket->value, reinterpret_cast(&peer), &size) != 0; - if (::shutdown(socket->value, how) && !unconnected && !AlreadyShut(socket->value, how)) return Fail(NativeError()); { std::lock_guard lock(socket->modeMutex); + if (::shutdown(socket->value, how) && !unconnected && !AlreadyShut(socket->value, how)) return Fail(NativeError()); if (how != 1) socket->receiveShut = true; if (how != 0) socket->sendShut = true; } @@ -1741,7 +1783,19 @@ int APS5_VABI getpeername_nid_postfix(int descriptor, void* address, std::uint32 return CopyAddressOut(native, false, address, length, capacity); } int APS5_VABI ioctl_nid_postfix(int descriptor, std::uint64_t request, void* argument) { - if (!GuestSockets::IsOpen(descriptor)) return File::Ioctl(descriptor, request, argument); + if (!GuestSockets::IsOpen(descriptor)) { + const auto command = static_cast(request); + std::shared_ptr owner; + std::uintptr_t native = 0; + if (command == 0x20006601 || command == 0x20006602 || !KernelFindSocketNative_nid_no_patch(descriptor, &owner, &native)) { + return File::Ioctl(descriptor, request, argument); + } + if (File::IoctlRequestMalformed(request)) return Fail(25); + if (!argument && (request & 0x1fff0000 || !(request & 0x20000000))) return Fail(14); + int error = 0; + if (!KernelControlSocketIoctl_nid_no_patch(descriptor, command, argument, &error)) return Fail(9); + return error == 0 ? 0 : Fail(error); + } if (File::IoctlRequestMalformed(request)) return Fail(25); const auto socket = Lookup(descriptor); if (!socket) return -1; @@ -1861,7 +1915,11 @@ std::int64_t APS5_VABI recvfrom_nid_postfix(int descriptor, void* buffer, std::u } sockaddr_storage native{}; socklen_t size = sizeof(native); - if (const int waited = WaitForTransfer(*socket, false, flags)) return Fail(waited); + if (const int waited = WaitForTransfer(*socket, false, flags, descriptor)) return Fail(waited); + if (ReceiveShut(*socket)) { + if (addressLength && !ReportAddress(address, addressLength, nullptr, address ? 0 : capacity)) return Fail(14); + return 0; + } std::uint64_t limit = length; #ifdef _WIN32 if (length != 0 && (limit = GuestPrefix(buffer, length, WritableProtection)) < length) { @@ -2187,11 +2245,12 @@ std::int64_t APS5_VABI recvmsg_nid_postfix(int descriptor, GuestMsghdr* message, ReceivedRights rights; bool controlTruncated = false; #endif - const bool shut = ReceiveShut(*socket); + bool shut = ReceiveShut(*socket); std::vector stamps; if (!shut && (total != 0 || socket->type == 1 || ReadableNow(*socket))) { size = sizeof(native); - if (const int waited = WaitForTransfer(*socket, false, flags)) return Fail(waited); + if (const int waited = WaitForTransfer(*socket, false, flags, descriptor)) return Fail(waited); + shut = ReceiveShut(*socket); #ifndef _WIN32 if (passing) { std::vector control(CMSG_SPACE(MostPassedDescriptors * sizeof(int))); @@ -2227,7 +2286,7 @@ std::int64_t APS5_VABI recvmsg_nid_postfix(int descriptor, GuestMsghdr* message, if (const int error = rights.Install((flags & GuestControlCloseOnExec) != 0)) return Fail(error); } else #endif - received = ::recvfrom(socket->value, buffer.data(), static_cast(buffer.size()), + if (!shut) received = ::recvfrom(socket->value, buffer.data(), static_cast(buffer.size()), NativeReceiveFlags(flags, socket->type), reinterpret_cast(&native), &size); if (received < 0) { #ifdef _WIN32 @@ -2237,7 +2296,7 @@ std::int64_t APS5_VABI recvmsg_nid_postfix(int descriptor, GuestMsghdr* message, return Fail(NativeError()); #endif } - if (header.control) { + if (header.control && !(received == 0 && ReceiveShut(*socket))) { int keptFlags = 0; { std::lock_guard lock(socket->modeMutex); @@ -2411,6 +2470,13 @@ int APS5_VABI sendfile_nid_postfix(int file, int descriptor, std::int64_t offset if (const int error = SendIovecs(*socket, trailerIovecs.data(), static_cast(trailerIovecs.size()), sent)) return finish(error); return finish(0); } +static bool SocketStillHeld(int descriptor, const void* owner) { + if (auto socket = Find(descriptor)) return socket.get() == owner; + std::shared_ptr current; + std::uintptr_t value = 0; + return KernelFindSocketNative_nid_no_patch(descriptor, ¤t, &value) && current.get() == owner; +} + static int PollDescriptors(GuestPollDescriptor* descriptors, std::uint32_t count, int timeout) { if (timeout < -1) return Fail(22); if (count && !descriptors) return Fail(14); @@ -2522,6 +2588,10 @@ static int PollDescriptors(GuestPollDescriptor* descriptors, std::uint32_t count }; const auto collect = [&](std::size_t i, bool pipe) { auto& entry = descriptors[owners[i]]; + if (entry.revents == GuestPollInvalid) { + ++ready; + return; + } #if defined(_WIN32) || defined(__APPLE__) if (!pipe) native[i].revents = ClosedStreamEvents(entry.descriptor, native[i].revents, native[i].events); #endif @@ -2555,10 +2625,12 @@ static int PollDescriptors(GuestPollDescriptor* descriptors, std::uint32_t count constexpr bool waitsForUrgent = false; #endif #ifdef _WIN32 - const bool sliced = !pipes.empty() || !kqueues.empty(); + const bool fastSlices = !pipes.empty() || !kqueues.empty(); #else - const bool sliced = !kqueues.empty() || waitsForUrgent; + const bool fastSlices = !kqueues.empty() || waitsForUrgent; #endif + const bool sliced = fastSlices || !held.empty(); + const int step = fastSlices ? 1 : SocketCloseCheckMilliseconds; const auto anyUrgentPending = [&]() -> bool { #ifdef __APPLE__ for (std::size_t i = 0; i < socketCount; ++i) { @@ -2570,6 +2642,12 @@ static int PollDescriptors(GuestPollDescriptor* descriptors, std::uint32_t count }; for (;;) { int checkedReady = 0; + bool closed = false; + for (std::size_t i = 0; i < held.size(); ++i) { + if (SocketStillHeld(descriptors[owners[i]].descriptor, held[i].get())) continue; + descriptors[owners[i]].revents = GuestPollInvalid; + closed = true; + } const bool urgent = anyUrgentPending(); #ifdef _WIN32 for (const auto index : pipes) { @@ -2590,14 +2668,14 @@ static int PollDescriptors(GuestPollDescriptor* descriptors, std::uint32_t count if (entry.revents) ++checkedReady; } int slice = 0; - if (!ready && !checkedReady && !urgent && !refreshShut()) { + if (!ready && !checkedReady && !urgent && !closed && !refreshShut()) { if (!sliced) { const auto left = std::chrono::ceil(deadline - std::chrono::steady_clock::now()).count(); slice = timeout < 0 ? -1 : static_cast(std::clamp(left, 0, INT_MAX)); - } else if (timeout < 0) slice = 1; + } else if (timeout < 0) slice = step; else { const auto left = std::chrono::duration_cast(deadline - std::chrono::steady_clock::now()).count(); - slice = static_cast(std::clamp(left, 0, 1)); + slice = static_cast(std::clamp(left, 0, step)); } } if (!native.empty()) { @@ -2611,6 +2689,11 @@ static int PollDescriptors(GuestPollDescriptor* descriptors, std::uint32_t count const auto wake = std::chrono::steady_clock::now() + std::chrono::milliseconds(1); PreciseSleepUntil(timeout < 0 ? wake : std::min(deadline, wake)); } + for (std::size_t i = 0; i < held.size(); ++i) { + if (SocketStillHeld(descriptors[owners[i]].descriptor, held[i].get())) continue; + descriptors[owners[i]].revents = GuestPollInvalid; + closed = true; + } int socketReady = 0; for (std::size_t i = 0; i < watched; ++i) if (native[i].revents) ++socketReady; @@ -2618,7 +2701,7 @@ static int PollDescriptors(GuestPollDescriptor* descriptors, std::uint32_t count if (woken) DrainWaker(native[watched].fd); const int shutReady = refreshShut(); const bool expired = timeout >= 0 && std::chrono::steady_clock::now() >= deadline; - if (ready || checkedReady || urgent || socketReady || shutReady || expired || (!sliced && !woken)) { + if (ready || checkedReady || urgent || socketReady || shutReady || expired || closed || (!sliced && !woken)) { ready += checkedReady; #ifdef _WIN32 for (std::size_t i = 0; i < watched; ++i) collect(i, false); diff --git a/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp b/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp index fa88568295..b8fbe7bf5d 100644 --- a/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp +++ b/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp @@ -213,7 +213,7 @@ std::optional TryLoadAddressValue(TValue* address) { const auto rangeEnd = range->address + range->bytes; if (rangeEnd <= cursor) return std::nullopt; pins[pinCount++] = std::move(range); - cursor = std::min(end, rangeEnd); + cursor = std::min(end, rangeEnd); } if (hasUnregistered) { TValue value; diff --git a/core/libs/prx/libkernel/System/src/MimallocMacOs.cpp b/core/libs/prx/libkernel/System/src/MimallocMacOs.cpp deleted file mode 100644 index 3171d3b3d6..0000000000 --- a/core/libs/prx/libkernel/System/src/MimallocMacOs.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#ifdef __APPLE__ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace { - -std::filesystem::path Allocator() { - Dl_info info{}; - if (dladdr(reinterpret_cast(&Allocator), &info) == 0 || info.dli_fname == nullptr) return {}; - return std::filesystem::path(info.dli_fname).parent_path() / "libmimalloc.dylib"; -} - -std::string Executable() { - std::uint32_t size = 0; - _NSGetExecutablePath(nullptr, &size); - std::vector executable(size); - if (_NSGetExecutablePath(executable.data(), &size) != 0) return {}; - return executable.data(); -} - -[[gnu::constructor]] void RestartWithMimalloc() { - const bool restarted = std::getenv("APS5_MIMALLOC_RESTARTED") != nullptr; - unsetenv("APS5_MIMALLOC_RESTARTED"); - if (std::getenv("APS5_MIMALLOC") == nullptr || dlsym(RTLD_DEFAULT, "mi_version") != nullptr) return; - if (restarted) { - std::fprintf(stderr, "[memory] APS5_MIMALLOC: dyld did not load libmimalloc.dylib, the title keeps the system allocator\n"); - return; - } - const auto allocator = Allocator(); - std::error_code error; - if (allocator.empty() || !std::filesystem::is_regular_file(allocator, error)) return; - const auto executable = Executable(); - if (executable.empty()) return; - const char* inserted = std::getenv("DYLD_INSERT_LIBRARIES"); - const std::string previous = inserted == nullptr ? std::string() : inserted; - const auto libraries = previous.empty() ? allocator.string() : allocator.string() + ":" + previous; - setenv("DYLD_INSERT_LIBRARIES", libraries.c_str(), 1); - setenv("APS5_MIMALLOC_RESTARTED", "1", 1); - execv(executable.c_str(), *_NSGetArgv()); - std::perror("[memory] APS5_MIMALLOC: execv"); - unsetenv("APS5_MIMALLOC_RESTARTED"); - if (inserted == nullptr) unsetenv("DYLD_INSERT_LIBRARIES"); - else setenv("DYLD_INSERT_LIBRARIES", previous.c_str(), 1); -} - -} - -#endif diff --git a/core/libs/prx/libkernel/System/src/MimallocRestart.cpp b/core/libs/prx/libkernel/System/src/MimallocRestart.cpp new file mode 100644 index 0000000000..3cc543cba4 --- /dev/null +++ b/core/libs/prx/libkernel/System/src/MimallocRestart.cpp @@ -0,0 +1,104 @@ +#if defined(__APPLE__) || defined(__linux__) + +#include "prx/libc/include/Shutdown.hpp" +#include +#include +#include +#include +#include +#include +#include +#ifdef __APPLE__ +#include +#include +#include +#else +#include +#endif + +namespace { + +#ifdef __APPLE__ +constexpr const char* Loader = "dyld"; +constexpr const char* AllocatorName = "libmimalloc.dylib"; +constexpr const char* PreloadVariable = "DYLD_INSERT_LIBRARIES"; +constexpr const char* PreloadSeparators = ":"; +#else +constexpr const char* Loader = "ld.so"; +constexpr const char* AllocatorName = "libmimalloc.so"; +constexpr const char* PreloadVariable = "LD_PRELOAD"; +constexpr const char* PreloadSeparators = ": "; +#endif + +std::filesystem::path Allocator() { + Dl_info info{}; + if (dladdr(reinterpret_cast(&Allocator), &info) == 0 || info.dli_fname == nullptr) return {}; + std::error_code error; + return std::filesystem::absolute(std::filesystem::path(info.dli_fname).parent_path() / AllocatorName, error); +} + +#ifdef __APPLE__ +std::string Executable() { + std::uint32_t size = 0; + _NSGetExecutablePath(nullptr, &size); + std::vector executable(size); + if (_NSGetExecutablePath(executable.data(), &size) != 0) return {}; + return executable.data(); +} + +std::vector Arguments() { + std::vector arguments; + for (char** argument = *_NSGetArgv(); *argument != nullptr; ++argument) arguments.emplace_back(*argument); + return arguments; +} +#else +std::string Executable() { + std::error_code error; + return std::filesystem::read_symlink("/proc/self/exe", error).string(); +} + +std::vector Arguments() { + std::ifstream file("/proc/self/cmdline", std::ios::binary); + std::vector arguments; + for (std::string argument; std::getline(file, argument, '\0');) arguments.push_back(argument); + return arguments; +} +#endif + +[[gnu::constructor(101)]] void RestartWithMimalloc() { + const bool restarted = std::getenv("APS5_MIMALLOC_RESTARTED") != nullptr; + unsetenv("APS5_MIMALLOC_RESTARTED"); + if (std::getenv("APS5_MIMALLOC") == nullptr || dlsym(RTLD_DEFAULT, "mi_version") != nullptr) return; + if (restarted) { + std::fprintf(stderr, "[memory] APS5_MIMALLOC: %s did not load %s, the title keeps the system allocator\n", Loader, AllocatorName); + return; + } + const auto allocator = Allocator(); + std::error_code error; + if (allocator.empty() || !std::filesystem::is_regular_file(allocator, error)) return; + if (allocator.string().find_first_of(PreloadSeparators) != std::string::npos) { + std::fprintf(stderr, "[memory] APS5_MIMALLOC: the path %s has a separator of %s, the title keeps the system allocator\n", allocator.c_str(), PreloadVariable); + return; + } + const auto executable = Executable(); + auto arguments = Arguments(); + if (executable.empty() || arguments.empty()) return; + std::vector values; + for (auto& argument : arguments) values.push_back(argument.data()); + values.push_back(nullptr); + const char* inserted = std::getenv(PreloadVariable); + const std::string previous = inserted == nullptr ? std::string() : inserted; + const auto libraries = previous.empty() ? allocator.string() : allocator.string() + ":" + previous; + setenv(PreloadVariable, libraries.c_str(), 1); + setenv("APS5_MIMALLOC_RESTARTED", "1", 1); + LogFileRelease_nid_no_patch(); + execv(executable.c_str(), values.data()); + std::perror("[memory] APS5_MIMALLOC: execv"); + unsetenv("APS5_MIMALLOC_RESTARTED"); + if (inserted == nullptr) unsetenv(PreloadVariable); + else setenv(PreloadVariable, previous.c_str(), 1); +} + +} + +#endif diff --git a/core/libs/prx/libkernel_sys/AdditionalExports.cpp b/core/libs/prx/libkernel_sys/AdditionalExports.cpp index e0541db05d..5f32d99d78 100644 --- a/core/libs/prx/libkernel_sys/AdditionalExports.cpp +++ b/core/libs/prx/libkernel_sys/AdditionalExports.cpp @@ -1,6 +1,21 @@ #include "prx/libkernel_sys/include/AdditionalExports.hpp" #include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/FilesystemError.hpp" +#include "prx/libkernel/include/Api.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libkernel/KernelErrors.hpp" #include @@ -98,8 +113,14 @@ int APS5_VABI __sys_namedobj_delete_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI __sys_nanosleep_nid_postfix() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI __sys_nanosleep_nid_postfix(const KernelTimespec* rqtp, KernelTimespec* rmtp) { + int oldCancelState = 0; + scePthreadSetcancelstate(1, &oldCancelState); + const int result = sceKernelNanosleep(rqtp, rmtp); + scePthreadSetcancelstate(oldCancelState, nullptr); + if (result == 0) return 0; + *__error_nid_postfix() = result & 0xFFFF; + return -1; } int APS5_VABI __sys_netabort_nid_postfix() { @@ -206,20 +227,258 @@ int APS5_VABI _dup2_nid_postfix(int from, int to) { return dup2_nid_postfix(from, to); } -int APS5_VABI _execve_nid_postfix() { - NotImplemented_nid_no_patch(__func__); +static int GuestExecve_nid_no_patch(const char* path, char* const* argv, char* const* envp) { + constexpr int GuestEfault = 14; + constexpr int GuestEacces = 13; + constexpr int GuestEnoexec = 8; + constexpr int GuestE2big = 7; + constexpr int GuestEnoent = 2; + constexpr int GuestEnametoolong = 63; + constexpr std::size_t GuestPathMax = 1024; + constexpr std::size_t GuestArgMax = 262144; + const auto fail = [](int error) { + *__error_nid_postfix() = error; + return -1; + }; + const auto gather = [](const char* guest, std::size_t maxLength, std::string& copy) -> int { + for (std::size_t read = 0; read < maxLength;) { + const auto address = reinterpret_cast(guest) + read; + const std::size_t boundary = 4096 - address % 4096; + const std::size_t bytes = boundary < maxLength - read ? boundary : maxLength - read; + char buffer[4096]; + if (!GuestCopyIn_nid_no_patch(buffer, reinterpret_cast(address), bytes)) return GuestEfault; + const auto end = static_cast(std::memchr(buffer, 0, bytes)); + if (end != nullptr) { + copy.append(buffer, static_cast(end - buffer)); + return 0; + } + copy.append(buffer, bytes); + read += bytes; + } + return GuestEnametoolong; + }; + if (argv == nullptr) return fail(GuestEfault); + std::string fname; + if (path != nullptr) { + const int error = gather(path, GuestPathMax, fname); + if (error != 0) return fail(error); + } + std::string text; + std::vector arguments; + std::vector environment; + std::size_t stringspace = GuestArgMax; + for (char* const* slot = argv;; ++slot) { + char* argument = nullptr; + if (!GuestCopyIn_nid_no_patch(&argument, slot, sizeof(argument))) return fail(GuestEfault); + if (argument == nullptr) break; + text.clear(); + const int error = gather(argument, stringspace, text); + if (error != 0) return fail(error == GuestEnametoolong ? GuestE2big : error); + stringspace -= text.size() + 1; + arguments.push_back(std::move(text)); + } + if (envp != nullptr) { + for (char* const* slot = envp;; ++slot) { + char* variable = nullptr; + if (!GuestCopyIn_nid_no_patch(&variable, slot, sizeof(variable))) return fail(GuestEfault); + if (variable == nullptr) break; + text.clear(); + const int error = gather(variable, stringspace, text); + if (error != 0) return fail(error == GuestEnametoolong ? GuestE2big : error); + stringspace -= text.size() + 1; + environment.push_back(std::move(text)); + } + } + if (path == nullptr) NotImplemented_nid_no_patch(__func__); + if (fname.empty()) return fail(GuestEnoent); + std::filesystem::path native; + if (const int error = ResolvePathError_nid_no_patch(fname.c_str(), native)) return fail(error); + std::error_code statusError; + const auto status = std::filesystem::status(native, statusError); + if (status.type() == std::filesystem::file_type::not_found) return fail(NotFoundError_nid_no_patch(native)); + if (statusError) return fail(FilesystemError(statusError)); +#ifdef _WIN32 + const bool executable = std::filesystem::is_regular_file(status); +#else + constexpr std::filesystem::perms execute = std::filesystem::perms::owner_exec | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec; + const bool executable = (status.permissions() & execute) != std::filesystem::perms::none && std::filesystem::is_regular_file(status); +#endif + if (!executable) return fail(GuestEacces); + std::error_code sizeError; + const auto size = std::filesystem::file_size(native, sizeError); + if (sizeError) return fail(FilesystemError(sizeError)); + if (size == 0) return fail(GuestEnoexec); + if (!ExecutePermission_nid_no_patch(native)) return fail(GuestEacces); + std::ifstream image(native, std::ios::binary); + if (!image) return fail(GuestEacces); + unsigned char header[4]{}; + image.read(reinterpret_cast(header), sizeof(header)); + if (image.gcount() < static_cast(sizeof(header))) return fail(GuestEnoexec); + if (header[0] == '#' && header[1] == '!') { + NotImplemented_nid_no_patch(__func__); + } + constexpr unsigned char ElfMagic[] = {0x7f, 0x45, 0x4c, 0x46}; + constexpr unsigned char Ps4SelfMagic[] = {0x4f, 0x15, 0x3d, 0x1d}; + constexpr unsigned char Ps5SelfMagic[] = {0x54, 0x14, 0xf5, 0xee}; + if (std::memcmp(header, ElfMagic, sizeof(ElfMagic)) == 0 || + std::memcmp(header, Ps4SelfMagic, sizeof(Ps4SelfMagic)) == 0 || + std::memcmp(header, Ps5SelfMagic, sizeof(Ps5SelfMagic)) == 0) { + std::vector argumentPointers; + for (auto& argument : arguments) argumentPointers.push_back(argument.data()); + argumentPointers.push_back(nullptr); + std::vector environmentPointers; + for (auto& variable : environment) environmentPointers.push_back(variable.data()); + environmentPointers.push_back(nullptr); + return execve_nid_postfix(fname.c_str(), argumentPointers.data(), environmentPointers.data()); + } + return fail(GuestEnoexec); } -int APS5_VABI _execvpe_nid_postfix() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _execve_nid_postfix(const char* path, char* const* argv, char* const* envp) { + return GuestExecve_nid_no_patch(path, argv, envp); +} + +int APS5_VABI _execvpe_nid_postfix(const char* name, char* const* argv, char* const* envp) { + constexpr int GuestEacces = 13; + constexpr int GuestEnoent = 2; + constexpr int GuestE2big = 7; + constexpr int GuestEnoexec = 8; + constexpr int GuestEnomem = 12; + constexpr int GuestEtxtbsy = 26; + constexpr int GuestEloop = 62; + constexpr int GuestEnametoolong = 63; + constexpr int GuestEnotdir = 20; + constexpr int GuestEfault = 14; + constexpr std::size_t GuestCandidateMax = 1024; + const std::string_view DefaultSearchPath = "/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin"; + const std::string_view ShellPath = "/bin/sh"; + const auto fail = [](int error) { + *__error_nid_postfix() = error; + return -1; + }; + std::string searchPath; + if (!CopyGuestEnvironment_nid_no_patch("PATH", searchPath)) searchPath = std::string(DefaultSearchPath); + std::string nameText; + if (name != nullptr) { + for (std::size_t read = 0; read < GuestCandidateMax;) { + const auto address = reinterpret_cast(name) + read; + const std::size_t boundary = 4096 - address % 4096; + const std::size_t bytes = std::min(boundary, GuestCandidateMax - read); + char buffer[4096]; + if (!GuestCopyIn_nid_no_patch(buffer, reinterpret_cast(address), bytes)) return fail(GuestEfault); + const auto end = static_cast(std::memchr(buffer, 0, bytes)); + if (end != nullptr) { + nameText.append(buffer, static_cast(end - buffer)); + break; + } + nameText.append(buffer, bytes); + read += bytes; + } + } else { + return fail(GuestEfault); + } + if (nameText.size() == GuestCandidateMax) return fail(GuestEnoent); + bool accessFailure = false; + const auto ladder = [&](const std::string& candidate) { + GuestExecve_nid_no_patch(candidate.c_str(), argv, envp); + const int refusal = *__error_nid_postfix(); + switch (refusal) { + case GuestE2big: + case GuestEnomem: + case GuestEtxtbsy: + return true; + case GuestEloop: + case GuestEnametoolong: + case GuestEnoent: + case GuestEnotdir: + return false; + case GuestEnoexec: { + std::size_t count = 0; + for (;;) { + char* entry = nullptr; + if (!GuestCopyIn_nid_no_patch(&entry, argv + count, sizeof(entry))) { + fail(GuestEfault); + return true; + } + if (entry == nullptr) break; + ++count; + } + const std::string shell("sh"); + std::vector shellArgs; + shellArgs.reserve(count + 2); + shellArgs.push_back(const_cast(shell.c_str())); + shellArgs.push_back(const_cast(candidate.c_str())); + for (std::size_t index = 1; index < count; ++index) { + char* next = nullptr; + if (!GuestCopyIn_nid_no_patch(&next, argv + index, sizeof(next))) { + fail(GuestEfault); + return true; + } + shellArgs.push_back(next); + } + shellArgs.push_back(nullptr); + GuestExecve_nid_no_patch(ShellPath.data(), shellArgs.data(), envp); + return true; + } + default: { + std::filesystem::path native; + if (const int resolved = ResolvePathError_nid_no_patch(candidate.c_str(), native)) return false; + std::error_code lookupError; + const auto status = std::filesystem::status(native, lookupError); + if (lookupError || status.type() == std::filesystem::file_type::not_found) return false; + if (refusal == GuestEacces) { + accessFailure = true; + return false; + } + return true; + } + } + }; + if (nameText.find('/') != std::string::npos) { + if (ladder(nameText)) return -1; + return fail(accessFailure ? GuestEacces : GuestEnoent); + } + if (nameText.empty()) return fail(GuestEnoent); + std::size_t offset = 0; + for (bool finished = false; !finished;) { + const auto separator = searchPath.find(':', offset); + finished = separator == std::string::npos; + const std::string_view element(searchPath.data() + offset, finished ? searchPath.size() - offset : separator - offset); + offset = finished ? searchPath.size() : separator + 1; + const std::string_view directory = element.empty() ? std::string_view(".") : element; + if (directory.size() + nameText.size() + 2 > GuestCandidateMax) continue; + std::string candidate(directory); + candidate.push_back('/'); + candidate += nameText; + if (ladder(candidate)) return -1; + } + return fail(accessFailure ? GuestEacces : GuestEnoent); } int APS5_VABI _wait4_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI adjtime_nid_postfix() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI adjtime_nid_postfix(const KernelTimeval* delta, KernelTimeval* oldDelta) { + constexpr int GuestEperm = 1; + constexpr int GuestEfault = 14; + KernelTimeval adjustment{}; + if (delta != nullptr && !GuestCopyIn_nid_no_patch(&adjustment, delta, sizeof(adjustment))) { + *__error_nid_postfix() = GuestEfault; + return -1; + } + if (delta != nullptr) { + *__error_nid_postfix() = GuestEperm; + return -1; + } + if (oldDelta != nullptr) { + const KernelTimeval pending{}; + if (!GuestCopyOut_nid_no_patch(oldDelta, &pending, sizeof(pending))) { + *__error_nid_postfix() = GuestEfault; + return -1; + } + } + return 0; } int APS5_VABI libkernel_sysUnknown_0CV40L3tznI_nid_no_patch() { diff --git a/core/libs/prx/libkernel_sys/Export.Discovery.cpp b/core/libs/prx/libkernel_sys/Export.Discovery.cpp index 4ef1b4ad44..e9658519ec 100644 --- a/core/libs/prx/libkernel_sys/Export.Discovery.cpp +++ b/core/libs/prx/libkernel_sys/Export.Discovery.cpp @@ -229,8 +229,10 @@ int APS5_VABI pthread_attr_setaffinity_np_nid_postfix(void) { int APS5_VABI pthread_attr_setcreatesuspend_np_nid_postfix(PthreadAttr* attr) { constexpr int GuestEinval = 22; - if (!attr || !*attr) return GuestEinval; - (*attr)->_createSuspended = true; + if (!attr) return GuestEinval; + PthreadAttr localAttr{}; + if (!GuestCopyIn_nid_no_patch(&localAttr, attr, sizeof(localAttr)) || !localAttr) return GuestEinval; + localAttr->_createSuspended = true; return 0; } @@ -265,15 +267,20 @@ int APS5_VABI pthread_mutex_init_for_mono_nid_postfix(void) { } int APS5_VABI pthread_mutexattr_getgen_np_nid_postfix(const PthreadMutexattr* attr, int* generation) { - if (!attr || !*attr || !generation) return PosixThread::GUEST_EINVAL; - *generation = (*attr)->generation & 1; + if (!attr || !generation) return PosixThread::GUEST_EINVAL; + PthreadMutexattr localAttr{}; + if (!GuestCopyIn_nid_no_patch(&localAttr, attr, sizeof(localAttr)) || !localAttr) return PosixThread::GUEST_EINVAL; + const int val = localAttr->generation & 1; + if (!GuestCopyOut_nid_no_patch(generation, &val, sizeof(val))) return PosixThread::GUEST_EFAULT; return 0; } int APS5_VABI pthread_mutexattr_setgen_np_nid_postfix(PthreadMutexattr* attr, int generation) { - if (!attr || !*attr) return PosixThread::GUEST_EINVAL; + if (!attr) return PosixThread::GUEST_EINVAL; + PthreadMutexattr localAttr{}; + if (!GuestCopyIn_nid_no_patch(&localAttr, attr, sizeof(localAttr)) || !localAttr) return PosixThread::GUEST_EINVAL; if (generation != 0 && generation != 1) return PosixThread::GUEST_ENOTSUP; - (*attr)->generation = generation; + localAttr->generation = generation; return 0; } @@ -1400,12 +1407,14 @@ int APS5_VABI sceKernelMapTraceMemory(void) { } int APS5_VABI sceKernelMapperGetParam(std::uint64_t* param) { - if (!param || *param > MapperParamBytes) return SCE_KERNEL_ERROR_EINVAL; - const auto bytes = *param; + if (!param) return SCE_KERNEL_ERROR_EINVAL; + std::uint64_t bytes = 0; + if (!GuestCopyIn_nid_no_patch(&bytes, param, sizeof(bytes))) return SCE_KERNEL_ERROR_EFAULT; + if (bytes > MapperParamBytes) return SCE_KERNEL_ERROR_EINVAL; if (bytes > MapperKnownBytes) NotImplemented_nid_no_patch(__func__); std::uint64_t values[5]{MapperParamBytes}; AmmVirtualAddressRanges_nid_no_patch(&values[1], &values[2], &values[3], &values[4]); - std::memcpy(param, values, static_cast(bytes)); + if (!GuestCopyOut_nid_no_patch(param, values, static_cast(bytes))) return SCE_KERNEL_ERROR_EFAULT; return 0; } diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index 3a4d6e7f5a..185fb31b15 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -457,7 +457,8 @@ int APS5_VABI sceKernelGetOpenPsIdForSystem(void* id) { int APS5_VABI sceKernelGetCpumodeGame(int* mode) { if (mode == nullptr) throw std::runtime_error("sceKernelGetCpumodeGame: a null output faults on the console"); - *mode = g_gameCpumode.load(std::memory_order_relaxed); + const int val = g_gameCpumode.load(std::memory_order_relaxed); + if (!GuestCopyOut_nid_no_patch(mode, &val, sizeof(val))) return SCE_KERNEL_ERROR_EFAULT; return 0; } @@ -569,10 +570,14 @@ int APS5_VABI sceKernelIsModuleLoaded(const char* name) { KernelModule handles[0x300]; std::size_t count = 0; if (const int result = sceKernelGetModuleListInternal(handles, std::size(handles), &count)) return result; + std::string copy; + const int copied = CopyGuestString_nid_no_patch(name, 256, copy); + if (copied == 63) return SCE_KERNEL_ERROR_ESRCH; + if (copied != 0) return SCE_KERNEL_ERROR_EFAULT; for (std::size_t i = 0; i < count; ++i) { ModuleInfo info{}; info.st_size = sizeof(info); - if (sceKernelGetModuleInfo(handles[i], &info) == 0 && std::strncmp(info.name, name, sizeof(info.name)) == 0) return 0; + if (sceKernelGetModuleInfo(handles[i], &info) == 0 && std::strncmp(info.name, copy.c_str(), sizeof(info.name)) == 0) return 0; } return SCE_KERNEL_ERROR_ESRCH; } @@ -594,20 +599,20 @@ std::int64_t APS5_VABI readlinkat_nid_postfix(int fd, const char* path, char* bu if (size > static_cast(std::numeric_limits::max())) return Fail(GuestEinval); std::string copy; if (const int error = CopyPath(path, copy)) return Fail(error); - if (fd == GuestAtFdcwd || *path == '/') return readlink_nid_postfix(path, buffer, size); + if (fd == GuestAtFdcwd || copy[0] == '/') return readlink_nid_postfix(copy.c_str(), buffer, size); int error = 0; const char* base = GetDirectoryDescriptorPath_nid_no_patch(fd, &error); if (base == nullptr) return Fail(error); return ReadNativeLink(std::filesystem::path(reinterpret_cast(base)) / - std::filesystem::path(reinterpret_cast(path)), buffer, size); + std::filesystem::path(reinterpret_cast(copy.c_str())), buffer, size); } std::int64_t APS5_VABI pathconf_nid_postfix(const char* path, int name) { std::string copy; if (const int error = CopyPath(path, copy)) return Fail(error); - if (std::strlen(path) >= GuestPathMax || HasLongComponent(path)) return Fail(GuestEnametoolong); + if (copy.size() >= GuestPathMax || HasLongComponent(copy.c_str())) return Fail(GuestEnametoolong); std::filesystem::path native; - if (const int error = ResolvePathError_nid_no_patch(path, native)) return Fail(error); + if (const int error = ResolvePathError_nid_no_patch(copy.c_str(), native)) return Fail(error); std::error_code error; const auto status = std::filesystem::status(native, error); if (error) return Fail(error == std::errc::no_such_file_or_directory && HasNonDirectoryPrefix(native) ? GuestEnotdir : GuestError(error)); @@ -791,9 +796,9 @@ int APS5_VABI settimeofday_nid_postfix(const KernelTimeval* tv, const KernelTime int APS5_VABI chroot_nid_postfix(const char* path) { std::string copy; if (const int error = CopyPath(path, copy)) return static_cast(Fail(error)); - if (std::strlen(path) >= GuestPathMax || HasLongComponent(path)) return static_cast(Fail(GuestEnametoolong)); + if (copy.size() >= GuestPathMax || HasLongComponent(copy.c_str())) return static_cast(Fail(GuestEnametoolong)); std::filesystem::path native; - if (const int error = ResolvePathError_nid_no_patch(path, native)) return static_cast(Fail(error)); + if (const int error = ResolvePathError_nid_no_patch(copy.c_str(), native)) return static_cast(Fail(error)); std::error_code error; const auto status = std::filesystem::status(native, error); if (status.type() == std::filesystem::file_type::not_found) return static_cast(Fail(HasNonDirectoryPrefix(native) ? GuestEnotdir : GuestEnoent)); @@ -933,8 +938,8 @@ int APS5_VABI openat_nid_postfix(int fd, const char* path, int flags, ...) { if (File::InvalidOpenFlags(flags)) return static_cast(Fail(GuestEinval)); std::string copy; if (const int error = CopyPath(path, copy)) return static_cast(Fail(error)); - if (fd == GuestAtFdcwd || *path == '/') return open_nid_postfix(path, flags, mode); - const int result = OpenInDirectory_nid_no_patch(fd, path, flags, static_cast(mode)); + if (fd == GuestAtFdcwd || copy[0] == '/') return open_nid_postfix(copy.c_str(), flags, mode); + const int result = OpenInDirectory_nid_no_patch(fd, copy.c_str(), flags, static_cast(mode)); if (result < 0) return static_cast(Fail(result & 0xffff)); return result; } @@ -961,13 +966,13 @@ int APS5_VABI _openat_nid_postfix(int fd, const char* path, int flags, ...) { int APS5_VABI mkdirat_nid_postfix(int fd, const char* path, std::uint16_t mode) { std::string copy; if (const int error = CopyPath(path, copy)) return static_cast(Fail(error)); - if (fd == GuestAtFdcwd || *path == '/') return mkdir_nid_postfix(path, mode); - if (std::strlen(path) >= GuestPathMax || HasLongComponent(path)) return static_cast(Fail(GuestEnametoolong)); + if (fd == GuestAtFdcwd || copy[0] == '/') return mkdir_nid_postfix(copy.c_str(), mode); + if (copy.size() >= GuestPathMax || HasLongComponent(copy.c_str())) return static_cast(Fail(GuestEnametoolong)); int error = 0; const char* base = GetDirectoryDescriptorPath_nid_no_patch(fd, &error); if (base == nullptr) return static_cast(Fail(error)); auto native = std::filesystem::path(reinterpret_cast(base)) / - std::filesystem::path(reinterpret_cast(path)); + std::filesystem::path(reinterpret_cast(copy.c_str())); while (!native.has_filename() && native.has_relative_path()) native = native.parent_path(); #ifdef _WIN32 if (!CreateDirectoryW(native.wstring().c_str(), nullptr)) { @@ -999,13 +1004,13 @@ int APS5_VABI fchmodat_nid_postfix(int fd, const char* path, int mode, int flag) std::string copy; if (const int error = CopyPath(path, copy)) return static_cast(Fail(error)); std::filesystem::path native; - if (fd == GuestAtFdcwd || *path == '/') { - if (const int error = ResolvePathError_nid_no_patch(path, native)) return static_cast(Fail(error)); + if (fd == GuestAtFdcwd || copy[0] == '/') { + if (const int error = ResolvePathError_nid_no_patch(copy.c_str(), native)) return static_cast(Fail(error)); } else { int error = 0; const char* base = GetDirectoryDescriptorPath_nid_no_patch(fd, &error); if (base == nullptr) return static_cast(Fail(error)); - native = std::filesystem::path(reinterpret_cast(base)) / std::filesystem::path(reinterpret_cast(path)); + native = std::filesystem::path(reinterpret_cast(base)) / std::filesystem::path(reinterpret_cast(copy.c_str())); } if (flag == GuestAtSymlinkNofollow) { #ifdef _WIN32 @@ -1017,13 +1022,14 @@ int APS5_VABI fchmodat_nid_postfix(int fd, const char* path, int mode, int flag) #endif if (symbolic) { #ifdef __APPLE__ - if (::fchmodat(AT_FDCWD, native.c_str(), static_cast(mode & 07777), AT_SYMLINK_NOFOLLOW) != 0) { - return static_cast(Fail(GuestFileError(errno))); - } + if (::fchmodat(AT_FDCWD, native.c_str(), static_cast(mode & 07777), AT_SYMLINK_NOFOLLOW) != 0) + return static_cast(Fail(GuestFileError(errno))); +#else + NotImplemented_nid_no_patch("fchmodat: the mode of a symbolic link on a host that cannot change it"); #endif - RecordWrittenPath_nid_no_patch(native); - return 0; - } + RecordWrittenPath_nid_no_patch(native); + return 0; + } } if (ChmodNative_nid_no_patch(native, mode & 07777) != 0) return static_cast(Fail(errno == ENOENT && HasNonDirectoryPrefix(native) ? GuestEnotdir : GuestFileError(errno))); RecordWrittenPath_nid_no_patch(native); @@ -1035,19 +1041,6 @@ int APS5_VABI lchmod_nid_postfix(const char* path, std::uint16_t mode) { if (path != nullptr) { if (const int error = CopyPath(path, copy)) return static_cast(Fail(error)); } -#ifndef __APPLE__ - std::filesystem::path native; - if (path != nullptr && ResolvePathError_nid_no_patch(path, native) == 0) { -#ifdef _WIN32 - const DWORD attributes = GetFileAttributesW(native.wstring().c_str()); - const bool symbolic = attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; -#else - std::error_code status; - const bool symbolic = std::filesystem::is_symlink(std::filesystem::symlink_status(native, status)); -#endif - if (symbolic) NotImplemented_nid_no_patch("lchmod: the mode of a symbolic link on a host that cannot change it"); - } -#endif return fchmodat_nid_postfix(GuestAtFdcwd, path, mode, GuestAtSymlinkNofollow); } @@ -1097,8 +1090,8 @@ int APS5_VABI fstatat_nid_postfix(int fd, const char* path, FileStat* sb, int fl if (flag & ~GuestAtSymlinkNofollow) return static_cast(Fail(GuestEinval)); std::string copy; if (const int error = CopyPath(path, copy)) return static_cast(Fail(error)); - if (fd == GuestAtFdcwd || *path == '/') return flag ? lstat_nid_postfix(path, sb) : stat_nid_postfix(path, sb); - const int result = StatInDirectory_nid_no_patch(fd, path, sb, flag == 0); + if (fd == GuestAtFdcwd || copy[0] == '/') return flag ? lstat_nid_postfix(copy.c_str(), sb) : stat_nid_postfix(copy.c_str(), sb); + const int result = StatInDirectory_nid_no_patch(fd, copy.c_str(), sb, flag == 0); if (result < 0) return static_cast(Fail(result & 0xffff)); return 0; } @@ -1113,18 +1106,18 @@ int APS5_VABI unlinkat_nid_postfix(int fd, const char* path, int flag) { if (flag & ~GuestAtRemovedir) return static_cast(Fail(GuestEinval)); std::string copy; if (const int error = CopyPath(path, copy)) return static_cast(Fail(error)); - if (fd == GuestAtFdcwd || *path == '/') return (flag & GuestAtRemovedir) ? rmdir_nid_postfix(path) : unlink_nid_postfix(path); - if (std::strlen(path) >= GuestPathMax || HasLongComponent(path)) return static_cast(Fail(GuestEnametoolong)); + if (fd == GuestAtFdcwd || copy[0] == '/') return (flag & GuestAtRemovedir) ? rmdir_nid_postfix(copy.c_str()) : unlink_nid_postfix(copy.c_str()); + if (copy.size() >= GuestPathMax || HasLongComponent(copy.c_str())) return static_cast(Fail(GuestEnametoolong)); int error = 0; const char* base = GetDirectoryDescriptorPath_nid_no_patch(fd, &error); if (base == nullptr) return static_cast(Fail(error)); auto native = std::filesystem::path(reinterpret_cast(base)) / - std::filesystem::path(reinterpret_cast(path)); + std::filesystem::path(reinterpret_cast(copy.c_str())); const bool trailingSlash = !native.has_filename() && native.has_relative_path(); while (!native.has_filename() && native.has_relative_path()) native = native.parent_path(); std::error_code statusError; const auto symlinkStatus = std::filesystem::symlink_status(native, statusError); - if (IsDotOrDotDot(path)) return static_cast(Fail(GuestEinval)); + if (IsDotOrDotDot(copy.c_str())) return static_cast(Fail(GuestEinval)); if (flag & GuestAtRemovedir) { if (std::filesystem::is_symlink(symlinkStatus)) return static_cast(Fail(GuestEnotdir)); #ifdef _WIN32 @@ -1199,32 +1192,35 @@ int APS5_VABI unlinkat_nid_postfix(int fd, const char* path, int flag) { } int APS5_VABI scePthreadCondattrGetclock(const PthreadCondattr* attr, KernelClockid* clockId) { - if (!attr || !*attr) return SCE_KERNEL_ERROR_EINVAL; + if (!attr) return SCE_KERNEL_ERROR_EINVAL; + PthreadCondattr localAttr{}; + if (!GuestCopyIn_nid_no_patch(&localAttr, attr, sizeof(localAttr)) || !localAttr) return SCE_KERNEL_ERROR_EINVAL; if (!clockId) throw std::invalid_argument("scePthreadCondattrGetclock: null clock id"); - *clockId = (*attr)->_clockid; + const auto val = localAttr->_clockid; + if (!GuestCopyOut_nid_no_patch(clockId, &val, sizeof(val))) return SCE_KERNEL_ERROR_EFAULT; return 0; } int APS5_VABI renameat_nid_postfix(int oldfd, const char* from, int newfd, const char* to) { std::string fromCopy, toCopy; if (const int error = CopyPath(from, fromCopy)) return static_cast(Fail(error)); - if ((oldfd == GuestAtFdcwd || *from == '/') && newfd == GuestAtFdcwd) - return RenameGuestPath_nid_no_patch(from, to); - if (std::strlen(from) >= GuestPathMax || HasLongComponent(from)) + if ((oldfd == GuestAtFdcwd || fromCopy[0] == '/') && newfd == GuestAtFdcwd) + return RenameGuestPath_nid_no_patch(fromCopy.c_str(), to); + if (fromCopy.size() >= GuestPathMax || HasLongComponent(fromCopy.c_str())) return static_cast(Fail(GuestEnametoolong)); - if (IsDotOrDotDot(from)) return static_cast(Fail(GuestEinval)); + if (IsDotOrDotDot(fromCopy.c_str())) return static_cast(Fail(GuestEinval)); std::filesystem::path source; - if (oldfd == GuestAtFdcwd || *from == '/') { - if (const int error = ResolvePathError_nid_no_patch(from, source)) return static_cast(Fail(error)); + if (oldfd == GuestAtFdcwd || fromCopy[0] == '/') { + if (const int error = ResolvePathError_nid_no_patch(fromCopy.c_str(), source)) return static_cast(Fail(error)); } else { int error = 0; const char* base = GetDirectoryDescriptorPath_nid_no_patch(oldfd, &error); if (base == nullptr) return static_cast(Fail(error)); source = std::filesystem::path(reinterpret_cast(base)) / - std::filesystem::path(reinterpret_cast(from)); + std::filesystem::path(reinterpret_cast(fromCopy.c_str())); } - const std::string_view fromView(from); + const std::string_view fromView(fromCopy); const bool oldTrailingSlash = !fromView.empty() && fromView.back() == '/'; while (!source.has_filename() && source.has_relative_path()) source = source.parent_path(); std::error_code statusError; @@ -1236,21 +1232,21 @@ int APS5_VABI renameat_nid_postfix(int oldfd, const char* from, int newfd, const if (oldTrailingSlash && !std::filesystem::is_directory(sourceStatus)) return static_cast(Fail(GuestEnotdir)); if (const int error = CopyPath(to, toCopy)) return static_cast(Fail(error)); - if (std::strlen(to) >= GuestPathMax || HasLongComponent(to)) return static_cast(Fail(GuestEnametoolong)); - if (IsDotOrDotDot(to)) return static_cast(Fail(GuestEinval)); + if (toCopy.size() >= GuestPathMax || HasLongComponent(toCopy.c_str())) return static_cast(Fail(GuestEnametoolong)); + if (IsDotOrDotDot(toCopy.c_str())) return static_cast(Fail(GuestEinval)); std::filesystem::path destination; - if (newfd == GuestAtFdcwd || *to == '/') { - if (const int error = ResolvePathError_nid_no_patch(to, destination)) return static_cast(Fail(error)); + if (newfd == GuestAtFdcwd || toCopy[0] == '/') { + if (const int error = ResolvePathError_nid_no_patch(toCopy.c_str(), destination)) return static_cast(Fail(error)); } else { int error = 0; const char* base = GetDirectoryDescriptorPath_nid_no_patch(newfd, &error); if (base == nullptr) return static_cast(Fail(error)); destination = std::filesystem::path(reinterpret_cast(base)) / - std::filesystem::path(reinterpret_cast(to)); + std::filesystem::path(reinterpret_cast(toCopy.c_str())); } - const std::string_view toView(to); + const std::string_view toView(toCopy); const bool newTrailingSlash = !toView.empty() && toView.back() == '/'; while (!destination.has_filename() && destination.has_relative_path()) destination = destination.parent_path(); diff --git a/core/libs/prx/libkernel_sys/include/AdditionalExports.hpp b/core/libs/prx/libkernel_sys/include/AdditionalExports.hpp index 3288335bfe..b2200dd069 100644 --- a/core/libs/prx/libkernel_sys/include/AdditionalExports.hpp +++ b/core/libs/prx/libkernel_sys/include/AdditionalExports.hpp @@ -26,7 +26,7 @@ int APS5_VABI __sys_is_development_mode_nid_postfix(); int APS5_VABI __sys_kqueueex_nid_postfix(); int APS5_VABI __sys_namedobj_create_nid_postfix(); int APS5_VABI __sys_namedobj_delete_nid_postfix(); -int APS5_VABI __sys_nanosleep_nid_postfix(); +int APS5_VABI __sys_nanosleep_nid_postfix(const KernelTimespec* rqtp, KernelTimespec* rmtp); int APS5_VABI __sys_netabort_nid_postfix(); int APS5_VABI __sys_netcontrol_nid_postfix(); int APS5_VABI __sys_netgetiflist_nid_postfix(); @@ -53,10 +53,10 @@ int APS5_VABI __sys_workaround8849_nid_postfix(); int APS5_VABI __ubsan_handle_cfi_check_fail_abort_nid_postfix(); int APS5_VABI __ubsan_handle_cfi_check_fail_nid_postfix(); int APS5_VABI _dup2_nid_postfix(int from, int to); -int APS5_VABI _execve_nid_postfix(); -int APS5_VABI _execvpe_nid_postfix(); +int APS5_VABI _execve_nid_postfix(const char* path, char* const* argv, char* const* envp); +int APS5_VABI _execvpe_nid_postfix(const char* name, char* const* argv, char* const* envp); int APS5_VABI _wait4_nid_postfix(); -int APS5_VABI adjtime_nid_postfix(); +int APS5_VABI adjtime_nid_postfix(const KernelTimeval* delta, KernelTimeval* oldDelta); int APS5_VABI libkernel_sysUnknown_0CV40L3tznI_nid_no_patch(); std::uint64_t APS5_VABI libkernel_sysUnknown_1WXQlxMpnNA_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_1v23w7Rnkic_nid_no_patch(); diff --git a/core/libs/prx/libwebrtc/CMakeLists.txt b/core/libs/prx/libwebrtc/CMakeLists.txt index ee814bd03a..3498f3759b 100644 --- a/core/libs/prx/libwebrtc/CMakeLists.txt +++ b/core/libs/prx/libwebrtc/CMakeLists.txt @@ -12,6 +12,7 @@ target_sources(libwebrtc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/IpAddress.cpp) target_sources(libwebrtc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/RtcpCommonHeader.cpp) target_sources(libwebrtc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/CpuFeatures.cpp) target_sources(libwebrtc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/RmsLevel.cpp) +target_sources(libwebrtc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/RtcpPli.cpp) target_sources(libwebrtc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Opus.cpp) target_sources(libwebrtc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/RecursiveCriticalSection.cpp) target_link_libraries(libwebrtc PRIVATE opus) @@ -68,6 +69,16 @@ if(BUILD_TESTING) set_tests_properties(guest_webrtc_cpu_features PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_webrtc_rtcp_pli_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/RtcpPli.cpp) + target_include_directories(guest_webrtc_rtcp_pli_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_webrtc_rtcp_pli_tests PRIVATE libwebrtc libc) + configure_windows_unwind(guest_webrtc_rtcp_pli_tests) + add_test(NAME guest_webrtc_rtcp_pli COMMAND guest_webrtc_rtcp_pli_tests) + set_tests_properties(guest_webrtc_rtcp_pli PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_webrtc_rtcp_pli PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_webrtc_opus_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Opus.cpp) target_include_directories(guest_webrtc_opus_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_webrtc_opus_tests PRIVATE libwebrtc libc opus) diff --git a/core/libs/prx/libwebrtc/Export.cpp b/core/libs/prx/libwebrtc/Export.cpp index 33a2f75b41..f94fcee8d5 100644 --- a/core/libs/prx/libwebrtc/Export.cpp +++ b/core/libs/prx/libwebrtc/Export.cpp @@ -1081,11 +1081,6 @@ int APS5_VABI _ZN6webrtc4rtcp4NackD1Ev_nid_postfix(void) { return 0; } -int APS5_VABI _ZN6webrtc4rtcp3Pli5ParseERKNS0_12CommonHeaderE_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN6webrtc4rtcp3PliC1Ev_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libwebrtc/include/Api.hpp b/core/libs/prx/libwebrtc/include/Api.hpp index ba554559ba..d42e756666 100644 --- a/core/libs/prx/libwebrtc/include/Api.hpp +++ b/core/libs/prx/libwebrtc/include/Api.hpp @@ -9,6 +9,7 @@ #include "prx/libwebrtc/include/IpAddress.hpp" #include "prx/libwebrtc/include/RmsLevel.hpp" #include "prx/libwebrtc/include/RtcpCommonHeader.hpp" +#include "prx/libwebrtc/include/RtcpPli.hpp" #include "prx/libwebrtc/include/Opus.hpp" #include "prx/libwebrtc/include/RecursiveCriticalSection.hpp" #include "prx/libwebrtc/include/CpuFeatures.hpp" @@ -244,7 +245,7 @@ int APS5_VABI _ZN6webrtc4rtcp14CompoundPacketD1Ev_nid_postfix(void); int APS5_VABI _ZN6webrtc4rtcp4Nack5ParseERKNS0_12CommonHeaderE_nid_postfix(void); int APS5_VABI _ZN6webrtc4rtcp4NackC1Ev_nid_postfix(void); int APS5_VABI _ZN6webrtc4rtcp4NackD1Ev_nid_postfix(void); -int APS5_VABI _ZN6webrtc4rtcp3Pli5ParseERKNS0_12CommonHeaderE_nid_postfix(void); +bool APS5_VABI _ZN6webrtc4rtcp3Pli5ParseERKNS0_12CommonHeaderE_nid_postfix(WebRtcRtcpPli* pli, const WebRtcRtcpCommonHeader* packet); int APS5_VABI _ZN6webrtc4rtcp3PliC1Ev_nid_postfix(void); int APS5_VABI _ZN6webrtc4rtcp3PliD1Ev_nid_postfix(void); int APS5_VABI _ZN6webrtc4rtcp14ReceiverReportC1Ev_nid_postfix(void); diff --git a/core/libs/prx/libwebrtc/include/RtcpPli.hpp b/core/libs/prx/libwebrtc/include/RtcpPli.hpp new file mode 100644 index 0000000000..405880cf8a --- /dev/null +++ b/core/libs/prx/libwebrtc/include/RtcpPli.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +constexpr std::size_t WebRtcRtcpCommonFeedbackSize = 8; + +struct WebRtcRtcpPli { + const void* vtable; + std::uint32_t senderSsrc; + std::uint32_t mediaSsrc; +}; + +static_assert(sizeof(WebRtcRtcpPli) == 16); +static_assert(offsetof(WebRtcRtcpPli, senderSsrc) == 8); +static_assert(offsetof(WebRtcRtcpPli, mediaSsrc) == 12); diff --git a/core/libs/prx/libwebrtc/src/RtcpPli.cpp b/core/libs/prx/libwebrtc/src/RtcpPli.cpp new file mode 100644 index 0000000000..7ca3445464 --- /dev/null +++ b/core/libs/prx/libwebrtc/src/RtcpPli.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * Copyright (c) 2011, The WebRTC project authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "prx/libwebrtc/libwebrtc.hpp" + +#include +#include + +namespace { + +std::uint32_t ReadBigEndian(const std::uint8_t* bytes) { + return (static_cast(bytes[0]) << 24) | (static_cast(bytes[1]) << 16) | (static_cast(bytes[2]) << 8) | bytes[3]; +} + +} + +extern "C" { + +bool APS5_VABI _ZN6webrtc4rtcp3Pli5ParseERKNS0_12CommonHeaderE_nid_postfix(WebRtcRtcpPli* pli, const WebRtcRtcpCommonHeader* packet) { + if (!pli) + throw std::invalid_argument("Pli::Parse requires a packet object"); + if (!packet) + throw std::invalid_argument("Pli::Parse requires a common header"); + if (packet->payloadSize < WebRtcRtcpCommonFeedbackSize) + return false; + if (!packet->payload) + throw std::invalid_argument("Pli::Parse requires a payload"); + pli->senderSsrc = ReadBigEndian(packet->payload); + pli->mediaSsrc = ReadBigEndian(packet->payload + 4); + return true; +} + +} diff --git a/core/libs/prx/libwebrtc/tests/RtcpPli.cpp b/core/libs/prx/libwebrtc/tests/RtcpPli.cpp new file mode 100644 index 0000000000..56b935ce79 --- /dev/null +++ b/core/libs/prx/libwebrtc/tests/RtcpPli.cpp @@ -0,0 +1,97 @@ +#include "prx/libwebrtc/libwebrtc.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "libwebrtc rtcp::Pli check failed at line %u\n", location.line()); + std::abort(); +} + +template void RequireInvalidArgument(TAction action, std::source_location location = std::source_location::current()) { + try { + action(); + } catch (const std::invalid_argument&) { + return; + } + Require(false, location); +} + +WebRtcRtcpPli Filled() { + WebRtcRtcpPli pli; + std::memset(&pli, 0xA5, sizeof(pli)); + return pli; +} + +bool Unchanged(const WebRtcRtcpPli& pli) { + WebRtcRtcpPli filled = Filled(); + return std::memcmp(&pli, &filled, sizeof(pli)) == 0; +} + +bool ParsePacket(WebRtcRtcpPli& pli, const std::vector& packet) { + WebRtcRtcpCommonHeader header; + Require(_ZN6webrtc4rtcp12CommonHeader5ParseEPKhm_nid_postfix(&header, packet.data(), packet.size())); + return _ZN6webrtc4rtcp3Pli5ParseERKNS0_12CommonHeaderE_nid_postfix(&pli, &header); +} + +void TestPli() { + std::vector packet {0x81, 206, 0x00, 0x02, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + WebRtcRtcpPli pli = Filled(); + WebRtcRtcpPli filled = Filled(); + Require(ParsePacket(pli, packet)); + Require(pli.senderSsrc == 0x12345678u); + Require(pli.mediaSsrc == 0x9ABCDEF0u); + Require(pli.vtable == filled.vtable); +} + +void TestLongerPayloadReadsTheCommonFeedbackOnly() { + std::vector packet {0x81, 206, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFE, 0x55, 0x55, 0x55, 0x55}; + WebRtcRtcpPli pli = Filled(); + Require(ParsePacket(pli, packet)); + Require(pli.senderSsrc == 1); + Require(pli.mediaSsrc == 0xFFFFFFFEu); +} + +void TestShortPayloadLeavesThePacketUntouched() { + std::vector empty {0x81, 206, 0x00, 0x00}; + WebRtcRtcpPli pli = Filled(); + Require(!ParsePacket(pli, empty)); + Require(Unchanged(pli)); + + std::vector padded {0xA1, 206, 0x00, 0x02, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0x01}; + Require(!ParsePacket(pli, padded)); + Require(Unchanged(pli)); + + WebRtcRtcpCommonHeader header {206, 1, 0, 7, nullptr}; + Require(!_ZN6webrtc4rtcp3Pli5ParseERKNS0_12CommonHeaderE_nid_postfix(&pli, &header)); + Require(Unchanged(pli)); +} + +void TestNullArgumentsThrow() { + std::vector payload(8, 0x11); + WebRtcRtcpCommonHeader header {206, 1, 0, 8, payload.data()}; + WebRtcRtcpPli pli = Filled(); + RequireInvalidArgument([&] { _ZN6webrtc4rtcp3Pli5ParseERKNS0_12CommonHeaderE_nid_postfix(nullptr, &header); }); + RequireInvalidArgument([&] { _ZN6webrtc4rtcp3Pli5ParseERKNS0_12CommonHeaderE_nid_postfix(&pli, nullptr); }); + header.payload = nullptr; + RequireInvalidArgument([&] { _ZN6webrtc4rtcp3Pli5ParseERKNS0_12CommonHeaderE_nid_postfix(&pli, &header); }); + Require(Unchanged(pli)); +} + +} + +int main() { + TestPli(); + TestLongerPayloadReadsTheCommonFeedbackOnly(); + TestShortPayloadLeavesThePacketUntouched(); + TestNullArgumentsThrow(); + return 0; +} diff --git a/core/libs/tests/BufferAtomicCsubTranslation.cpp b/core/libs/tests/BufferAtomicCsubTranslation.cpp new file mode 100644 index 0000000000..5530233d07 --- /dev/null +++ b/core/libs/tests/BufferAtomicCsubTranslation.cpp @@ -0,0 +1,60 @@ +#include "Translation/TranslationContext.hpp" +#include "RdnaDecoder/RdnaMemoryOpDecoder.hpp" +#include +#include +#include +#include + +using namespace ShaderRecompiler; + +namespace { + +void Require(bool value, const std::string& message) { + if (!value) throw std::runtime_error("buffer atomic csub: " + message); +} + +void CheckTranslation(bool glc) { + constexpr std::uint32_t destination = 5u; + constexpr std::uint32_t opcode = 0x34u; + const std::array code{ + (0x38u << 26u) | (opcode << 18u) | (static_cast(glc) << 14u) | (1u << 12u), + (10u << 24u) | (4u << 16u) | (destination << 8u) | 2u + }; + const RdnaInstruction instruction = DecodeRdnaMubuf(0u, code, 0u); + Require(instruction.op == RdnaOpcode::BufferAtomicCsub, "instruction does not decode to BufferAtomicCsub"); + Require(instruction.glc == glc, "instruction glc flag mismatch"); + Require(instruction.destination.reg == destination, "destination register index mismatch"); + + IrProgram program; + auto& block = program.CreateBlock(); + program.SetEntryBlock(block); + TranslationContext context(program, block, 256); + context.TranslateInstruction(instruction); + + const IrValue* atomicOperation = nullptr; + const IrValue* writeOperation = nullptr; + + for (const auto* value : block.Instructions()) { + if (value->Opcode() == IrOpcode::BufferAtomicUSubSat32) { + atomicOperation = value; + } + if (value->Opcode() == IrOpcode::SetVectorRegister && value->Argument(0)->Register().index == destination) { + writeOperation = value; + } + } + + Require(atomicOperation != nullptr, "BufferAtomicUSubSat32 was not emitted"); + if (glc) { + Require(writeOperation != nullptr, "destination register was not written when glc is set"); + } else { + Require(writeOperation == nullptr, "destination register was written when glc is clear"); + } +} + +} + +int main() { + CheckTranslation(false); + CheckTranslation(true); + return 0; +} diff --git a/core/libs/tests/Fiber.cpp b/core/libs/tests/Fiber.cpp index d4eedb1c3a..7850d50164 100644 --- a/core/libs/tests/Fiber.cpp +++ b/core/libs/tests/Fiber.cpp @@ -566,6 +566,23 @@ int main(int argc, char** argv) { #endif FiberObject* self = nullptr; Require(sceFiberGetSelf(&self) == FiberErrorPermission, "no fiber on the thread"); + Require(sceFiberOptParamInitialize(nullptr) == FiberErrorNull, "null opt param"); + Require(sceFiberOptParamInitialize(reinterpret_cast(0x10)) == FiberErrorInvalid, "unwritable opt param"); + struct alignas(8) AlignedOptParamBuffer { + std::uint8_t bytes[0x80]; + std::uint32_t sentinel = 0x5a5a5a5a; + } storage; + std::memset(storage.bytes, 0xA5, sizeof(storage.bytes)); + Require(sceFiberOptParamInitialize(reinterpret_cast(storage.bytes)) == 0, "initialize opt param"); + bool allZero = true; + for (std::size_t i = 0; i < sizeof(storage.bytes); ++i) { + if (storage.bytes[i] != 0) { + allZero = false; + break; + } + } + Require(allZero, "opt param cleared"); + Require(storage.sentinel == 0x5a5a5a5a, "sentinel intact"); Require(_sceFiberInitializeImpl_nid_postfix(g_first, "first", FirstEntry, 11, g_firstContext.data(), g_firstContext.size(), nullptr, 0) == 0, "initialize first"); Require(_sceFiberInitializeImpl_nid_postfix(g_second, "second", SecondEntry, 22, g_secondContext.data(), g_secondContext.size(), nullptr, 0) == 0, "initialize second"); const auto threadCsr = _mm_getcsr(); diff --git a/core/libs/tests/Formatting.cpp b/core/libs/tests/Formatting.cpp index 3bc44824c1..ef482ad3fd 100644 --- a/core/libs/tests/Formatting.cpp +++ b/core/libs/tests/Formatting.cpp @@ -114,6 +114,21 @@ static int APS5_VABI PrintList(const char* format, ...) { return result; } +static void CheckPositionalUnknownConversion() { + char output[32]; + for (const std::uint32_t gpOffset : {0u, 48u}) { + VaList args = {gpOffset, 176u, nullptr, nullptr}; + const VaList original = args; + for (const char* format : {"%1$C", "%2$C", "%1$hC"}) { + Require(vsnprintf_nid_postfix(output, sizeof(output), format, &args) == 2 && std::strcmp(output, "%C") == 0); + Require(std::memcmp(&args, &original, sizeof(args)) == 0); + } + Require(vsnprintf_nid_postfix(output, sizeof(output), "[%1$5C]", &args) == 7 && std::strcmp(output, "[ %C]") == 0); + Require(std::memcmp(&args, &original, sizeof(args)) == 0); + } + Require(FormatList(output, sizeof(output), "%1$C%d", 7) == 3 && std::strcmp(output, "%C7") == 0); +} + static void CheckIgnoredLengths() { CheckCharacterScanLengths(); char output[96]; @@ -231,10 +246,20 @@ static bool CheckWidePrecision() { Require(snprintf_nid_postfix(output, sizeof(output), "%.2ls", bounded) == 2 && std::strcmp(output, "AB") == 0); Require(snprintf_nid_postfix(nullptr, 0, "%.1ls", u"\u00e9") == 0); Require(snprintf_nid_postfix(output, sizeof(output), "%.1s", "\xc3\xa9") == 1 && static_cast(output[0]) == 0xc3 && output[1] == 0); - Require(FormatList(output, sizeof(output), "%S:%lc:%C:%d", u"\u00e9", 0x20ac, 0x41, 7) == 10 && std::strcmp(output, "\xc3\xa9:\xe2\x82\xac:A:7") == 0); + Require(FormatList(output, sizeof(output), "%S:%lc:%C:%d", u"\u00e9", 0x20ac, 0x41, 7) == 12 && std::strcmp(output, "\xc3\xa9:\xe2\x82\xac:%C:65") == 0); Require(snprintf_nid_postfix(output, sizeof(output), "%.*S", 3, u"\u00e9\u20ac") == 2 && std::strcmp(output, "\xc3\xa9") == 0); Require(snprintf_nid_postfix(output, sizeof(output), "[%ls]", static_cast(nullptr)) == 8 && std::strcmp(output, "[(null)]") == 0); - Require(snprintf_nid_postfix(output, sizeof(output), "a%lcb%C", 0, 0) == 4 && std::memcmp(output, "a\0b\0", 5) == 0); + Require(snprintf_nid_postfix(output, sizeof(output), "a%lcb%C", 0, 0) == 4 && std::strcmp(output, "ab%C") == 0); + Require(snprintf_nid_postfix(output, sizeof(output), "%3lc|%-3lc|", 0, 0) == 2 && std::strcmp(output, "||") == 0); + for (const char* format : {"%lc", "%.0lc", "%.1lc"}) + Require(snprintf_nid_postfix(output, sizeof(output), format, 0xe9) == 2 && std::strcmp(output, "\xc3\xa9") == 0); + Require(snprintf_nid_postfix(output, sizeof(output), "%5lc|%-5lc|%5.1lc", 0xe9, 0xe9, 0xe9) == 17 + && std::strcmp(output, " \xc3\xa9|\xc3\xa9 | \xc3\xa9") == 0); + Require(FormatList(output, sizeof(output), "%*.*lc", 5, 1, 0xe9) == 5 && std::strcmp(output, " \xc3\xa9") == 0); + Require(snprintf_nid_postfix(output, sizeof(output), "%3.0lc|%-3.1lc", 0, 0) == 1 && std::strcmp(output, "|") == 0); + Require(snprintf_nid_postfix(output, sizeof(output), "%5C|%-3C|%hC%lC%#C", 0xe9) == 16 && std::strcmp(output, " %C|%C |%C%C%C") == 0); + Require(snprintf_nid_postfix(output, sizeof(output), "%C%d", 65) == 4 && std::strcmp(output, "%C65") == 0); + Require(snprintf_nid_postfix(output, sizeof(output), "%hS|%lS|%5S", u"ab", u"c\u00e9", u"d") == 12 && std::strcmp(output, "ab|c\xc3\xa9| d") == 0); Require(FormatList(output, sizeof(output), "%%ls:%2$d:%1$d", 3, 7) == 7 && std::strcmp(output, "%ls:7:3") == 0); Require(FormatList(output, sizeof(output), "%ls[%f][%+f][% F][%5.1e]", u"", -NAN, -NAN, NAN, -NAN) == 25 && std::strcmp(output, "[-nan][-nan][ NAN][ -nan]") == 0); @@ -404,7 +429,7 @@ __attribute__((noinline)) static void APS5_VABI RunChecks() { Require(std::strcmp(buffer, "[ 3.50][3.5 ][ z]") == 0); Require(sprintf_nid_postfix(buffer, "[%2$*1$d][%2$.*1$d]", -4, 5) == 9 && std::strcmp(buffer, "[5 ][5]") == 0); Require(sprintf_nid_postfix(buffer, "[%1$*2$d]", 5, 3) == 5 && std::strcmp(buffer, "[ 5]") == 0); - Require(FormatList(buffer, sizeof(buffer), "%2$ls|%1$C|%3$d", 0x41, u"\u00e9", 7) == 6 && std::strcmp(buffer, "\xc3\xa9|A|7") == 0); + Require(FormatList(buffer, sizeof(buffer), "%2$ls|%1$lc|%3$d", 0x41, u"\u00e9", 7) == 6 && std::strcmp(buffer, "\xc3\xa9|A|7") == 0); Require(FormatList(buffer, sizeof(buffer), "%2$d %d %ls|%*5$d|%1$d", 1, 2, 3, u"x", 4) == 12 && std::strcmp(buffer, "2 3 x| 4|1") == 0); Require(FormatList(buffer, sizeof(buffer), "[%*3$d]%ls", 5, u"", 3) == 5 && std::strcmp(buffer, "[ 5]") == 0); Require(FormatList(buffer, sizeof(buffer), "%100$d %2$d%1$ls", u"", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100) == 4 && std::strcmp(buffer, "99 1") == 0); @@ -775,4 +800,4 @@ static void CheckLongDoubleHexFloat() { } } -int main() { CheckSizeLimits(); CheckIgnoredLengths(); RunChecks(); CheckHexFloat(); CheckDecimalFloat(); CheckLongDoubleHexFloat(); CheckAllocatingFormat(); return CheckWidePrecision() ? 0 : 1; } +int main() { CheckPositionalUnknownConversion(); CheckSizeLimits(); CheckIgnoredLengths(); RunChecks(); CheckHexFloat(); CheckDecimalFloat(); CheckLongDoubleHexFloat(); CheckAllocatingFormat(); return CheckWidePrecision() ? 0 : 1; } diff --git a/core/libs/tests/GuestAppContent.cpp b/core/libs/tests/GuestAppContent.cpp index a57e43df2c..1afcef1452 100644 --- a/core/libs/tests/GuestAppContent.cpp +++ b/core/libs/tests/GuestAppContent.cpp @@ -15,6 +15,7 @@ static constexpr int ErrorNotFound = static_cast(0x80D90005); static constexpr int ErrorDrmNoEntitlement = static_cast(0x80D90007); static constexpr int ErrorBusy = static_cast(0x80D90003); static constexpr int ErrorNotMounted = static_cast(0x80D90004); +static constexpr int ErrorGetStoreCountry = static_cast(0x80D91000); static void Require(bool value) { if (!value) std::abort(); } int main() { @@ -38,6 +39,7 @@ int main() { Require(sceAppContentTemporaryDataGetAvailableSpaceKb(nullptr, nullptr) == notInitialized); Require(sceAppContentDownloadDataFormat(nullptr) == notInitialized); Require(sceAppContentDownloadDataGetAvailableSpaceKb(nullptr, nullptr) == notInitialized); + Require(sceAppContentDownload1Expand(1024) == notInitialized); Require(sceAppContentDownload2Expand(1024) == notInitialized); Require(sceAppContentAddcontDelete(0, nullptr) == notInitialized); Require(sceAppContentAddcontEnqueueDownload(0, nullptr) == notInitialized); @@ -51,6 +53,7 @@ int main() { std::uint32_t pftFlag = 0x5a5a5a5a; Require(sceAppContentGetPftFlag(&pftFlag) == notInitialized); Require(pftFlag == 0x5a5a5a5a); + Require(sceAppContentGetDownloadedStoreCountry(0, nullptr, nullptr) == notInitialized); const auto original = std::filesystem::current_path(); const auto directory = original / ("appcontent-test-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); Require(std::filesystem::create_directory(directory)); @@ -94,6 +97,7 @@ int main() { Require(sceAppContentDownloadDataGetAvailableSpaceKb(nullptr, nullptr) == ErrorParameter); AppContentMountPoint invalidMount{}; Require(sceAppContentDownloadDataGetAvailableSpaceKb(&invalidMount, &availableKb) == ErrorNotMounted); + Require(availableKb == 12345); const auto downloadData = ResolvePath_nid_no_patch("/download0"); Require(sceAppContentDownloadDataFormat(&download) == 0); Require(std::filesystem::is_directory(downloadData) && std::filesystem::is_empty(downloadData)); @@ -123,6 +127,25 @@ int main() { Require(sceAppContentDownload2Expand(1024 * 1024) == 0); Require(sceAppContentDownloadDataGetAvailableSpaceKb(&download2Mount, &download2Space) == 0); Require(download2Space == 2048); + AppContentMountPoint download1Mount{}; + std::memcpy(download1Mount.data, "/download1", 11); + const auto download1Data = ResolvePath_nid_no_patch("/download1"); + std::filesystem::create_directories(download1Data); + { std::ofstream file(download1Data / "old.bin"); file << "download1"; } + Require(sceAppContentDownloadDataFormat(&download1Mount) == 0); + Require(std::filesystem::is_directory(download1Data) && std::filesystem::is_empty(download1Data)); + Require(sceAppContentDownload1Expand(0) == ErrorParameter); + Require(sceAppContentDownload1Expand(2 * 1024 * 1024) == 0); + Require(std::filesystem::is_directory(download1Data)); + size_t download1Space = 12345; + Require(sceAppContentDownloadDataGetAvailableSpaceKb(&download1Mount, &download1Space) == 0); + Require(download1Space == 2048); + Require(sceAppContentDownload1Expand(1024 * 1024) == 0); + Require(sceAppContentDownloadDataGetAvailableSpaceKb(&download1Mount, &download1Space) == 0); + Require(download1Space == 2048); + { std::ofstream file(download1Data / "used.bin", std::ios::binary); file << std::string(1024, 'x'); } + Require(sceAppContentDownloadDataGetAvailableSpaceKb(&download1Mount, &download1Space) == 0); + Require(download1Space == 2047); const int32_t expectedParams[] = {3, 23566, -7, 0, 2147483647}; for (uint32_t id = 0; id < 5; ++id) { @@ -257,6 +280,15 @@ int main() { Require(sceAppContentGetEntitlementKey(0, nullptr, &key) == ErrorParameter); Require(std::memcmp(&key, &untouchedKey, sizeof(key)) == 0); Require(sceAppContentGetEntitlementKey(0, &label, nullptr) == ErrorParameter); + unsigned char country[24]; + std::memset(country, 0x5a, sizeof(country)); + unsigned char untouchedCountry[sizeof(country)]; + std::memcpy(untouchedCountry, country, sizeof(country)); + Require(sceAppContentGetDownloadedStoreCountry(0, nullptr, country) == ErrorParameter); + Require(sceAppContentGetDownloadedStoreCountry(0, &label, nullptr) == ErrorParameter); + Require(sceAppContentGetDownloadedStoreCountry(0, &label, country) == ErrorGetStoreCountry); + Require(sceAppContentGetDownloadedStoreCountry(7, &label, country) == ErrorGetStoreCountry); + Require(std::memcmp(country, untouchedCountry, sizeof(country)) == 0); for (const std::uint32_t version : {0x014fffffu, 0x01500000u, 0x034fffffu, 0x03500000u}) { std::memcpy(process.data() + 16, &version, sizeof(version)); @@ -265,7 +297,10 @@ int main() { const int earlyError = version >= 0x01500000 ? ErrorParameter : ErrorNotFound; const int infoError = version >= 0x01500000 ? ErrorParameter : ErrorDrmNoEntitlement; const int lateError = version >= 0x03500000 ? ErrorParameter : ErrorNotFound; + const int countryError = version >= 0x01500000 ? ErrorParameter : ErrorGetStoreCountry; Require(sceAppContentAddcontMount(0, &label, &mountPoint) == earlyError); + Require(sceAppContentGetDownloadedStoreCountry(0, &label, info) == countryError); + Require(std::memcmp(info, untouchedInfo, sizeof(info)) == 0); Require(sceAppContentGetAddcontInfo(0, &label, reinterpret_cast(info)) == infoError); Require(sceAppContentGetEntitlementKey(0, &label, &key) == infoError); Require(std::memcmp(&key, &untouchedKey, sizeof(key)) == 0); diff --git a/core/libs/tests/GuestAudioIn.cpp b/core/libs/tests/GuestAudioIn.cpp index 7fcbb2a879..4c00bcca04 100644 --- a/core/libs/tests/GuestAudioIn.cpp +++ b/core/libs/tests/GuestAudioIn.cpp @@ -84,6 +84,7 @@ void TestValidation() { Require(sceAudioInGetGain(badHandle, &gain) == invalidHandle && gain == 1.0f); Require(sceAudioInGetRerouteCount(badHandle) == invalidHandle); Require(sceAudioInGetGain(badHandle, nullptr) == invalidPointer); + Require(sceAudioInGetGain(badHandle, reinterpret_cast(static_cast(0x10))) == invalidPointer); Require(sceAudioInClose(badHandle) == invalidHandle); Require(sceAudioInInput(badHandle, nullptr) == invalidHandle); Require(sceAudioInGetSilentState(badHandle) == invalidHandle); @@ -92,6 +93,7 @@ void TestValidation() { float gain = 0.0f; Require(port > 0 && sceAudioInGetGain(port, &gain) == 0 && gain == 1.0f && sceAudioInGetRerouteCount(port) == 0); Require(sceAudioInGetGain(port, nullptr) == invalidPointer); + Require(sceAudioInGetGain(port, reinterpret_cast(static_cast(0x10))) == invalidPointer); Require(sceAudioInClose(port) == 0); gain = 0.25f; Require(sceAudioInGetGain(port, &gain) == invalidHandle && gain == 1.0f && sceAudioInGetRerouteCount(port) == invalidHandle); diff --git a/core/libs/tests/GuestBemp2sys.cpp b/core/libs/tests/GuestBemp2sys.cpp new file mode 100644 index 0000000000..66f679336b --- /dev/null +++ b/core/libs/tests/GuestBemp2sys.cpp @@ -0,0 +1,27 @@ +#include "prx/libSceBemp2sys/libSceBemp2sys.hpp" +#include +#include + +static void Require(bool value) { if (!value) std::abort(); } + +namespace { + +void LogFunction(int, const char*) {} +void* Allocate(std::size_t) { return nullptr; } +void Deallocate(void*) {} +void* Reallocate(void*, std::size_t) { return nullptr; } + +} + +int main() { + Require(bemp2sys_logprintf_getlevel_nid_postfix() == 0); + bemp2sys_logprintf_setlevel_nid_postfix(3); + Require(bemp2sys_logprintf_getlevel_nid_postfix() == 3); + bemp2sys_logprintf_setlevel_nid_postfix(4); + Require(bemp2sys_logprintf_getlevel_nid_postfix() == 3); + bemp2sys_logprintf_setlevel_nid_postfix(-1); + Require(bemp2sys_logprintf_getlevel_nid_postfix() == 3); + bemp2sys_logprintf_setfunc_nid_postfix(reinterpret_cast(&LogFunction)); + bemp2sys_memmanager_set_nid_postfix(reinterpret_cast(&Allocate), reinterpret_cast(&Deallocate), reinterpret_cast(&Reallocate)); + return 0; +} diff --git a/core/libs/tests/GuestBoundsChecking.cpp b/core/libs/tests/GuestBoundsChecking.cpp index f40aae0255..a081e0650c 100644 --- a/core/libs/tests/GuestBoundsChecking.cpp +++ b/core/libs/tests/GuestBoundsChecking.cpp @@ -137,6 +137,64 @@ void CheckWcstok() { RequireViolation(before, "wcstok_s: bad argument", 22); } +void CheckStrtok() { + char text[] = ",,ab;c,,;"; + std::size_t remaining = sizeof(text); + char* context = nullptr; + char* token = strtok_s_nid_postfix(text, &remaining, ",;", &context); + Require(token == text + 2 && std::strcmp(token, "ab") == 0 && remaining == 5 && context == text + 5, "strtok_s did not return the first token"); + token = strtok_s_nid_postfix(nullptr, &remaining, ",;", &context); + Require(token == text + 5 && std::strcmp(token, "c") == 0 && remaining == 3 && context == text + 7, "strtok_s did not return the second token"); + token = strtok_s_nid_postfix(nullptr, &remaining, ",;", &context); + Require(token == nullptr && remaining == 1 && context == text + 9, "strtok_s did not stop at the end of the string"); + + int before = constraintCalls; + char unterminated[] = "abc"; + remaining = 2; + Require(strtok_s_nid_postfix(unterminated, &remaining, ",;", &context) == nullptr, "strtok_s read past s1max"); + RequireViolation(before, "strtok_s: missing delimiter", 22); + before = constraintCalls; + char separators[] = ",,,x"; + remaining = 3; + Require(strtok_s_nid_postfix(separators, &remaining, ",;", &context) == nullptr, "strtok_s skipped past s1max"); + RequireViolation(before, "strtok_s: missing token", 22); + before = constraintCalls; + remaining = 0; + Require(strtok_s_nid_postfix(separators, &remaining, ",;", &context) == nullptr, "strtok_s accepted a zero s1max"); + RequireViolation(before, "strtok_s: missing token", 22); + before = constraintCalls; + remaining = tooLarge; + Require(strtok_s_nid_postfix(separators, &remaining, ",;", &context) == nullptr, "strtok_s accepted an oversized s1max"); + RequireViolation(before, "strtok_s: bad argument", 22); + before = constraintCalls; + context = nullptr; + remaining = 4; + Require(strtok_s_nid_postfix(nullptr, &remaining, ",;", &context) == nullptr, "strtok_s accepted a null context string"); + RequireViolation(before, "strtok_s: bad argument", 22); + before = constraintCalls; + Require(strtok_s_nid_postfix(separators, &remaining, nullptr, &context) == nullptr, "strtok_s accepted null delimiters"); + RequireViolation(before, "strtok_s: bad argument", 22); + before = constraintCalls; + Require(strtok_s_nid_postfix(separators, nullptr, ",;", &context) == nullptr, "strtok_s accepted a null s1max"); + RequireViolation(before, "strtok_s: bad argument", 22); + before = constraintCalls; + Require(strtok_s_nid_postfix(separators, &remaining, ",;", nullptr) == nullptr && remaining == 4, "strtok_s accepted a null ptr"); + RequireViolation(before, "strtok_s: bad argument", 22); + before = constraintCalls; + char exact[] = "ab,"; + remaining = 2; + context = nullptr; + Require(strtok_s_nid_postfix(exact, &remaining, ",;", &context) == nullptr && remaining == 2 && context == nullptr, "strtok_s accepted a token that fills s1max"); + RequireViolation(before, "strtok_s: missing delimiter", 22); + before = constraintCalls; + char largest[] = ",x"; + remaining = tooLarge - 1; + Require(strtok_s_nid_postfix(largest, &remaining, ",;", &context) == largest + 1 && remaining == tooLarge - 3 && context == largest + 2 && constraintCalls == before, "strtok_s rejected the largest s1max"); + remaining = 3; + Require(strtok_s_nid_postfix(separators, &remaining, ",;", &context) == nullptr && remaining == 3 && context == largest + 2, "strtok_s changed its state on a missing token"); + RequireViolation(before, "strtok_s: missing token", 22); +} + void CheckWideToMultibyte() { int status = 7; char bytes[4] = {'x', 'x', 'x', 'x'}; @@ -261,6 +319,7 @@ int main(int argc, char** argv) { CheckBsearch(); CheckWcstok(); + CheckStrtok(); CheckWideToMultibyte(); CheckMultibyteToWide(); CheckGets(argv[1]); diff --git a/core/libs/tests/GuestCairoFontOptions.cpp b/core/libs/tests/GuestCairoFontOptions.cpp new file mode 100644 index 0000000000..c7e546bd2a --- /dev/null +++ b/core/libs/tests/GuestCairoFontOptions.cpp @@ -0,0 +1,102 @@ +#include "prx/libcairo/include/InventoryExports.hpp" +#include +#include +#include +#include + +namespace { + +struct Values { + cairo_antialias_t antialias; + cairo_subpixel_order_t subpixel_order; + cairo_hint_style_t hint_style; + cairo_hint_metrics_t hint_metrics; +}; + +static_assert(sizeof(Values) == sizeof(int) * 4); +static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(std::is_same_v); + +void Require(bool valid, const char* reason) { + if (valid) return; + std::fprintf(stderr, "cairo font options: %s\n", reason); + std::abort(); +} + +Values ReadValues(const cairo_font_options_t* options) { + Values values{}; + std::memcpy(&values, options, sizeof(values)); + return values; +} + +void RequireValues(const cairo_font_options_t* options, Values expected) { + auto values = ReadValues(options); + Require(values.antialias == expected.antialias, "antialias was not retained"); + Require(values.subpixel_order == expected.subpixel_order, "subpixel order was not retained"); + Require(values.hint_style == expected.hint_style, "hint style was not retained"); + Require(values.hint_metrics == expected.hint_metrics, "hint metrics were not retained"); +} + +} + +int main() { + Require(CAIRO_ANTIALIAS_FAST == 4 && CAIRO_ANTIALIAS_GOOD == 5 && CAIRO_ANTIALIAS_BEST == 6, + "antialias enum values differ from Cairo"); + Require(CAIRO_SUBPIXEL_ORDER_VBGR == 4 && CAIRO_HINT_STYLE_FULL == 4 && CAIRO_HINT_METRICS_ON == 2, + "font hinting enum values differ from Cairo"); + + auto* nil = cairo_font_options_copy(nullptr); + Require(nil != nullptr && nil == cairo_font_options_copy(nil), "null copy does not return the shared error object"); + Require(cairo_font_options_get_antialias(nullptr) == CAIRO_ANTIALIAS_DEFAULT, + "null getter does not return the default antialias mode"); + cairo_font_options_set_antialias(nullptr, CAIRO_ANTIALIAS_BEST); + cairo_font_options_set_hint_style(nullptr, CAIRO_HINT_STYLE_FULL); + cairo_font_options_set_hint_metrics(nullptr, CAIRO_HINT_METRICS_ON); + cairo_font_options_set_subpixel_order(nullptr, CAIRO_SUBPIXEL_ORDER_VBGR); + cairo_font_options_set_antialias(nil, CAIRO_ANTIALIAS_BEST); + cairo_font_options_set_hint_style(nil, CAIRO_HINT_STYLE_FULL); + cairo_font_options_set_hint_metrics(nil, CAIRO_HINT_METRICS_ON); + cairo_font_options_set_subpixel_order(nil, CAIRO_SUBPIXEL_ORDER_VBGR); + Require(cairo_font_options_get_antialias(nil) == CAIRO_ANTIALIAS_DEFAULT, + "error object can be changed"); + cairo_font_options_destroy(nullptr); + cairo_font_options_destroy(nil); + + for (int iteration = 0; iteration != 4096; ++iteration) { + auto* original = cairo_font_options_create(); + Require(original != nullptr && original != nil, "create did not allocate a mutable object"); + RequireValues(original, {CAIRO_ANTIALIAS_DEFAULT, CAIRO_SUBPIXEL_ORDER_DEFAULT, + CAIRO_HINT_STYLE_DEFAULT, CAIRO_HINT_METRICS_DEFAULT}); + Require(cairo_font_options_get_antialias(original) == CAIRO_ANTIALIAS_DEFAULT, + "new options have nondefault antialiasing"); + + const auto antialias = static_cast(iteration % 9 - 1); + const auto subpixel = static_cast(iteration % 7 - 1); + const auto style = static_cast(iteration % 8 - 1); + const auto metrics = static_cast(iteration % 5 - 1); + cairo_font_options_set_antialias(original, antialias); + cairo_font_options_set_subpixel_order(original, subpixel); + cairo_font_options_set_hint_style(original, style); + cairo_font_options_set_hint_metrics(original, metrics); + RequireValues(original, {antialias, subpixel, style, metrics}); + Require(cairo_font_options_get_antialias(original) == antialias, + "getter rejects an enum value that Cairo accepts"); + + auto* copy = cairo_font_options_copy(original); + Require(copy != nullptr && copy != original && copy != nil, "copy did not allocate a new object"); + RequireValues(copy, {antialias, subpixel, style, metrics}); + cairo_font_options_set_antialias(copy, CAIRO_ANTIALIAS_BEST); + cairo_font_options_set_subpixel_order(copy, CAIRO_SUBPIXEL_ORDER_VBGR); + cairo_font_options_set_hint_style(copy, CAIRO_HINT_STYLE_FULL); + cairo_font_options_set_hint_metrics(copy, CAIRO_HINT_METRICS_ON); + RequireValues(original, {antialias, subpixel, style, metrics}); + cairo_font_options_destroy(original); + RequireValues(copy, {CAIRO_ANTIALIAS_BEST, CAIRO_SUBPIXEL_ORDER_VBGR, + CAIRO_HINT_STYLE_FULL, CAIRO_HINT_METRICS_ON}); + cairo_font_options_destroy(copy); + } + + std::puts("cairo font options tests passed"); + return 0; +} diff --git a/core/libs/tests/GuestCesJiscs.cpp b/core/libs/tests/GuestCesJiscs.cpp new file mode 100644 index 0000000000..9d292a959e --- /dev/null +++ b/core/libs/tests/GuestCesJiscs.cpp @@ -0,0 +1,143 @@ +#include "prx/libSceCesCs/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +namespace { + +constexpr int invalidProfile = static_cast(0x805C0004); +constexpr int illegalCode = static_cast(0x805C0015); +constexpr int unassignedCode = static_cast(0x805C0020); +constexpr int invalidDstBuffer = static_cast(0x805C0030); +constexpr std::uint64_t fnvBasis = 0xCBF29CE484222325ULL; +constexpr std::uint32_t poison = 0xDEADBEEF; + +void Require(bool value) { if (!value) std::abort(); } + +std::uint64_t Fnv(std::uint64_t hash, std::uint64_t value, int bytes) { + for (int i = 0; i < bytes; ++i) { + hash ^= value >> (8 * i) & 0xFF; + hash *= 0x100000001B3ULL; + } + return hash; +} + +std::uint64_t Pair(std::uint64_t hash, int result, std::uint32_t out) { + return Fnv(Fnv(hash, static_cast(result), 4), out, 4); +} + +std::uint64_t JisRows(const void* profile) { + std::uint64_t fold = fnvBasis; + for (std::uint32_t plane = 0; plane < 3; ++plane) { + for (std::uint32_t row = 0x21; row <= 0x7E; ++row) { + std::uint64_t hash = fnvBasis; + for (std::uint32_t cell = 0x21; cell <= 0x7E; ++cell) { + std::uint32_t out = poison; + const int result = sceCesJiscsToUcs(profile, plane << 16 | row << 8 | cell, &out); + hash = Pair(hash, result, out); + } + fold = Fnv(fold, hash, 8); + } + } + return fold; +} + +std::uint64_t UcsBlocks(const void* profile, std::uint32_t plane) { + std::uint64_t fold = fnvBasis; + for (std::uint32_t base = plane << 16; base < (plane + 1) << 16; base += 256) { + std::uint64_t hash = fnvBasis; + for (std::uint32_t code = base; code < base + 256; ++code) { + std::uint32_t out = poison; + const int result = sceCesUcsToJiscs(code, profile, &out); + hash = Pair(hash, result, out); + } + fold = Fnv(fold, hash, 8); + } + return fold; +} + +std::uint64_t LowScan(const void* profile, bool fromUcs) { + std::uint64_t hash = fnvBasis; + for (std::uint32_t value = 0; value < 256; ++value) { + std::uint32_t out = poison; + const int result = fromUcs ? sceCesUcsToJiscs(value, profile, &out) : sceCesJiscsToUcs(profile, value, &out); + hash = Pair(hash, result, out); + } + return hash; +} + +using ProfileInit = void* (APS5_VABI*)(void*); + +struct Expected { + ProfileInit init; + std::uint64_t rows; + std::uint64_t bmp; + std::uint64_t bytes; + std::uint64_t low; +}; + +const Expected expected[8] = { + {sceCesUcsProfileInitSJis1997X0208, 0x0A43EFAC27D27F0BULL, 0xC95A209A94B4733DULL, 0xF318D248701074E5ULL, 0xFE1F5C05D3553969ULL}, + {sceCesUcsProfileInitSJis1997Cp932, 0xB41491CD924A5628ULL, 0x7B9BC09A31B566BBULL, 0x6FBF52A74848866CULL, 0xC3FEB7F6573CCCE8ULL}, + {sceCesUcsProfileInitEucJpX0208, 0x6039B027421C7715ULL, 0xB808FE1D63F85985ULL, 0xF30BF109ED45C325ULL, 0xC3FEB7F6573CCCE8ULL}, + {sceCesUcsProfileInitEucJpX0208Ss2, 0x6039B027421C7715ULL, 0xD35C4BD66A76E55BULL, 0x6FBF52A74848866CULL, 0xC3FEB7F6573CCCE8ULL}, + {sceCesUcsProfileInitEucJpX0208Ss2Ss3, 0x6039B027421C7715ULL, 0x0775772C00CF90CCULL, 0x6FBF52A74848866CULL, 0x43D15427FBD6742FULL}, + {sceCesUcsProfileInitEucJpCp51932, 0xB41491CD924A5628ULL, 0x7B9BC09A31B566BBULL, 0x6FBF52A74848866CULL, 0xC3FEB7F6573CCCE8ULL}, + {sceCesUcsProfileInitSJis2004X0213, 0x2D347DA414591DB8ULL, 0x5E9E46E4D0A6B7E1ULL, 0x6FBF52A74848866CULL, 0x1D71EC505E9C63E2ULL}, + {sceCesUcsProfileInitEucJis2004, 0x2D347DA414591DB8ULL, 0xC5C280A43F74E4ECULL, 0x6FBF52A74848866CULL, 0x1D71EC505E9C63E2ULL}, +}; + +constexpr std::uint64_t jis2004Plane2 = 0xBE5A8D1261719CCAULL; + +void RequireDecode(const void* profile, std::uint32_t jiscs, int error, std::uint32_t code) { + std::uint32_t out = poison; + Require(sceCesJiscsToUcs(profile, jiscs, &out) == error && out == code); +} + +void RequireEncode(const void* profile, std::uint32_t code, int error, std::uint32_t jiscs) { + std::uint32_t out = poison; + Require(sceCesUcsToJiscs(code, profile, &out) == error && out == jiscs); +} + +} + +int main() { + alignas(8) static std::uint8_t sheets[8][64] = {}; + const void* profiles[8] = {}; + for (int i = 0; i < 8; ++i) { + profiles[i] = expected[i].init(sheets[i]); + Require(profiles[i] == sheets[i]); + Require(JisRows(profiles[i]) == expected[i].rows); + Require(UcsBlocks(profiles[i], 0) == expected[i].bmp); + Require(LowScan(profiles[i], false) == expected[i].bytes); + Require(LowScan(profiles[i], true) == expected[i].low); + } + Require(UcsBlocks(profiles[6], 2) == jis2004Plane2); + Require(UcsBlocks(profiles[7], 2) == jis2004Plane2); + + RequireDecode(profiles[0], 0x2422, 0, 0x3042); + RequireDecode(profiles[0], 0x2140, 0, 0x005C); + RequireDecode(profiles[1], 0x7921, 0, 0x7E8A); + RequireDecode(profiles[6], 0x7E7E, 0, 0x7E6B); + RequireDecode(profiles[0], 0x7E7E, unassignedCode, 0); + RequireDecode(profiles[7], 0x12121, illegalCode, 0); + RequireEncode(profiles[0], 0x3042, 0, 0x03010402); + RequireEncode(profiles[0], 0x00A5, 0, 0x5C); + RequireEncode(profiles[1], 0x2160, 0, 0x03010D15); + RequireEncode(profiles[4], 0x4E02, 0, 0x04001001); + RequireEncode(profiles[7], 0x4E02, 0, 0x03020102); + RequireEncode(profiles[6], 0x20B9F, 0, 0x03012F34); + RequireEncode(profiles[2], 0xFF61, unassignedCode, 0); + RequireEncode(profiles[0], 0xD800, illegalCode, 0); + RequireEncode(profiles[0], 0x110000, illegalCode, 0); + + RequireDecode(nullptr, 0x2422, invalidProfile, 0); + RequireDecode(nullptr, 0x12121, invalidProfile, 0); + RequireEncode(nullptr, 0x3042, invalidProfile, 0); + RequireEncode(nullptr, 0x20B9F, invalidProfile, 0); + RequireEncode(nullptr, 0xFFFFFFFF, illegalCode, 0); + Require(sceCesJiscsToUcs(profiles[0], 0x2422, nullptr) == invalidDstBuffer); + Require(sceCesUcsToJiscs(0x3042, profiles[0], nullptr) == invalidDstBuffer); + Require(sceCesJiscsToUcs(nullptr, 0x2422, nullptr) == invalidProfile); + return 0; +} diff --git a/core/libs/tests/GuestCompanionHttpd.cpp b/core/libs/tests/GuestCompanionHttpd.cpp index 7a712c7b14..a1704a8a4a 100644 --- a/core/libs/tests/GuestCompanionHttpd.cpp +++ b/core/libs/tests/GuestCompanionHttpd.cpp @@ -44,6 +44,7 @@ void CheckOptionBlock() { unsigned char block[0x70]; std::memset(block, 0x5a, sizeof(block)); Require(sceCompanionHttpdOptParamInitialize(nullptr) == InvalidParam, "a null option block must be rejected"); + Require(sceCompanionHttpdOptParamInitialize(reinterpret_cast(0x10)) == InvalidParam, "an unwritable option block must return InvalidParam"); Require(sceCompanionHttpdOptParamInitialize(reinterpret_cast(block)) == 0, "sceCompanionHttpdOptParamInitialize failed"); for (std::size_t index = 0; index < sizeof(block); ++index) { Require(block[index] == (index < 0x60 ? 0u : 0x5au), "the option block must be zeroed over its 0x60 bytes and nothing past them"); diff --git a/core/libs/tests/GuestContentSearch.cpp b/core/libs/tests/GuestContentSearch.cpp index 7101d21c48..e3cc01a47e 100644 --- a/core/libs/tests/GuestContentSearch.cpp +++ b/core/libs/tests/GuestContentSearch.cpp @@ -49,7 +49,11 @@ int main() { std::int64_t size = -1; std::int64_t updateId = -1; std::int32_t metadataId = -1; + std::int32_t applicationIndex = -1; + Require(sceContentSearchGetApplicationLastUpdateId(&updateId) == ErrorNotInitialized); + Require(sceContentSearchGetMyApplicationIndex(&applicationIndex) == ErrorNotInitialized); + Require(sceContentSearchGetNumOfApplication(&columnSet, 1, &orderBy, 1, &count, &updateId) == ErrorNotInitialized); Require(sceContentSearchGetContentLastUpdateId(&updateId) == ErrorNotInitialized); Require(sceContentSearchGetNumOfContent(&columnSet, 1, &count, &updateId) == ErrorNotInitialized); Require(sceContentSearchGetTotalContentSize(&columnSet, 1, &size, &updateId) == ErrorNotInitialized); @@ -92,8 +96,26 @@ int main() { updateId = -1; Require(sceContentSearchSearchContent(&columnSet, 1, &orderBy, 1, 0, 93, &count, infos, &updateId) == ErrorLimitTooBig); Require(count == -1 && updateId == -1); + updateId = -1; + Require(sceContentSearchGetApplicationLastUpdateId(&updateId) == 0 && updateId == 0); + count = -1; + updateId = -1; + Require(sceContentSearchGetNumOfApplication(&columnSet, 1, &orderBy, 1, &count, &updateId) == 0); + Require(count == 0 && updateId == 0); + count = -1; + updateId = -1; + Require(sceContentSearchGetNumOfApplication(nullptr, 0, nullptr, 0, &count, &updateId) == 0); + Require(count == 0 && updateId == 0); + count = -1; + updateId = -1; Require(sceContentSearchGetContentLastUpdateId(nullptr) == ErrorInvalidArgument); + Require(sceContentSearchGetApplicationLastUpdateId(nullptr) == ErrorInvalidArgument); + Require(sceContentSearchGetMyApplicationIndex(nullptr) == ErrorInvalidArgument); + Require(sceContentSearchGetNumOfApplication(&columnSet, 1, &orderBy, 1, nullptr, &updateId) == ErrorInvalidArgument); + Require(sceContentSearchGetNumOfApplication(&columnSet, 1, &orderBy, 1, &count, nullptr) == ErrorInvalidArgument); + Require(sceContentSearchGetNumOfApplication(&columnSet, 1, &orderBy, 0, &count, &updateId) == ErrorInvalidArgument); + Require(sceContentSearchGetNumOfApplication(&columnSet, 0, &orderBy, 1, &count, &updateId) == ErrorInvalidArgument); Require(sceContentSearchGetNumOfContent(&columnSet, 1, nullptr, &updateId) == ErrorInvalidArgument); Require(sceContentSearchGetNumOfContent(&columnSet, 1, &count, nullptr) == ErrorInvalidArgument); Require(sceContentSearchGetTotalContentSize(nullptr, 1, &size, &updateId) == ErrorInvalidArgument); @@ -113,6 +135,7 @@ int main() { ContentSearchContentColumn unknownColumn{8, 0, &photo, 2}; ContentSearchContentColumnSet unknownColumnSet{&unknownColumn, 1, 2}; Require(Throws([&] { sceContentSearchGetNumOfContent(&unknownColumnSet, 1, &count, &updateId); })); + Require(Throws([&] { sceContentSearchGetNumOfApplication(&unknownColumnSet, 1, &orderBy, 1, &count, &updateId); })); ContentSearchContentColumn missingValue{3, 0, nullptr, 2}; ContentSearchContentColumnSet missingValueSet{&missingValue, 1, 2}; Require(Throws([&] { sceContentSearchGetTotalContentSize(&missingValueSet, 1, &size, &updateId); })); @@ -121,6 +144,7 @@ int main() { Require(Throws([&] { sceContentSearchSearchContent(&unknownOperatorSet, 1, &orderBy, 1, 0, 2, &count, infos, &updateId); })); ContentSearchContentOrderByCondition unknownSort{3, 0}; Require(Throws([&] { sceContentSearchSearchContent(&columnSet, 1, &unknownSort, 1, 0, 2, &count, infos, &updateId); })); + Require(Throws([&] { sceContentSearchGetNumOfApplication(&columnSet, 1, &unknownSort, 1, &count, &updateId); })); Require(count == -1 && updateId == -1); Require(sceContentSearchSearchContent(nullptr, 0, nullptr, 0, 0, 0, nullptr, nullptr, nullptr) == ErrorInvalidArgument); Require(sceContentSearchSearchContent(nullptr, 0, nullptr, 0, 0, 93, nullptr, nullptr, nullptr) == ErrorLimitTooBig); @@ -141,6 +165,9 @@ int main() { Require(sceContentSearchSearchContent(&columnSet, 1, &orderBy, 1, 0, 2, &count, infos, &updateId) == ErrorNotInitialized); Require(sceContentSearchCloseMetadata(1) == ErrorNotInitialized); Require(sceContentSearchGetContentLastUpdateId(&updateId) == ErrorNotInitialized); + Require(sceContentSearchGetApplicationLastUpdateId(&updateId) == ErrorNotInitialized); + Require(sceContentSearchGetMyApplicationIndex(&applicationIndex) == ErrorNotInitialized); + Require(sceContentSearchGetNumOfApplication(&columnSet, 1, &orderBy, 1, &count, &updateId) == ErrorNotInitialized); Require(updateId == -1); constexpr int binderResult = static_cast(0x809D000F); Require(sceContentBinderInitialize() == binderResult); @@ -152,8 +179,5 @@ int main() { Require(sceContentBinderEnd() == binderResult); Require(sceContentBinderTerminate() == binderResult); - Require(ThrowsRuntimeError([] { sceContentSearchGetApplicationLastUpdateId(); })); - Require(ThrowsRuntimeError([] { sceContentSearchGetMyApplicationIndex(); })); - Require(ThrowsRuntimeError([] { sceContentSearchGetNumOfApplication(); })); Require(ThrowsRuntimeError([] { sceContentSearchSearchApplication(); })); } diff --git a/core/libs/tests/GuestCrt.cpp b/core/libs/tests/GuestCrt.cpp index 1e8f1887f5..885adae800 100644 --- a/core/libs/tests/GuestCrt.cpp +++ b/core/libs/tests/GuestCrt.cpp @@ -201,14 +201,17 @@ int main() { char line[] = "a,b,,c"; char* context = nullptr; - Require(std::strcmp(sceAmprStrtokS(line, ",", &context), "a") == 0); - Require(std::strcmp(sceAmprStrtokS(nullptr, ",", &context), "b") == 0); - Require(std::strcmp(sceAmprStrtokS(nullptr, ",", &context), "c") == 0); - Require(sceAmprStrtokS(nullptr, ",", &context) == nullptr); - Require(sceAmprStrtokS(line, nullptr, &context) == nullptr); + std::size_t remaining = sizeof(line); + Require(std::strcmp(sceAmprStrtokS(line, &remaining, ",", &context), "a") == 0 && remaining == 5); + Require(std::strcmp(sceAmprStrtokS(nullptr, &remaining, ",", &context), "b") == 0 && remaining == 3); + Require(std::strcmp(sceAmprStrtokS(nullptr, &remaining, ",", &context), "c") == 0 && remaining == 1); + Require(sceAmprStrtokS(nullptr, &remaining, ",", &context) == nullptr); + g_handlerCalls = 0; + Require(sceAmprStrtokS(line, &remaining, nullptr, &context) == nullptr && g_handlerCalls == 1); char again[] = "x y"; char* again_context = nullptr; - Require(std::strcmp(strtok_s_nid_postfix(again, " ", &again_context), "x") == 0); + std::size_t again_remaining = sizeof(again); + Require(std::strcmp(strtok_s_nid_postfix(again, &again_remaining, " ", &again_context), "x") == 0 && again_remaining == 2); std::uint16_t wide[8] = {}; Require(sceAmprMbstowcs(wide, "ABC", 8) == 3); diff --git a/core/libs/tests/GuestEditMp4.cpp b/core/libs/tests/GuestEditMp4.cpp index a20ba36cc3..749ee51d37 100644 --- a/core/libs/tests/GuestEditMp4.cpp +++ b/core/libs/tests/GuestEditMp4.cpp @@ -30,6 +30,7 @@ int main() { for (std::size_t index = 0x1c; index < bytes.size(); ++index) Require(bytes[index] == fill); } sceEditMp4InitializeParam(nullptr); + sceEditMp4InitializeParam(reinterpret_cast(static_cast(0x10))); Require(sceEditMp4QueryMemSize() == 0x387d30); Require(sceEditMp4QueryMemSize() == 0x387d30); diff --git a/core/libs/tests/GuestEqueueEvents.cpp b/core/libs/tests/GuestEqueueEvents.cpp index e2a9f0ffec..fb808f286e 100644 --- a/core/libs/tests/GuestEqueueEvents.cpp +++ b/core/libs/tests/GuestEqueueEvents.cpp @@ -505,4 +505,10 @@ int main() { Require(RejectsNull(sceKernelGetEventFilter)); Require(RejectsNull(sceKernelGetEventId)); Require(RejectsNull(sceKernelGetEventUserData)); + + KernelEqueue closedEq = 0; + Require(sceKernelCreateEqueue(&closedEq, "closed") == SCE_OK); + Require(sceKernelDeleteEqueue(closedEq) == SCE_OK); + Require(sceKernelAddUserEvent(closedEq, 7) == SCE_KERNEL_ERROR_EBADF); + Require(sceKernelAddUserEvent(0, 7) == SCE_KERNEL_ERROR_EBADF); } diff --git a/core/libs/tests/GuestExceptionFault.cpp b/core/libs/tests/GuestExceptionFault.cpp index 53dad68281..e8723aad18 100644 --- a/core/libs/tests/GuestExceptionFault.cpp +++ b/core/libs/tests/GuestExceptionFault.cpp @@ -218,7 +218,13 @@ static constexpr const char* HandledMarker = "[guest_exception_fault] handler re enum class Mode { Resume, Registers, Nested, Jump, Retry, Unprotect, Remove }; -static void Require(bool value) { if (!value) std::abort(); } +static void RequireFailed(const char* expression, const char* file, int line) { + std::fprintf(stderr, "[guest_exception_fault] Require failed: %s at %s:%d\n", expression, file, line); + std::fflush(stderr); + std::abort(); +} + +#define Require(...) do { if (!(__VA_ARGS__)) RequireFailed(#__VA_ARGS__, __FILE__, __LINE__); } while (false) template static TValue Field(void* context, std::size_t offset) { @@ -493,6 +499,11 @@ static int RemoveAndReturnChild() { return EXIT_SUCCESS; } +static void ReportChild(unsigned long code, const std::string& output) { + std::fprintf(stderr, "[guest_exception_fault] child status 0x%lx, %zu bytes of output:\n%s\n[guest_exception_fault] end of child output\n", code, output.size(), output.c_str()); + std::fflush(stderr); +} + static void TestRemoveAndReturn(const char* self) { std::string output; #ifdef _WIN32 @@ -512,7 +523,7 @@ static void TestRemoveAndReturn(const char* self) { startup.dwFlags = STARTF_USESTDHANDLES; startup.hStdInput = nul; startup.hStdOutput = writeEnd; - startup.hStdError = nul; + startup.hStdError = writeEnd; PROCESS_INFORMATION child{}; Require(CreateProcessW(path, command.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &startup, &child) != 0); CloseHandle(writeEnd); @@ -525,9 +536,11 @@ static void TestRemoveAndReturn(const char* self) { const DWORD waited = WaitForSingleObject(child.hProcess, ChildTimeoutSeconds * 1000); if (waited != WAIT_OBJECT_0) TerminateProcess(child.hProcess, 1); reader.join(); + if (waited != WAIT_OBJECT_0) ReportChild(waited, output); Require(waited == WAIT_OBJECT_0); DWORD code = 0; Require(GetExitCodeProcess(child.hProcess, &code) != 0); + if (code != static_cast(EXCEPTION_ACCESS_VIOLATION)) ReportChild(code, output); Require(code == static_cast(EXCEPTION_ACCESS_VIOLATION)); CloseHandle(readEnd); CloseHandle(child.hThread); @@ -562,6 +575,7 @@ static void TestRemoveAndReturn(const char* self) { close(pipeEnds[0]); #endif const auto first = output.find(HandledMarker); + if (first == std::string::npos || output.find(HandledMarker, first + 1) != std::string::npos) ReportChild(0, output); Require(first != std::string::npos && output.find(HandledMarker, first + 1) == std::string::npos); } diff --git a/core/libs/tests/GuestExecHost.cpp b/core/libs/tests/GuestExecHost.cpp index 82faaf1b6b..ecb942c04a 100644 --- a/core/libs/tests/GuestExecHost.cpp +++ b/core/libs/tests/GuestExecHost.cpp @@ -2,6 +2,7 @@ #include "prx/libc/include/Shutdown.hpp" #include "prx/libc/include/Api.hpp" #include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel_sys/include/AdditionalExports.hpp" #include #include #include @@ -198,6 +199,16 @@ extern "C" void APS5_VABI ExecFirst_nid_no_patch(const std::uintptr_t* block) { char* const target[] = {const_cast("/app0/picker/second.bin"), const_cast("argument with spaces"), const_cast("quote\"and\\slash\\ Кириллица 東京"), nullptr}; try { + const char* mode = std::getenv("ANYPS5_KERNEL_SYS_EXEC_MODE"); + if (mode != nullptr && std::strcmp(mode, "ve") == 0) { + _execve_nid_postfix("/app0/picker/second.bin", target, environment.data()); + Require(false, "_execve returned instead of replacing the image"); + } + if (mode != nullptr && std::strcmp(mode, "vpe") == 0) { + Require(setenv_nid_postfix("PATH", "/missing:/app0/picker", 1) == 0, "setting guest PATH failed"); + _execvpe_nid_postfix("second.bin", target, environment.data()); + Require(false, "_execvpe returned instead of replacing the image"); + } execve_nid_postfix("/app0/picker/second.bin", target, environment.data()); } catch (...) { Record("throw"); diff --git a/core/libs/tests/GuestFirmwareConstantReturns.cpp b/core/libs/tests/GuestFirmwareConstantReturns.cpp index 961bd458f3..b1f9f9a944 100644 --- a/core/libs/tests/GuestFirmwareConstantReturns.cpp +++ b/core/libs/tests/GuestFirmwareConstantReturns.cpp @@ -46,6 +46,38 @@ void CheckAvSettingCallbackFuncs() { CheckAvSettingCallbackFuncsInit(sceAvSettingCallbackFuncsInitForLnc_, 0x10); } +void CheckAvSettingModeHelpers() { + constexpr std::uint8_t Fill = 0xaa; + const auto check = [Fill](void (APS5_VABI* helper)(void*, std::int32_t), std::uint8_t fill) { + std::array buffer{}; + buffer.fill(Fill); + helper(buffer.data(), 8); + Require(buffer[0] == 8 && buffer[1] == 0 && buffer[2] == 0 && buffer[3] == 0); + for (std::size_t i = 4; i < buffer.size(); ++i) Require(buffer[i] == fill); + buffer.fill(Fill); + helper(buffer.data(), 4); + Require(buffer[0] == 4 && buffer[1] == 0 && buffer[2] == 0 && buffer[3] == 0); + for (std::size_t i = 4; i < buffer.size(); ++i) Require(buffer[i] == Fill); + buffer.fill(Fill); + helper(buffer.data(), 0); + for (std::size_t i = 0; i < 4; ++i) Require(buffer[i] == 0); + for (std::size_t i = 4; i < buffer.size(); ++i) Require(buffer[i] == Fill); + buffer.fill(Fill); + helper(nullptr, 8); + for (const auto value : buffer) Require(value == Fill); + }; + check(sceAvSettingSetAudioOutModeAny_, 0xff); + check(sceAvSettingSetAudioOutModeInvalid_, 0x00); + check(sceAvSettingSetVideoOutModeAny_, 0xff); + check(sceAvSettingSetVideoOutModeInvalid_, 0x00); + std::int32_t source = 0x12345678; + sceAvSettingSetVideoOutSource(&source, 7); + Require(source == 7); + source = 0x12345678; + sceAvSettingSetVideoOutSource(nullptr, 7); + Require(source == 0x12345678); +} + void CheckComposite() { constexpr int Unsupported = static_cast(0x80d40006); Require(sceCompositorCreateIndirectRenderTarget() == Unsupported); @@ -205,6 +237,7 @@ void CheckDebugSyscore() { int main() { CheckAvSetting(); CheckAvSettingCallbackFuncs(); + CheckAvSettingModeHelpers(); CheckComposite(); CheckFsInternal(); CheckHttpCacheAndPlayReady(); diff --git a/core/libs/tests/GuestFreeTypeBitmapEmbolden.cpp b/core/libs/tests/GuestFreeTypeBitmapEmbolden.cpp new file mode 100644 index 0000000000..d86bafc0b2 --- /dev/null +++ b/core/libs/tests/GuestFreeTypeBitmapEmbolden.cpp @@ -0,0 +1,76 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include FT_BITMAP_H + +#include +#include +#include +#include +#include +#include +#include + +using namespace FreeType; + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "FreeType bitmap embolden check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +static void Matches(const GuestBitmap& guest, const FT_Bitmap& native) { + Require(guest.rows == native.rows && guest.width == native.width && guest.pitch == native.pitch); + Require(guest.num_grays == native.num_grays && guest.pixel_mode == native.pixel_mode); + Require((guest.buffer == nullptr) == (native.buffer == nullptr)); + if (!guest.buffer) return; + const auto pitch = static_cast(native.pitch < 0 ? -native.pitch : native.pitch); + Require(std::memcmp(guest.buffer, native.buffer, pitch * native.rows) == 0); +} + +static void Compare(GuestLibrary& library, FT_Library native, unsigned char mode, std::uint32_t width, std::uint32_t rows, + FT_Pos xStrength, FT_Pos yStrength) { + const std::uint32_t pitch = mode == FT_PIXEL_MODE_MONO ? (width + 7) / 8 : mode == FT_PIXEL_MODE_LCD ? width * 3 : width; + std::array pixels{}; + for (std::size_t index = 0; index < pixels.size(); ++index) pixels[index] = static_cast(index * 37 + 11); + GuestBitmap source{rows, mode == FT_PIXEL_MODE_LCD ? width * 3 : width, static_cast(pitch), pixels.data(), + static_cast(mode == FT_PIXEL_MODE_MONO ? 2 : 256), mode, 0, nullptr}; + FT_Bitmap nativeSource{source.rows, source.width, source.pitch, source.buffer, source.num_grays, source.pixel_mode, 0, nullptr}; + GuestBitmap guest{}; + FT_Bitmap expected{}; + FT_Bitmap_Init(&expected); + Require(FT_Bitmap_Copy_nid_postfix(&library, &source, &guest) == FT_Bitmap_Copy(native, &nativeSource, &expected)); + Require(FT_Bitmap_Embolden_nid_postfix(&library, &guest, xStrength, yStrength) == FT_Bitmap_Embolden(native, &expected, xStrength, yStrength)); + Matches(guest, expected); + Require(FT_Bitmap_Done_nid_postfix(&library, &guest) == FT_Bitmap_Done(native, &expected)); +} + +int main() { + GuestLibrary library{}; + FT_Library native = nullptr; + Require(FT_Init_FreeType(&library.host) == 0 && FT_Init_FreeType(&native) == 0); + for (const unsigned char mode : {FT_PIXEL_MODE_MONO, FT_PIXEL_MODE_GRAY, FT_PIXEL_MODE_LCD, FT_PIXEL_MODE_BGRA}) { + for (const FT_Pos strength : {0, 32, 64, 128, 256, -64}) { + Compare(library, native, mode, 5, 4, strength, strength); + Compare(library, native, mode, 3, 3, strength, 2 * strength); + } + } + GuestBitmap bitmap{}; + Require(FT_Bitmap_Embolden_nid_postfix(nullptr, &bitmap, 64, 64) == FT_Err_Invalid_Library_Handle); + Require(FT_Bitmap_Embolden_nid_postfix(&library, nullptr, 64, 64) == FT_Err_Invalid_Argument); + unsigned char pixel = 0x80; + GuestBitmap bounded{1, 1, 1, &pixel, 256, FT_PIXEL_MODE_GRAY, 0, nullptr}; + Require(FT_Bitmap_Embolden_nid_postfix(&library, &bounded, std::numeric_limits::max(), 0) == FT_Err_Invalid_Argument); + if constexpr (sizeof(FT_Pos) > sizeof(FT_Int)) { + constexpr std::int64_t minimumRounded = static_cast(std::numeric_limits::min()) * 64 - 32; + constexpr std::int64_t maximumRounded = static_cast(std::numeric_limits::max()) * 64 + 31; + Require(FT_Bitmap_Embolden_nid_postfix(&library, &bounded, maximumRounded + 1, 0) == FT_Err_Invalid_Argument); + Require(FT_Bitmap_Embolden_nid_postfix(&library, &bounded, minimumRounded - 1, 0) == FT_Err_Invalid_Argument); + } + FT_Bitmap emptyNative{}; + Require(FT_Bitmap_Embolden_nid_postfix(&library, &bitmap, 64, 64) == FT_Bitmap_Embolden(native, &emptyNative, 64, 64)); + Require(FT_Done_FreeType(library.host) == 0 && FT_Done_FreeType(native) == 0); +} diff --git a/core/libs/tests/GuestFreeTypeLcdFilter.cpp b/core/libs/tests/GuestFreeTypeLcdFilter.cpp new file mode 100644 index 0000000000..0de2c36f7d --- /dev/null +++ b/core/libs/tests/GuestFreeTypeLcdFilter.cpp @@ -0,0 +1,37 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include FT_LCD_FILTER_H + +#include +#include +#include +#include + +using namespace FreeType; + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "FreeType LCD filter check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +int main() { + GuestLibrary library{}; + FT_Library native = nullptr; + Require(FT_Init_FreeType(&library.host) == 0 && FT_Init_FreeType(&native) == 0); + constexpr std::int32_t filters[] = {FT_LCD_FILTER_NONE, FT_LCD_FILTER_DEFAULT, FT_LCD_FILTER_LIGHT, FT_LCD_FILTER_LEGACY, + FT_LCD_FILTER_LEGACY1, FT_LCD_FILTER_MAX, -1, 99}; + for (const std::int32_t filter : filters) { + Require(FT_Library_SetLcdFilter_nid_postfix(&library, filter) == FT_Library_SetLcdFilter(native, static_cast(filter))); + } + std::array weights{0x08, 0x4D, 0x56, 0x4D, 0x08}; + Require(FT_Library_SetLcdFilterWeights_nid_postfix(&library, weights.data()) == FT_Library_SetLcdFilterWeights(native, weights.data())); + Require(FT_Library_SetLcdFilterWeights_nid_postfix(&library, nullptr) == FT_Library_SetLcdFilterWeights(native, nullptr)); + Require(FT_Library_SetLcdFilter_nid_postfix(nullptr, FT_LCD_FILTER_DEFAULT) == FT_Library_SetLcdFilter(nullptr, FT_LCD_FILTER_DEFAULT)); + Require(FT_Library_SetLcdFilterWeights_nid_postfix(nullptr, weights.data()) == FT_Library_SetLcdFilterWeights(nullptr, weights.data())); + Require(FT_Done_FreeType(library.host) == 0 && FT_Done_FreeType(native) == 0); +} diff --git a/core/libs/tests/GuestFreeTypeModuleManagement.cpp b/core/libs/tests/GuestFreeTypeModuleManagement.cpp new file mode 100644 index 0000000000..992c0c61a3 --- /dev/null +++ b/core/libs/tests/GuestFreeTypeModuleManagement.cpp @@ -0,0 +1,97 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include FT_MODULE_H +#include FT_RENDER_H + +#include +#include +#include +#include + +using namespace FreeType; + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "FreeType module check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +struct Libraries { + GuestLibrary guest{}; + FT_Library native = nullptr; + + Libraries() { Require(FT_Init_FreeType(&guest.host) == 0 && FT_Init_FreeType(&native) == 0); } + ~Libraries() { Require(FT_Done_FreeType(guest.host) == 0 && FT_Done_FreeType(native) == 0); } + Libraries(const Libraries&) = delete; + Libraries& operator=(const Libraries&) = delete; +}; + +static void TestGetModule() { + Libraries libraries; + for (const char* name : {"truetype", "cff", "type1", "bdf", "sfnt", "smooth", "raster1", "psnames", "missing", ""}) { + void* actual = FT_Get_Module_nid_postfix(&libraries.guest, name); + FT_Module expected = FT_Get_Module(libraries.native, name); + Require((actual != nullptr) == (expected != nullptr)); + Require(actual == FT_Get_Module(libraries.guest.host, name)); + } + Require(FT_Get_Module_nid_postfix(nullptr, "truetype") == FT_Get_Module(nullptr, "truetype")); +} + +static void TestGetRenderer() { + Libraries libraries; + for (const std::uint32_t format : {static_cast(FT_GLYPH_FORMAT_NONE), static_cast(FT_GLYPH_FORMAT_COMPOSITE), + static_cast(FT_GLYPH_FORMAT_BITMAP), static_cast(FT_GLYPH_FORMAT_OUTLINE), + static_cast(FT_GLYPH_FORMAT_PLOTTER), 0x12345678u}) { + void* actual = FT_Get_Renderer_nid_postfix(&libraries.guest, format); + FT_Renderer expected = FT_Get_Renderer(libraries.native, static_cast(format)); + Require((actual != nullptr) == (expected != nullptr)); + Require(actual == FT_Get_Renderer(libraries.guest.host, static_cast(format))); + } + Require(FT_Get_Renderer_nid_postfix(nullptr, FT_GLYPH_FORMAT_OUTLINE) == FT_Get_Renderer(nullptr, FT_GLYPH_FORMAT_OUTLINE)); +} + +static void TestRemoveModule() { + for (const char* name : {"truetype", "cff", "type1", "smooth", "psnames"}) { + Libraries libraries; + void* guestModule = FT_Get_Module(libraries.guest.host, name); + FT_Module nativeModule = FT_Get_Module(libraries.native, name); + Require(guestModule != nullptr && nativeModule != nullptr); + Require(FT_Remove_Module_nid_postfix(&libraries.guest, guestModule) == FT_Remove_Module(libraries.native, nativeModule)); + Require(FT_Get_Module(libraries.guest.host, name) == nullptr && FT_Get_Module(libraries.native, name) == nullptr); + } + Libraries libraries; + Libraries other; + void* foreign = FT_Get_Module(other.guest.host, "truetype"); + FT_Module nativeForeign = FT_Get_Module(other.native, "truetype"); + Require(FT_Remove_Module_nid_postfix(&libraries.guest, foreign) == FT_Remove_Module(libraries.native, nativeForeign)); + Require(FT_Remove_Module_nid_postfix(&libraries.guest, nullptr) == FT_Remove_Module(libraries.native, nullptr)); + Require(FT_Remove_Module_nid_postfix(nullptr, foreign) == FT_Remove_Module(nullptr, nativeForeign)); +} + +static void TestAddDefaultModules() { + Libraries libraries; + for (const char* name : {"truetype", "cff", "smooth"}) { + FT_Remove_Module(libraries.guest.host, FT_Get_Module(libraries.guest.host, name)); + FT_Remove_Module(libraries.native, FT_Get_Module(libraries.native, name)); + } + Require(FT_Get_Module(libraries.guest.host, "truetype") == nullptr); + FT_Add_Default_Modules_nid_postfix(&libraries.guest); + FT_Add_Default_Modules(libraries.native); + for (const char* name : {"truetype", "cff", "type1", "bdf", "smooth", "raster1", "missing"}) { + Require((FT_Get_Module(libraries.guest.host, name) != nullptr) == (FT_Get_Module(libraries.native, name) != nullptr)); + } + Require(FT_Get_Module(libraries.guest.host, "truetype") != nullptr); + FT_Add_Default_Modules_nid_postfix(&libraries.guest); + FT_Add_Default_Modules_nid_postfix(nullptr); +} + +int main() { + TestGetModule(); + TestGetRenderer(); + TestRemoveModule(); + TestAddDefaultModules(); +} diff --git a/core/libs/tests/GuestFreeTypeOtConstants.cpp b/core/libs/tests/GuestFreeTypeOtConstants.cpp new file mode 100644 index 0000000000..80ef6dbaca --- /dev/null +++ b/core/libs/tests/GuestFreeTypeOtConstants.cpp @@ -0,0 +1,22 @@ +#include "prx/libSceFreeTypeOt/libSceFreeTypeOt.hpp" +#include +#include + +namespace { + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "FreeType OpenType constants: %s\n", message); + std::exit(1); +} + +} + +int main() { + Require(FT_Trace_Get_Count_nid_no_patch() == 0, "FT_Trace_Get_Count returns zero"); + Require(FT_Trace_Get_Name_nid_no_patch(-1) == nullptr, "FT_Trace_Get_Name returns null for negative index"); + Require(FT_Trace_Get_Name_nid_no_patch(0) == nullptr, "FT_Trace_Get_Name returns null for zero index"); + Require(FT_Trace_Get_Name_nid_no_patch(128) == nullptr, "FT_Trace_Get_Name returns null for positive index"); + std::puts("Guest FreeType OpenType constant tests passed"); + return 0; +} diff --git a/core/libs/tests/GuestFreeTypeOutlineTransform.cpp b/core/libs/tests/GuestFreeTypeOutlineTransform.cpp new file mode 100644 index 0000000000..2a4ee84888 --- /dev/null +++ b/core/libs/tests/GuestFreeTypeOutlineTransform.cpp @@ -0,0 +1,121 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include FT_OUTLINE_H + +#include +#include +#include +#include +#include + +using namespace FreeType; + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "FreeType outline transform check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +struct Shape { + std::vector points; + std::vector tags; + std::vector contours; +}; + +static Shape Square(bool clockwise) { + Shape shape{{{0, 0}, {640, 0}, {640, 640}, {0, 640}}, {1, 1, 1, 1}, {3}}; + if (clockwise) std::swap(shape.points[1], shape.points[3]); + return shape; +} + +static Shape Frame() { + return {{{0, 0}, {960, 0}, {960, 960}, {0, 960}, {240, 240}, {240, 720}, {720, 720}, {720, 240}}, {1, 1, 1, 1, 1, 1, 1, 1}, {3, 7}}; +} + +static Shape Curved() { + return {{{0, 0}, {320, 0}, {640, 320}, {320, 640}, {0, 640}}, {1, 0, 1, 0, 1}, {4}}; +} + +struct Pair { + std::vector guestPoints; + Shape shape; + GuestOutline guest{}; + FT_Outline native{}; + std::vector nativePoints; + + explicit Pair(Shape input) : shape(std::move(input)), nativePoints(shape.points) { + for (const FT_Vector& point : shape.points) guestPoints.push_back({point.x, point.y}); + const auto points = static_cast(shape.points.size()); + const auto contours = static_cast(shape.contours.size()); + guest = {contours, points, guestPoints.data(), shape.tags.data(), shape.contours.data(), 0}; + native = {static_cast(contours), static_cast(points), nativePoints.data(), shape.tags.data(), shape.contours.data(), 0}; + } + + void Matches() const { + Require(guest.n_points == native.n_points && guest.n_contours == native.n_contours); + for (std::size_t index = 0; index < nativePoints.size(); ++index) { + Require(guestPoints[index].x == nativePoints[index].x && guestPoints[index].y == nativePoints[index].y); + } + } +}; + +static void TestEmbolden() { + for (const auto& make : {+[] { return Square(false); }, +[] { return Square(true); }, +[] { return Frame(); }, +[] { return Curved(); }}) { + for (const std::int64_t strength : {0, 1, 32, 64, 128, 512, -32, -64}) { + Pair pair(make()); + const int guestError = FT_Outline_Embolden_nid_postfix(&pair.guest, strength); + const int nativeError = FT_Outline_Embolden(&pair.native, static_cast(strength)); + Require(guestError == nativeError); + pair.Matches(); + } + } + Require(FT_Outline_Embolden_nid_postfix(nullptr, 64) == FT_Outline_Embolden(nullptr, 64)); + GuestOutline empty{}; + FT_Outline nativeEmpty{}; + Require(FT_Outline_Embolden_nid_postfix(&empty, 64) == FT_Outline_Embolden(&nativeEmpty, 64)); +} + +static void TestEmboldenXY() { + for (const auto& make : {+[] { return Square(false); }, +[] { return Frame(); }, +[] { return Curved(); }}) { + for (const std::int64_t x : {0, 32, 64, -32}) { + for (const std::int64_t y : {0, 64, 128, -64}) { + Pair pair(make()); + const int guestError = FT_Outline_EmboldenXY_nid_postfix(&pair.guest, x, y); + const int nativeError = FT_Outline_EmboldenXY(&pair.native, static_cast(x), static_cast(y)); + Require(guestError == nativeError); + pair.Matches(); + } + } + } + Require(FT_Outline_EmboldenXY_nid_postfix(nullptr, 64, 32) == FT_Outline_EmboldenXY(nullptr, 64, 32)); +} + +static void TestTransform() { + const GuestMatrix matrices[] = {{0x10000, 0, 0, 0x10000}, {0x8000, 0, 0, 0x20000}, {0, -0x10000, 0x10000, 0}, {0x10000, 0x4000, 0, 0x10000}, + {-0x12345, 0x6789, -0x2468, 0x13579}}; + for (const GuestMatrix& matrix : matrices) { + for (const auto& make : {+[] { return Square(false); }, +[] { return Frame(); }, +[] { return Curved(); }}) { + Pair pair(make()); + const FT_Matrix native{matrix.xx, matrix.xy, matrix.yx, matrix.yy}; + FT_Outline_Transform_nid_postfix(&pair.guest, &matrix); + FT_Outline_Transform(&pair.native, &native); + pair.Matches(); + } + } + Pair pair(Square(false)); + FT_Outline_Transform_nid_postfix(nullptr, &matrices[0]); + FT_Outline_Transform_nid_postfix(&pair.guest, nullptr); + pair.Matches(); + GuestOutline noPoints{}; + FT_Outline_Transform_nid_postfix(&noPoints, &matrices[0]); +} + +int main() { + TestEmbolden(); + TestEmboldenXY(); + TestTransform(); +} diff --git a/core/libs/tests/GuestFreeTypeSlotOwnBitmap.cpp b/core/libs/tests/GuestFreeTypeSlotOwnBitmap.cpp new file mode 100644 index 0000000000..f31cd57f88 --- /dev/null +++ b/core/libs/tests/GuestFreeTypeSlotOwnBitmap.cpp @@ -0,0 +1,64 @@ +#include "FontFixture.hpp" +#include "prx/libSceFreeType/include/Api.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include FT_BITMAP_H + +#include +#include +#include +#include +#include +#include + +using namespace FreeType; + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "FreeType slot own bitmap check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +static void Matches(const GuestBitmap& guest, const FT_Bitmap& native) { + Require(guest.rows == native.rows && guest.width == native.width && guest.pitch == native.pitch); + Require(guest.num_grays == native.num_grays && guest.pixel_mode == native.pixel_mode); + Require(guest.buffer && native.buffer); + Require(std::memcmp(guest.buffer, native.buffer, static_cast(native.pitch) * native.rows) == 0); +} + +int main() { + GuestLibrary library{}; + FT_Library nativeLibrary = nullptr; + Require(FT_Init_FreeType(&library.host) == 0 && FT_Init_FreeType(&nativeLibrary) == 0); + { + const std::vector bytes = FontFixture::BitmapFont(); + GuestFace guest; + guest.library = &library; + FT_Face oracle = nullptr; + Require(FT_New_Memory_Face(library.host, bytes.data(), static_cast(bytes.size()), 0, &guest.host) == 0); + Require(FT_New_Memory_Face(nativeLibrary, bytes.data(), static_cast(bytes.size()), 0, &oracle) == 0); + SyncFace(guest); + const std::uint32_t glyph = FT_Get_Char_Index(oracle, 65); + Require(glyph != 0); + Require(FT_Load_Glyph_nid_postfix(&guest.rec, glyph, FT_LOAD_DEFAULT) == FT_Load_Glyph(oracle, glyph, FT_LOAD_DEFAULT)); + Require(guest.host->glyph->format == FT_GLYPH_FORMAT_BITMAP); + const unsigned char* before = guest.slot.bitmap.buffer; + Require(before != nullptr); + Require(FT_GlyphSlot_Own_Bitmap_nid_postfix(&guest.slot) == FT_GlyphSlot_Own_Bitmap(oracle->glyph)); + Matches(guest.slot.bitmap, oracle->glyph->bitmap); + Require(guest.slot.bitmap.buffer != before); + Require(guest.slot.bitmap.buffer == guest.host->glyph->bitmap.buffer); + const unsigned char* owned = guest.slot.bitmap.buffer; + Require(FT_GlyphSlot_Own_Bitmap_nid_postfix(&guest.slot) == FT_GlyphSlot_Own_Bitmap(oracle->glyph)); + Require(guest.slot.bitmap.buffer == owned); + Require(FT_Done_Face(guest.host) == 0); + Require(FT_Done_Face(oracle) == 0); + } + Require(FT_GlyphSlot_Own_Bitmap_nid_postfix(nullptr) == FT_GlyphSlot_Own_Bitmap(nullptr)); + GuestGlyphSlotRec unregistered{}; + Require(FT_GlyphSlot_Own_Bitmap_nid_postfix(&unregistered) == FT_GlyphSlot_Own_Bitmap(nullptr)); + Require(FT_Done_FreeType(library.host) == 0 && FT_Done_FreeType(nativeLibrary) == 0); +} diff --git a/core/libs/tests/GuestFreeTypeSlotSynthesis.cpp b/core/libs/tests/GuestFreeTypeSlotSynthesis.cpp new file mode 100644 index 0000000000..876a6dda07 --- /dev/null +++ b/core/libs/tests/GuestFreeTypeSlotSynthesis.cpp @@ -0,0 +1,133 @@ +#include "FontFixture.hpp" +#include "FreeTypeFontFixture.hpp" +#include "prx/libSceFreeType/include/Api.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include FT_OUTLINE_H +#include FT_SYNTHESIS_H + +#include +#include +#include +#include +#include +#include + +using namespace FreeType; + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "FreeType slot synthesis check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +struct SlotPair { + std::vector bytes; + GuestFace guest; + FT_Face oracle = nullptr; + + SlotPair(GuestLibrary& library, FT_Library nativeLibrary, std::vector font) : bytes(std::move(font)) { + guest.library = &library; + Require(FT_New_Memory_Face(library.host, bytes.data(), static_cast(bytes.size()), 0, &guest.host) == 0); + Require(FT_New_Memory_Face(nativeLibrary, bytes.data(), static_cast(bytes.size()), 0, &oracle) == 0); + Require(FT_Set_Pixel_Sizes(guest.host, 18, 18) == 0 && FT_Set_Pixel_Sizes(oracle, 18, 18) == 0); + Require(FT_Load_Glyph(guest.host, 1, FT_LOAD_DEFAULT) == 0 && FT_Load_Glyph(oracle, 1, FT_LOAD_DEFAULT) == 0); + SyncFace(guest); + } + + ~SlotPair() { + Require(FT_Done_Face(guest.host) == 0); + Require(FT_Done_Face(oracle) == 0); + } + + void Matches() const { + const GuestGlyphSlotRec& actual = guest.slot; + const FT_GlyphSlot expected = oracle->glyph; + Require(actual.format == static_cast(expected->format)); + Require(actual.advance.x == expected->advance.x && actual.advance.y == expected->advance.y); + Require(actual.metrics.width == expected->metrics.width && actual.metrics.height == expected->metrics.height); + Require(actual.metrics.horiAdvance == expected->metrics.horiAdvance && actual.metrics.horiBearingX == expected->metrics.horiBearingX); + Require(actual.metrics.horiBearingY == expected->metrics.horiBearingY && actual.metrics.vertAdvance == expected->metrics.vertAdvance); + Require(actual.outline.n_points == expected->outline.n_points && actual.outline.n_contours == expected->outline.n_contours); + for (std::uint16_t index = 0; index < expected->outline.n_points; ++index) { + Require(actual.outline.points[index].x == expected->outline.points[index].x); + Require(actual.outline.points[index].y == expected->outline.points[index].y); + } + Require(guest.host->glyph->outline.n_points == expected->outline.n_points); + } +}; + +static void EmboldenBitmapGlyph(GuestLibrary& library, FT_Library nativeLibrary) { + const std::vector bytes = FontFixture::BitmapFont(); + GuestFace guest; + guest.library = &library; + FT_Face oracle = nullptr; + Require(FT_New_Memory_Face(library.host, bytes.data(), static_cast(bytes.size()), 0, &guest.host) == 0); + Require(FT_New_Memory_Face(nativeLibrary, bytes.data(), static_cast(bytes.size()), 0, &oracle) == 0); + SyncFace(guest); + const std::uint32_t glyph = FT_Get_Char_Index(oracle, 65); + Require(glyph != 0); + Require(FT_Load_Glyph_nid_postfix(&guest.rec, glyph, FT_LOAD_DEFAULT) == FT_Load_Glyph(oracle, glyph, FT_LOAD_DEFAULT)); + Require(guest.host->glyph->format == FT_GLYPH_FORMAT_BITMAP); + const FT_Bitmap before = oracle->glyph->bitmap; + FT_GlyphSlot_Embolden_nid_postfix(&guest.slot); + FT_GlyphSlot_Embolden(oracle->glyph); + const GuestBitmap& actual = guest.slot.bitmap; + const FT_Bitmap& expected = oracle->glyph->bitmap; + Require(expected.width != before.width || expected.rows != before.rows); + Require(actual.rows == expected.rows && actual.width == expected.width && actual.pitch == expected.pitch); + Require(actual.pixel_mode == expected.pixel_mode && actual.buffer && expected.buffer); + Require(std::memcmp(actual.buffer, expected.buffer, static_cast(expected.pitch) * expected.rows) == 0); + Require(guest.slot.bitmap_left == oracle->glyph->bitmap_left && guest.slot.bitmap_top == oracle->glyph->bitmap_top); + Require(guest.slot.advance.x == oracle->glyph->advance.x && guest.slot.metrics.width == oracle->glyph->metrics.width); + Require(FT_Done_Face(guest.host) == 0 && FT_Done_Face(oracle) == 0); +} + +int main() { + GuestLibrary library{}; + FT_Library nativeLibrary = nullptr; + Require(FT_Init_FreeType(&library.host) == 0 && FT_Init_FreeType(&nativeLibrary) == 0); + { + SlotPair pair(library, nativeLibrary, SquareGlyphFont('A', 700)); + pair.Matches(); + const FT_Outline& outline = pair.oracle->glyph->outline; + std::vector initial(outline.points, outline.points + outline.n_points); + FT_GlyphSlot_Oblique_nid_postfix(&pair.guest.slot); + FT_GlyphSlot_Oblique(pair.oracle->glyph); + pair.Matches(); + bool changed = false; + for (std::size_t index = 0; index < initial.size(); ++index) changed |= initial[index].x != outline.points[index].x; + Require(changed); + pair.guest.slot.outline.points[1].y += 64; + pair.oracle->glyph->outline.points[1].y += 64; + FT_GlyphSlot_Oblique_nid_postfix(&pair.guest.slot); + FT_GlyphSlot_Oblique(pair.oracle->glyph); + pair.Matches(); + } + { + SlotPair pair(library, nativeLibrary, SquareGlyphFont('A', 700)); + const FT_Outline& outline = pair.oracle->glyph->outline; + std::vector initial(outline.points, outline.points + outline.n_points); + FT_GlyphSlot_Embolden_nid_postfix(&pair.guest.slot); + FT_GlyphSlot_Embolden(pair.oracle->glyph); + pair.Matches(); + bool changed = false; + for (std::size_t index = 0; index < initial.size(); ++index) changed |= initial[index].x != outline.points[index].x || initial[index].y != outline.points[index].y; + Require(changed); + pair.guest.slot.outline.points[0].x += 32; + pair.oracle->glyph->outline.points[0].x += 32; + FT_GlyphSlot_Embolden_nid_postfix(&pair.guest.slot); + FT_GlyphSlot_Embolden(pair.oracle->glyph); + pair.Matches(); + } + EmboldenBitmapGlyph(library, nativeLibrary); + FT_GlyphSlot_Embolden_nid_postfix(nullptr); + FT_GlyphSlot_Oblique_nid_postfix(nullptr); + GuestGlyphSlotRec unregistered{}; + FT_GlyphSlot_Oblique_nid_postfix(&unregistered); + FT_GlyphSlot_Embolden_nid_postfix(&unregistered); + Require(FT_Done_FreeType(library.host) == 0 && FT_Done_FreeType(nativeLibrary) == 0); +} diff --git a/core/libs/tests/GuestFreeTypeStroker.cpp b/core/libs/tests/GuestFreeTypeStroker.cpp index 55e5567685..1c6b22e448 100644 --- a/core/libs/tests/GuestFreeTypeStroker.cpp +++ b/core/libs/tests/GuestFreeTypeStroker.cpp @@ -4,9 +4,14 @@ #include FT_STROKER_H +#include #include #include #include +#include +#include +#include +#include using namespace FreeType; @@ -18,6 +23,114 @@ static void Check(bool value, int line) { } #define Require(value) Check((value), __LINE__) +constexpr bool SharedPoints = sizeof(FT_Pos) == sizeof(std::int64_t); + +template +static bool Throws(TCall call) { + try { + call(); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +struct Shape { + std::vector points; + std::vector tags; + std::vector contours; + std::vector nativePoints; + + Shape(std::vector guestPoints, std::vector pointTags, std::vector contourEnds) + : points(std::move(guestPoints)), tags(std::move(pointTags)), contours(std::move(contourEnds)) { + for (const GuestVector& point : points) nativePoints.push_back({static_cast(point.x), static_cast(point.y)}); + } + + GuestOutline Guest() { + return {static_cast(contours.size()), static_cast(points.size()), points.data(), tags.data(), contours.data(), 0}; + } + + FT_Outline Native() { + return {static_cast(contours.size()), static_cast(points.size()), nativePoints.data(), tags.data(), contours.data(), 0}; + } +}; + +static Shape SquareOutline() { + return Shape({{0, 0}, {640, 0}, {640, 640}, {0, 640}}, {1, 1, 1, 1}, {3}); +} + +static std::vector Shapes() { + std::vector shapes; + shapes.push_back(SquareOutline()); + shapes.push_back(Shape({{0, 320}, {320, 640}, {640, 320}, {320, 0}}, {0, 1, 0, 1}, {3})); + shapes.push_back(Shape({{-321, 5}, {320, 641}, {639, 320}, {-2, -7}}, {0, 0, 0, 0}, {3})); + shapes.push_back(Shape({{0, 0}, {0, 640}, {640, 640}, {640, 0}, {640, -640}, {0, -640}}, {1, 2, 2, 1, 2, 2}, {5})); + shapes.push_back(Shape({{5, 5}, {0, 0}, {640, 0}, {640, 640}, {0, 640}, {100, 100}, {200, 300}, {300, 100}}, + {1, 0x09, 1, 0x11, 1, 0x05, 0, 1}, {0, 4, 7})); + return shapes; +} + +struct Target { + std::uint16_t prefix; + std::vector guestPoints; + std::vector nativePoints; + std::vector guestTags; + std::vector nativeTags; + std::vector guestContours; + std::vector nativeContours; + GuestOutline guest{}; + FT_Outline native{}; + + Target(std::uint16_t prefixPoints, std::size_t points, std::size_t contours) + : prefix(prefixPoints), guestPoints(prefixPoints + points), nativePoints(guestPoints.size()), guestTags(guestPoints.size()), + nativeTags(guestPoints.size()), guestContours(contours + 1), nativeContours(contours + 1) { + for (std::size_t index = 0; index < guestPoints.size(); ++index) { + guestPoints[index] = Point(index); + nativePoints[index] = {static_cast(guestPoints[index].x), static_cast(guestPoints[index].y)}; + guestTags[index] = nativeTags[index] = Tag(index); + } + for (std::size_t index = 0; index < guestContours.size(); ++index) guestContours[index] = nativeContours[index] = Contour(index); + guest = {1, prefix, guestPoints.data(), guestTags.data(), guestContours.data(), 0}; + native = {1, prefix, nativePoints.data(), nativeTags.data(), nativeContours.data(), 0}; + } + + GuestVector Point(std::size_t index) const { + if (index >= prefix) return {-77, 99}; + return {static_cast(index % 640), -static_cast(index % 320) - 2}; + } + + unsigned char Tag(std::size_t index) const { + return index < prefix ? 1 : 0xa5; + } + + std::uint16_t Contour(std::size_t index) const { + return index == 0 ? static_cast(prefix - 1) : 0xbeef; + } + + bool Matches() const { + bool same = guest.n_points == native.n_points && guest.n_contours == native.n_contours && guest.flags == native.flags; + for (std::size_t index = 0; index < guestPoints.size(); ++index) { + same = same && guestPoints[index].x == nativePoints[index].x && guestPoints[index].y == nativePoints[index].y && + guestTags[index] == nativeTags[index]; + } + for (std::size_t index = 0; index < guestContours.size(); ++index) same = same && guestContours[index] == nativeContours[index]; + return same; + } + + bool Pristine() const { + bool same = guest.n_points == prefix && guest.n_contours == 1 && native.n_points == prefix && native.n_contours == 1; + for (std::size_t index = 0; index < guestPoints.size(); ++index) { + const GuestVector point = Point(index); + same = same && guestPoints[index].x == point.x && guestPoints[index].y == point.y && guestTags[index] == Tag(index); + same = same && nativePoints[index].x == point.x && nativePoints[index].y == point.y && nativeTags[index] == Tag(index); + } + for (std::size_t index = 0; index < guestContours.size(); ++index) { + same = same && guestContours[index] == Contour(index) && nativeContours[index] == Contour(index); + } + return same; + } +}; + struct Pair { void* guest = nullptr; FT_Stroker native = nullptr; @@ -43,6 +156,24 @@ struct Pair { Require(points == nativePoints && contours == nativeContours); } + void BorderCountsMatch() const { + for (const std::int32_t border : {0, 1}) { + std::uint32_t points = 11; + std::uint32_t contours = 13; + FT_UInt nativePoints = 11; + FT_UInt nativeContours = 13; + Require(FT_Stroker_GetBorderCounts_nid_postfix(guest, border, &points, &contours) == + FT_Stroker_GetBorderCounts(native, static_cast(border), &nativePoints, &nativeContours)); + Require(points == nativePoints && contours == nativeContours); + } + } + + int Parse(GuestOutline outline, FT_Outline nativeOutline, std::uint8_t opened) const { + const int error = FT_Stroker_ParseOutline_nid_postfix(guest, &outline, opened); + Require(error == FT_Stroker_ParseOutline(native, &nativeOutline, opened)); + return error; + } + void Square(std::uint8_t open, bool finish = true) const { const GuestVector corners[] = {{0, 0}, {640, 0}, {640, 640}, {0, 640}}; FT_Vector nativeStart{0, 0}; @@ -219,6 +350,231 @@ static void TestCubicTo(GuestLibrary& library, FT_Library native) { Require(FT_Stroker_CubicTo_nid_postfix(nullptr, &first, &second, &to) == FT_Stroker_CubicTo(nullptr, &nativeFirst, &nativeSecond, &nativeTo)); } +static int ParseFresh(GuestLibrary& library, FT_Library native, Shape& shape, std::uint8_t opened) { + Pair pair(library, native); + const int error = pair.Parse(shape.Guest(), shape.Native(), opened); + pair.CountsMatch(); + pair.BorderCountsMatch(); + return error; +} + +static void TestParseOutline(GuestLibrary& library, FT_Library native) { + for (Shape& shape : Shapes()) { + for (const std::uint8_t opened : {0, 1}) { + for (const std::int32_t join : {0, 1, 2}) { + Pair pair(library, native); + FT_Stroker_Set_nid_postfix(pair.guest, 48, FT_STROKER_LINECAP_ROUND, join, 0x20000); + FT_Stroker_Set(pair.native, 48, FT_STROKER_LINECAP_ROUND, static_cast(join), 0x20000); + pair.Square(1); + Require(pair.Parse(shape.Guest(), shape.Native(), opened) == 0); + pair.CountsMatch(); + pair.BorderCountsMatch(); + std::uint32_t points = 0; + std::uint32_t contours = 0; + Require(FT_Stroker_GetCounts_nid_postfix(pair.guest, &points, &contours) == 0 && points != 0 && contours != 0); + } + } + } + std::vector invalid; + invalid.push_back(Shape({{0, 0}, {320, 640}, {640, 0}}, {1, 2, 1}, {2})); + invalid.push_back(Shape({{0, 0}, {320, 640}, {640, 0}}, {1, 0, 2}, {2})); + invalid.push_back(Shape({{0, 0}, {640, 0}, {640, 640}, {0, 640}, {320, 320}, {0, 0}}, {1, 1, 1, 1, 2, 1}, {3, 5})); + Shape cubicStart({{0, 0}, {320, 640}, {640, 0}}, {2, 2, 1}, {2}); + for (const std::uint8_t opened : {0, 1}) { + for (Shape& shape : invalid) Require(ParseFresh(library, native, shape, opened) == FT_Err_Invalid_Outline); + ParseFresh(library, native, cubicStart, opened); + } + Pair pair(library, native); + pair.Square(0); + Shape square = SquareOutline(); + const GuestOutline outline = square.Guest(); + FT_Outline nativeOutline = square.Native(); + Require(FT_Stroker_ParseOutline_nid_postfix(pair.guest, nullptr, 0) == FT_Err_Invalid_Outline); + Require(FT_Stroker_ParseOutline(pair.native, nullptr, 0) == FT_Err_Invalid_Outline); + Require(FT_Stroker_ParseOutline_nid_postfix(nullptr, nullptr, 1) == FT_Err_Invalid_Outline); + Require(FT_Stroker_ParseOutline_nid_postfix(nullptr, &outline, 0) == FT_Err_Invalid_Argument); + Require(FT_Stroker_ParseOutline(nullptr, &nativeOutline, 0) == FT_Err_Invalid_Argument); + std::uint32_t points = 0; + std::uint32_t contours = 0; + Require(FT_Stroker_GetCounts_nid_postfix(pair.guest, &points, &contours) == 0 && points != 0 && contours != 0); + pair.CountsMatch(); + Shape empty({}, {}, {}); + Require(pair.Parse(empty.Guest(), empty.Native(), 0) == 0); + Require(FT_Stroker_GetCounts_nid_postfix(pair.guest, &points, &contours) == 0 && points == 0 && contours == 0); + pair.CountsMatch(); +} + +static void ParseAtHostLimit(GuestLibrary& library, FT_Library native, GuestOutline outline, FT_Outline nativeOutline, bool forwarded) { + Pair pair(library, native); + pair.Square(0); + std::uint32_t points = 0; + std::uint32_t contours = 0; + Require(FT_Stroker_GetCounts_nid_postfix(pair.guest, &points, &contours) == 0 && points != 0); + if (forwarded) { + Require(pair.Parse(outline, nativeOutline, 0) == 0); + pair.CountsMatch(); + } else { + Require(Throws([&] { FT_Stroker_ParseOutline_nid_postfix(pair.guest, &outline, 0); })); + std::uint32_t keptPoints = 0; + std::uint32_t keptContours = 0; + Require(FT_Stroker_GetCounts_nid_postfix(pair.guest, &keptPoints, &keptContours) == 0 && keptPoints == points && keptContours == contours); + } +} + +static void TestParseOutlineHostLimits(GuestLibrary& library, FT_Library native) { + constexpr std::int64_t top = std::numeric_limits::max(); + constexpr std::int64_t bottom = std::numeric_limits::min(); + Shape aboveX({{0, 0}, {640, 0}, {640, 640}, {top + 1, 640}}, {1, 1, 1, 1}, {3}); + Shape belowY({{0, 0}, {640, bottom - 1}, {640, 640}, {0, 640}}, {1, 1, 1, 1}, {3}); + ParseAtHostLimit(library, native, aboveX.Guest(), aboveX.Native(), false); + ParseAtHostLimit(library, native, belowY.Guest(), belowY.Native(), false); + const GuestOutline wide = aboveX.Guest(); + Require(FT_Stroker_ParseOutline_nid_postfix(nullptr, &wide, 0) == FT_Err_Invalid_Argument); + Shape skipped({{top + 1, bottom - 1}, {0, 0}, {640, 0}, {640, 640}, {0, 640}}, {1, 1, 1, 1, 1}, {0, 4}); + ParseAtHostLimit(library, native, skipped.Guest(), skipped.Native(), SharedPoints); + if constexpr (SharedPoints) { + Shape edge({{0, 0}, {top, 0}, {top, 640}, {0, bottom}}, {1, 1, 1, 1}, {3}); + ParseAtHostLimit(library, native, edge.Guest(), edge.Native(), true); + } +} + +static void TestParseOutlineArrays(GuestLibrary& library, FT_Library native) { + Shape square = SquareOutline(); + const GuestOutline valid = square.Guest(); + const FT_Outline nativeValid = square.Native(); + GuestOutline noContours = valid; + noContours.contours = nullptr; + GuestOutline noPoints = valid; + noPoints.points = nullptr; + GuestOutline noTags = valid; + noTags.tags = nullptr; + GuestOutline endAtCount = valid; + endAtCount.n_points = 3; + GuestOutline endPastCount = valid; + endPastCount.n_points = 2; + for (const GuestOutline& outline : {noContours, noPoints, noTags, endAtCount, endPastCount}) { + ParseAtHostLimit(library, native, outline, nativeValid, false); + Require(FT_Stroker_ParseOutline_nid_postfix(nullptr, &outline, 0) == FT_Err_Invalid_Argument); + } + std::uint16_t end = 0; + ParseAtHostLimit(library, native, GuestOutline{1, 1, nullptr, nullptr, &end, 0}, FT_Outline{1, 1, nullptr, nullptr, &end, 0}, false); + ParseAtHostLimit(library, native, GuestOutline{0, 4, nullptr, nullptr, nullptr, 0}, FT_Outline{0, 4, nullptr, nullptr, nullptr, 0}, true); +} + +static void TestExportBorder(GuestLibrary& library, FT_Library native) { + std::size_t exported = 0; + for (Shape& shape : Shapes()) { + for (const std::uint8_t opened : {0, 1}) { + for (const std::int32_t border : {0, 1}) { + Pair pair(library, native); + Require(pair.Parse(shape.Guest(), shape.Native(), opened) == 0); + std::uint32_t points = 0; + std::uint32_t contours = 0; + FT_UInt nativePoints = 0; + FT_UInt nativeContours = 0; + Require(FT_Stroker_GetBorderCounts_nid_postfix(pair.guest, border, &points, &contours) == 0); + Require(FT_Stroker_GetBorderCounts(pair.native, static_cast(border), &nativePoints, &nativeContours) == 0); + Require(points == nativePoints && contours == nativeContours); + Target target(3, points, contours); + if constexpr (SharedPoints) { + FT_Stroker_ExportBorder_nid_postfix(pair.guest, border, &target.guest); + FT_Stroker_ExportBorder(pair.native, static_cast(border), &target.native); + Require(target.Matches() && target.guest.n_points == 3 + points && target.guest.n_contours == 1 + contours); + } else { + Require(Throws([&] { FT_Stroker_ExportBorder_nid_postfix(pair.guest, border, &target.guest); }) && target.Pristine()); + } + exported += points; + } + } + } + Require(exported != 0); +} + +static void TestExport(GuestLibrary& library, FT_Library native) { + for (Shape& shape : Shapes()) { + for (const std::uint8_t opened : {0, 1}) { + Pair pair(library, native); + Require(pair.Parse(shape.Guest(), shape.Native(), opened) == 0); + std::uint32_t points = 0; + std::uint32_t contours = 0; + Require(FT_Stroker_GetCounts_nid_postfix(pair.guest, &points, &contours) == 0 && points != 0 && contours != 0); + pair.CountsMatch(); + Target target(3, points, contours); + if constexpr (SharedPoints) { + FT_Stroker_Export_nid_postfix(pair.guest, &target.guest); + FT_Stroker_Export(pair.native, &target.native); + Require(target.Matches() && target.guest.n_points == 3 + points && target.guest.n_contours == 1 + contours); + } else { + Require(Throws([&] { FT_Stroker_Export_nid_postfix(pair.guest, &target.guest); }) && target.Pristine()); + } + } + } + if constexpr (SharedPoints) { + Shape square = SquareOutline(); + Pair pair(library, native); + Require(pair.Parse(square.Guest(), square.Native(), 0) == 0); + std::uint32_t points = 0; + std::uint32_t contours = 0; + Require(FT_Stroker_GetCounts_nid_postfix(pair.guest, &points, &contours) == 0 && points > 6); + pair.CountsMatch(); + Target target(65530, points, contours); + FT_Stroker_Export_nid_postfix(pair.guest, &target.guest); + FT_Stroker_Export(pair.native, &target.native); + Require(target.Matches() && target.guest.n_points == static_cast(65530 + points) && target.guest.n_points < 65530); + } +} + +static void TestExportUnchanged(GuestLibrary& library, FT_Library native) { + Shape square = SquareOutline(); + Pair sizing(library, native); + Require(sizing.Parse(square.Guest(), square.Native(), 0) == 0); + std::uint32_t points = 0; + std::uint32_t contours = 0; + Require(FT_Stroker_GetCounts_nid_postfix(sizing.guest, &points, &contours) == 0 && points != 0 && contours != 0); + Pair pair(library, native); + Require(pair.Parse(square.Guest(), square.Native(), 0) == 0); + Target target(3, points, contours); + if constexpr (SharedPoints) { + FT_Stroker_Export_nid_postfix(pair.guest, &target.guest); + FT_Stroker_Export(pair.native, &target.native); + for (const std::int32_t border : {0, 1}) { + FT_Stroker_ExportBorder_nid_postfix(pair.guest, border, &target.guest); + FT_Stroker_ExportBorder(pair.native, static_cast(border), &target.native); + } + } else { + Require(Throws([&] { FT_Stroker_Export_nid_postfix(pair.guest, &target.guest); })); + Require(Throws([&] { FT_Stroker_ExportBorder_nid_postfix(pair.guest, 0, &target.guest); })); + } + Require(target.Pristine()); + pair.CountsMatch(); + for (const std::int32_t border : {2, -1}) { + FT_Stroker_ExportBorder_nid_postfix(pair.guest, border, &target.guest); + FT_Stroker_ExportBorder(pair.native, static_cast(border), &target.native); + } + FT_Stroker_ExportBorder_nid_postfix(nullptr, 0, &target.guest); + FT_Stroker_ExportBorder(nullptr, FT_STROKER_BORDER_LEFT, &target.native); + FT_Stroker_ExportBorder_nid_postfix(pair.guest, 1, nullptr); + FT_Stroker_ExportBorder(pair.native, FT_STROKER_BORDER_RIGHT, nullptr); + FT_Stroker_Export_nid_postfix(nullptr, &target.guest); + FT_Stroker_Export(nullptr, &target.native); + FT_Stroker_Export_nid_postfix(pair.guest, nullptr); + FT_Stroker_Export(pair.native, nullptr); + Require(target.Pristine()); + if constexpr (SharedPoints) { + FT_Stroker_Export_nid_postfix(pair.guest, &target.guest); + FT_Stroker_Export(pair.native, &target.native); + Require(target.Matches() && target.guest.n_points == 3 + points && target.guest.n_contours == 1 + contours); + FT_Stroker_Rewind_nid_postfix(pair.guest); + FT_Stroker_Rewind(pair.native); + Target rewound(3, points, contours); + FT_Stroker_Export_nid_postfix(pair.guest, &rewound.guest); + FT_Stroker_Export(pair.native, &rewound.native); + Require(rewound.Pristine()); + } else { + Require(Throws([&] { FT_Stroker_ExportBorder_nid_postfix(pair.guest, 1, &target.guest); }) && target.Pristine()); + } +} + int main() { GuestLibrary library{}; FT_Library native = nullptr; @@ -234,5 +590,11 @@ int main() { TestGetBorderCounts(library, native); TestConicTo(library, native); TestCubicTo(library, native); + TestParseOutline(library, native); + TestParseOutlineHostLimits(library, native); + TestParseOutlineArrays(library, native); + TestExportBorder(library, native); + TestExport(library, native); + TestExportUnchanged(library, native); Require(FT_Done_FreeType(library.host) == 0 && FT_Done_FreeType(native) == 0); } diff --git a/core/libs/tests/GuestFreebsdClocks.cpp b/core/libs/tests/GuestFreebsdClocks.cpp index 2331ab57b2..c7497f5ba6 100644 --- a/core/libs/tests/GuestFreebsdClocks.cpp +++ b/core/libs/tests/GuestFreebsdClocks.cpp @@ -76,10 +76,12 @@ static void UnsupportedResolutionStillThrows() { } static void OmittedTimeIsFaultAfterClockValidation() { + constexpr int fault = static_cast(0x8002000eu); for (const int clockId : {GUEST_CLOCK_REALTIME, GUEST_CLOCK_VIRTUAL, GUEST_CLOCK_PROF, GUEST_CLOCK_MONOTONIC, GUEST_CLOCK_REALTIME_PRECISE, GUEST_CLOCK_SECOND, GUEST_CLOCK_THREAD_CPUTIME_ID, GUEST_CLOCK_PROCESS_CPUTIME_ID}) { *__error_nid_postfix() = 0; Require(clock_gettime_nid_postfix(clockId, nullptr) == -1 && *__error_nid_postfix() == 14); + Require(sceKernelClockGettime(clockId, nullptr) == fault); } bool rejected = false; try { @@ -89,6 +91,7 @@ static void OmittedTimeIsFaultAfterClockValidation() { rejected = true; } Require(rejected); + Require(sceKernelClockGettime(3, nullptr) == fault); } static void InvalidClockIdFailsGettime() { diff --git a/core/libs/tests/GuestFreopenHostStandard.cpp b/core/libs/tests/GuestFreopenHostStandard.cpp index cf8e85e536..40403925cb 100644 --- a/core/libs/tests/GuestFreopenHostStandard.cpp +++ b/core/libs/tests/GuestFreopenHostStandard.cpp @@ -1,4 +1,8 @@ #include "prx/libc/include/Api.hpp" +#ifdef _WIN32 +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/NativeStat.hpp" +#endif #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -51,11 +55,28 @@ static std::string Contents(const std::string& name) { } int main() { - const auto sinkName = "anyps5-freopen-host-standard-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + ".txt"; - std::FILE* sink = std::fopen(sinkName.c_str(), "wb"); + const auto stamp = std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()); + const auto sinkName = "anyps5-freopen-host-standard-" + stamp + ".txt"; +#ifdef _WIN32 + const auto directoryName = "anyps5-freopen-host-standard-" + stamp; +#endif + FileStream* sink = fopen_nid_postfix(sinkName.c_str(), "wb"); Check(sink != nullptr); + const int sinkDescriptor = fileno_nid_postfix(sink); const int original = HostDup(1); - Check(original >= 0 && HostDup2(HostFileno(sink), 1) == 0); + Check(sinkDescriptor >= 0 && original >= 0 && HostDup2(sinkDescriptor, 1) == 0); + +#ifdef _WIN32 + Check(std::filesystem::create_directory(directoryName)); + errno = 0; + Check(freopen_nid_postfix(directoryName.c_str(), "r", __stdoutp_nid_postfix) == __stdoutp_nid_postfix); + FileStat status{}; + Check(sceKernelFstat(fileno_nid_postfix(__stdoutp_nid_postfix), &status) == 0 && (status.st_mode & 0170000) == 0040000); + char block[16]; + Check(fread_nid_postfix(block, 1, sizeof(block), __stdoutp_nid_postfix) == 0); + Check(ferror_nid_postfix(__stdoutp_nid_postfix) != 0 && errno == EISDIR); + Check(HostDup2(sinkDescriptor, 1) == 0); +#endif errno = 0; FileStream* const reopened = freopen_nid_postfix("", "w", __stdoutp_nid_postfix); @@ -63,7 +84,7 @@ int main() { const int written = HostWrite(1, "host", 4); const bool restored = HostDup2(original, 1) == 0 && HostClose(original) == 0; - Check(std::fclose(sink) == 0); + Check(fclose_nid_postfix(sink) == 0); Check(restored); Check(reopened == nullptr); Check(error == ENOENT); @@ -73,5 +94,8 @@ int main() { errno = 0; Check(fputs_nid_postfix("text", __stdoutp_nid_postfix) == -1 && errno == EBADF); Check(std::filesystem::remove(sinkName)); +#ifdef _WIN32 + Check(std::filesystem::remove(directoryName)); +#endif return 0; } diff --git a/core/libs/tests/GuestG2PDialog.cpp b/core/libs/tests/GuestG2PDialog.cpp index 7a363fc84d..150fbdbc7e 100644 --- a/core/libs/tests/GuestG2PDialog.cpp +++ b/core/libs/tests/GuestG2PDialog.cpp @@ -77,6 +77,7 @@ int main() { Require(sceG2PDialogOpen(nullptr) == kErrArgNull); Require(sceG2PDialogGetResult(nullptr) == kErrArgNull); + Require(sceG2PDialogGetResult(reinterpret_cast(0x10)) == static_cast(0x80B8000Au)); std::int32_t prefixResult[8] = {-1, -1, -1, -1, 0x5a, 0x5a, 0x5a, 0x5a}; Require(sceG2PDialogGetResult(reinterpret_cast(prefixResult)) == 0); Require(prefixResult[2] == kResultUserCanceled); diff --git a/core/libs/tests/GuestHmd.cpp b/core/libs/tests/GuestHmd.cpp index b12d4ebf2e..9d3c4594e0 100644 --- a/core/libs/tests/GuestHmd.cpp +++ b/core/libs/tests/GuestHmd.cpp @@ -42,6 +42,7 @@ void CheckDisconnected() { Require(sceHmdInternalGetDeviceStatus(&status) == 0, "status query failed"); Require(status == Hmd::DeviceStatus::NotDetected, "headset reported connected"); Require(sceHmdInternalGetDeviceStatus(nullptr) == Hmd::ParameterNull, "null status"); + Require(sceHmdInternalGetDeviceStatus(reinterpret_cast(static_cast(0x10))) == Hmd::ParameterNull, "unwritable status"); Require(sceHmdInternalMmapIsConnect() == 0, "connection reported present"); Require(!sceHmdInternalIsGameVr2d(), "game reported in VR2D without a headset"); Require(!sceHmdInternalIsMiniAppVr2d(), "mini app reported in VR2D without a headset"); @@ -211,6 +212,7 @@ int main() { expected.userId = -1; Require(std::memcmp(&info, &expected, sizeof(info)) == 0, "incorrect absent-device output"); Require(sceHmdGetDeviceInformation(nullptr) == Hmd::ParameterNull, "null device query"); + Require(sceHmdGetDeviceInformation(reinterpret_cast(static_cast(0x10))) == Hmd::ParameterNull, "unwritable device query"); Require(sceHmdOpen(-1, 0, 0, nullptr) == Hmd::ParameterInvalid, "invalid user"); Require(sceHmdOpen(0xff, 0, 0, nullptr) == Hmd::ParameterInvalid, "system user"); diff --git a/core/libs/tests/GuestHttp2.cpp b/core/libs/tests/GuestHttp2.cpp index ce904ae56d..39d4d34eca 100644 --- a/core/libs/tests/GuestHttp2.cpp +++ b/core/libs/tests/GuestHttp2.cpp @@ -9,6 +9,8 @@ static void Require(bool value) { if (!value) std::abort(); } static constexpr int InvalidId = static_cast(0x817B1100); +static constexpr int NotFound = static_cast(0x817B1025); +static constexpr int NullPointer = static_cast(0x817B1225); template static bool Throws(TCall call) { @@ -56,7 +58,35 @@ int main() { Require(callbackCalls == 0); Require(sceHttp2SetCookieBox(tmpl, 0) == 0); - Require(Throws([&] { sceHttp2SetCookieBox(tmpl, box); })); + int bound = -7; + Require(sceHttp2GetCookieBox(tmpl, &bound) == NotFound && bound == 0); + Require(sceHttp2GetCookieBox(tmpl, nullptr) == NullPointer && sceHttp2GetCookieBox(context, nullptr) == NullPointer); + Require(sceHttp2SetCookieBox(tmpl, box) == 0); + bound = -7; + Require(sceHttp2GetCookieBox(tmpl, &bound) == 0 && bound == box); + bound = -7; + Require(sceHttp2GetCookieBox(request, &bound) == NotFound && bound == 0); + const int copied = sceHttp2CreateRequestWithURL(tmpl, "GET", "https://localhost/", 0); + bound = -7; + Require(sceHttp2GetCookieBox(copied, &bound) == 0 && bound == box); + Require(sceHttp2SetCookieBox(tmpl, 0) == 0); + bound = -7; + Require(sceHttp2GetCookieBox(tmpl, &bound) == NotFound && bound == 0); + bound = -7; + Require(sceHttp2GetCookieBox(copied, &bound) == 0 && bound == box); + for (const int id : {context, 0, -1, 0x7fffffff}) { + Require(sceHttp2SetCookieBox(id, box) == InvalidId && sceHttp2SetCookieBox(id, 0) == InvalidId); + bound = -7; + Require(sceHttp2GetCookieBox(id, &bound) == InvalidId && bound == 0); + } + const int deletedBox = sceHttp2CreateCookieBox(0x10000); + Require(deletedBox > 0 && sceHttp2DeleteCookieBox(deletedBox) == 0); + for (const int value : {-1, 0x7fffffff, context, tmpl, deletedBox}) Require(sceHttp2SetCookieBox(request, value) == InvalidId); + const int heldBox = sceHttp2CreateCookieBox(0x10000); + Require(heldBox > 0 && sceHttp2SetCookieBox(copied, heldBox) == 0 && sceHttp2DeleteCookieBox(heldBox) == 0); + bound = -7; + Require(sceHttp2GetCookieBox(copied, &bound) == 0 && bound == heldBox); + Require(sceHttp2SetCookieBox(copied, 0) == 0 && sceHttp2DeleteRequest(copied) == 0); Require(sceHttp2CookieFlush(box) == 0); Require(sceHttp2SetResolveRetry(tmpl, 0) == 0); Require(sceHttp2SetResolveRetry(tmpl, 3) == 0); diff --git a/core/libs/tests/GuestHttp2Transport.cpp b/core/libs/tests/GuestHttp2Transport.cpp index 27caabeac2..2c55d303ac 100644 --- a/core/libs/tests/GuestHttp2Transport.cpp +++ b/core/libs/tests/GuestHttp2Transport.cpp @@ -54,7 +54,7 @@ bool Throws(TCall call) { return false; } -enum class Mode { Normal, Large, NoLength, Head, NotFound, Redirect, Auth, Reset, Truncate, Gzip, GzipSplit, GzipTruncated, GzipChecksum, GzipConcat, GzipLarge, GzipEmpty, GzipEmptyMembers, GzipLargeMembers, GzipHeaders, GzipHeaderChecksum, GzipSecondHeaderChecksum, GzipSecondChecksum, GzipSecondSize, GzipTrailingJunk, StallHandshake, StallHeaders, StallBody, NoAlpn, Informational, StallUpload, RedirectOnce, GzipRedirectEmpty, GzipRedirectBody, CloseHandshake, AlertHandshake, Expired, Http10, Http11, Http11Chunked, Http11Close, Http11Early, Http11Info, Http11Redirect, Http11StallHeaders, Http11StallBody, Http11BadLength, Http11Ambiguous, Http11Truncated, Http11BadChunk, Http11Gzip, Http1Plain, EarlyFinal, EarlyReset, EarlyEmpty, EarlyLarge, EarlyResetIncomplete }; +enum class Mode { Normal, Large, NoLength, Head, NotFound, Redirect, Auth, Reset, Truncate, Gzip, GzipSplit, GzipTruncated, GzipChecksum, GzipConcat, GzipLarge, GzipEmpty, GzipEmptyMembers, GzipLargeMembers, GzipHeaders, GzipHeaderChecksum, GzipSecondHeaderChecksum, GzipSecondChecksum, GzipSecondSize, GzipTrailingJunk, StallHandshake, StallHeaders, StallBody, NoAlpn, Informational, StallUpload, Cookie, CookieFlood, RedirectOnce, GzipRedirectEmpty, GzipRedirectBody, CloseHandshake, AlertHandshake, Expired, Http10, Http11, Http11Chunked, Http11Close, Http11Early, Http11Info, Http11Redirect, Http11StallHeaders, Http11StallBody, Http11BadLength, Http11Ambiguous, Http11Truncated, Http11BadChunk, Http11Gzip, Http1Plain, EarlyFinal, EarlyReset, EarlyEmpty, EarlyLarge, EarlyResetIncomplete }; const char* Protocols[] = {"h2", nullptr}; const char* Http1Protocols[] = {"http/1.1", nullptr}; @@ -109,10 +109,17 @@ std::string HeaderGzip(const std::string& payload, bool corrupt = false) { return header + member.substr(10); } +struct ResponseSpec { + int status = 0; + std::vector> headers; + std::vector> informationalHeaders; +}; + class Server { public: - explicit Server(Mode mode, std::string location = "/another", int redirectStatus = 302) - : mode(mode), location(std::move(location)), redirectStatus(redirectStatus) { + explicit Server(Mode mode, std::string location = "/another", int redirectStatus = 302, + std::vector responseCookies = {}) + : mode(mode), location(std::move(location)), redirectStatus(redirectStatus), responseCookies(std::move(responseCookies)) { mbedtls_net_init(&listener); mbedtls_entropy_init(&entropy); mbedtls_ctr_drbg_init(&random); @@ -168,6 +175,10 @@ public: changed.notify_all(); } + void SetResponse(ResponseSpec response) { + std::lock_guard lock(responseMutex); + responseSpec = std::move(response); + } void Join() { Release(); if (worker.joinable()) worker.join(); } const std::multimap& Headers() const { return headers; } const std::string& Upload() const { return upload; } @@ -178,6 +189,9 @@ private: const Mode mode; const std::string location; const int redirectStatus; + const std::vector responseCookies; + std::mutex responseMutex; + ResponseSpec responseSpec; unsigned accepted = 0; mbedtls_net_context listener; mbedtls_entropy_context entropy; @@ -251,7 +265,13 @@ private: return static_cast(size); } + ResponseSpec GetResponse() { + std::lock_guard lock(responseMutex); + return responseSpec; + } + void Respond(nghttp2_session* session, std::int32_t stream) { + const auto configured = GetResponse(); Require(!responded, "one response per request"); responded = true; if (mode == Mode::StallHeaders) { Stage(2); Stall(); return; } @@ -259,6 +279,7 @@ private: body = (mode == Mode::Large || mode == Mode::EarlyLarge) ? std::string(200000, 'x') : "response body"; const bool redirect = mode == Mode::Redirect || mode == Mode::GzipRedirectEmpty || mode == Mode::GzipRedirectBody || (mode == Mode::RedirectOnce && accepted == 1); std::string status = IsEarly() ? "413" : mode == Mode::NotFound ? "404" : redirect ? std::to_string(redirectStatus) : mode == Mode::Auth ? "401" : "200"; + if (configured.status) status = std::to_string(configured.status); if (mode == Mode::EarlyEmpty) body.clear(); auto length = std::to_string(body.size()); const auto requestMethod = headers.find(":method"); @@ -298,14 +319,23 @@ private: if (mode == Mode::GzipTrailingJunk) body = StoredGzip("first") + "trailing junk"; length = std::to_string(body.size()); } - if (mode == Mode::Informational) { - nghttp2_nv early{reinterpret_cast(const_cast(":status")), reinterpret_cast(const_cast("103")), 7, 3, 0}; - Require(nghttp2_submit_headers(session, 0, stream, nullptr, &early, 1, nullptr) == 0, "server informational response"); + if (mode == Mode::Informational || !configured.informationalHeaders.empty()) { + std::vector> earlyFields{{":status", "103"}}; + earlyFields.insert(earlyFields.end(), configured.informationalHeaders.begin(), configured.informationalHeaders.end()); + std::vector early; + for (auto& [name, value] : earlyFields) early.push_back({reinterpret_cast(name.data()), reinterpret_cast(value.data()), name.size(), value.size(), 0}); + Require(nghttp2_submit_headers(session, 0, stream, nullptr, early.data(), early.size(), nullptr) == 0, "server informational response"); } std::vector> fields{{":status", status}, {"x-loopback", "yes"}}; if (mode != Mode::NoLength) fields.emplace_back("content-length", length); if (redirect) fields.emplace_back("location", location); if (mode == Mode::GzipRedirectEmpty || mode == Mode::GzipRedirectBody || mode == Mode::Gzip || mode == Mode::GzipSplit || mode == Mode::GzipTruncated || mode == Mode::GzipChecksum || mode == Mode::GzipConcat || mode == Mode::GzipLarge || (mode >= Mode::GzipEmpty && mode <= Mode::GzipTrailingJunk)) fields.emplace_back("content-encoding", "gzip"); + if (mode == Mode::Cookie) fields.emplace_back("set-cookie", "session=fresh; Path=/"); + if (mode == Mode::CookieFlood) { + for (int i = 0; i < 1500; ++i) fields.emplace_back("set-cookie", "f" + std::to_string(i) + "=v; Path=/"); + } + if (mode == Mode::Auth) fields.emplace_back("www-authenticate", "Basic realm=\"loopback\""); + fields.insert(fields.end(), configured.headers.begin(), configured.headers.end()); std::vector response; for (auto& [name, value] : fields) response.push_back({reinterpret_cast(name.data()), reinterpret_cast(value.data()), name.size(), value.size(), 0}); nghttp2_data_provider2 provider{}; @@ -373,6 +403,7 @@ private: } void ExchangeHttp1(mbedtls_net_context& client, mbedtls_ssl_context* ssl) { + const auto configured = GetResponse(); std::string input; std::array buffer; const auto readMore = [&] { @@ -410,7 +441,11 @@ private: position = next + 2; } end += 4; - if (mode == Mode::Http11Info) write("HTTP/1.1 100 Continue\r\n\r\n"); + if (mode == Mode::Http11Info || !configured.informationalHeaders.empty()) { + std::string informational = "HTTP/1.1 100 Continue\r\n"; + for (const auto& [name, value] : configured.informationalHeaders) informational += name + ": " + value + "\r\n"; + write(informational + "\r\n"); + } if (mode == Mode::Http11Early) upload = input.substr(end); else if (headers.count("transfer-encoding")) { Require(headers.find("transfer-encoding")->second == "chunked", "HTTP1 request transfer coding"); @@ -432,7 +467,7 @@ private: upload = input.substr(end, length); } if (mode == Mode::Http11StallHeaders) { Stage(2); Stall(); return; } - const int code = mode == Mode::Http11Early ? 413 : mode == Mode::Http11Redirect ? redirectStatus : 200; + const int code = configured.status ? configured.status : mode == Mode::Http11Early ? 413 : mode == Mode::Http11Redirect ? redirectStatus : 200; std::string response = mode == Mode::Http10 ? "HTTP/1.0 " : "HTTP/1.1 "; response += std::to_string(code) + " Fixture\r\n"; if (mode == Mode::Http11Chunked || mode == Mode::Http11BadChunk) response += "Transfer-Encoding: chunked\r\n"; @@ -441,6 +476,8 @@ private: if (mode == Mode::Http11BadLength) response += "Content-Length: 14\r\n"; if (mode == Mode::Http11Ambiguous) response += "Transfer-Encoding: chunked\r\n"; if (mode == Mode::Http11Redirect) response += "Location: " + location + "\r\n"; + for (const auto& cookie : responseCookies) response += "Set-Cookie: " + cookie + "\r\n"; + for (const auto& [name, value] : configured.headers) response += name + ": " + value + "\r\n"; response += "X-Loopback: yes\r\nConnection: close\r\n\r\n"; if (mode == Mode::Http11StallBody) { write(response); Stage(2); Stall(); return; } if (headers.find(":method")->second != "HEAD") { @@ -532,6 +569,129 @@ struct Client { ~Client() { if (context > 0) Require(sceHttp2Term(context) == 0, "client term"); } }; +void CookieBox(int ssl) { + std::fprintf(stderr, "HTTP2 cookie box scenario\n"); + Server server(Mode::Cookie); + Client client(ssl); + const std::string url = server.Url("127.0.0.1"); + const int box = sceHttp2CreateCookieBox(0x10000); + Require(box > 0 && sceHttp2AddCookie(box, url.c_str(), "stored=old", 10) == 0, "stored cookie"); + Require(sceHttp2SetCookieBox(client.tmpl, box) == 0, "bind cookie box"); + client.Create(url); + Require(sceHttp2SendRequest(client.request, nullptr, 0) == 0, "send with a cookie box"); + std::array buffer; + while (sceHttp2ReadData(client.request, buffer.data(), buffer.size()) > 0) {} + server.Join(); + const auto& headers = server.Headers(); + Require(headers.count("cookie") == 1 && headers.find("cookie")->second == "stored=old", "box cookie sent"); + std::array cookie{}; + std::size_t required = 0; + Require(sceHttp2GetCookie(box, url.c_str(), cookie.data(), &required, cookie.size()) == 0, "response cookie lookup"); + Require(std::string_view(cookie.data()) == "session=fresh; stored=old", "response cookie stored in the box"); + Require(sceHttp2DeleteCookieBox(box) == 0, "delete cookie box"); +} + +void Http1CookieBox(int ssl, Mode mode) { + std::fprintf(stderr, "HTTP1 cookie box scenario %d\n", static_cast(mode)); + Server server(mode, "/another", 302, {"first=one; Path=/", "second=two; Path=/"}); + Client client(ssl, 1, 0, mode == Mode::Http10 ? 1 : 2); + const auto url = server.Url(); + const auto secureUrl = mode == Mode::Http1Plain ? "https" + url.substr(4) : url; + const int box = sceHttp2CreateCookieBox(0x10000); + Require(box > 0, "HTTP1 cookie box"); + for (const std::string cookie : {"stored=old; Path=/", "secure=secret; Path=/; Secure", "hidden=path; Path=/other"}) + Require(sceHttp2AddCookie(box, secureUrl.c_str(), cookie.c_str(), cookie.size()) == 0, "HTTP1 seed cookie"); + Require(sceHttp2SetCookieBox(client.tmpl, box) == 0, "HTTP1 bind template cookie box"); + Require(sceHttp2AddRequestHeader(client.tmpl, "Cookie", "replaced=template", 0) == 0, "HTTP1 template cookie"); + client.Create(url); + Require(sceHttp2AddRequestHeader(client.request, "Cookie", "manual=one", 0) == 0, "HTTP1 replace manual cookie"); + Require(sceHttp2AddRequestHeader(client.request, "Cookie", "extra=two", 1) == 0, "HTTP1 append manual cookie"); + Require(sceHttp2AddRequestHeader(client.request, "Host", "other.example", 0) == 0, "HTTP1 override host without changing cookie origin"); + Require(sceHttp2SendRequest(client.request, nullptr, 0) == 0, "HTTP1 send with cookie box"); + std::array buffer{}; + while (sceHttp2ReadData(client.request, buffer.data(), buffer.size()) > 0) {} + server.Join(); + const auto& headers = server.Headers(); + Require(headers.count("cookie") == 1, "HTTP1 exactly one cookie header"); + const auto& sent = headers.find("cookie")->second; + Require(sent.starts_with("manual=one; extra=two; ") && sent.find("stored=old") != std::string::npos, + "HTTP1 manual and matching box cookies sent together"); + Require((sent.find("secure=secret") != std::string::npos) == (mode != Mode::Http1Plain), "HTTP1 secure cookie follows URL scheme"); + Require(sent.find("hidden=") == std::string::npos && sent.find("replaced=") == std::string::npos, + "HTTP1 path selection and manual replacement"); + std::size_t required = 0; + Require(sceHttp2GetCookie(box, url.c_str(), buffer.data(), &required, buffer.size()) == 0, "HTTP1 response cookie lookup"); + const std::string_view stored(buffer.data()); + Require(stored.find("first=one") != std::string_view::npos && stored.find("second=two") != std::string_view::npos, + "HTTP1 each response cookie stored"); + Require(sceHttp2DeleteCookieBox(box) == 0, "HTTP1 delete cookie box"); +} + +void Http1CookieRedirect(int ssl) { + Server target(Mode::Http1Plain); + Server redirect(Mode::Http11Redirect, target.Url(), 302, {"ordinary=next; Path=/", "secure=secret; Path=/; Secure"}); + Client client(ssl, 1, 0, 2); + const int box = sceHttp2CreateCookieBox(0x10000); + Require(box > 0 && sceHttp2SetCookieBox(client.tmpl, box) == 0, "HTTP1 redirect bind cookie box"); + client.Create(redirect.Url()); + Require(sceHttp2AddRequestHeader(client.request, "Cookie", "manual=origin", 0) == 0, "HTTP1 origin manual cookie"); + Require(sceHttp2SendRequest(client.request, nullptr, 0) == 0, "HTTP1 cookie redirect"); + std::array buffer; + while (sceHttp2ReadData(client.request, buffer.data(), buffer.size()) > 0) {} + redirect.Join(); + target.Join(); + const auto& headers = target.Headers(); + Require(headers.count("cookie") == 1 && headers.find("cookie")->second == "ordinary=next", + "HTTP1 redirect stores cookies and excludes secure and origin-only manual cookies on plain target"); + Require(sceHttp2DeleteCookieBox(box) == 0, "HTTP1 redirect delete cookie box"); +} + +void Http1EmptyCookie(int ssl, bool explicitCookie) { + Server server(Mode::Http1Plain); + Client client(ssl, 1, 0, 2); + const int box = sceHttp2CreateCookieBox(0x10000); + Require(box > 0 && sceHttp2SetCookieBox(client.tmpl, box) == 0, "HTTP1 empty bind cookie box"); + client.Create(server.Url()); + if (explicitCookie) Require(sceHttp2AddRequestHeader(client.request, "Cookie", "", 0) == 0, "HTTP1 explicit empty cookie"); + Require(sceHttp2SendRequest(client.request, nullptr, 0) == 0, "HTTP1 empty box send"); + std::array buffer; + while (sceHttp2ReadData(client.request, buffer.data(), buffer.size()) > 0) {} + server.Join(); + const auto& headers = server.Headers(); + Require(headers.count("cookie") == (explicitCookie ? 1u : 0u), "HTTP1 empty box does not invent a cookie field"); + if (explicitCookie) Require(headers.find("cookie")->second.empty(), "HTTP1 explicit empty cookie preserved"); + Require(sceHttp2DeleteCookieBox(box) == 0, "HTTP1 empty delete cookie box"); +} + +void CookieBoxDelete(int ssl, Mode mode = Mode::CookieFlood) { + std::fprintf(stderr, "cookie box delete scenario %d\n", static_cast(mode)); + std::vector responseCookies; + if (mode != Mode::CookieFlood) + for (int index = 0; index < 1500; ++index) responseCookies.push_back("item" + std::to_string(index) + "=value; Path=/"); + Server server(mode, "/another", 302, std::move(responseCookies)); + Client client(ssl, 4, 0, mode == Mode::CookieFlood ? 3 : 2); + const std::string url = server.Url("127.0.0.1"); + const int box = sceHttp2CreateCookieBox(0x10000); + Require(box > 0 && sceHttp2SetCookieBox(client.tmpl, box) == 0, "bind cookie box"); + Require(sceHttp2SetCookieMaxNumPerDomain(box, 2000) == 0, "room for every response cookie"); + client.Create(url); + auto pending = std::async(std::launch::async, [&] { + try { static_cast(sceHttp2SendRequest(client.request, nullptr, 0)); } catch (const std::exception&) {} + }); + std::size_t required = 0; + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); + while (sceHttp2GetCookie(box, url.c_str(), nullptr, &required, 0) != 0) { + Require(std::chrono::steady_clock::now() < deadline, "first response cookie stored"); + std::this_thread::yield(); + } + auto deleting = std::async(std::launch::async, [&] { return sceHttp2DeleteRequest(client.request); }); + Require(deleting.wait_for(std::chrono::seconds(2)) == std::future_status::ready, "delete returns while the transport stores response cookies"); + Require(deleting.get() == 0, "delete a request whose transport stores response cookies"); + Require(pending.wait_for(std::chrono::seconds(2)) == std::future_status::ready, "send ends after the delete"); + server.Join(); + Require(sceHttp2DeleteCookieBox(box) == 0, "delete cookie box"); +} + struct RedirectObservation { int calls = 0; int request = 0; @@ -1425,6 +1585,51 @@ void HostAuthority(int ssl) { Require(headers.count("host") == 0, "host header is not sent"); } +void AuthenticationStatus(int ssl, int version, ResponseSpec response, bool authEnabled) { + std::fprintf(stderr, "HTTP2 authentication status scenario version=%d status=%d enabled=%d\n", version, response.status, authEnabled); + const int expected = response.status; + Server server(version == 1 ? Mode::Http10 : version == 2 ? Mode::Http11 : Mode::Normal); + server.SetResponse(std::move(response)); + Client client(ssl, 4, 0, version); + int enabled = -1; + Require(sceHttp2GetAuthEnabled(client.tmpl, &enabled) == 0 && enabled == 1, "authentication remains enabled by default"); + if (!authEnabled) Require(sceHttp2SetAuthEnabled(client.tmpl, 0) == 0, "disable automatic authentication"); + client.Create(server.Url()); + Require(sceHttp2SendRequest(client.request, nullptr, 0) == 0, "receive response without negotiating authentication"); + int status = 0; + Require(sceHttp2GetStatusCode(client.request, &status) == 0 && status == expected, "preserve authentication response status"); + int kind = -1; + std::uint64_t length = 0; + Require(sceHttp2GetResponseContentLength(client.request, &kind, &length) == 0 && kind == 0 && length == 13, "preserve authentication response length"); + std::string body; + std::array buffer{}; + for (;;) { + const int count = sceHttp2ReadData(client.request, buffer.data(), buffer.size()); + Require(count >= 0 && count <= static_cast(buffer.size()), "read authentication response body"); + if (count == 0) break; + body.append(buffer.data(), static_cast(count)); + } + Require(body == "response body", "preserve authentication response body"); + server.Join(); +} + +void AuthenticationStatuses(int ssl) { + for (const int version : {1, 2, 3}) { + const std::string origin = version == 3 ? "www-authenticate" : "WwW-AuThEnTiCaTe"; + const std::string proxy = version == 3 ? "proxy-authenticate" : "PrOxY-AuThEnTiCaTe"; + const std::string challenge = "Basic realm=\"loopback\""; + for (const int status : {401, 407}) { + const auto& matching = status == 401 ? origin : proxy; + const auto& other = status == 401 ? proxy : origin; + AuthenticationStatus(ssl, version, {status, {}, {}}, true); + AuthenticationStatus(ssl, version, {status, {{other, challenge}}, {}}, true); + AuthenticationStatus(ssl, version, {status, {{matching, challenge}}, {}}, false); + if (version != 1) AuthenticationStatus(ssl, version, {status, {}, {{matching, challenge}}}, true); + } + AuthenticationStatus(ssl, version, {200, {{origin, challenge}, {proxy, challenge}}, {}}, true); + } +} + int main(int argc, char** argv) { #ifndef _WIN32 std::signal(SIGPIPE, SIG_IGN); @@ -1443,6 +1648,8 @@ int main(int argc, char** argv) { Require(unloaded > 0, "SSL context without certificates"); Success(unloaded, Mode::Normal); Require(sceSslTerm_nid_postfix(unloaded) == 0, "SSL context without certificates term"); + } else if (argc == 2 && std::string_view(argv[1]) == "--auth-status") { + AuthenticationStatuses(ssl); } else if (argc == 2 && std::string_view(argv[1]) == "--gzip-members") { GzipMembers(ssl); } else if (argc == 2 && std::string_view(argv[1]) == "--gzip-headers") { @@ -1464,6 +1671,12 @@ int main(int argc, char** argv) { Http1Policies(ssl); Http1Cancellation(ssl); Http1BufferedDeadline(ssl); + } else if (argc == 2 && std::string_view(argv[1]) == "--http1-cookies") { + for (const auto mode : {Mode::Http10, Mode::Http11, Mode::Http11Chunked, Mode::Http11Close, Mode::Http1Plain}) Http1CookieBox(ssl, mode); + Http1CookieRedirect(ssl); + for (const bool explicitCookie : {false, true}) Http1EmptyCookie(ssl, explicitCookie); + CookieBoxDelete(ssl, Mode::Http11); + CookieBoxDelete(ssl, Mode::Http1Plain); } else if (argc == 2 && std::string_view(argv[1]) == "--ssl-callbacks") { SslCallbacks(ssl); } else if (argc == 2 && std::string_view(argv[1]) == "--redirects") { @@ -1486,6 +1699,8 @@ int main(int argc, char** argv) { GzipFailure(ssl, Mode::GzipTruncated, "truncated gzip throws"); GzipFailure(ssl, Mode::GzipChecksum, "gzip checksum mismatch throws"); for (const auto mode : {Mode::NoAlpn, Mode::Reset, Mode::Truncate, Mode::Auth}) Failure(ssl, mode); + CookieBox(ssl); + CookieBoxDelete(ssl); Handshake(ssl, Mode::Normal, static_cast(0x8095f00b), 0x4); Handshake(ssl, Mode::CloseHandshake, static_cast(0x8095170d)); Handshake(ssl, Mode::AlertHandshake, static_cast(0x8095f010)); diff --git a/core/libs/tests/GuestIcuVersionExtents.cpp b/core/libs/tests/GuestIcuVersionExtents.cpp new file mode 100644 index 0000000000..bcafa93aba --- /dev/null +++ b/core/libs/tests/GuestIcuVersionExtents.cpp @@ -0,0 +1,116 @@ +#include "prx/libSceIcu/libSceIcu.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +static void Require(bool value, std::source_location location = std::source_location::current()) { + if (!value) { + std::fprintf(stderr, "boundary check failed at line %u\n", location.line()); + std::abort(); + } +} + +class Guarded { + unsigned char* memory; + std::size_t page; + std::size_t writable; +public: + explicit Guarded(std::size_t capacity = 1) { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + page = info.dwPageSize; +#else + const long size = sysconf(_SC_PAGESIZE); + Require(size > 0); + page = static_cast(size); +#endif + writable = ((capacity + 128 + page - 1) / page) * page; +#ifdef _WIN32 + memory = static_cast(VirtualAlloc(nullptr, writable + page * 2, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS)); + Require(memory != nullptr); +#else + memory = static_cast(mmap(nullptr, writable + page * 2, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(memory != MAP_FAILED); +#endif + Reset(); + } + ~Guarded() { +#ifdef _WIN32 + Require(VirtualFree(memory, 0, MEM_RELEASE) != 0); +#else + Require(munmap(memory, writable + page * 2) == 0); +#endif + } + Guarded(const Guarded&) = delete; + Guarded& operator=(const Guarded&) = delete; + void Protect(bool readOnly) { +#ifdef _WIN32 + DWORD previous; + Require(VirtualProtect(memory + page, writable, readOnly ? PAGE_READONLY : PAGE_READWRITE, &previous) != 0); +#else + Require(mprotect(memory + page, writable, readOnly ? PROT_READ : PROT_READ | PROT_WRITE) == 0); +#endif + } + void Reset() { + Protect(false); + std::memset(memory + page, 0xa5, writable); + } + unsigned char* End(std::size_t count) { + Require(count + 64 <= writable); + return memory + page + writable - (std::max)(count, std::size_t{1}); + } + void Prefix(const void* address) const { + const auto* bytes = static_cast(address); + for (int index = 1; index <= 32; ++index) Require(bytes[-index] == 0xa5); + } +}; + +struct Version { const char* input; std::array bytes; const char* text; }; +int main() { + const Version versions[]{ + {"",{0,0,0,0},"0.0"},{"0",{0,0,0,0},"0.0"},{"1",{1,0,0,0},"1.0"}, + {"1.2.3.4",{1,2,3,4},"1.2.3.4"},{"255.0.255.1",{255,0,255,1},"255.0.255.1"}, + {"105.101.100.110",{105,101,100,110},"15.11.10.110"}, + {" +1.2.3.4",{1,2,3,4},"1.2.3.4"},{"1.2.3.4.567890123456789",{1,2,3,4},"1.2.3.4"}}; + std::size_t cases = 0; + for (const auto& version : versions) { + const auto length = std::strlen(version.input); + Guarded source(length+1), wide((length+1)*2), destination(4), text(20); + auto* input = reinterpret_cast(source.End(length+1)); + std::memcpy(input,version.input,length+1); + auto* units = reinterpret_cast(wide.End((length+1)*2)); + for (std::size_t i = 0; i <= length; ++i) std::construct_at(units+i,static_cast(version.input[i])); + source.Protect(true); wide.Protect(true); + for (bool utf16 : {false,true}) { + destination.Reset(); text.Reset(); + auto* bytes = destination.End(4); + if (utf16) u_versionFromUString_nid_postfix(bytes,units); + else u_versionFromString_nid_postfix(bytes,input); + Require(std::equal(version.bytes.begin(),version.bytes.end(),bytes)); + auto* output = reinterpret_cast(text.End(20)); + u_versionToString_nid_postfix(bytes,output); + Require(std::string(output) == version.text); + Require(std::memcmp(input,version.input,length+1) == 0); + source.Prefix(input); wide.Prefix(units); destination.Prefix(bytes); text.Prefix(output); + ++cases; + } + } + std::printf("%zu guarded ICU version conversions passed\n", cases); +} diff --git a/core/libs/tests/GuestIpmiConfigEstimators.cpp b/core/libs/tests/GuestIpmiConfigEstimators.cpp new file mode 100644 index 0000000000..1e7f9d597b --- /dev/null +++ b/core/libs/tests/GuestIpmiConfigEstimators.cpp @@ -0,0 +1,43 @@ +#include "prx/libSceIpmi/include/Api.hpp" +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "libSceIpmi config estimator: %s\n", message); + std::abort(); +} + +} + +int main() { + alignas(8) unsigned char config[0x40]; + std::memset(config, 0, sizeof(config)); + + Require(_ZN4IPMI7Session6Config25estimateSessionMemorySizeEv(config) == 0, "Session::Config size is zero"); + Require(_ZN4IPMI6Client6Config24estimateClientMemorySizeEv(config) == 0x1000, "Client::Config size is 0x1000"); + + const std::uint64_t first[] = {0x200, 0x300}; + Require(_ZNK4IPMI6Server6Config29estimateTempWorkingMemorySizeEv(first) == 0x400, "Server::Config takes the larger first value"); + const std::uint64_t second[] = {0x500, 0x100}; + Require(_ZNK4IPMI6Server6Config29estimateTempWorkingMemorySizeEv(second) == 0x600, "Server::Config takes the larger second value"); + + alignas(8) unsigned char object[0x40]; + std::memset(object, 0x5a, sizeof(object)); + Require(_ZN4IPMI4impl10ClientImpl12tryGetResultEjPiPvPmm(object, 0, nullptr, nullptr, nullptr, 0) == 0, + "ClientImpl::tryGetResult returns zero"); + Require(_ZN4IPMI4impl10ClientImpl17invokeAsyncMethodEjPKvmPjPKNS_6Client12EventNotifeeE(object, 0, nullptr, 0, nullptr, nullptr) == 0, + "ClientImpl::invokeAsyncMethod returns zero"); + Require(_ZN4IPMI4impl11SessionImpl11tryDispatchEPvm(object, nullptr, 0) == 0, "SessionImpl::tryDispatch returns zero"); + for (std::size_t index = 0; index < sizeof(object); ++index) { + Require(object[index] == 0x5a, "the query leaves the object untouched"); + } + + std::puts("libSceIpmi config estimator checks passed"); + return 0; +} diff --git a/core/libs/tests/GuestIpmiNoOps.cpp b/core/libs/tests/GuestIpmiNoOps.cpp new file mode 100644 index 0000000000..5c61c82e7f --- /dev/null +++ b/core/libs/tests/GuestIpmiNoOps.cpp @@ -0,0 +1,47 @@ +#include "prx/libSceIpmi/include/Api.hpp" +#include +#include +#include +#include + +namespace { + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "libSceIpmi no-op handler: %s\n", message); + std::abort(); +} + +using Handler = void (APS5_VABI*)(void*); + +} + +int main() { + const Handler handlers[] = { + _ZN4IPMI7SessionD1Ev, + _ZN4IPMI7SessionD2Ev, + _ZN4IPMI6ClientD1Ev, + _ZN4IPMI6ClientD2Ev, + _ZN4IPMI6ServerD1Ev, + _ZN4IPMI6ServerD2Ev, + _ZN4IPMI6Server12EventHandlerD1Ev, + _ZN4IPMI6Server12EventHandlerD2Ev, + _ZN4IPMI6Server12EventHandler15onSessionKilledEPNS_7SessionE, + _ZN4IPMI6Server12EventHandler20onSyncMethodDispatchEPNS_7SessionEjPvmmS4_m, + _ZN4IPMI6Server12EventHandler20onSyncMethodDispatchEPNS_7SessionEjPKNS_8DataInfoEjPNS_10BufferInfoEj, + _ZN4IPMI6Server12EventHandler21onAsyncMethodDispatchEPNS_7SessionEjjPvmS4_m, + _ZN4IPMI6Server12EventHandler21onAsyncMethodDispatchEPNS_7SessionEjjPKNS_8DataInfoEj, + }; + + for (const Handler handler : handlers) { + alignas(8) unsigned char object[0x100]; + for (std::size_t index = 0; index < sizeof(object); ++index) object[index] = 0x5a; + handler(object); + for (std::size_t index = 0; index < sizeof(object); ++index) { + Require(object[index] == 0x5a, "the handler leaves the object untouched"); + } + } + + std::puts("libSceIpmi no-op handler checks passed"); + return 0; +} diff --git a/core/libs/tests/GuestJxrBitIo.cpp b/core/libs/tests/GuestJxrBitIo.cpp new file mode 100644 index 0000000000..bc00be2dff --- /dev/null +++ b/core/libs/tests/GuestJxrBitIo.cpp @@ -0,0 +1,256 @@ +#include "prx/libSceJxr/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include + +extern "C" std::uint32_t JxrBitIoReference_nid_no_patch(int operation, void* state, std::uint32_t value, std::uint32_t bits); + +namespace { + +constexpr std::size_t RingAlignment = 16384; +constexpr std::int32_t WriteMask = ~8192; +constexpr std::int32_t ReadMask = WriteMask & ~1; + +void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "JXR bit I/O: %s\n", message); + std::abort(); +} + +template +void RequireInvalidArgument(TAction action, const char* message) { + try { + action(); + } catch (const std::invalid_argument&) { + return; + } + Require(false, message); +} + +struct Ring { + std::vector storage = std::vector(RingAlignment * 3); + std::uint8_t* base = reinterpret_cast( + (reinterpret_cast(storage.data()) + RingAlignment - 1) & ~std::uintptr_t{RingAlignment - 1}); +}; + +std::uint32_t seed = 0x2468ace1; +std::uint32_t Next() { + seed = seed * 1664525u + 1013904223u; + return seed >> 8; +} + +std::uint32_t LoadBigEndian(const std::uint8_t* pointer) { + return (std::uint32_t{pointer[0]} << 24) | (std::uint32_t{pointer[1]} << 16) | (std::uint32_t{pointer[2]} << 8) | pointer[3]; +} + +Jxr::BitIo Attach(Ring& ring, std::size_t offset, std::uint32_t bitsUsed, std::size_t reference) { + Jxr::BitIo io{}; + io.mask = ReadMask; + io.start = ring.base; + io.current = ring.base + offset; + io.bitsUsed = bitsUsed; + io.accumulator = LoadBigEndian(io.current) << bitsUsed; + io.referenceOffset = reference; + return io; +} + +void RequireSame(const Jxr::BitIo& guest, const Ring& guestRing, const Jxr::BitIo& native, const Ring& nativeRing, + const char* message) { + Require(guest.accumulator == native.accumulator && guest.bitsUsed == native.bitsUsed && guest.mask == native.mask && + guest.current - guestRing.base == native.current - nativeRing.base && + guest.start - guestRing.base == native.start - nativeRing.base && + guest.referenceOffset == native.referenceOffset, + message); + Require(std::memcmp(guestRing.base, nativeRing.base, RingAlignment) == 0, message); +} + +std::uint32_t Guest(int operation, Jxr::BitIo* io, std::uint32_t value, std::uint32_t bits) { + switch (operation) { + case 0: return peekBit16_nid_postfix(io, bits); + case 1: return flushBit16_nid_postfix(io, bits); + case 2: return getBit16_nid_postfix(io, bits); + case 3: return getBool16_nid_postfix(io); + case 4: return static_cast(getBit16s_nid_postfix(io, bits)); + case 5: return getBit32_nid_postfix(io, bits); + case 6: return flushToByte_nid_postfix(io); + case 7: return getSizeRead_nid_postfix(io); + case 8: return getPosRead_nid_postfix(io); + case 9: putBit16z_nid_postfix(io, value, bits); return 0; + case 10: putBit16_nid_postfix(io, value, bits); return 0; + case 11: putBit32_nid_postfix(io, value, bits); return 0; + case 12: fillToByte_nid_postfix(io); return 0; + case 13: return getSizeWrite_nid_postfix(io); + default: return 0xffffffffu; + } +} + +void RunReads() { + Ring guestRing, nativeRing; + for (std::size_t i = 0; i < RingAlignment; ++i) guestRing.base[i] = nativeRing.base[i] = static_cast(Next()); + for (int round = 0; round < 64; ++round) { + const auto offset = (Next() % 4096) * 2; + const auto used = Next() % 16; + const std::size_t reference = 100000 + Next() % 100000; + auto guest = Attach(guestRing, offset, used, reference); + auto native = Attach(nativeRing, offset, used, reference); + for (int step = 0; step < 2000; ++step) { + const int operation = static_cast(Next() % 9); + std::uint32_t bits = Next() % 17; + if (operation == 4) bits %= 16; + if (operation == 5) bits = Next() % 33; + const bool zeroBits = bits == 0 && (operation == 0 || operation == 2 || operation == 5); + const auto expected = zeroBits ? 0u : JxrBitIoReference_nid_no_patch(operation, &native, 0, bits); + if (zeroBits && operation != 0) JxrBitIoReference_nid_no_patch(1, &native, 0, 0); + const auto actual = Guest(operation, &guest, 0, bits); + Require(actual == expected, "read result differs from jxrlib"); + RequireSame(guest, guestRing, native, nativeRing, "read state differs from jxrlib"); + } + } +} + +void RunWrites() { + Ring guestRing, nativeRing; + for (int round = 0; round < 64; ++round) { + for (std::size_t i = 0; i < RingAlignment; ++i) guestRing.base[i] = nativeRing.base[i] = static_cast(Next()); + const auto offset = (Next() % 4096) * 2; + const auto used = Next() % 16; + const auto accumulator = Next() & (used == 0 ? 0u : 0xffffffffu >> (32 - used)); + auto guest = Attach(guestRing, offset, used, 0); + guest.start = guestRing.base + (Next() % 2) * 4096; + guest.accumulator = accumulator; + guest.mask = WriteMask; + auto native = guest; + native.start = nativeRing.base + (guest.start - guestRing.base); + native.current = nativeRing.base + offset; + for (int step = 0; step < 2000; ++step) { + const int operation = 9 + static_cast(Next() % 5); + std::uint32_t bits = operation == 11 ? Next() % 33 : Next() % 17; + std::uint32_t value = Next() ^ (Next() << 24); + if (operation == 9) value &= bits == 0 ? 0u : 0xffffffffu >> (32 - bits); + const bool emptyStore = native.bitsUsed == 0 && (operation == 12 || (operation != 13 && bits == 0)); + const auto expected = emptyStore ? 0u : JxrBitIoReference_nid_no_patch(operation, &native, value, bits); + const auto actual = Guest(operation, &guest, value, bits); + Require(actual == expected, "write result differs from jxrlib"); + RequireSame(guest, guestRing, native, nativeRing, "write state differs from jxrlib"); + } + } +} + +void RunZeroBits(Ring& ring) { + for (std::size_t i = 0; i < RingAlignment; ++i) ring.base[i] = static_cast(i * 13 + 5); + for (std::uint32_t used = 0; used < 16; ++used) { + auto io = Attach(ring, 64, used, 0); + const auto before = io; + Require(peekBit16_nid_postfix(&io, 0) == 0, "zero-bit peek"); + Require(std::memcmp(&io, &before, sizeof(io)) == 0, "zero-bit peek keeps the state"); + Require(getBit16_nid_postfix(&io, 0) == 0, "zero-bit read"); + Require(io.current == before.current && io.bitsUsed == used && io.accumulator == before.accumulator, + "zero-bit read keeps the position"); + Require(getBit32_nid_postfix(&io, 0) == 0, "zero-bit 32-bit read"); + Require(io.current == before.current && io.bitsUsed == used && io.accumulator == before.accumulator, + "zero-bit 32-bit read keeps the position"); + } + + std::vector snapshot(ring.base, ring.base + RingAlignment); + Jxr::BitIo io{}; + io.mask = WriteMask; + io.start = ring.base; + io.current = ring.base + 128; + const auto before = io; + putBit16z_nid_postfix(&io, 0, 0); + putBit16_nid_postfix(&io, 0xffffffffu, 0); + putBit32_nid_postfix(&io, 0xffffffffu, 0); + fillToByte_nid_postfix(&io); + Require(std::memcmp(&io, &before, sizeof(io)) == 0, "aligned empty stores keep the state"); + Require(std::memcmp(snapshot.data(), ring.base, RingAlignment) == 0, "aligned empty stores keep the buffer"); + + putBit16_nid_postfix(&io, 0xfff5u, 4); + Require(io.accumulator == 5 && io.bitsUsed == 4 && io.current == ring.base + 128, "masked 4-bit store state"); + Require(ring.base[128] == 0x50 && ring.base[129] == 0x00, "masked 4-bit store bytes"); + fillToByte_nid_postfix(&io); + Require(io.bitsUsed == 8 && ring.base[128] == 0x50, "fill to the byte boundary"); + fillToByte_nid_postfix(&io); + putBit16_nid_postfix(&io, 0x1234u, 0); + Require(io.bitsUsed == 8 && io.current == ring.base + 128, "empty stores at a byte boundary"); + putBit32_nid_postfix(&io, 0xfabcdeu, 20); + Require(io.bitsUsed == 12 && io.current == ring.base + 130, "20-bit store position"); + Require(ring.base[128] == 0x50 && ring.base[129] == 0xab && ring.base[130] == 0xcd && ring.base[131] == 0xe0, + "20-bit store bytes"); + putBit32_nid_postfix(&io, 0xffffffffu, 32); + Require(io.bitsUsed == 12 && io.current == ring.base + 134, "32-bit store position"); + Require(ring.base[130] == 0xcd && ring.base[131] == 0xef && ring.base[132] == 0xff && ring.base[133] == 0xff, + "32-bit store bytes"); +} + +void RunEdges() { + for (std::uint32_t qp = 0; qp < 256; ++qp) + Require(dquantBits_nid_postfix(static_cast(qp)) == + JxrBitIoReference_nid_no_patch(14, nullptr, qp, 0), + "dquantBits differs from jxrlib"); + + Ring ring; + for (std::size_t i = 0; i < RingAlignment; ++i) ring.base[i] = static_cast(i * 7); + auto io = Attach(ring, 8190, 4, 0); + Require(getBit16_nid_postfix(&io, 12) == (LoadBigEndian(ring.base + 8190) << 4 >> 20), "wrap read value"); + Require(io.current == ring.base && io.bitsUsed == 0, "read wraps to the ring start"); + + auto copy = Attach(ring, 10, 3, 0); + Require(getBit16_S_nid_postfix(nullptr, ©, 5) == ((LoadBigEndian(ring.base + 10) << 3) >> 27), "getBit16_S value"); + Require(copy.current == ring.base + 10 && copy.bitsUsed == 8, "getBit16_S state"); + auto crossing = Attach(ring, 4096, 0, 0); + RequireInvalidArgument([&] { getBit16_S_nid_postfix(nullptr, &crossing, 1); }, "getBit16_S packet without stream"); + + RunZeroBits(ring); + + for (const std::uint32_t used : {0u, 1u, 8u, 15u}) { + auto odd = Attach(ring, 129, used, 0); + odd.mask = WriteMask; + const auto before = odd; + const std::vector snapshot(ring.base, ring.base + RingAlignment); + RequireInvalidArgument([&] { putBit16z_nid_postfix(&odd, 0, 0); }, "odd-position unmasked store"); + RequireInvalidArgument([&] { putBit16_nid_postfix(&odd, 1, 1); }, "odd-position masked store"); + RequireInvalidArgument([&] { putBit32_nid_postfix(&odd, 0xffffffffu, 32); }, "odd-position 32-bit store"); + RequireInvalidArgument([&] { fillToByte_nid_postfix(&odd); }, "odd-position byte fill"); + Require(std::memcmp(&odd, &before, sizeof(odd)) == 0, "rejected odd-position stores keep the state"); + Require(std::memcmp(ring.base, snapshot.data(), RingAlignment) == 0, "rejected odd-position stores keep the buffer"); + } + + RequireInvalidArgument([] { getBit16_nid_postfix(nullptr, 1); }, "null read state"); + RequireInvalidArgument([] { putBit16_nid_postfix(nullptr, 1, 1); }, "null write state"); + RequireInvalidArgument([] { getSizeRead_nid_postfix(nullptr); }, "null size state"); + RequireInvalidArgument([] { getSizeWrite_nid_postfix(nullptr); }, "null size write state"); + RequireInvalidArgument([] { getPosRead_nid_postfix(nullptr); }, "null position state"); + RequireInvalidArgument([] { flushToByte_nid_postfix(nullptr); }, "null flush state"); + RequireInvalidArgument([] { fillToByte_nid_postfix(nullptr); }, "null fill state"); + auto edge = Attach(ring, 0, 0, 0); + RequireInvalidArgument([&] { peekBit16_nid_postfix(&edge, 17); }, "17-bit peek"); + RequireInvalidArgument([&] { flushBit16_nid_postfix(&edge, 17); }, "17-bit flush"); + RequireInvalidArgument([&] { getBit16s_nid_postfix(&edge, 16); }, "16-bit signed read"); + RequireInvalidArgument([&] { getBit32_nid_postfix(&edge, 33); }, "33-bit read"); + RequireInvalidArgument([&] { putBit16z_nid_postfix(&edge, 2, 1); }, "wide unmasked value"); + RequireInvalidArgument([&] { putBit16_nid_postfix(&edge, 0, 17); }, "17-bit write"); + RequireInvalidArgument([&] { putBit32_nid_postfix(&edge, 0, 33); }, "33-bit write"); + edge.mask = WriteMask; + edge.bitsUsed = 16; + RequireInvalidArgument([&] { putBit16z_nid_postfix(&edge, 0, 0); }, "write position past the word"); + RequireInvalidArgument([&] { fillToByte_nid_postfix(&edge); }, "fill position past the word"); + edge.bitsUsed = 0; + edge.mask = -1; + RequireInvalidArgument([&] { flushBit16_nid_postfix(&edge, 1); }, "odd wrap mask"); + edge.mask = ReadMask; + edge.current = nullptr; + RequireInvalidArgument([&] { getBit16_nid_postfix(&edge, 1); }, "null current pointer"); +} + +} // namespace + +int main() { + RunReads(); + RunWrites(); + RunEdges(); + return 0; +} diff --git a/core/libs/tests/GuestJxrBitIoReference.c b/core/libs/tests/GuestJxrBitIoReference.c new file mode 100644 index 0000000000..2e4e5fd335 --- /dev/null +++ b/core/libs/tests/GuestJxrBitIoReference.c @@ -0,0 +1,36 @@ +#include "strcodec.h" +#include + +_Static_assert(sizeof(BitIOInfo) == 48, "BitIOInfo size"); + +static void PutMasked(BitIOInfo* io, uint32_t value, uint32_t bits) { + putBit16z(io, value & (0xffffu >> (16 - bits)), bits); +} + +uint32_t JxrBitIoReference_nid_no_patch(int operation, void* state, uint32_t value, uint32_t bits) { + BitIOInfo* io = (BitIOInfo*)state; + switch (operation) { + case 0: return peekBit16(io, bits); + case 1: return flushBit16(io, bits); + case 2: return getBit16(io, bits); + case 3: return getBool16(io); + case 4: return (uint32_t)getBit16s(io, bits); + case 5: return getBit32(io, bits); + case 6: return flushToByte(io); + case 7: return getSizeRead(io); + case 8: return getPosRead(io); + case 9: putBit16z(io, value, bits); return 0; + case 10: PutMasked(io, value, bits); return 0; + case 11: + if (bits > 16) { + PutMasked(io, value >> (bits - 16), 16); + bits -= 16; + } + PutMasked(io, value, bits); + return 0; + case 12: fillToByte(io); return 0; + case 13: return getSizeWrite(io); + case 14: return dquantBits((U8)value); + default: return 0xffffffffu; + } +} diff --git a/core/libs/tests/GuestJxrGlue.cpp b/core/libs/tests/GuestJxrGlue.cpp new file mode 100644 index 0000000000..7c4031147a --- /dev/null +++ b/core/libs/tests/GuestJxrGlue.cpp @@ -0,0 +1,202 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libSceJxr/include/Api.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +extern "C" const void* GetPixelFormatFromHash(unsigned char hash); + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +void* lastCalloc = nullptr; +std::size_t lastCount = 0; +std::size_t lastSize = 0; +void* lastFreed = nullptr; +int frees = 0; +bool failCalloc = false; + +void* APS5_VABI Allocate(std::size_t size) { return std::malloc(size); } +void APS5_VABI Free(void* pointer) { + if (pointer != nullptr) { + lastFreed = pointer; + ++frees; + } + std::free(pointer); +} +void* APS5_VABI Reallocate(void* pointer, std::size_t size) { return std::realloc(pointer, size); } +void* APS5_VABI Calloc(std::size_t count, std::size_t size) { + lastCount = count; + lastSize = size; + lastCalloc = failCalloc ? nullptr : std::calloc(count, size); + return lastCalloc; +} +void* APS5_VABI Align(std::size_t, std::size_t size) { return std::malloc(size); } +void* APS5_VABI Realign(void* pointer, std::size_t size, std::size_t) { return std::realloc(pointer, size); } +int APS5_VABI PosixAlign(void** pointer, std::size_t, std::size_t size) { + *pointer = std::malloc(size); + return *pointer ? 0 : 12; +} + +void InitializeHeap() { + alignas(16) static std::array process{}; + alignas(16) static std::array libc{}; + alignas(16) static std::array replacement{}; + const auto write = [](auto& data, std::size_t offset, auto value) { + std::memcpy(data.data() + offset, &value, sizeof(value)); + }; + write(process, 0, std::uint64_t{0x40}); + write(process, 8, std::uint32_t{0x4942524f}); + write(process, 0x38, libc.data()); + write(libc, 0, std::uint64_t{0x38}); + write(libc, 0x30, replacement.data()); + write(replacement, 0, std::uint64_t{0x78}); + write(replacement, 8, std::uint64_t{2}); + write(replacement, 0x20, reinterpret_cast(&Allocate)); + write(replacement, 0x28, reinterpret_cast(&Free)); + write(replacement, 0x30, reinterpret_cast(&Calloc)); + write(replacement, 0x38, reinterpret_cast(&Reallocate)); + write(replacement, 0x40, reinterpret_cast(&Align)); + write(replacement, 0x48, reinterpret_cast(&Realign)); + write(replacement, 0x50, reinterpret_cast(&PosixAlign)); + ApplicationHeapInitialize_nid_no_patch(process.data()); +} + +void TestPixelFormatHash() { + int found = 0; + for (int hash = 0; hash < 256; ++hash) { + const void* expected = GetPixelFormatFromHash(static_cast(hash)); + const void* actual = GetPixelFormatFromHash_nid_postfix(static_cast(hash)); + Require((expected == nullptr) == (actual == nullptr)); + if (!actual) continue; + ++found; + Require(std::memcmp(expected, actual, 16) == 0); + Require(static_cast(actual)[15] == hash); + } + Require(found > 0 && found < 256); + const std::uint8_t gray8[16] = {0x24, 0xc3, 0xdd, 0x6f, 0x03, 0x4e, 0xfe, 0x4b, + 0xb1, 0x85, 0x3d, 0x77, 0x76, 0x8d, 0xc9, 0x08}; + const void* gray = GetPixelFormatFromHash_nid_postfix(0x08); + Require(gray && std::memcmp(gray, gray8, sizeof(gray8)) == 0); +} + +template +void RequireThrows(Function function) { + bool threw = false; + try { + function(); + } catch (const std::invalid_argument&) { + threw = true; + } catch (const std::length_error&) { + threw = true; + } + Require(threw); +} + +void TestImageIids() { + const std::uint32_t sentinel = 7; + const std::uint32_t* iid = &sentinel; + Require(GetImageEncodeIID_nid_postfix(".jxr", &iid) == 0 && *iid == 101); + Require(GetImageDecodeIID_nid_postfix(".jxr", &iid) == 0 && *iid == 201); + Require(GetImageEncodeIID_nid_postfix(".JXR", &iid) == 0 && *iid == 101); + Require(GetImageEncodeIID_nid_postfix(".jxr.tmp", &iid) == 0 && *iid == 101); + Require(GetImageEncodeIID_nid_postfix(".wdp", &iid) == 0 && *iid == 100); + Require(GetImageDecodeIID_nid_postfix(".WDP", &iid) == 0 && *iid == 201); + Require(GetImageEncodeIID_nid_postfix(".hdp", &iid) == 0 && *iid == 100); + Require(GetImageDecodeIID_nid_postfix(".Hdp", &iid) == 0 && *iid == 201); + const std::uint32_t* encode = nullptr; + const std::uint32_t* again = nullptr; + Require(GetImageEncodeIID_nid_postfix(".jxr", &encode) == 0); + Require(GetImageEncodeIID_nid_postfix(".jxr", &again) == 0 && encode == again); + for (const char* extension : {".png", ".jx", "", "jxr", ".j\xc3r", ".jpg"}) { + iid = &sentinel; + Require(GetImageEncodeIID_nid_postfix(extension, &iid) == -106 && iid == &sentinel); + Require(GetImageDecodeIID_nid_postfix(extension, &iid) == -106 && iid == &sentinel); + Require(GetImageEncodeIID_nid_postfix(extension, nullptr) == -106); + Require(GetImageDecodeIID_nid_postfix(extension, nullptr) == -106); + } + RequireThrows([] { GetImageEncodeIID_nid_postfix(".jxr", nullptr); }); + RequireThrows([] { GetImageDecodeIID_nid_postfix(".wdp", nullptr); }); + RequireThrows([] { + const std::uint32_t* out = nullptr; + GetImageEncodeIID_nid_postfix(nullptr, &out); + }); + RequireThrows([] { + const std::uint32_t* out = nullptr; + GetImageDecodeIID_nid_postfix(nullptr, &out); + }); +} + +void TestAlignedAllocation() { + for (std::size_t alignment : {std::size_t{8}, std::size_t{16}, std::size_t{24}, std::size_t{64}, + std::size_t{4096}}) { + for (std::size_t size : {std::size_t{0}, std::size_t{1}, std::size_t{100}}) { + void* memory = reinterpret_cast(std::uintptr_t{1}); + const int before = frees; + Require(PKAllocAligned_nid_postfix(&memory, size, alignment) == 0); + Require(memory != nullptr && lastCalloc != nullptr); + Require(lastCount == 1 && lastSize == size + sizeof(void*) + alignment - 1); + const auto address = reinterpret_cast(memory); + Require(address % alignment == 0); + const auto offset = address - reinterpret_cast(lastCalloc); + Require(offset >= sizeof(void*) && offset + size <= lastSize); + void* stored = nullptr; + std::memcpy(&stored, static_cast(memory) - sizeof(void*), sizeof(stored)); + Require(stored == lastCalloc); + for (std::size_t index = 0; index < size; ++index) + Require(static_cast(memory)[index] == 0); + void* const original = lastCalloc; + Require(PKFreeAligned_nid_postfix(&memory) == 0); + Require(memory == nullptr && frees == before + 1 && lastFreed == original); + } + } + void* memory = reinterpret_cast(std::uintptr_t{1}); + failCalloc = true; + Require(PKAllocAligned_nid_postfix(&memory, 32, 16) == -101 && memory == nullptr); + failCalloc = false; + const int before = frees; + Require(PKFreeAligned_nid_postfix(&memory) == 0 && frees == before); + Require(PKFreeAligned_nid_postfix(nullptr) == 0 && frees == before); + RequireThrows([] { PKAllocAligned_nid_postfix(nullptr, 32, 16); }); + memory = reinterpret_cast(std::uintptr_t{1}); + RequireThrows([&memory] { PKAllocAligned_nid_postfix(&memory, 32, 0); }); + Require(memory == nullptr); + for (std::size_t alignment : {std::size_t{1}, std::size_t{2}, std::size_t{4}, std::size_t{7}}) { + const int before = frees; + void* const previous = lastCalloc; + lastCalloc = nullptr; + memory = reinterpret_cast(std::uintptr_t{1}); + RequireThrows([&memory, alignment] { PKAllocAligned_nid_postfix(&memory, 32, alignment); }); + Require(memory == nullptr && frees == before && lastCalloc == nullptr); + lastCalloc = previous; + } + constexpr auto maximum = std::numeric_limits::max(); + RequireThrows([] { + void* out = nullptr; + PKAllocAligned_nid_postfix(&out, maximum - 4, 16); + }); + RequireThrows([] { + void* out = nullptr; + PKAllocAligned_nid_postfix(&out, 16, maximum - 8); + }); +} + +} + +int main() { + InitializeHeap(); + TestPixelFormatHash(); + TestImageIids(); + TestAlignedAllocation(); + Require(IDPEmpty_nid_postfix(nullptr) == 0); + int codec = 0; + Require(IDPEmpty_nid_postfix(&codec) == 0 && codec == 0); + return 0; +} diff --git a/core/libs/tests/GuestJxrQuantizer.cpp b/core/libs/tests/GuestJxrQuantizer.cpp new file mode 100644 index 0000000000..78ce968cc8 --- /dev/null +++ b/core/libs/tests/GuestJxrQuantizer.cpp @@ -0,0 +1,171 @@ +#include "prx/libSceJxr/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +void JxrQuantizerReferenceRemap_nid_no_patch(void* quantizer, std::int32_t shift, std::int32_t scaled); +void JxrQuantizerReferenceFormat_nid_no_patch(void** quantizers, std::uint8_t mode, std::size_t channels, + std::size_t position, std::int32_t shiftedUv, std::int32_t scaled); +std::int32_t JxrQuantizerReferenceAcMode_nid_no_patch(void* macroblock, std::int32_t format); +void JxrQuantizerReferenceZigzag_nid_no_patch(void* context); +} + +namespace { + +constexpr std::size_t QuantizerSize = 20; +constexpr std::size_t MacroblockSize = 1160; +constexpr std::size_t ContextSize = 704; + +struct Quantizer { + std::uint8_t index; + std::int32_t qp; + std::int32_t offset; + std::int32_t mantissa; + std::int32_t exponent; +}; +static_assert(sizeof(Quantizer) == QuantizerSize); + +void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "JXR quantizer: %s\n", message); + std::abort(); +} + +template +void RequireInvalidArgument(TAction action, const char* message) { + try { + action(); + } catch (const std::invalid_argument&) { + return; + } + Require(false, message); +} + +std::uint32_t seed = 0x13579bdf; +std::uint32_t Next() { + seed = seed * 1664525u + 1013904223u; + return seed >> 8; +} + +void TestRemap() { + for (std::int32_t scaled = 0; scaled < 2; ++scaled) { + for (std::int32_t shift = 0; shift < 3; ++shift) { + for (int index = 0; index < 256; ++index) { + Quantizer guest{}; + guest.index = static_cast(index); + guest.qp = -1; + Quantizer native = guest; + remapQP_nid_postfix(&guest, shift, scaled); + JxrQuantizerReferenceRemap_nid_no_patch(&native, shift, scaled); + Require(std::memcmp(&guest, &native, sizeof(guest)) == 0, "remapQP differs from jxrlib"); + } + } + } + Quantizer lossless{}; + lossless.qp = 9; + lossless.offset = lossless.mantissa = lossless.exponent = 7; + remapQP_nid_postfix(&lossless, 1, 1); + Require(lossless.qp == 1 && lossless.offset == 0 && lossless.mantissa == 0 && lossless.exponent == 0, + "index 0 is lossless"); + Quantizer plain{}; + plain.index = 4; + remapQP_nid_postfix(&plain, 1, 0); + Require(plain.qp == 1 && plain.offset == 0, "index 4 maps to QP 1 without scaled arithmetic"); + plain.index = 255; + remapQP_nid_postfix(&plain, 1, 1); + Require(plain.qp == 31 << 15 && plain.offset == ((plain.qp * 3 + 1) >> 3), "index 255 with shift 1"); + + RequireInvalidArgument([] { remapQP_nid_postfix(nullptr, 1, 1); }, "null quantizer"); + Quantizer wide{}; + wide.index = 255; + RequireInvalidArgument([&] { remapQP_nid_postfix(&wide, 11, 1); }, "QP over 32 bits"); + wide.index = 3; + RequireInvalidArgument([&] { remapQP_nid_postfix(&wide, -1, 1); }, "negative shift"); + for (const std::uint8_t index : {std::uint8_t{3}, std::uint8_t{255}}) { + wide.index = index; + RequireInvalidArgument([&] { remapQP_nid_postfix(&wide, INT32_MAX, 1); }, "largest shift"); + RequireInvalidArgument([&] { remapQP_nid_postfix(&wide, INT32_MIN, 1); }, "smallest shift"); + } + wide.index = 3; + remapQP_nid_postfix(&wide, -1, 0); +} + +void TestFormat() { + for (int round = 0; round < 2000; ++round) { + const std::size_t channels = 1 + Next() % 16; + const std::size_t cqp = 1 + Next() % 16; + const std::size_t position = Next() % cqp; + const auto mode = static_cast(Next() % 3); + const std::int32_t shifted = static_cast(Next() % 2); + const std::int32_t scaled = static_cast(Next() % 2); + std::array guestStore{}, nativeStore{}; + for (std::size_t i = 0; i < channels * cqp; ++i) { + guestStore[i].index = static_cast(Next()); + guestStore[i].qp = static_cast(Next()); + } + nativeStore = guestStore; + std::array guest{}, native{}; + for (std::size_t channel = 0; channel < channels; ++channel) { + guest[channel] = &guestStore[channel * cqp]; + native[channel] = &nativeStore[channel * cqp]; + } + formatQuantizer_nid_postfix(guest.data(), mode, channels, position, shifted, scaled); + JxrQuantizerReferenceFormat_nid_no_patch(native.data(), mode, channels, position, shifted, scaled); + Require(std::memcmp(guestStore.data(), nativeStore.data(), sizeof(guestStore)) == 0, + "formatQuantizer differs from jxrlib"); + } + formatQuantizer_nid_postfix(nullptr, 0, 0, 0, 0, 0); + std::array table{}; + RequireInvalidArgument([] { formatQuantizer_nid_postfix(nullptr, 0, 1, 0, 0, 0); }, "null quantizer table"); + RequireInvalidArgument([&] { formatQuantizer_nid_postfix(table.data(), 0, 1, 0, 0, 0); }, "null channel"); + RequireInvalidArgument([&] { formatQuantizer_nid_postfix(table.data(), 0, 17, 0, 0, 0); }, "17 channels"); +} + +void TestAcPredMode() { + std::array block{}; + for (int round = 0; round < 20000; ++round) { + for (auto& value : block) value = static_cast(Next() % 2001) - 1000; + if (round % 3 == 0) + for (std::size_t i = 0; i < 48; ++i) block[i] /= 1 + static_cast(Next() % 64); + const auto format = static_cast(round % 7); + Require(getACPredMode_nid_postfix(block.data(), format) == + JxrQuantizerReferenceAcMode_nid_no_patch(block.data(), format), + "getACPredMode differs from jxrlib"); + } + block.fill(0); + Require(getACPredMode_nid_postfix(block.data(), 0) == 2, "no energy"); + block[1] = 5; + Require(getACPredMode_nid_postfix(block.data(), 0) == 0, "horizontal energy"); + block[4] = 5; + Require(getACPredMode_nid_postfix(block.data(), 0) == 2, "balanced energy"); + block[1] = 0; + Require(getACPredMode_nid_postfix(block.data(), 0) == 1, "vertical energy"); + RequireInvalidArgument([] { getACPredMode_nid_postfix(nullptr, 0); }, "null macroblock"); +} + +void TestZigzag() { + std::array guest{}, native{}; + for (std::size_t i = 0; i < ContextSize; ++i) guest[i] = native[i] = static_cast(Next()); + InitZigzagScan_nid_postfix(guest.data()); + JxrQuantizerReferenceZigzag_nid_no_patch(native.data()); + Require(guest == native, "InitZigzagScan differs from jxrlib"); + std::uint32_t first = 0; + std::memcpy(&first, guest.data() + 216 + 4, sizeof(first)); + Require(first == 0, "lowpass scan starts at coefficient 0"); + InitZigzagScan_nid_postfix(nullptr); +} + +} // namespace + +int main() { + TestRemap(); + TestFormat(); + TestAcPredMode(); + TestZigzag(); + return 0; +} diff --git a/core/libs/tests/GuestJxrQuantizerReference.c b/core/libs/tests/GuestJxrQuantizerReference.c new file mode 100644 index 0000000000..d342b56e35 --- /dev/null +++ b/core/libs/tests/GuestJxrQuantizerReference.c @@ -0,0 +1,19 @@ +#include "strcodec.h" +#include + +void JxrQuantizerReferenceRemap_nid_no_patch(void* quantizer, int32_t shift, int32_t scaled) { + remapQP((CWMIQuantizer*)quantizer, shift, scaled); +} + +void JxrQuantizerReferenceFormat_nid_no_patch(void** quantizers, uint8_t mode, size_t channels, size_t position, + int32_t shiftedUv, int32_t scaled) { + formatQuantizer((CWMIQuantizer**)quantizers, mode, channels, position, shiftedUv, scaled); +} + +int32_t JxrQuantizerReferenceAcMode_nid_no_patch(void* macroblock, int32_t format) { + return getACPredMode((CWMIMBInfo*)macroblock, (COLORFORMAT)format); +} + +void JxrQuantizerReferenceZigzag_nid_no_patch(void* context) { + InitZigzagScan((CCodingContext*)context); +} diff --git a/core/libs/tests/GuestKernelSys.cpp b/core/libs/tests/GuestKernelSys.cpp index b4ab6feb34..a523ce4af2 100644 --- a/core/libs/tests/GuestKernelSys.cpp +++ b/core/libs/tests/GuestKernelSys.cpp @@ -340,6 +340,7 @@ void CheckUnreadablePaths(const std::string& renameSource, int directory) { posixFault([&] { return lutimes_nid_postfix(paths.Unreadable(), nullptr); }); posixFault([&] { return readlink_nid_postfix(paths.Unreadable(), linkBuffer, sizeof(linkBuffer)); }); posixFault([&] { return pathconf_nid_postfix(paths.Unreadable(), 4); }); + posixFault([&] { return chroot_nid_postfix(paths.Unreadable()); }); posixFault([&] { return link_nid_postfix(paths.Unreadable(), "unused-link"); }); posixFault([&] { return link_nid_postfix(renameSource.c_str(), paths.Unreadable()); }); posixFault([&] { return symlink_nid_postfix(paths.Unreadable(), "unused-symlink"); }); @@ -604,10 +605,12 @@ void CheckCondattrClock() { Require(scePthreadCondattrGetclock(nullptr, &clock) == SCE_KERNEL_ERROR_EINVAL && clock == -1); Require(scePthreadCondattrGetclock(nullptr, nullptr) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadCondattrGetclock(&attr, &clock) == SCE_KERNEL_ERROR_EINVAL && clock == -1); + Require(scePthreadCondattrGetclock(reinterpret_cast(0x10), &clock) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadCondattrInit(&attr) == 0 && attr != nullptr); Require(scePthreadCondattrGetclock(&attr, &clock) == 0 && clock == Realtime); Require(scePthreadCondattrSetclock(&attr, Monotonic) == 0); Require(scePthreadCondattrGetclock(&attr, &clock) == 0 && clock == Monotonic); + Require(scePthreadCondattrGetclock(&attr, reinterpret_cast(0x10)) == SCE_KERNEL_ERROR_EFAULT); bool getclockThrew = false; try { scePthreadCondattrGetclock(&attr, nullptr); } catch (const std::invalid_argument&) { getclockThrew = true; } Require(getclockThrew); @@ -849,6 +852,7 @@ void CheckAmmMapperCalls() { std::array param{}; param.fill(0xAAAAAAAAAAAAAAAAull); Require(sceKernelMapperGetParam(nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelMapperGetParam(reinterpret_cast(0x10)) == SCE_KERNEL_ERROR_EFAULT); param[0] = 0x39; Require(sceKernelMapperGetParam(param.data()) == SCE_KERNEL_ERROR_EINVAL && param[0] == 0x39 && param[1] == 0xAAAAAAAAAAAAAAAAull); param[0] = 0x28; diff --git a/core/libs/tests/GuestKernelSysAdjtime.cpp b/core/libs/tests/GuestKernelSysAdjtime.cpp new file mode 100644 index 0000000000..9d968247e3 --- /dev/null +++ b/core/libs/tests/GuestKernelSysAdjtime.cpp @@ -0,0 +1,49 @@ +#include "prx/libkernel_sys/include/AdditionalExports.hpp" +#include "prx/libc/include/Api.hpp" + +#include +#include +#include +#include +#include + +namespace { + +constexpr int GuestEperm = 1; +constexpr int GuestEfault = 14; + +void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "kernel sys adjtime: %s\n", message); + std::abort(); + } +} + +void RequirePermissionDenied(const KernelTimeval& delta) { + KernelTimeval remaining{-3, -2}; + Require(adjtime_nid_postfix(&delta, &remaining) == -1 && *__error_nid_postfix() == GuestEperm, "jailed clock adjustment did not fail with EPERM"); + Require(remaining.tv_sec == -3 && remaining.tv_usec == -2, "denied adjustment modified oldDelta"); + Require(adjtime_nid_postfix(&delta, reinterpret_cast(1)) == -1 && *__error_nid_postfix() == GuestEperm, "adjtime checked oldDelta before denying the adjustment"); +} + +} // namespace + +int main() { + *__error_nid_postfix() = 123; + Require(adjtime_nid_postfix(nullptr, nullptr) == 0, "adjtime with both pointers null did not succeed"); + Require(*__error_nid_postfix() == 123, "successful adjtime query changed guest errno"); + KernelTimeval remaining{-3, -2}; + Require(adjtime_nid_postfix(nullptr, &remaining) == 0, "adjtime with a null delta did not succeed"); + Require(remaining.tv_sec == 0 && remaining.tv_usec == 0, "adjtime did not report a zero pending adjustment"); + Require(adjtime_nid_postfix(nullptr, reinterpret_cast(1)) == -1 && *__error_nid_postfix() == GuestEfault, "adjtime with an unwritable oldDelta did not fail with EFAULT"); + remaining = {-3, -2}; + Require(adjtime_nid_postfix(reinterpret_cast(1), &remaining) == -1 && *__error_nid_postfix() == GuestEfault, "adjtime with an unreadable delta did not fail with EFAULT"); + Require(remaining.tv_sec == -3 && remaining.tv_usec == -2, "adjtime wrote oldDelta before reading delta"); + Require(adjtime_nid_postfix(reinterpret_cast(1), reinterpret_cast(1)) == -1 && *__error_nid_postfix() == GuestEfault, "adjtime checked privileges before reading delta"); + for (const KernelTimeval delta : {KernelTimeval{2, 0}, KernelTimeval{0, -1}, KernelTimeval{0, 0}, + KernelTimeval{std::numeric_limits::max(), std::numeric_limits::max()}, + KernelTimeval{std::numeric_limits::min(), std::numeric_limits::min()}}) RequirePermissionDenied(delta); + Require(adjtime_nid_postfix(nullptr, &remaining) == 0 && remaining.tv_sec == 0 && remaining.tv_usec == 0, "denied adjustment changed the pending query result"); + std::puts("guest_kernel_sys_adjtime passed"); + return 0; +} diff --git a/core/libs/tests/GuestKernelSysCpumode.cpp b/core/libs/tests/GuestKernelSysCpumode.cpp index 3c5cb4fd65..76d4db0923 100644 --- a/core/libs/tests/GuestKernelSysCpumode.cpp +++ b/core/libs/tests/GuestKernelSysCpumode.cpp @@ -37,6 +37,7 @@ int main() { Require(Throws([] { sceKernelSetCpumodeGame(8); }), "an unmeasured mode did not throw"); Require(Throws([] { sceKernelSetCpumodeGame(-1); }), "a negative mode did not throw"); Require(Throws([] { sceKernelGetCpumodeGame(nullptr); }), "a null output did not throw"); + Require(sceKernelGetCpumodeGame(reinterpret_cast(0x10)) == static_cast(0x8002000Eu), "unwritable mode did not return EFAULT"); std::puts("game CPU mode tests passed"); return 0; } diff --git a/core/libs/tests/GuestKernelSysExecve.cpp b/core/libs/tests/GuestKernelSysExecve.cpp new file mode 100644 index 0000000000..a1f117785c --- /dev/null +++ b/core/libs/tests/GuestKernelSysExecve.cpp @@ -0,0 +1,110 @@ +#include "prx/libkernel_sys/include/AdditionalExports.hpp" +#include "prx/libc/include/General.hpp" +#include "prx/libc/include/Api.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int GuestE2big = 7; +constexpr int GuestEacces = 13; +constexpr int GuestEnoent = 2; +constexpr int GuestEnoexec = 8; +constexpr int GuestEfault = 14; +constexpr int GuestEnotdir = 20; +constexpr int GuestEnametoolong = 63; + +void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "kernel sys execve: %s\n", message); + std::abort(); + } +} + +void RequireFailure(int result, int error, const char* message) { + Require(result == -1 && *__error_nid_postfix() == error, message); +} + +std::string RepeatedComponents(std::size_t length) { + std::string path = "a"; + while (path.size() + 2 <= length) path += "/a"; + return path; +} + +void WriteFile(const char* name, const char* content, std::streamsize bytes, std::filesystem::perms mode) { + std::ofstream file(name, std::ios::binary); + file.write(content, bytes); + file.close(); +#ifndef _WIN32 + std::filesystem::permissions(name, mode); +#else + (void)mode; +#endif +} + +} // namespace + +int main() { + char first[] = "first"; + char second[] = "second"; + char* const argv[] = {first, second, nullptr}; + char* const emptyArgv[] = {nullptr}; + char* const environment[] = {second, nullptr}; + char* const junkArgument[] = {reinterpret_cast(1), nullptr}; + RequireFailure(_execve_nid_postfix("", nullptr, nullptr), GuestEfault, "execve with a null argv did not fail with EFAULT"); + static char unterminated[1101]; + std::memset(unterminated, 'x', sizeof(unterminated)); + unterminated[sizeof(unterminated) - 1] = '\0'; + RequireFailure(_execve_nid_postfix(unterminated, nullptr, nullptr), GuestEfault, "execve did not check a null argv before the pathname"); + RequireFailure(_execve_nid_postfix("kernel_execve_missing.bin", reinterpret_cast(1), nullptr), GuestEfault, "execve with an unreadable argv array did not fail with EFAULT"); + RequireFailure(_execve_nid_postfix("kernel_execve_missing.bin", junkArgument, nullptr), GuestEfault, "execve with an unreadable argument did not fail with EFAULT"); + RequireFailure(_execve_nid_postfix("kernel_execve_missing.bin", emptyArgv, reinterpret_cast(1)), GuestEfault, "execve with an unreadable envp array did not fail with EFAULT"); + const std::string severalComponents = RepeatedComponents(1023); + Require(severalComponents.size() == 1023, "the 1023 byte pathname test built the wrong length"); + RequireFailure(_execve_nid_postfix(severalComponents.c_str(), emptyArgv, nullptr), GuestEnoent, "execve with a 1023 byte pathname did not reach the lookup"); + const std::string tooLongPath = "a" + std::string(1024, 'x'); + Require(tooLongPath.size() == 1025, "the 1024 byte unterminated pathname test built the wrong length"); + RequireFailure(_execve_nid_postfix(tooLongPath.c_str(), emptyArgv, nullptr), GuestEnametoolong, "execve with an unterminated 1024 byte pathname did not fail with ENAMETOOLONG"); + RequireFailure(_execve_nid_postfix("", emptyArgv, nullptr), GuestEnoent, "execve with an empty pathname did not fail with ENOENT"); + RequireFailure(_execve_nid_postfix("kernel_execve_missing.bin", argv, environment), GuestEnoent, "execve of a missing file did not fail with ENOENT"); + constexpr char prefixBody[] = "prefix"; + WriteFile("kernel_execve_prefix.bin", prefixBody, sizeof(prefixBody) - 1, std::filesystem::perms::owner_all); + RequireFailure(_execve_nid_postfix("kernel_execve_prefix.bin/inside", argv, nullptr), GuestEnotdir, "execve through a file prefix did not fail with ENOTDIR"); + std::filesystem::create_directory("kernel_execve_dir"); + RequireFailure(_execve_nid_postfix("kernel_execve_dir", argv, nullptr), GuestEacces, "execve of a directory did not fail with EACCES"); + WriteFile("kernel_execve_zero.bin", "", 0, std::filesystem::perms::owner_all | std::filesystem::perms::group_all); + RequireFailure(_execve_nid_postfix("kernel_execve_zero.bin", argv, nullptr), GuestEnoexec, "execve of an empty file did not fail with ENOEXEC"); + WriteFile("kernel_execve_short.bin", "EL", 2, std::filesystem::perms::owner_all | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec); + RequireFailure(_execve_nid_postfix("kernel_execve_short.bin", argv, nullptr), GuestEnoexec, "execve of a two byte file did not fail with ENOEXEC"); + WriteFile("kernel_execve_plain.bin", "just data", 9, std::filesystem::perms::owner_all | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec); + RequireFailure(_execve_nid_postfix("kernel_execve_plain.bin", argv, nullptr), GuestEnoexec, "execve of a plain file did not fail with ENOEXEC"); +#ifndef _WIN32 + WriteFile("kernel_execve_unexec.bin", "just data", 9, std::filesystem::perms::owner_read | std::filesystem::perms::owner_write | std::filesystem::perms::group_read | std::filesystem::perms::others_read); + RequireFailure(_execve_nid_postfix("kernel_execve_unexec.bin", argv, nullptr), GuestEacces, "execve without execute bits did not fail with EACCES"); +#endif + const std::string oversized(2048, 'a'); + std::vector storage; + storage.reserve(128); + std::vector oversizedArgv; + oversizedArgv.reserve(129); + for (int index = 0; index < 128; ++index) { + storage.push_back(oversized); + oversizedArgv.push_back(storage.back().data()); + } + oversizedArgv.push_back(nullptr); + RequireFailure(_execve_nid_postfix("kernel_execve_missing.bin", oversizedArgv.data(), nullptr), GuestE2big, "execve with arguments beyond ARG_MAX did not fail with E2BIG"); + std::filesystem::remove("kernel_execve_prefix.bin"); + std::filesystem::remove("kernel_execve_zero.bin"); + std::filesystem::remove("kernel_execve_short.bin"); + std::filesystem::remove("kernel_execve_plain.bin"); + std::filesystem::remove("kernel_execve_unexec.bin"); + std::filesystem::remove("kernel_execve_dir"); + std::puts("guest_kernel_sys_execve passed"); + return 0; +} diff --git a/core/libs/tests/GuestKernelSysExecvpe.cpp b/core/libs/tests/GuestKernelSysExecvpe.cpp new file mode 100644 index 0000000000..2fd217cfd4 --- /dev/null +++ b/core/libs/tests/GuestKernelSysExecvpe.cpp @@ -0,0 +1,133 @@ +#include "prx/libkernel_sys/include/AdditionalExports.hpp" +#include "prx/libc/include/General.hpp" +#include "prx/libc/include/Api.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int GuestEnoent = 2; +constexpr int GuestEacces = 13; +constexpr int GuestEnoexec = 8; +constexpr int GuestE2big = 7; +constexpr int GuestEfault = 14; + +void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "kernel sys execvpe: %s\n", message); + std::abort(); + } +} + +void RequireFailure(int result, int error, const char* message) { + Require(result == -1 && *__error_nid_postfix() == error, message); +} + +void WriteFile(const char* name, const char* content, std::streamsize bytes, std::filesystem::perms mode) { + std::ofstream file(name, std::ios::binary); + file.write(content, bytes); + file.close(); +#ifndef _WIN32 + std::filesystem::permissions(name, mode); +#else + (void)mode; +#endif +} + +} // namespace + +int main() { + const auto originalDirectory = std::filesystem::current_path(); + const auto stamp = std::chrono::steady_clock::now().time_since_epoch().count(); + std::filesystem::path work; + for (unsigned int attempt = 0;; ++attempt) { + work = std::filesystem::temp_directory_path() / ("anyps5-execvpe-" + std::to_string(stamp) + "-" + std::to_string(attempt)); + if (std::filesystem::create_directory(work)) break; + } + std::filesystem::current_path(work); + char first[] = "first"; + char second[] = "second"; + char* const argv[] = {first, second, nullptr}; + constexpr auto fullExecute = std::filesystem::perms::owner_all | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec; + constexpr auto plainRead = std::filesystem::perms::owner_read | std::filesystem::perms::owner_write | std::filesystem::perms::group_read | std::filesystem::perms::others_read; + constexpr char plainBody[] = "plain data"; + RequireFailure(_execvpe_nid_postfix("", argv, nullptr), GuestEnoent, "execvpe with an empty name did not fail with ENOENT"); + RequireFailure(_execvpe_nid_postfix(nullptr, argv, nullptr), GuestEfault, "execvpe with a null name did not fail with EFAULT"); + RequireFailure(_execvpe_nid_postfix("kernel_execvpe_absent.bin", nullptr, nullptr), GuestEnoent, "execvpe with a null argv did not fail with ENOENT"); + const std::string longName(1024 * 1024, 'x'); + RequireFailure(_execvpe_nid_postfix(longName.c_str(), argv, nullptr), GuestEnoent, "an overlong name did not end the bounded search"); + RequireFailure(_execvpe_nid_postfix(("/" + longName).c_str(), argv, nullptr), GuestEnoent, "an overlong slash-qualified name did not end the bounded search"); + const std::string oversized(2048, 'a'); + std::vector storage; + std::vector oversizedArgv; + for (int index = 0; index < 128; ++index) { + storage.push_back(oversized); + oversizedArgv.push_back(storage.back().data()); + } + oversizedArgv.push_back(nullptr); + RequireFailure(_execvpe_nid_postfix("kernel_execvpe_absent.bin", oversizedArgv.data(), nullptr), GuestE2big, "execvpe with arguments beyond ARG_MAX did not fail with E2BIG"); + WriteFile("kernel_execvpe_data.bin", plainBody, sizeof(plainBody) - 1, static_cast(fullExecute)); + RequireFailure(_execvpe_nid_postfix("./kernel_execvpe_data.bin", argv, nullptr), GuestEnoent, "execvpe did not return ENOENT for a missing shell"); + std::filesystem::remove("kernel_execvpe_data.bin"); + std::filesystem::create_directory("bin"); + WriteFile("bin/sh", "", 0, static_cast(fullExecute)); + RequireFailure(_execvpe_nid_postfix("./bin/sh", argv, nullptr), GuestEnoexec, "execvpe did not reach the empty shell through its search and fallback"); + WriteFile("bin/plain.bin", "plain", 5, static_cast(fullExecute)); + RequireFailure(_execvpe_nid_postfix("./bin/plain.bin", argv, nullptr), GuestEnoexec, "execvpe did not reach the empty shell through its search and fallback"); + std::filesystem::create_directory("sbin"); + RequireFailure(_execvpe_nid_postfix("kernel_execvpe_absent.bin", argv, nullptr), GuestEnoent, "execvpe with the default search path and a missing file did not fail with ENOENT"); +#ifndef _WIN32 + WriteFile("bin/kernel_execvpe_refused.bin", "plain", 5, static_cast(plainRead)); + RequireFailure(_execvpe_nid_postfix("kernel_execvpe_refused.bin", argv, nullptr), GuestEacces, "execvpe without execute bits did not report EACCES after the search"); + std::filesystem::remove("bin/kernel_execvpe_refused.bin"); +#endif + std::filesystem::create_directory("usr"); + std::filesystem::create_directory("usr/bin"); + WriteFile("usr/bin/kernel_execvpe_data.bin", plainBody, sizeof(plainBody) - 1, static_cast(fullExecute)); + RequireFailure(_execvpe_nid_postfix("kernel_execvpe_data.bin", argv, nullptr), GuestEnoexec, "execvpe did not reach the empty shell through its search and fallback"); + std::filesystem::create_directory("sandbox"); + WriteFile("sandbox/kernel_execvpe_tool.bin", plainBody, sizeof(plainBody) - 1, static_cast(fullExecute)); + setenv_nid_postfix("PATH", "/sandbox", 1); + RequireFailure(_execvpe_nid_postfix("kernel_execvpe_tool.bin", argv, nullptr), GuestEnoexec, "execvpe did not reach the empty shell through its search and fallback"); + unsetenv_nid_postfix("PATH"); + RequireFailure(_execvpe_nid_postfix("kernel_execvpe_tool.bin", argv, nullptr), GuestEnoent, "execvpe with a cleared PATH did not fail with ENOENT"); + WriteFile("kernel_execvpe_tool.bin", plainBody, sizeof(plainBody) - 1, static_cast(fullExecute)); + setenv_nid_postfix("PATH", "", 1); + RequireFailure(_execvpe_nid_postfix("kernel_execvpe_tool.bin", argv, nullptr), GuestEnoexec, "execvpe did not reach the empty shell through its search and fallback"); + unsetenv_nid_postfix("PATH"); + std::filesystem::remove("kernel_execvpe_tool.bin"); + setenv_nid_postfix("PATH", "/sandbox", 1); + WriteFile("sandbox/kernel_execvpe_plain.bin", "plain", 5, static_cast(fullExecute)); + RequireFailure(_execvpe_nid_postfix("kernel_execvpe_plain.bin", argv, nullptr), GuestEnoexec, "execvpe did not reach the empty shell through its search and fallback"); + std::filesystem::remove("bin/sh"); + RequireFailure(_execvpe_nid_postfix("./bin/plain.bin", argv, nullptr), GuestEnoent, "execvpe of a slash-named plain file without the shell did not fail with ENOENT"); + std::filesystem::remove("bin/plain.bin"); + std::filesystem::create_directory("scripts"); + WriteFile("scripts/kernel_execvpe_plain.bin", "plain", 5, static_cast(fullExecute)); + setenv_nid_postfix("PATH", "/scripts", 1); + RequireFailure(_execvpe_nid_postfix("kernel_execvpe_plain.bin", argv, nullptr), GuestEnoent, "execvpe of an unrecognised file without the shell did not fail with ENOENT"); + setenv_nid_postfix("PATH", "/absent:/scripts", 1); + RequireFailure(_execvpe_nid_postfix("kernel_execvpe_plain.bin", argv, nullptr), GuestEnoent, "execvpe over a missing first element did not fail with ENOENT"); + unsetenv_nid_postfix("PATH"); + std::filesystem::remove("sandbox/kernel_execvpe_tool.bin"); + std::filesystem::remove("sandbox/kernel_execvpe_plain.bin"); + std::filesystem::remove("usr/bin/kernel_execvpe_data.bin"); + std::filesystem::remove("scripts/kernel_execvpe_plain.bin"); + std::filesystem::remove("bin"); + std::filesystem::remove("sbin"); + std::filesystem::remove("sandbox"); + std::filesystem::remove("scripts"); + std::filesystem::remove("usr/bin"); + std::filesystem::remove("usr"); + std::filesystem::current_path(originalDirectory); + std::filesystem::remove_all(work); + std::puts("guest_kernel_sys_execvpe passed"); + return 0; +} diff --git a/core/libs/tests/GuestKernelSysModuleInternal.cpp b/core/libs/tests/GuestKernelSysModuleInternal.cpp index 84dc88f295..c7d68fb6b0 100644 --- a/core/libs/tests/GuestKernelSysModuleInternal.cpp +++ b/core/libs/tests/GuestKernelSysModuleInternal.cpp @@ -104,6 +104,9 @@ static void CheckIsModuleLoaded() { } Require(sceKernelIsModuleLoaded("libSceNoSuchModule.sprx") == SCE_KERNEL_ERROR_ESRCH); Require(sceKernelIsModuleLoaded("") == expected("")); + const std::string longName(300, 'a'); + Require(sceKernelIsModuleLoaded(longName.c_str()) == SCE_KERNEL_ERROR_ESRCH); + Require(sceKernelIsModuleLoaded(reinterpret_cast(0x10)) == SCE_KERNEL_ERROR_EFAULT); } int main() { diff --git a/core/libs/tests/GuestKernelSysNanosleep.cpp b/core/libs/tests/GuestKernelSysNanosleep.cpp new file mode 100644 index 0000000000..9000072010 --- /dev/null +++ b/core/libs/tests/GuestKernelSysNanosleep.cpp @@ -0,0 +1,104 @@ +#include "prx/libkernel_sys/include/AdditionalExports.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include + +static constexpr int SCE_OK = 0; +static constexpr int GUEST_EACCES = 13; +static constexpr int GUEST_EFAULT = 14; +static constexpr int GUEST_EINVAL = 22; +static constexpr std::int64_t NANOS_PER_SECOND = 1000000000LL; +static constexpr std::int64_t NANOS_PER_MILLISECOND = 1000000LL; +static constexpr std::int64_t SLEEP_NANOS = 20 * NANOS_PER_MILLISECOND; + +static std::atomic cancelThreadReady{false}; +static std::atomic cancelThreadRelease{false}; +static std::atomic cancelSleepReturned{false}; + +static void Require(bool value) { + if (!value) std::abort(); +} + +static void PosixRejects(const KernelTimespec* request, int error) { + *__error_nid_postfix() = GUEST_EACCES; + Require(__sys_nanosleep_nid_postfix(request, nullptr) == -1); + Require(*__error_nid_postfix() == error); +} + +static void TestRejectsInvalidRequests() { + const KernelTimespec negativeNanos{0, -1}; + const KernelTimespec wholeSecondOfNanos{0, NANOS_PER_SECOND}; + const KernelTimespec negativeBoth{-1, -1}; + PosixRejects(&negativeNanos, GUEST_EINVAL); + PosixRejects(&wholeSecondOfNanos, GUEST_EINVAL); + PosixRejects(&negativeBoth, GUEST_EINVAL); + PosixRejects(nullptr, GUEST_EFAULT); +} + +static void TestNegativeAndZeroSeconds() { + *__error_nid_postfix() = GUEST_EACCES; + KernelTimespec request{-1, 0}; + Require(__sys_nanosleep_nid_postfix(&request, nullptr) == SCE_OK); + Require(*__error_nid_postfix() == GUEST_EACCES); + + request = {0, 0}; + Require(__sys_nanosleep_nid_postfix(&request, nullptr) == SCE_OK); + Require(*__error_nid_postfix() == GUEST_EACCES); +} + +static void TestValidSleep() { + const KernelTimespec request{0, SLEEP_NANOS}; + KernelTimespec remaining{5, 7}; + *__error_nid_postfix() = GUEST_EACCES; + const auto start = std::chrono::steady_clock::now(); + Require(__sys_nanosleep_nid_postfix(&request, &remaining) == SCE_OK); + const auto elapsed = std::chrono::steady_clock::now() - start; + Require(*__error_nid_postfix() == GUEST_EACCES); + Require(remaining.tv_sec == 5 && remaining.tv_nsec == 7); + Require(elapsed >= std::chrono::milliseconds(10)); +} + +static void* APS5_VABI PendingCancelWorker(void*) { + cancelThreadReady.store(true); + cancelThreadReady.notify_all(); + while (!cancelThreadRelease.load()) { + std::this_thread::yield(); + } + const KernelTimespec request{0, 20 * NANOS_PER_MILLISECOND}; + Require(__sys_nanosleep_nid_postfix(&request, nullptr) == SCE_OK); + cancelSleepReturned.store(true); + return nullptr; +} + +static void TestPendingCancelDoesNotFire() { + cancelThreadReady.store(false); + cancelThreadRelease.store(false); + cancelSleepReturned.store(false); + + Pthread thread = nullptr; + Require(scePthreadCreate(&thread, nullptr, PendingCancelWorker, nullptr, "nanosleep_cancel_test") == SCE_OK); + while (!cancelThreadReady.load()) { + std::this_thread::yield(); + } + Require(scePthreadCancel(thread) == SCE_OK); + cancelThreadRelease.store(true); + cancelThreadRelease.notify_all(); + + void* result = nullptr; + Require(scePthreadJoin(thread, &result) == SCE_OK); + Require(cancelSleepReturned.load()); + Require(result == nullptr); +} + +int main() { + TestRejectsInvalidRequests(); + TestNegativeAndZeroSeconds(); + TestValidSleep(); + TestPendingCancelDoesNotFire(); + return 0; +} diff --git a/core/libs/tests/GuestKernelSysPthread.cpp b/core/libs/tests/GuestKernelSysPthread.cpp index ef42542017..cfe88e126e 100644 --- a/core/libs/tests/GuestKernelSysPthread.cpp +++ b/core/libs/tests/GuestKernelSysPthread.cpp @@ -59,9 +59,24 @@ static void TestMutexAttributeGeneration() { Require(scePthreadMutexattrGetgen(&attr, &generation) == 0 && generation == 0); Require(scePthreadMutexattrSetgen(&attr, 2) == SCE_KERNEL_ERROR_EOPNOTSUPP); Require(scePthreadMutexattrGetgen(nullptr, &generation) == SCE_KERNEL_ERROR_EINVAL); + Require(pthread_mutexattr_getgen_np_nid_postfix(&attr, reinterpret_cast(0x10)) == 14); + Require(pthread_mutexattr_getgen_np_nid_postfix(reinterpret_cast(0x10), &generation) == 22); + Require(pthread_mutexattr_setgen_np_nid_postfix(reinterpret_cast(0x10), 0) == 22); + Require(scePthreadMutexattrGetgen(&attr, reinterpret_cast(0x10)) == static_cast(0x80020000u | 14u)); + Require(scePthreadMutexattrGetgen(reinterpret_cast(0x10), &generation) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrSetgen(reinterpret_cast(0x10), 0) == SCE_KERNEL_ERROR_EINVAL); Require(pthread_mutexattr_destroy_nid_postfix(&attr) == 0); } +static void TestThreadAttrCreateSuspend() { + Require(pthread_attr_setcreatesuspend_np_nid_postfix(nullptr) == 22); + Require(pthread_attr_setcreatesuspend_np_nid_postfix(reinterpret_cast(0x10)) == 22); + PthreadAttr attr{}; + Require(scePthreadAttrInit(&attr) == 0); + Require(pthread_attr_setcreatesuspend_np_nid_postfix(&attr) == 0); + Require(scePthreadAttrDestroy(&attr) == 0); +} + static void TestConcurrency() { Require(scePthreadSetconcurrency(3) == 0 && scePthreadGetconcurrency() == 3); Require(scePthreadSetconcurrency(-1) == SCE_KERNEL_ERROR_EINVAL && scePthreadGetconcurrency() == 3); @@ -116,6 +131,7 @@ static void TestTimedjoin() { int main() { TestScope(); TestMutexAttributeGeneration(); + TestThreadAttrCreateSuspend(); TestConcurrency(); TestAtfork(); TestTimedjoin(); diff --git a/core/libs/tests/GuestLibcInternalFirmwareAnswers.cpp b/core/libs/tests/GuestLibcInternalFirmwareAnswers.cpp new file mode 100644 index 0000000000..a07b48fb45 --- /dev/null +++ b/core/libs/tests/GuestLibcInternalFirmwareAnswers.cpp @@ -0,0 +1,62 @@ +#include "prx/libSceLibcInternal/include/MathInternals.hpp" +#include "prx/libSceLibcInternal/include/RuntimeInternals.hpp" +#include +#include +#include +#include + +static void Require(bool value, const char* message) { + if (!value) { + std::fprintf(stderr, "%s\n", message); + std::abort(); + } +} + +static void TestDaysto() { + Require(_Daysto_nid_postfix(1, 0) == 0, "_Daysto(1, 0)"); + Require(_Daysto_nid_postfix(1, 1) == 31, "_Daysto(1, 1)"); + Require(_Daysto_nid_postfix(4, 2) == 60, "a positive multiple of four uses the leap table"); + Require(_Daysto_nid_postfix(5, 2) == 60, "year 5 adds one leap day to the common table"); + Require(_Daysto_nid_postfix(0, 2) == 59, "year 0 uses the common table with no leap days"); + Require(_Daysto_nid_postfix(-1, 2) == 59, "years -1 to -3 add no leap days"); + Require(_Daysto_nid_postfix(-3, 11) == 334, "_Daysto(-3, 11)"); + Require(_Daysto_nid_postfix(-4, 2) == 63, "year -4 adds 1 + 8 / 4 days to the leap table"); + Require(_Daysto_nid_postfix(-5, 0) == 3, "year -5 adds 1 + 9 / 4 days to the common table"); + Require(_Daysto_nid_postfix(100, 11) == 359, "every fourth year uses the leap table, centuries included"); + Require(_Daysto_nid_postfix(2023, 11) == 334 + 505, "_Daysto(2023, 11)"); + Require(_Daysto_nid_postfix(2024, 11) == 335 + 505, "_Daysto(2024, 11)"); + constexpr int Lowest = std::numeric_limits::min(); + constexpr int Highest = std::numeric_limits::max(); + Require(_Daysto_nid_postfix(Lowest, 0) == 536870914 && _Daysto_nid_postfix(Lowest, 11) == 536871249, "the lowest year wraps 4 - year as an unsigned 32-bit value"); + Require(_Daysto_nid_postfix(Lowest + 1, 0) == 536870913 && _Daysto_nid_postfix(Lowest + 3, 11) == 536871247, "years just above the lowest wrap 4 - year"); + Require(_Daysto_nid_postfix(Lowest + 4, 0) == 536870913 && _Daysto_nid_postfix(Lowest + 4, 11) == 536871248, "the last wrapping year"); + Require(_Daysto_nid_postfix(Lowest + 5, 0) == 536870912 && _Daysto_nid_postfix(Lowest + 5, 11) == 536871246, "the first year whose 4 - year fits in int"); + Require(_Daysto_nid_postfix(Highest, 0) == 536870911 && _Daysto_nid_postfix(Highest, 11) == 536871245, "the highest year"); +} + +static void TestXpGetw() { + const float one[] = {5.0f, 7.0f, 11.0f, 13.0f}; + Require(_FXp_getw_nid_postfix(one, 0) == 0.0f, "no parts sum to 0"); + Require(_FXp_getw_nid_postfix(one, 1) == 5.0f, "one part is returned as it is"); + const float zeroHead[] = {0.0f, 7.0f, 11.0f}; + Require(_FXp_getw_nid_postfix(zeroHead, 3) == 0.0f, "a zero first part ends the sum"); + const float zeroSecond[] = {5.0f, 0.0f, 11.0f}; + Require(_FXp_getw_nid_postfix(zeroSecond, 3) == 5.0f, "a zero second part ends the sum"); + Require(_FXp_getw_nid_postfix(one, 2) == 12.0f, "two parts are added"); + const float zeroThird[] = {5.0f, 7.0f, 0.0f, 13.0f}; + Require(_FXp_getw_nid_postfix(zeroThird, 4) == 12.0f, "a zero third part ends the sum"); + Require(_FXp_getw_nid_postfix(one, 3) == 23.0f, "three exact parts are added"); + Require(_FXp_getw_nid_postfix(one, 4) == 36.0f, "the fourth part joins the third"); + const float carry[] = {16777216.0f, 1.0f, 1.0f, 2.0f}; + Require(_FXp_getw_nid_postfix(carry, 3) == 16777218.0f, "a second part lost in the first sum is folded with the third first"); + Require(_FXp_getw_nid_postfix(carry, 4) == 16777220.0f, "a second part lost in the first sum is folded with the third and fourth first"); + const float nan[] = {std::nanf(""), 1.0f}; + Require(std::isnan(_FXp_getw_nid_postfix(nan, 2)), "a NaN first part is summed, not compared away"); +} + +int main() { + Require(_Isdst_nid_postfix() == -1, "_Isdst did not return -1"); + TestDaysto(); + TestXpGetw(); + return 0; +} diff --git a/core/libs/tests/GuestLibcInternalMultiPrecision.cpp b/core/libs/tests/GuestLibcInternalMultiPrecision.cpp new file mode 100644 index 0000000000..f9b2f65b66 --- /dev/null +++ b/core/libs/tests/GuestLibcInternalMultiPrecision.cpp @@ -0,0 +1,146 @@ +#include "prx/libSceLibcInternal/include/MultiPrecision.hpp" +#include +#include +#include +#include + +namespace { + +using Words = std::array; + +constexpr unsigned long long DigitMask = 0xFFFFFFFFull; + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "libc internal multiprecision: %s\n", message); + std::abort(); +} + +template +bool Rejects(const TCall& call) { + try { + call(); + } catch (const std::invalid_argument&) { + return true; + } + return false; +} + +Words Multiply(unsigned long long left, unsigned long long right) { + Words product; + product.fill(0xAAAAAAAAAAAAAAAAull); + sceLibcInternalMultiPrecisionMultiply_nid_no_patch(product.data(), left, right); + return product; +} + +Words Add(Words value, unsigned long long addend) { + sceLibcInternalMultiPrecisionAdd_nid_no_patch(value.data(), addend); + return value; +} + +Words Remainder(Words value, unsigned long long divisor) { + sceLibcInternalMultiPrecisionRemainder_nid_no_patch(value.data(), divisor); + return value; +} + +Words SchoolbookProduct(unsigned long long left, unsigned long long right) { + const unsigned long long low = (left & DigitMask) * (right & DigitMask); + const unsigned long long crossLeft = (left >> 32) * (right & DigitMask); + const unsigned long long crossRight = (left & DigitMask) * (right >> 32); + const unsigned long long middle = (low >> 32) + (crossLeft & DigitMask) + (crossRight & DigitMask); + const unsigned long long upper = (left >> 32) * (right >> 32) + (crossLeft >> 32) + (crossRight >> 32) + (middle >> 32); + return {low & DigitMask, middle & DigitMask, upper & DigitMask, upper >> 32, 0}; +} + +unsigned long long AddModulo(unsigned long long left, unsigned long long right, unsigned long long modulus) { + return left >= modulus - right ? left - (modulus - right) : left + right; +} + +unsigned long long MultiplyModulo(unsigned long long left, unsigned long long right, unsigned long long modulus) { + unsigned long long result = 0; + left %= modulus; + for (; right != 0; right >>= 1) { + if (right & 1) result = AddModulo(result, left, modulus); + left = AddModulo(left, left, modulus); + } + return result; +} + +void CheckMultiply() { + const unsigned long long values[] = {0, 1, DigitMask, DigitMask + 1, 1ull << 63, ~0ull, 0x0123456789ABCDEFull}; + for (const auto left : values) { + for (const auto right : values) { + Require(Multiply(left, right) == SchoolbookProduct(left, right), "_MP_Mul writes the 128-bit product as four digits and clears the fifth word"); + } + } +} + +void CheckGet() { + Words value{5, 7, 9, 11, 13}; + Require(sceLibcInternalMultiPrecisionGet_nid_no_patch(value.data()) == (7ull << 32) + 5, "_MP_Get joins the two low digits"); + value = {1, 0x100000002ull, 0, 0, 0}; + Require(sceLibcInternalMultiPrecisionGet_nid_no_patch(value.data()) == 0x200000001ull, "_MP_Get wraps a second word wider than a digit"); +} + +void CheckAdd() { + Require(Add({DigitMask, DigitMask, 0, 0, 0}, 1) == Words{0, 0, 1, 0, 0}, "_MP_Add carries into the third digit"); + Require(Add({DigitMask, DigitMask, DigitMask, DigitMask, 0}, 1) == Words{0, 0, 0, 0, 1}, "_MP_Add carries past 2^128 into the fifth word"); + Require(Add({DigitMask, DigitMask, DigitMask, DigitMask, DigitMask}, 1) == Words{}, "_MP_Add drops the carry out of the fifth word"); + Require(Add({0x1FFFFFFFFull, 0, 0, 0, 7}, 0) == Words{DigitMask, 1, 0, 0, 7}, "_MP_Add carries a first word wider than a digit"); + Require(Add({3, 0x100000000ull, 0, 0, 0}, 0xFFFFFFFF00000001ull) == Words{4, DigitMask, 1, 0, 0}, "_MP_Add adds both digits of the addend"); +} + +void CheckRemainder() { + for (const auto modulus : {(1ull << 32) + 15, (1ull << 61) - 1, ~0ull - 58}) { + const unsigned long long multiplier = 6364136223846793005ull; + const unsigned long long increment = 1442695040888963407ull % modulus; + unsigned long long state = 12345; + for (int step = 0; step < 10000; ++step) { + const unsigned long long expected = AddModulo(MultiplyModulo(multiplier, state, modulus), increment, modulus); + Words value{}; + sceLibcInternalMultiPrecisionMultiply_nid_no_patch(value.data(), multiplier, state); + sceLibcInternalMultiPrecisionAdd_nid_no_patch(value.data(), increment); + sceLibcInternalMultiPrecisionRemainder_nid_no_patch(value.data(), modulus); + Require(value[2] == 0 && value[3] == 0 && value[4] == 0, "_MP_Rem leaves the remainder in the two low digits"); + state = sceLibcInternalMultiPrecisionGet_nid_no_patch(value.data()); + Require(state == expected, "_MP_Rem gives the remainder of a linear congruential step"); + } + } + const Words wide{0x89ABCDEF, 0x01234567, 0xFEDCBA98, 0x76543210, 0}; + Require(Remainder(wide, ~0ull - 58) == Words{0x468AD530, 0x468ACF52, 0, 0, 0}, "_MP_Rem reduces a four-digit value"); + Require(Remainder(wide, (1ull << 61) - 1) == Words{0x8091A2CC, 0x13C4D5EF, 0, 0, 0}, "_MP_Rem normalizes a divisor with a small high digit"); + Require(Remainder(wide, 1ull << 32) == Words{0x89ABCDEF, 0, 0, 0, 0}, "_MP_Rem by 2^32 keeps the lowest digit"); + Require(Remainder({5, 0, 0, 0, 0}, 1ull << 32) == Words{5, 0, 0, 0, 0}, "_MP_Rem keeps a value below the divisor"); + Require(Remainder({0, 0, 1, 0, 0}, 1ull << 32) == Words{}, "_MP_Rem of a multiple is zero"); + Require(Remainder({0, 0, 0x7FFFFFFF, 0, 0}, 0x80000000FFFFFFFEull) == Words{0xFFFFFFF8, 5, 0, 0, 0}, "_MP_Rem corrects the trial quotient twice"); + Require(Remainder({7, 0, 0, 0, 0}, DigitMask) == Words{7, 0, 0, 0, 0}, "_MP_Rem keeps a digit below a one-digit divisor"); + Require(Remainder({5, 0, 0, 0, 0}, 7) == Words{5, 0, 0, 0, 0}, "_MP_Rem keeps a digit below a scaled one-digit divisor"); + Require(Remainder({0x123456789ull, 0xFFFFFFFFFull, 3, 0, 0}, 0x1FFFFFFFFull) == Words{0xA3456789, 1, 0, 0, 0}, "_MP_Rem scales words wider than a digit"); + Require(Remainder({~0ull, ~0ull, 0, 0, 0}, 0xFFFFFFFFFFFFFFC5ull) == Words{0xFFFFFF89, 0x39, 2, DigitMask, DigitMask}, "_MP_Rem propagates a borrow past the top digit"); +} + +void CheckRejections() { + Words zero{}; + Require(Rejects([&] { sceLibcInternalMultiPrecisionRemainder_nid_no_patch(zero.data(), 1ull << 32); }) && zero == Words{}, "_MP_Rem rejects a zero value"); + Words fifth{1, 0, 0, 0, 1}; + Require(Rejects([&] { sceLibcInternalMultiPrecisionRemainder_nid_no_patch(fifth.data(), 1ull << 32); }) && fifth == Words{1, 0, 0, 0, 1}, "_MP_Rem rejects a nonzero fifth word"); + Words small{8, 0, 0, 0, 0}; + Require(Rejects([&] { sceLibcInternalMultiPrecisionRemainder_nid_no_patch(small.data(), 0); }), "_MP_Rem rejects a zero divisor"); + Require(Rejects([&] { sceLibcInternalMultiPrecisionRemainder_nid_no_patch(small.data(), 7); }) && small == Words{8, 0, 0, 0, 0}, "_MP_Rem rejects a nonzero quotient digit for a one-digit divisor"); + Require(Rejects([] { sceLibcInternalMultiPrecisionGet_nid_no_patch(nullptr); }), "_MP_Get rejects a null value"); + Require(Rejects([] { sceLibcInternalMultiPrecisionAdd_nid_no_patch(nullptr, 1); }), "_MP_Add rejects a null value"); + Require(Rejects([] { sceLibcInternalMultiPrecisionMultiply_nid_no_patch(nullptr, 1, 1); }), "_MP_Mul rejects a null product"); + Require(Rejects([] { sceLibcInternalMultiPrecisionRemainder_nid_no_patch(nullptr, 1ull << 32); }), "_MP_Rem rejects a null value"); +} + +} + +int main() { + CheckMultiply(); + CheckGet(); + CheckAdd(); + CheckRemainder(); + CheckRejections(); + std::puts("libc internal multiprecision checks passed"); + return 0; +} diff --git a/core/libs/tests/GuestLibcInternalShowmanyc.cpp b/core/libs/tests/GuestLibcInternalShowmanyc.cpp new file mode 100644 index 0000000000..e4ef867d03 --- /dev/null +++ b/core/libs/tests/GuestLibcInternalShowmanyc.cpp @@ -0,0 +1,24 @@ +#include "prx/libSceLibcInternal/include/InventoryExports.hpp" +#include +#include +#include +#include + +static_assert(std::is_same_v*)>); +static_assert(std::is_same_v*)>); + +int main() { + std::stringbuf narrow("data"); + std::wstringbuf wide(L"data"); + if (libSceLibcInternalInventory_047_nid_no_patch(&narrow) != 0 || narrow.str() != "data") { + std::fputs("narrow showmanyc did not return zero without changing the buffer\n", stderr); + return EXIT_FAILURE; + } + if (libSceLibcInternalInventory_050_nid_no_patch(&wide) != 0 || wide.str() != L"data") { + std::fputs("wide showmanyc did not return zero without changing the buffer\n", stderr); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} diff --git a/core/libs/tests/GuestLibreSsl3Asn1.cpp b/core/libs/tests/GuestLibreSsl3Asn1.cpp new file mode 100644 index 0000000000..303e7104d7 --- /dev/null +++ b/core/libs/tests/GuestLibreSsl3Asn1.cpp @@ -0,0 +1,87 @@ +#include "prx/libSceLibreSsl3/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +static_assert(sizeof(Asn1String) == 24 && offsetof(Asn1String, data) == 8 && offsetof(Asn1String, flags) == 16); + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL 3 ASN.1 check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestGet0Data() { + unsigned char bytes[] {1, 2, 3}; + Asn1String string {3, 2, bytes, 0}; + Require(ASN1_STRING_get0_data_nid_postfix(&string) == bytes); + string = {0, 4, nullptr, 0}; + Require(ASN1_STRING_get0_data_nid_postfix(&string) == nullptr); + string = {-1, 0x102, bytes, 0x010}; + Require(ASN1_STRING_get0_data_nid_postfix(&string) == bytes); + RequireThrows([] { ASN1_STRING_get0_data_nid_postfix(nullptr); }); +} + +static void InitializeHeap() { + void* const api[10] {}; + ApplicationHeapRegister_nid_no_patch(api); +} + +static Asn1String* NewInteger(int length, int type, std::int64_t flags) { + auto* integer = static_cast(malloc_nid_postfix(sizeof(Asn1String))); + Require(integer != nullptr); + integer->length = length; + integer->type = type; + integer->flags = flags; + integer->data = nullptr; + if (length > 0) { + integer->data = static_cast(malloc_nid_postfix(static_cast(length))); + Require(integer->data != nullptr); + std::memset(integer->data, 0x5A, static_cast(length)); + } + return integer; +} + +static void TestIntegerFree() { + ASN1_INTEGER_free_nid_postfix(nullptr); + ASN1_INTEGER_free_nid_postfix(NewInteger(4, 2, 0)); + ASN1_INTEGER_free_nid_postfix(NewInteger(16, 0x102, 0)); + ASN1_INTEGER_free_nid_postfix(NewInteger(0, 2, 0)); + ASN1_INTEGER_free_nid_postfix(NewInteger(4, 6, 0)); + + Asn1String* borrowed = NewInteger(4, 2, 0x010); + unsigned char* const data = borrowed->data; + ASN1_INTEGER_free_nid_postfix(borrowed); + Require(data[0] == 0x5A && data[3] == 0x5A); + free_nid_postfix(data); + + Asn1String* negative = NewInteger(4, 2, 0); + negative->length = -1; + RequireThrows([&] { ASN1_INTEGER_free_nid_postfix(negative); }); + Require(negative->data != nullptr && negative->data[0] == 0x5A); + negative->length = 4; + ASN1_INTEGER_free_nid_postfix(negative); + + Asn1String* empty = NewInteger(0, 2, 0); + empty->length = -1; + ASN1_INTEGER_free_nid_postfix(empty); +} + +int main() { + InitializeHeap(); + TestGet0Data(); + TestIntegerFree(); + std::puts("libSceLibreSsl3 ASN.1 tests passed"); +} diff --git a/core/libs/tests/GuestLibreSsl3ObjCmp.cpp b/core/libs/tests/GuestLibreSsl3ObjCmp.cpp new file mode 100644 index 0000000000..0080650c68 --- /dev/null +++ b/core/libs/tests/GuestLibreSsl3ObjCmp.cpp @@ -0,0 +1,70 @@ +#include "prx/libSceLibreSsl3/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include + + +namespace { +struct Object { + const char* shortName; + const char* longName; + int nid; + int length; + const unsigned char* data; + int flags; +}; + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "failed: %s\n", message); + std::terminate(); +} + +int Sign(int value) { + return value < 0 ? -1 : value > 0 ? 1 : 0; +} +} + +int main() { + const unsigned char short1[] = {0x2a, 0x86, 0x48}; + const unsigned char same1[] = {0x2a, 0x86, 0x48}; + const unsigned char low[] = {0x2a, 0x86, 0x47}; + const unsigned char high[] = {0x2a, 0x86, 0x49}; + const unsigned char longer[] = {0x2a, 0x86, 0x48, 0x01}; + const unsigned char first[] = {0x01, 0xff, 0xff}; + const unsigned char second[] = {0x02, 0x00, 0x00}; + const Object a{nullptr, nullptr, 0, 3, short1, 0}; + const Object b{nullptr, nullptr, 0, 3, same1, 0}; + const Object c{nullptr, nullptr, 0, 3, low, 0}; + const Object d{nullptr, nullptr, 0, 3, high, 0}; + const Object e{nullptr, nullptr, 0, 4, longer, 0}; + const Object f{nullptr, nullptr, 0, 3, first, 0}; + const Object g{nullptr, nullptr, 0, 3, second, 0}; + const Object emptyA{nullptr, nullptr, 0, 0, nullptr, 0}; + const Object emptyB{nullptr, nullptr, 0, 0, short1, 0}; + Require(OBJ_cmp_nid_postfix(&a, &a) == 0 && OBJ_cmp_nid_postfix(&a, &b) == 0, "equal encodings compare equal"); + Require(OBJ_cmp_nid_postfix(&a, &c) == 1 && OBJ_cmp_nid_postfix(&c, &a) == -1, "the first differing byte decides, as its difference"); + Require(OBJ_cmp_nid_postfix(&a, &d) == -1 && OBJ_cmp_nid_postfix(&d, &a) == 1, "a higher byte compares greater"); + Require(OBJ_cmp_nid_postfix(&a, &e) == -1 && OBJ_cmp_nid_postfix(&e, &a) == 1, "the length difference is returned before the bytes"); + Require(Sign(OBJ_cmp_nid_postfix(&f, &g)) == -1 && OBJ_cmp_nid_postfix(&f, &g) == -1, "bytes are compared as unsigned values"); + Require(OBJ_cmp_nid_postfix(&emptyA, &emptyB) == 0, "empty encodings compare equal without reading data"); + auto throws = [](const void* left, const void* right) { + try { + static_cast(OBJ_cmp_nid_postfix(left, right)); + } catch (const std::exception&) { + return true; + } + return false; + }; + Require(throws(nullptr, &a) && throws(&a, nullptr), "null objects are rejected"); + const Object missing{nullptr, nullptr, 0, 3, nullptr, 0}; + const Object negative{nullptr, nullptr, 0, -1, short1, 0}; + const Object empty{nullptr, nullptr, 0, 0, nullptr, 0}; + const Object minimum{nullptr, nullptr, 0, std::numeric_limits::min(), nullptr, 0}; + const Object one{nullptr, nullptr, 0, 1, short1, 0}; + Require(throws(&missing, &missing) && throws(&negative, &negative) && throws(&negative, &empty), "missing data and negative lengths are rejected"); + Require(throws(&minimum, &one), "extreme negative lengths are rejected without overflowing"); + return 0; +} diff --git a/core/libs/tests/GuestLibreSsl3X509Extensions.cpp b/core/libs/tests/GuestLibreSsl3X509Extensions.cpp new file mode 100644 index 0000000000..a89f8c328e --- /dev/null +++ b/core/libs/tests/GuestLibreSsl3X509Extensions.cpp @@ -0,0 +1,69 @@ +#include "prx/libSceLibreSsl3/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL X509 extension check failed at line %u\n", location.line()); + std::abort(); +} + +static void TestGet0Extensions() { + LibreSsl3Stack extensions {}; + LibreSsl3ExtensionsCertInfo info {}; + LibreSsl3ExtensionsX509 certificate {}; + certificate.certInfo = &info; + Require(X509_get0_extensions_nid_postfix(&certificate) == nullptr); + std::memset(info.beforeExtensions, 0xFF, sizeof(info.beforeExtensions)); + std::memset(info.rest, 0xFF, sizeof(info.rest)); + info.extensions = &extensions; + Require(X509_get0_extensions_nid_postfix(&certificate) == &extensions); +} + +static void TestGetExtCount() { + LibreSsl3Stack extensions {}; + LibreSsl3ExtensionsCertInfo info {}; + LibreSsl3ExtensionsX509 certificate {}; + certificate.certInfo = &info; + Require(X509_get_ext_count_nid_postfix(&certificate) == 0); + info.extensions = &extensions; + Require(X509_get_ext_count_nid_postfix(&certificate) == 0); + extensions.num = 3; + extensions.numAlloc = 8; + Require(X509_get_ext_count_nid_postfix(&certificate) == 3); +} + +static void TestGetExt() { + int first = 0; + int second = 0; + int third = 0; + char* slots[] {reinterpret_cast(&first), reinterpret_cast(&second), reinterpret_cast(&third)}; + LibreSsl3Stack extensions {}; + LibreSsl3ExtensionsCertInfo info {}; + LibreSsl3ExtensionsX509 certificate {}; + certificate.certInfo = &info; + Require(X509_get_ext_nid_postfix(&certificate, 0) == nullptr); + info.extensions = &extensions; + Require(X509_get_ext_nid_postfix(&certificate, 0) == nullptr); + extensions = {2, slots, 0, 3, nullptr}; + Require(X509_get_ext_nid_postfix(&certificate, 0) == &first); + Require(X509_get_ext_nid_postfix(&certificate, 1) == &second); + Require(X509_get_ext_nid_postfix(&certificate, 2) == nullptr); + Require(X509_get_ext_nid_postfix(&certificate, -1) == nullptr); + Require(X509_get_ext_nid_postfix(&certificate, INT_MIN) == nullptr); + Require(X509_get_ext_nid_postfix(&certificate, INT_MAX) == nullptr); + extensions.num = 3; + Require(X509_get_ext_nid_postfix(&certificate, 2) == &third); + Require(X509_get_ext_nid_postfix(&certificate, 3) == nullptr); +} + +int main() { + TestGet0Extensions(); + TestGetExtCount(); + TestGetExt(); + return 0; +} diff --git a/core/libs/tests/GuestLibreSslBio.cpp b/core/libs/tests/GuestLibreSslBio.cpp index 650b44b984..b9c324b9a9 100644 --- a/core/libs/tests/GuestLibreSslBio.cpp +++ b/core/libs/tests/GuestLibreSslBio.cpp @@ -1,5 +1,6 @@ #include "prx/libSceLibreSsl/libSceLibreSsl.hpp" #include "prx/libc/include/Api.hpp" +#include "prx/libc/include/FileStream.hpp" #include "prx/libSceLibreSsl/ErrorQueue.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include @@ -15,6 +16,13 @@ #include #include +#ifdef _WIN32 +#include +#include +#else +#include +#endif + #ifdef ANYPS5_BIO_DYNAMIC #ifdef _WIN32 #include @@ -501,6 +509,10 @@ void TestUnsupported() { Require(BIO_write_nid_postfix(bio, "a", 1) == -2, "reject uninitialized write"); RequireError(0x20fff078); bio->initialized = 1; + bio->extraData = bio; + RequireUnsupported([&] { BIO_free_nid_postfix(bio); }); + Require(bio->references == 1, "unsupported ex-data preserves reference"); + bio->extraData = nullptr; const BioMethod* method = bio->method; bio->method = nullptr; Require(BIO_ctrl_nid_postfix(bio, 1, 0, nullptr) == -2, "reject control without a method"); @@ -540,6 +552,77 @@ void TestUnsupported() { Require(BIO_free_nid_postfix(bio) == 1, "free after method errors"); } +#ifndef ANYPS5_BIO_DYNAMIC +void TestFileAndSocket() { + const BioMethod* fileMethod = BIO_s_file_nid_postfix(); + Require(fileMethod && fileMethod->type == 0x402, "file BIO method descriptor"); + Require(BIO_new_fp_nid_postfix(nullptr, 0) == nullptr, "reject null file BIO"); + std::FILE* native = std::tmpfile(); + Require(native != nullptr, "open temporary file for BIO"); + FileStream stream(native); + Bio* file = BIO_new_fp_nid_postfix(&stream, 0); + Require(file && file->method == fileMethod && file->data == &stream, "file BIO guest stream"); + Require(file->initialized && BIO_ctrl_nid_postfix(file, 8, 0, nullptr) == 0, "file BIO no-close flag"); + Require(BIO_write_nid_postfix(file, "abc", 3) == 3, "write guest file through BIO"); + Require(BIO_ctrl_nid_postfix(file, 11, 0, nullptr) == 1, "flush guest file through BIO"); + Require(BIO_ctrl_nid_postfix(file, 1, 0, nullptr) == 0, "rewind guest file through BIO"); + char buffer[8]{}; + Require(file->method->read(file, buffer, 3) == 3 && std::memcmp(buffer, "abc", 3) == 0, "read guest file through BIO"); + Require(file->method->read(file, buffer, 1) == 0, "clean guest file EOF returns zero"); + Require(feof_nid_postfix(&stream) != 0 && ferror_nid_postfix(&stream) == 0, "clean EOF does not set stream error"); + Require(ERR_get_error_nid_postfix() == 0, "clean EOF does not queue LibreSSL errors"); + Require(BIO_ctrl_nid_postfix(file, 1, 0, nullptr) == 0, "file reset returns fseek success status"); + Require(file->method->read(file, buffer, 3) == 3 && std::memcmp(buffer, "abc", 3) == 0, "read after file BIO reset"); + FileStream* queried = nullptr; + Require(BIO_ctrl_nid_postfix(file, 107, 0, &queried) == 1 && queried == &stream, "get guest file pointer"); + Require(BIO_free_nid_postfix(file) == 1, "free BIO without closing file"); + Require(fseek_nid_postfix(&stream, 0, SEEK_SET) == 0, "borrowed guest stream remains open"); + Require(fclose_nid_postfix(&stream) == 0, "close borrowed guest stream"); + + std::FILE* ownedNative = std::tmpfile(); + Require(ownedNative != nullptr, "open owned temporary BIO file"); + FileStream owned(ownedNative); + Bio* ownedBio = BIO_new_fp_nid_postfix(&owned, 1); + Require(ownedBio && BIO_ctrl_nid_postfix(ownedBio, 8, 0, nullptr) == 1, "owned guest file BIO"); + Require(BIO_free_nid_postfix(ownedBio) == 1 && !owned.IsOpen(), "owned BIO closes guest file"); + + int descriptors[2]{-1, -1}; +#ifdef _WIN32 + Require(_pipe(descriptors, 4096, _O_BINARY) == 0, "create non-seekable guest file pipe"); + std::FILE* writeOnly = _fdopen(descriptors[1], "wb"); +#else + Require(::pipe(descriptors) == 0, "create non-seekable guest file pipe"); + std::FILE* writeOnly = ::fdopen(descriptors[1], "wb"); +#endif + Require(writeOnly != nullptr, "wrap pipe writer in host stream"); + FileStream pipeStream(writeOnly); + Bio* pipeBio = BIO_new_fp_nid_postfix(&pipeStream, 0); + Require(pipeBio != nullptr, "construct guest file BIO around pipe writer"); + Require(pipeBio->method->read(pipeBio, buffer, 1) == -1, "guest file read error must not report clean EOF"); + Require(ferror_nid_postfix(&pipeStream) != 0 && feof_nid_postfix(&pipeStream) == 0, "pipe read failure sets error, not EOF"); + const auto systemError = ERR_get_error_nid_postfix(); + const auto bioError = ERR_get_error_nid_postfix(); + Require((systemError >> 24) == 2 && (bioError >> 24) == 32 && (bioError & 0xfff) == 2, + "failed file BIO read queues system and BIO errors"); + Require(ERR_get_error_nid_postfix() == 0, "file read queues exactly two errors"); + Require(BIO_ctrl_nid_postfix(pipeBio, 1, 0, nullptr) == -1, "file reset preserves non-seekable stream failure"); + Require(BIO_free_nid_postfix(pipeBio) == 1, "free borrowed pipe BIO"); + Require(fclose_nid_postfix(&pipeStream) == 0, "close borrowed pipe writer"); +#ifdef _WIN32 + Require(_close(descriptors[0]) == 0, "close pipe reader"); +#else + Require(::close(descriptors[0]) == 0, "close pipe reader"); +#endif + + Require(BIO_new_socket_nid_postfix(-1, 0) == nullptr, "reject negative guest socket"); + Bio* socket = BIO_new_socket_nid_postfix(42, 0); + Require(socket && socket->initialized && socket->number == 42, "guest socket BIO descriptor"); + Require(socket->method && socket->method->type == 0x405, "socket BIO method descriptor"); + Require(BIO_ctrl_nid_postfix(socket, 8, 0, nullptr) == 0, "socket BIO no-close flag"); + Require(BIO_free_nid_postfix(socket) == 1, "free unowned guest socket BIO"); +} +#endif + } // namespace int main(int argc, char** argv) { @@ -559,6 +642,9 @@ int main(int argc, char** argv) { TestWriteBoundaries(); TestDispatchAndReferences(); TestUnsupported(); +#ifndef ANYPS5_BIO_DYNAMIC + TestFileAndSocket(); +#endif Require(allocations == 0, "BIO tests leaked guest heap storage"); Require(ERR_get_error_nid_postfix() == 0, "BIO tests left an unexpected error"); } diff --git a/core/libs/tests/GuestLibreSslOcsp.cpp b/core/libs/tests/GuestLibreSslOcsp.cpp index f0c3f82ad8..f49e763280 100644 --- a/core/libs/tests/GuestLibreSslOcsp.cpp +++ b/core/libs/tests/GuestLibreSslOcsp.cpp @@ -4,7 +4,9 @@ #include #include #include +#include #include +#include using StringFunction = const char* (APS5_VABI*)(std::int64_t); @@ -16,7 +18,108 @@ void Check(StringFunction function, const char* name, std::int64_t value, const } } +constexpr std::int32_t GeneralizedTime = 24; +constexpr std::uint64_t OcspError = 0x27fffull << 12; +constexpr std::uint64_t ErrorInNextUpdate = OcspError | 122; +constexpr std::uint64_t ErrorInThisUpdate = OcspError | 123; +constexpr std::uint64_t NextUpdateBeforeThisUpdate = OcspError | 124; +constexpr std::uint64_t StatusExpired = OcspError | 125; +constexpr std::uint64_t StatusNotYetValid = OcspError | 126; +constexpr std::uint64_t StatusTooOld = OcspError | 127; +constexpr std::int64_t Century = 3155760000; + +struct TimeString { + std::string text; + Asn1String value; + + explicit TimeString(const char* input, std::int32_t type = GeneralizedTime) + : text(input), value{static_cast(text.size()), type, reinterpret_cast(text.data()), 0} {} + TimeString(const TimeString&) = delete; + TimeString& operator=(const TimeString&) = delete; +}; + +void CheckValidity(const char* name, const Asn1String* thisUpdate, const Asn1String* nextUpdate, std::int64_t slack, std::int64_t maxAge, int expected, std::uint64_t error) { + ERR_clear_error_nid_postfix(); + const int actual = OCSP_check_validity_nid_postfix(thisUpdate, nextUpdate, slack, maxAge); + const std::uint64_t first = ERR_get_error_nid_postfix(); + const std::uint64_t second = ERR_get_error_nid_postfix(); + if (actual != expected || first != error || second != 0) { + std::fprintf(stderr, "OCSP_check_validity %s: expected %d and 0x%llx, got %d, 0x%llx and 0x%llx\n", name, expected, static_cast(error), actual, + static_cast(first), static_cast(second)); + std::abort(); + } +} + +void CheckValidity() { + constexpr auto Maximum = std::numeric_limits::max(); + constexpr auto Minimum = std::numeric_limits::min(); + const TimeString past("20000101000000Z"); + const TimeString pastSecond("20000101000001Z"); + const TimeString future("99991231235959Z"); + const TimeString yearZero("00000101000000Z"); + const TimeString february("20000231000000Z"); + const TimeString march("20000301000000Z"); + const TimeString utcType("20000101000000Z", 23); + const TimeString octetType("20000101000000Z", 4); + const TimeString untyped("99991231235959Z", 0); + + CheckValidity("valid window", &past.value, &future.value, 0, -1, 1, 0); + CheckValidity("no next update", &past.value, nullptr, 0, -1, 1, 0); + CheckValidity("minimum age skips the age check", &past.value, &future.value, 0, Minimum, 1, 0); + CheckValidity("year zero", &yearZero.value, &future.value, 0, -1, 1, 0); + CheckValidity("UTCTime type", &utcType.value, &untyped.value, 0, -1, 1, 0); + CheckValidity("octet string type", &octetType.value, &untyped.value, 0, -1, 1, 0); + CheckValidity("not yet valid", &future.value, &future.value, 0, -1, 0, StatusNotYetValid); + CheckValidity("not yet valid before expired", &future.value, &past.value, 0, -1, 0, StatusNotYetValid); + CheckValidity("expired", &past.value, &past.value, 0, -1, 0, StatusExpired); + CheckValidity("too old", &past.value, &future.value, 0, 3600, 0, StatusTooOld); + CheckValidity("zero age", &past.value, &future.value, 0, 0, 0, StatusTooOld); + CheckValidity("next update before this update", &pastSecond.value, &past.value, Century, -1, 0, NextUpdateBeforeThisUpdate); + CheckValidity("next update equal to this update", &past.value, &past.value, Century, -1, 1, 0); + CheckValidity("expired before next update before this update", &pastSecond.value, &past.value, 0, -1, 0, StatusExpired); + CheckValidity("too old before expired", &past.value, &past.value, 0, 3600, 0, StatusTooOld); + CheckValidity("not yet valid before too old", &past.value, &future.value, -Century, 0, 0, StatusNotYetValid); + CheckValidity("February 31 before March 1", &february.value, &march.value, Century, -1, 1, 0); + CheckValidity("March 1 after February 31", &march.value, &february.value, Century, -1, 0, NextUpdateBeforeThisUpdate); + + const char* invalid[] {"000101000000Z", "20000101000000.5Z", "2000010100000aZ", "200001010000000", "20000101000000z", "20001301000000Z", "20000001000000Z", + "20000100000000Z", "20000132000000Z", "20000101240000Z", "20000101006000Z", "20000101000060Z", " 0000101000000Z", "-0000101000000Z", "+0000101000000Z", + "\xb2" "0000101000000Z", "20000101000000Z0", ""}; + for (const char* text : invalid) { + const TimeString time(text); + CheckValidity(text, &time.value, &future.value, 0, -1, 0, ErrorInThisUpdate); + CheckValidity(text, &past.value, &time.value, 0, -1, 0, ErrorInNextUpdate); + } + const TimeString month("20001301000000Z"); + CheckValidity("invalid this update without next update", &month.value, nullptr, 0, -1, 0, ErrorInThisUpdate); + CheckValidity("not yet valid before invalid next update", &future.value, &month.value, 0, -1, 0, StatusNotYetValid); + const Asn1String missing{15, GeneralizedTime, nullptr, 0}; + CheckValidity("null data", &missing, &future.value, 0, -1, 0, ErrorInThisUpdate); + CheckValidity("null next update data", &past.value, &missing, 0, -1, 0, ErrorInNextUpdate); + const Asn1String negative{-1, GeneralizedTime, past.value.data, 0}; + CheckValidity("negative length", &negative, &future.value, 0, -1, 0, ErrorInThisUpdate); + + CheckValidity("wrapping slack", &past.value, &future.value, Maximum, -1, 0, 0); + CheckValidity("wrapping slack without next update", &past.value, nullptr, Maximum, -1, 0, 0); + CheckValidity("wrapping negative slack", &past.value, &future.value, Minimum, -1, 0, 0); + CheckValidity("slack beyond the calendar", &past.value, &future.value, 70000000000000000, -1, 0, 0); + CheckValidity("negative slack beyond the calendar", &past.value, &future.value, -70000000000000000, -1, 0, 0); + CheckValidity("age beyond the calendar", &past.value, &future.value, 0, Maximum, 0, 0); + CheckValidity("slack inside the calendar", &past.value, &future.value, 60000000000000000, -1, 1, 0); + CheckValidity("age inside the calendar", &past.value, nullptr, 0, 60000000000000000, 1, 0); + + try { + OCSP_check_validity_nid_postfix(nullptr, &future.value, 0, -1); + } catch (const std::exception&) { + return; + } + std::fprintf(stderr, "OCSP_check_validity: a null this update must throw\n"); + std::abort(); +} + int main() { + CheckValidity(); + constexpr auto Unknown = "(UNKNOWN)"; constexpr std::int64_t Outside[] {-1, 4, 7, 9, 0x100, 0x100000000, std::numeric_limits::min(), std::numeric_limits::max()}; diff --git a/core/libs/tests/GuestLibreSslOcspBasicResponse.cpp b/core/libs/tests/GuestLibreSslOcspBasicResponse.cpp new file mode 100644 index 0000000000..308af1f329 --- /dev/null +++ b/core/libs/tests/GuestLibreSslOcspBasicResponse.cpp @@ -0,0 +1,123 @@ +#include "prx/libSceLibreSsl/libSceLibreSsl.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +struct alignas(8) Guest { + std::array basic; + std::array data; + std::array stack; + std::array decoyData; + std::array decoyStack; + std::array entries; + std::array decoyEntries; +}; + +static constexpr std::int32_t MinIndex = std::numeric_limits::min(); +static constexpr std::int32_t MaxIndex = std::numeric_limits::max(); +static constexpr std::int32_t Indices[] {0, 1, 2, 3, -1, MinIndex, MaxIndex}; + +static int first; +static int third; +static int beyond; + +static void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "failed: %s\n", message); + std::abort(); +} + +template static void RequireThrows(TAction action, const char* message) { + try { action(); } catch (const std::exception&) { return; } + Require(false, message); +} + +template static void Store(std::array& object, std::size_t offset, TValue value) { + Require(offset + sizeof(value) <= TSize, "store stays inside the object"); + std::memcpy(object.data() + offset, &value, sizeof(value)); +} + +static void Build(Guest& guest, std::int32_t count) { + std::memset(&guest, 0xa5, sizeof(guest)); + guest.entries = {&first, nullptr, &third, &beyond}; + for (auto& entry : guest.decoyEntries) entry = &entry; + Store(guest.decoyStack, 0, std::int32_t{7}); + Store(guest.decoyStack, 8, guest.decoyEntries.data()); + for (std::size_t offset = 0; offset < 40; offset += 8) Store(guest.decoyData, offset, guest.decoyStack.data()); + for (std::size_t offset = 8; offset < 32; offset += 8) Store(guest.basic, offset, guest.decoyData.data()); + Store(guest.basic, 0, guest.data.data()); + for (std::size_t offset = 0; offset < 40; offset += 8) Store(guest.data, offset, guest.decoyStack.data()); + Store(guest.data, 24, guest.stack.data()); + Store(guest.stack, 0, count); + Store(guest.stack, 8, guest.entries.data()); + Store(guest.stack, 16, std::int32_t{5}); + Store(guest.stack, 20, std::int32_t{2}); + Store(guest.stack, 24, guest.decoyEntries.data()); +} + +static int Count(Guest& guest) { + return OCSP_resp_count_nid_postfix(guest.basic.data()); +} + +static void* Get(Guest& guest, std::int32_t index) { + return OCSP_resp_get0_nid_postfix(guest.basic.data(), index); +} + +int main() { + Require(OCSP_resp_count_nid_postfix(nullptr) == -1, "a null basic response counts -1"); + for (const auto index : Indices) + Require(OCSP_resp_get0_nid_postfix(nullptr, index) == nullptr, "a null basic response has no entries"); + + Guest guest; + Build(guest, 3); + const Guest snapshot = guest; + Require(Count(guest) == 3, "the count is the 32-bit size of the stack at offset 24 of the response data"); + Require(Get(guest, 0) == &first, "index 0 returns the first entry"); + Require(Get(guest, 1) == nullptr, "a null entry is returned as stored"); + Require(Get(guest, 2) == &third, "the last index returns the last entry"); + Require(Get(guest, 3) == nullptr, "an index equal to the count returns null"); + Require(Get(guest, -1) == nullptr, "a negative index returns null"); + Require(Get(guest, MinIndex) == nullptr, "the minimum index returns null"); + Require(Get(guest, MaxIndex) == nullptr, "the maximum index returns null"); + Require(std::memcmp(&guest, &snapshot, sizeof(guest)) == 0, "queries do not write the objects"); + + Store(guest.data, 24, static_cast(nullptr)); + Require(Count(guest) == -1, "a null responses stack counts -1"); + for (const auto index : Indices) Require(Get(guest, index) == nullptr, "a null responses stack has no entries"); + + Build(guest, 0); + Require(Count(guest) == 0, "an empty stack counts 0"); + for (const auto index : Indices) Require(Get(guest, index) == nullptr, "an empty stack has no entries"); + + Build(guest, -2); + Require(Count(guest) == -2, "a negative stack size is returned unchanged"); + for (const auto index : Indices) Require(Get(guest, index) == nullptr, "a negative stack size has no entries"); + + Build(guest, MaxIndex); + Require(Count(guest) == MaxIndex, "the maximum stack size is returned unchanged"); + Require(Get(guest, 0) == &first, "index 0 is below the maximum stack size"); + Require(Get(guest, 3) == &beyond, "the stack size alone bounds the index"); + Require(Get(guest, MaxIndex) == nullptr, "an index equal to the maximum stack size returns null"); + Require(Get(guest, -1) == nullptr, "a negative index returns null for the maximum stack size"); + Require(Get(guest, MinIndex) == nullptr, "the minimum index returns null for the maximum stack size"); + + Build(guest, 3); + Store(guest.stack, 8, static_cast(nullptr)); + Require(Count(guest) == 3, "the count does not read the entry array"); + Require(Get(guest, 3) == nullptr, "an index equal to the count returns null before the entry array is read"); + Require(Get(guest, -1) == nullptr, "a negative index returns null before the entry array is read"); + RequireThrows([&] { Get(guest, 0); }, "a null entry array with index 0 throws"); + RequireThrows([&] { Get(guest, 2); }, "a null entry array with the last index throws"); + + Build(guest, 3); + Store(guest.basic, 0, static_cast(nullptr)); + RequireThrows([&] { Count(guest); }, "a null response data pointer throws in the count"); + for (const auto index : Indices) + RequireThrows([&] { Get(guest, index); }, "a null response data pointer throws for every index"); + return 0; +} diff --git a/core/libs/tests/GuestLibreSslPkeyLifecycle.cpp b/core/libs/tests/GuestLibreSslPkeyLifecycle.cpp new file mode 100644 index 0000000000..7481cfdc3e --- /dev/null +++ b/core/libs/tests/GuestLibreSslPkeyLifecycle.cpp @@ -0,0 +1,34 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include + +namespace { +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "failed: %s\n", message); + std::terminate(); +} +} + +int main() { + void* const api[10]{}; + ApplicationHeapRegister_nid_no_patch(api); + void* pkey = EVP_PKEY_new_nid_postfix(); + Require(pkey != nullptr, "allocates a key"); + int fields[3]; + std::memcpy(fields, pkey, sizeof(fields)); + Require(fields[0] == 0 && fields[1] == 0, "a new key has type and save_type EVP_PKEY_NONE"); + Require(fields[2] == 1, "a new key holds one reference"); + const int extra = 2; + std::memcpy(static_cast(pkey) + 8, &extra, sizeof(extra)); + EVP_PKEY_free_nid_postfix(pkey); + std::memcpy(fields, pkey, sizeof(fields)); + Require(fields[2] == 1, "freeing a shared key only drops one reference"); + EVP_PKEY_free_nid_postfix(pkey); + EVP_PKEY_free_nid_postfix(nullptr); + return 0; +} diff --git a/core/libs/tests/GuestLibreSslRsa.cpp b/core/libs/tests/GuestLibreSslRsa.cpp index 80af7c5b46..063ad517f2 100644 --- a/core/libs/tests/GuestLibreSslRsa.cpp +++ b/core/libs/tests/GuestLibreSslRsa.cpp @@ -1,11 +1,16 @@ #include "prx/libSceLibreSsl/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include +#include #include #include +#include +#include struct TestBignum { std::uint64_t* d; @@ -111,8 +116,639 @@ static void TestSize() { RequireThrows([] { RSA_size_nid_postfix(nullptr); }); } +constexpr int BignumStaticData = 0x02; +constexpr int BignumMalloced = 0x01; + +struct Finishes { + int calls; + LibreSslRsa* rsa; +}; + +static Finishes finishes; + +static int APS5_VABI RecordFinish(LibreSslRsa* rsa) { + ++finishes.calls; + finishes.rsa = rsa; + return 1; +} + +static void InitializeHeap() { + void* const api[10] {}; + ApplicationHeapRegister_nid_no_patch(api); +} + +static bool Zero(const void* data, std::size_t size) { + const auto* bytes = static_cast(data); + for (std::size_t index = 0; index < size; ++index) + if (bytes[index]) return false; + return true; +} + +static TestBignum* AllocatedBignum(std::uint64_t value) { + auto* number = static_cast(malloc_nid_postfix(sizeof(TestBignum))); + auto* words = static_cast(malloc_nid_postfix(2 * sizeof(std::uint64_t))); + Require(number != nullptr && words != nullptr); + words[0] = value; + words[1] = 0; + *number = {words, 1, 2, 0, BignumMalloced}; + return number; +} + +static void TestNew() { + auto* rsa = RSA_new_nid_postfix(); + Require(rsa != nullptr && rsa->meth != nullptr); + Require(rsa->pad == 0 && rsa->version == 0 && rsa->engine == nullptr && rsa->references == 1 && rsa->flags == 0x0006); + for (void* field : {rsa->n, rsa->e, rsa->d, rsa->p, rsa->q, rsa->dmp1, rsa->dmq1, rsa->iqmp, rsa->exData}) Require(field == nullptr); + for (void* field : {rsa->methodModN, rsa->methodModP, rsa->methodModQ, rsa->blinding, rsa->mtBlinding}) Require(field == nullptr); + const auto* method = rsa->meth; + Require(std::string(method->name) == "Eric Young's PKCS#1 RSA" && method->flags == 0 && method->appData == nullptr); + for (void* operation : method->operations) Require(operation != nullptr); + Require(method->sign == nullptr && method->verify == nullptr && method->keygen == nullptr); + Require(RSA_flags_nid_postfix(rsa) == 0); + auto* second = RSA_new_nid_postfix(); + Require(second != nullptr && second != rsa && second->meth == method); + RSA_free_nid_postfix(second); + RSA_free_nid_postfix(rsa); +} + +static void TestFree() { + RSA_free_nid_postfix(nullptr); + auto* rsa = RSA_new_nid_postfix(); + std::uint64_t words[4] {0x1234, 0x5678, 0, 0}; + TestBignum stored {words, 2, 4, 0, BignumStaticData}; + rsa->n = &stored; + rsa->e = AllocatedBignum(65537); + rsa->d = AllocatedBignum(3); + rsa->references = 2; + RSA_free_nid_postfix(rsa); + Require(rsa->references == 1 && rsa->n == &stored && stored.top == 2); + RSA_free_nid_postfix(rsa); + Require(Zero(&stored, sizeof(stored)) && words[0] == 0x1234 && words[1] == 0x5678); + + rsa = RSA_new_nid_postfix(); + LibreSslRsaMethod method = *rsa->meth; + method.operations[7] = reinterpret_cast(RecordFinish); + rsa->meth = &method; + finishes = {}; + RSA_free_nid_postfix(rsa); + Require(finishes.calls == 1 && finishes.rsa == rsa); + method.operations[7] = nullptr; + rsa = RSA_new_nid_postfix(); + rsa->meth = &method; + RSA_free_nid_postfix(rsa); + Require(finishes.calls == 1); + + rsa = RSA_new_nid_postfix(); + rsa->meth = nullptr; + RequireThrows([&] { RSA_free_nid_postfix(rsa); }); +} + +using Bytes = std::vector; + +constexpr const char* CavpModulus = + "cea80475324c1dc8347827818da58bac069d3419c614a6ea1ac6a3b510dcd72cc516954905e9fef908d45e13006adf27" + "d467a7d83c111d1a5df15ef293771aefb920032a5bb989f8e4f5e1b05093d3f130f984c07a772a3683f4dc6fb28a9681" + "5b32123ccdd13954f19d5b8b24a103e771a34c328755c65ed64e1924ffd04d30b2142cc262f6e0048fef6dbc652f2147" + "9ea1c4b1d66d28f4d46ef7185e390cbfa2e02380582f3188bb94ebbf05d31487a09aff01fcbb4cd4bfd1f0a833b38c11" + "813c84360bb53c7d4481031c40bad8713bb6b835cb08098ed15ba31ee4ba728a8c8e10f7294e1b4163b7aee57277bfd8" + "81a6f9d43e02c6925aa3a043fb7fb78d"; +constexpr const char* CavpSpki = + "30820122300d06092a864886f70d01010105000382010f003082010a0282010100cea80475324c1dc8347827818da58b" + "ac069d3419c614a6ea1ac6a3b510dcd72cc516954905e9fef908d45e13006adf27d467a7d83c111d1a5df15ef293771a" + "efb920032a5bb989f8e4f5e1b05093d3f130f984c07a772a3683f4dc6fb28a96815b32123ccdd13954f19d5b8b24a103" + "e771a34c328755c65ed64e1924ffd04d30b2142cc262f6e0048fef6dbc652f21479ea1c4b1d66d28f4d46ef7185e390c" + "bfa2e02380582f3188bb94ebbf05d31487a09aff01fcbb4cd4bfd1f0a833b38c11813c84360bb53c7d4481031c40bad8" + "713bb6b835cb08098ed15ba31ee4ba728a8c8e10f7294e1b4163b7aee57277bfd881a6f9d43e02c6925aa3a043fb7fb7" + "8d0203260445"; + +constexpr const char* CavpPrivateExponent = + "0997634c477c1a039d44c810b2aaa3c7862b0b88d3708272e1e15f66fc9389709f8a11f3ea6a5af7effa2d01c189c50f" + "0d5bcbe3fa272e56cfc4a4e1d388a9dcd65df8628902556c8b6bb6a641709b5a35dd2622c73d4640bfa1359d0e76e1f2" + "19f8e33eb9bd0b59ec198eb2fccaae0346bd8b401e12e3c67cb629569c185a2e0f35a2f741644c1cca5ebb139d77a89a" + "2953fc5e30048c0e619f07c8d21d1e56b8af07193d0fdf3f49cd49f2ef3138b5138862f1470bd2d16e34a2b9e7777a6c" + "8c8d4cb94b4e8b5d616cd5393753e7b0f31cc7da559ba8e98d888914e334773baf498ad88d9631eb5fe32e53a4145bf0" + "ba548bf2b0a50c63f67b14e398a34b0d"; +constexpr const char* CavpSignature = + "6b8be97d9e518a2ede746ff4a7d91a84a1fc665b52f154a927650db6e7348c69f8c8881f7bcf9b1a6d3366eed30c3aed" + "4e93c203c43f5528a45de791895747ade9c5fa5eee81427edee02082147aa311712a6ad5fb1732e93b3d6cd23ffd46a0" + "b3caf62a8b69957cc68ae39f9993c1a779599cdda949bdaababb77f248fcfeaa44059be5459fb9b899278e929528ee13" + "0facd53372ecbc42f3e8de2998425860406440f248d817432de687112e504d734028e6c5620fa282ca07647006cf0a2f" + "f83e19a916554cc61810c2e855305db4e5cf893a6a96767365794556ff033359084d7e38a8456e68e21155b76151314a" + "29875feee09557161cbc654541e89e42"; +constexpr const char* CavpDigest = "077877895a428028f60998b985550820025a2a42c0beab27165c0802d3098150"; + +static Bytes Hex(const char* text) { + const auto nibble = [](char digit) { return static_cast(digit <= '9' ? digit - '0' : digit - 'a' + 10); }; + Bytes bytes; + for (; text[0] && text[1]; text += 2) bytes.push_back(static_cast(nibble(text[0]) << 4 | nibble(text[1]))); + return bytes; +} + +static Bytes Concat(std::initializer_list parts) { + Bytes bytes; + for (const auto& part : parts) bytes.insert(bytes.end(), part.begin(), part.end()); + return bytes; +} + +static Bytes Encode(unsigned char tag, const Bytes& content) { + Bytes bytes {tag}; + if (content.size() < 0x80) { + bytes.push_back(static_cast(content.size())); + } else if (content.size() < 0x100) { + bytes.insert(bytes.end(), {0x81, static_cast(content.size())}); + } else { + bytes.insert(bytes.end(), {0x82, static_cast(content.size() >> 8), static_cast(content.size())}); + } + return Concat({bytes, content}); +} + +static const Bytes RsaEncryption {0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01}; +static const Bytes Null {0x05, 0x00}; +static const Bytes CavpExponent {0x26, 0x04, 0x45}; + +static Bytes RsaKey(const Bytes& modulus, const Bytes& exponent) { + return Encode(0x30, Concat({Encode(0x02, modulus), Encode(0x02, exponent)})); +} + +static Bytes PublicKeyInfo(const Bytes& algorithm, const Bytes& bitString) { + return Encode(0x30, Concat({Encode(0x30, algorithm), Encode(0x03, bitString)})); +} + +static Bytes StandardKey() { + return RsaKey(Concat({{0x00}, Hex(CavpModulus)}), CavpExponent); +} + +static LibreSslRsa* Decode(const Bytes& der, long length, long* consumed = nullptr) { + const unsigned char* data = der.data(); + auto* rsa = d2i_RSA_PUBKEY_nid_postfix(nullptr, &data, length); + if (consumed) *consumed = data - der.data(); + return rsa; +} + +static const TestBignum& Number(const LibreSslRsa* rsa, bool modulus) { + return *static_cast(modulus ? rsa->n : rsa->e); +} + +static void RequireAccepted(const Bytes& der, std::uint64_t exponent, std::source_location location = std::source_location::current()) { + long consumed = 0; + auto* rsa = Decode(der, static_cast(der.size()), &consumed); + Require(rsa != nullptr && consumed == static_cast(der.size()), location); + Require(Number(rsa, false).top == (exponent ? 1 : 0) && (!exponent || Number(rsa, false).d[0] == exponent), location); + RSA_free_nid_postfix(rsa); +} + +static void RequireRefused(const Bytes& der, std::source_location location = std::source_location::current()) { + long consumed = -1; + Require(Decode(der, static_cast(der.size()), &consumed) == nullptr && consumed == 0, location); +} + +static void TestPublicKeyInfo() { + const auto published = Hex(CavpSpki); + const auto rsaKey = StandardKey(); + Require(PublicKeyInfo(Concat({RsaEncryption, Null}), Concat({{0x00}, rsaKey})) == published); + long consumed = 0; + auto* rsa = Decode(published, static_cast(published.size()), &consumed); + Require(rsa != nullptr && consumed == 294 && RSA_size_nid_postfix(rsa) == 256); + auto* fresh = RSA_new_nid_postfix(); + Require(rsa->meth == fresh->meth && rsa->flags == 0x0006 && rsa->references == 1 && rsa->engine == nullptr); + RSA_free_nid_postfix(fresh); + const auto& modulus = Number(rsa, true); + const auto& exponent = Number(rsa, false); + Require(modulus.dmax == 33 && modulus.top == 32 && modulus.neg == 0 && modulus.flags == BignumMalloced); + Require(modulus.d[0] == 0x5aa3a043fb7fb78dULL && modulus.d[31] == 0xcea80475324c1dc8ULL); + Require(exponent.dmax == 1 && exponent.top == 1 && exponent.d[0] == 0x260445 && exponent.flags == BignumMalloced); + for (void* field : {rsa->d, rsa->p, rsa->q, rsa->dmp1, rsa->dmq1, rsa->iqmp}) Require(field == nullptr); + + LibreSslRsa* previous = RSA_new_nid_postfix(); + LibreSslRsa* target = previous; + const unsigned char* data = published.data(); + auto* replaced = d2i_RSA_PUBKEY_nid_postfix(&target, &data, 294); + Require(replaced != nullptr && target == replaced && data == published.data() + 294); + RSA_free_nid_postfix(replaced); + RSA_free_nid_postfix(rsa); + + auto padded = Concat({published, Null}); + Require((rsa = Decode(padded, static_cast(padded.size()), &consumed)) != nullptr && consumed == 294); + RSA_free_nid_postfix(rsa); + Require(Decode(published, 293, &consumed) == nullptr && consumed == 0); + RequireRefused(Bytes(published.begin(), published.end() - 1)); + Require(Decode(published, 0) == nullptr && Decode(published, -1) == nullptr); + RequireThrows([&] { d2i_RSA_PUBKEY_nid_postfix(nullptr, nullptr, 294); }); + RequireThrows([&] { + const unsigned char* none = nullptr; + d2i_RSA_PUBKEY_nid_postfix(nullptr, &none, 294); + }); +} + +static void TestPublicKeyInfoVariants() { + const auto rsaKey = StandardKey(); + const auto bits = Concat({{0x00}, rsaKey}); + RequireAccepted(PublicKeyInfo(Concat({Encode(0x06, {0x55, 0x08, 0x01, 0x01}), Null}), bits), 0x260445); + RequireAccepted(PublicKeyInfo(RsaEncryption, bits), 0x260445); + for (const Bytes& parameter : {Bytes {0x04, 0x00}, Bytes {0x30, 0x00}, Bytes {0xA0, 0x00}, Bytes {0x02, 0x00}, Bytes {0x01, 0x01, 0xFF}, Bytes {0x05, 0x81, 0x00}}) + RequireAccepted(PublicKeyInfo(Concat({RsaEncryption, parameter}), bits), 0x260445); + for (const Bytes& parameter : {Bytes {0x05, 0x01, 0x00}, Bytes {0x10, 0x00}, Bytes {0x01, 0x02, 0x00, 0x00}, Bytes {0x06, 0x01, 0x80}, Bytes {0x03, 0x00}, Bytes {0x00, 0x00}}) + RequireRefused(PublicKeyInfo(Concat({RsaEncryption, parameter}), bits)); + RequireRefused(PublicKeyInfo(Concat({RsaEncryption, Null, Null}), bits)); + RequireRefused(PublicKeyInfo(Concat({Encode(0x06, {0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01}), Null}), bits)); + RequireRefused(PublicKeyInfo(Concat({Encode(0x06, {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0A}), Null}), bits)); + RequireRefused(PublicKeyInfo(Concat({Encode(0x06, {0x2A, 0x80, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01}), Null}), bits)); + RequireRefused(Encode(0x30, Concat({Encode(0x30, Concat({RsaEncryption, Null})), Encode(0x03, bits), Null}))); + RequireRefused(Encode(0x30, Concat({Encode(0x30, Concat({RsaEncryption, Null})), Bytes {0x00, 0x00}, Encode(0x03, bits)}))); + RequireRefused(Encode(0x30, Encode(0x30, Concat({RsaEncryption, Null})))); + RequireRefused(Encode(0x10, Concat({Encode(0x30, Concat({RsaEncryption, Null})), Encode(0x03, bits)}))); + RequireRefused(Encode(0x31, Concat({Encode(0x30, Concat({RsaEncryption, Null})), Encode(0x03, bits)}))); + const auto algorithm = Concat({RsaEncryption, Null}); + RequireAccepted(PublicKeyInfo(algorithm, Concat({{0x01}, rsaKey})), 0x260444); + RequireAccepted(PublicKeyInfo(algorithm, Concat({{0x08}, rsaKey})), 0x260400); + RequireAccepted(PublicKeyInfo(algorithm, Concat({{0x00}, rsaKey, Null})), 0x260445); + RequireAccepted(PublicKeyInfo(algorithm, Concat({{0x00}, RsaKey(Hex(CavpModulus), CavpExponent)})), 0x260445); + RequireAccepted(PublicKeyInfo(algorithm, Concat({{0x00}, RsaKey(Concat({{0x00}, Hex(CavpModulus)}), {})})), 0); + RequireAccepted(PublicKeyInfo(algorithm, Concat({{0x00}, RsaKey(Concat({{0x00}, Hex(CavpModulus)}), {0x00, 0x00, 0x01, 0x00, 0x01})})), 0x10001); + RequireRefused(PublicKeyInfo(algorithm, {})); + RequireRefused(PublicKeyInfo(algorithm, {0x00})); + RequireRefused(PublicKeyInfo(algorithm, Concat({{0x00}, Encode(0x30, Encode(0x02, Hex(CavpModulus)))}))); + RequireRefused(PublicKeyInfo(algorithm, Concat({{0x00}, Encode(0x30, Concat({Encode(0x02, Hex(CavpModulus)), Encode(0x02, CavpExponent), Encode(0x02, CavpExponent)}))}))); + RequireRefused(PublicKeyInfo(algorithm, Concat({{0x00}, Encode(0x31, Concat({Encode(0x02, Hex(CavpModulus)), Encode(0x02, CavpExponent)}))}))); + auto spki = Hex(CavpSpki); + auto longForm = Concat({{0x30, 0x83, 0x00, 0x01, 0x22}, Bytes(spki.begin() + 4, spki.end())}); + RequireAccepted(longForm, 0x260445); + auto highTag = Concat({{0x3F, 0x10}, Bytes(spki.begin() + 1, spki.end())}); + RequireAccepted(highTag, 0x260445); + RequireRefused(Concat({{0x30, 0x89, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x22}, Bytes(spki.begin() + 4, spki.end())})); +} + +constexpr int PaddingPkcs1 = 1; +constexpr int PaddingSslv23 = 2; +constexpr int PaddingNone = 3; +constexpr int PaddingOaep = 4; +constexpr int PaddingX931 = 5; + +struct StaticNumber { + std::vector words; + TestBignum number; +}; + +static void SetNumber(StaticNumber& storage, const Bytes& bigEndian) { + storage.words.assign(bigEndian.size() / 8 + 1, 0); + for (std::size_t position = 0; position < bigEndian.size(); ++position) + storage.words[position / 8] |= std::uint64_t(bigEndian[bigEndian.size() - 1 - position]) << (8 * (position % 8)); + int top = static_cast(storage.words.size()); + while (top > 0 && !storage.words[static_cast(top) - 1]) --top; + storage.number = {storage.words.data(), top, static_cast(storage.words.size()), 0, BignumStaticData}; +} + +struct TestKey { + StaticNumber modulus; + StaticNumber exponent; + LibreSslRsa* rsa; + + TestKey(const Bytes& n, const Bytes& e) : rsa(RSA_new_nid_postfix()) { + SetNumber(modulus, n); + SetNumber(exponent, e); + rsa->n = &modulus.number; + rsa->e = &exponent.number; + } + TestKey(const TestKey&) = delete; + ~TestKey() { RSA_free_nid_postfix(rsa); } +}; + +static Bytes Pattern(std::size_t length, unsigned seed) { + Bytes bytes(length); + for (std::size_t index = 0; index < length; ++index) bytes[index] = static_cast(seed + 29 * index); + return bytes; +} + +static Bytes Sha1(const Bytes& data) { + Sha1Context context {}; + unsigned char digest[20]; + Require(SHA1_Init_nid_postfix(&context) == 1 && SHA1_Update_nid_postfix(&context, data.data(), data.size()) == 1); + Require(SHA1_Final_nid_postfix(digest, &context) == 1); + return Bytes(digest, digest + 20); +} + +static Bytes Mgf1(const Bytes& seed, std::size_t length) { + Bytes mask; + for (std::uint32_t counter = 0; mask.size() < length; ++counter) { + auto input = seed; + for (int shift = 24; shift >= 0; shift -= 8) input.push_back(static_cast(counter >> shift)); + const auto digest = Sha1(input); + mask.insert(mask.end(), digest.begin(), digest.end()); + } + mask.resize(length); + return mask; +} + +static Bytes Recover(const Bytes& encrypted) { + TestKey privateKey(Hex(CavpModulus), Hex(CavpPrivateExponent)); + Bytes block(256); + Require(RSA_public_decrypt_nid_postfix(256, encrypted.data(), block.data(), privateKey.rsa, PaddingNone) == 256); + return block; +} + +static bool Pkcs1Block(const Bytes& block, const Bytes& message) { + if (block[0] != 0 || block[1] != 2) return false; + const std::size_t separator = block.size() - message.size() - 1; + for (std::size_t index = 2; index < separator; ++index) + if (!block[index]) return false; + return block[separator] == 0 && Bytes(block.begin() + static_cast(separator) + 1, block.end()) == message; +} + +static bool OaepBlock(const Bytes& block, const Bytes& message) { + if (block[0]) return false; + const Bytes maskedSeed(block.begin() + 1, block.begin() + 21); + const Bytes maskedData(block.begin() + 21, block.end()); + auto seed = maskedSeed; + const auto seedMask = Mgf1(maskedData, 20); + for (std::size_t index = 0; index < 20; ++index) seed[index] ^= seedMask[index]; + auto data = maskedData; + const auto dataMask = Mgf1(seed, data.size()); + for (std::size_t index = 0; index < data.size(); ++index) data[index] ^= dataMask[index]; + if (Bytes(data.begin(), data.begin() + 20) != Sha1({})) return false; + const std::size_t one = data.size() - message.size() - 1; + for (std::size_t index = 20; index < one; ++index) + if (data[index]) return false; + return data[one] == 1 && Bytes(data.begin() + static_cast(one) + 1, data.end()) == message; +} + +static void TestPublicDecrypt() { + const auto spki = Hex(CavpSpki); + auto* rsa = Decode(spki, static_cast(spki.size())); + const auto signature = Hex(CavpSignature); + const auto digestInfo = Concat({Hex("3031300d060960864801650304020105000420"), Hex(CavpDigest)}); + Bytes out(300, 0xEE); + Require(RSA_public_decrypt_nid_postfix(256, signature.data(), out.data(), rsa, PaddingPkcs1) == 51); + Require(Bytes(out.begin(), out.begin() + 51) == digestInfo && out[51] == 0xEE); + Require(RSA_public_decrypt_nid_postfix(256, signature.data(), out.data(), rsa, PaddingNone) == 256); + Require(out[0] == 0 && out[1] == 1 && out[2] == 0xFF && out[204] == 0 && Bytes(out.begin() + 205, out.begin() + 256) == digestInfo); + auto tampered = signature; + tampered[255] ^= 1; + Require(RSA_public_decrypt_nid_postfix(256, tampered.data(), out.data(), rsa, PaddingPkcs1) == -1); + Require(RSA_public_decrypt_nid_postfix(256, tampered.data(), out.data(), rsa, PaddingNone) == 256); + Require(RSA_public_decrypt_nid_postfix(257, Concat({signature, {0}}).data(), out.data(), rsa, PaddingPkcs1) == -1); + Require(RSA_public_decrypt_nid_postfix(-1, signature.data(), out.data(), rsa, PaddingPkcs1) == -1); + const Bytes large(256, 0xFF); + Require(RSA_public_decrypt_nid_postfix(256, large.data(), out.data(), rsa, PaddingNone) == -1); + Require(RSA_public_decrypt_nid_postfix(256, Hex(CavpModulus).data(), out.data(), rsa, PaddingNone) == -1); + std::memset(out.data(), 0xEE, out.size()); + Require(RSA_public_decrypt_nid_postfix(0, nullptr, out.data(), rsa, PaddingNone) == 256); + Require(Zero(out.data(), 256) && out[256] == 0xEE); + Require(RSA_public_decrypt_nid_postfix(0, nullptr, out.data(), rsa, PaddingPkcs1) == -1); + for (int padding : {0, PaddingSslv23, PaddingOaep, 6, 7}) Require(RSA_public_decrypt_nid_postfix(256, signature.data(), out.data(), rsa, padding) == -1); + RequireThrows([&] { RSA_public_decrypt_nid_postfix(256, nullptr, out.data(), rsa, PaddingPkcs1); }); + RequireThrows([&] { RSA_public_decrypt_nid_postfix(256, signature.data(), nullptr, rsa, PaddingPkcs1); }); + const auto payload = Pattern(245, 5); + const auto sevenPadding = Recover(Concat({{0x00, 0x01}, Bytes(7, 0xFF), {0x00}, Pattern(246, 5)})); + const auto eightPadding = Recover(Concat({{0x00, 0x01}, Bytes(8, 0xFF), {0x00}, payload})); + Require(RSA_public_decrypt_nid_postfix(256, sevenPadding.data(), out.data(), rsa, PaddingPkcs1) == -1); + Require(RSA_public_decrypt_nid_postfix(256, eightPadding.data(), out.data(), rsa, PaddingPkcs1) == 245); + Require(Bytes(out.begin(), out.begin() + 245) == payload); + RSA_free_nid_postfix(rsa); +} + +static void TestPublicEncrypt() { + const auto spki = Hex(CavpSpki); + auto* rsa = Decode(spki, static_cast(spki.size())); + const auto message = Pattern(32, 5); + Bytes first(256); + Bytes second(256); + for (int padding : {PaddingPkcs1, PaddingOaep}) { + Require(RSA_public_encrypt_nid_postfix(32, message.data(), first.data(), rsa, padding) == 256); + Require(RSA_public_encrypt_nid_postfix(32, message.data(), second.data(), rsa, padding) == 256); + Require(first != second); + for (const auto* encrypted : {&first, &second}) { + const auto block = Recover(*encrypted); + Require(padding == PaddingPkcs1 ? Pkcs1Block(block, message) : OaepBlock(block, message)); + } + } + for (auto [padding, longest] : {std::pair {PaddingPkcs1, 245}, std::pair {PaddingOaep, 214}}) { + const auto longMessage = Pattern(static_cast(longest) + 1, 9); + Require(RSA_public_encrypt_nid_postfix(longest, longMessage.data(), first.data(), rsa, padding) == 256); + const Bytes accepted(longMessage.begin(), longMessage.begin() + longest); + Require(padding == PaddingPkcs1 ? Pkcs1Block(Recover(first), accepted) : OaepBlock(Recover(first), accepted)); + Require(RSA_public_encrypt_nid_postfix(longest + 1, longMessage.data(), first.data(), rsa, padding) == -1); + Require(RSA_public_encrypt_nid_postfix(0, nullptr, first.data(), rsa, padding) == 256); + Require(padding == PaddingPkcs1 ? Pkcs1Block(Recover(first), {}) : OaepBlock(Recover(first), {})); + RequireThrows([&] { RSA_public_encrypt_nid_postfix(-1, message.data(), first.data(), rsa, padding); }); + } + auto raw = Concat({{0x00}, Pattern(255, 3)}); + Require(RSA_public_encrypt_nid_postfix(256, raw.data(), first.data(), rsa, PaddingNone) == 256 && Recover(first) == raw); + Require(RSA_public_encrypt_nid_postfix(255, raw.data(), first.data(), rsa, PaddingNone) == -1); + Require(RSA_public_encrypt_nid_postfix(257, Concat({raw, {0}}).data(), first.data(), rsa, PaddingNone) == -1); + Require(RSA_public_encrypt_nid_postfix(-1, raw.data(), first.data(), rsa, PaddingNone) == -1); + Require(RSA_public_encrypt_nid_postfix(256, Hex(CavpModulus).data(), first.data(), rsa, PaddingNone) == -1); + for (int padding : {0, PaddingX931, 6, 7}) Require(RSA_public_encrypt_nid_postfix(32, message.data(), first.data(), rsa, padding) == -1); + RequireThrows([&] { RSA_public_encrypt_nid_postfix(32, message.data(), nullptr, rsa, PaddingPkcs1); }); + RequireThrows([&] { RSA_public_encrypt_nid_postfix(32, nullptr, first.data(), rsa, PaddingPkcs1); }); + RSA_free_nid_postfix(rsa); +} + +struct Crypts { + int calls; + int length; + const unsigned char* from; + unsigned char* to; + LibreSslRsa* rsa; + int padding; +}; + +static Crypts crypts; + +static int APS5_VABI RecordCrypt(int length, const unsigned char* from, unsigned char* to, LibreSslRsa* rsa, int padding) { + crypts = {crypts.calls + 1, length, from, to, rsa, padding}; + return 77; +} + +static void TestPublicKeyChecks() { + const auto modulus = Hex(CavpModulus); + const auto message = Pattern(256, 1); + Bytes out(4096); + { + TestKey key(modulus, {}); + Require(RSA_public_encrypt_nid_postfix(256, Concat({{0}, Pattern(255, 1)}).data(), out.data(), key.rsa, PaddingNone) == 256); + Require(Zero(out.data(), 255) && out[255] == 1); + } + { + TestKey key(modulus, modulus); + Require(RSA_public_encrypt_nid_postfix(32, message.data(), out.data(), key.rsa, PaddingPkcs1) == -1); + Require(RSA_public_decrypt_nid_postfix(256, message.data(), out.data(), key.rsa, PaddingNone) == -1); + } + { + auto even = modulus; + even.back() &= 0xFE; + TestKey key(even, CavpExponent); + Require(RSA_public_encrypt_nid_postfix(32, message.data(), out.data(), key.rsa, PaddingPkcs1) == -1); + Require(RSA_public_decrypt_nid_postfix(255, message.data(), out.data(), key.rsa, PaddingNone) == -1); + } + Bytes large(385, 0); + large[0] = 0x01; + large.back() = 0x03; + Bytes wideExponent(9, 0); + wideExponent[0] = 0x01; + wideExponent.back() = 0x01; + { + TestKey key(large, wideExponent); + Require(RSA_public_encrypt_nid_postfix(385, Bytes(385, 0).data(), out.data(), key.rsa, PaddingNone) == -1); + Require(RSA_public_decrypt_nid_postfix(1, message.data(), out.data(), key.rsa, PaddingNone) == -1); + } + { + TestKey key(large, Bytes(wideExponent.begin() + 1, wideExponent.end())); + Require(RSA_public_decrypt_nid_postfix(1, message.data(), out.data(), key.rsa, PaddingNone) == 385); + } + Bytes huge(2049, 0); + huge[0] = 0x01; + huge.back() = 0x01; + { + TestKey key(huge, {0x03}); + Require(RSA_public_decrypt_nid_postfix(1, message.data(), out.data(), key.rsa, PaddingNone) == -1); + } + { + TestKey key(modulus, CavpExponent); + const auto* defaultMethod = key.rsa->meth; + LibreSslRsaMethod method = *defaultMethod; + method.operations[0] = reinterpret_cast(RecordCrypt); + method.operations[1] = reinterpret_cast(RecordCrypt); + key.rsa->meth = &method; + crypts = {}; + Require(RSA_public_encrypt_nid_postfix(5, message.data(), out.data(), key.rsa, 9) == 77); + Require(crypts.calls == 1 && crypts.length == 5 && crypts.from == message.data() && crypts.to == out.data() && crypts.rsa == key.rsa && crypts.padding == 9); + Require(RSA_public_decrypt_nid_postfix(6, message.data(), out.data(), key.rsa, 8) == 77 && crypts.calls == 2 && crypts.length == 6); + method = *defaultMethod; + method.operations[0] = nullptr; + RequireThrows([&] { RSA_public_encrypt_nid_postfix(32, message.data(), out.data(), key.rsa, PaddingPkcs1); }); + key.rsa->meth = nullptr; + RequireThrows([&] { RSA_public_decrypt_nid_postfix(32, message.data(), out.data(), key.rsa, PaddingPkcs1); }); + key.rsa->meth = defaultMethod; + key.rsa->e = nullptr; + RequireThrows([&] { RSA_public_decrypt_nid_postfix(32, message.data(), out.data(), key.rsa, PaddingPkcs1); }); + } + RequireThrows([&] { RSA_public_encrypt_nid_postfix(32, message.data(), out.data(), nullptr, PaddingPkcs1); }); +} + +constexpr int DigestSha1 = 64; +constexpr int DigestMd5Sha1 = 114; +constexpr int DigestSha256 = 672; +constexpr int RsaFlagSignVerify = 0x0040; + +static Bytes Sign(const Bytes& encoded) { + return Recover(Concat({{0x00, 0x01}, Bytes(253 - encoded.size(), 0xFF), {0x00}, encoded})); +} + +static Bytes DigestInfo(const Bytes& object, const Bytes& parameter, const Bytes& digest) { + return Encode(0x30, Concat({Encode(0x30, Concat({Encode(0x06, object), parameter})), Encode(0x04, digest)})); +} + +static int Verify(int type, const Bytes& digest, const Bytes& signature, LibreSslRsa* rsa) { + return RSA_verify_nid_postfix(type, digest.data(), static_cast(digest.size()), signature.data(), static_cast(signature.size()), rsa); +} + +struct Verifies { + int calls; + int type; + const unsigned char* digest; + unsigned digestLength; + const unsigned char* signature; + unsigned signatureLength; + const LibreSslRsa* rsa; +}; + +static Verifies verifies; + +static int APS5_VABI RecordVerify(int type, const unsigned char* digest, unsigned int digestLength, const unsigned char* signature, unsigned int signatureLength, const LibreSslRsa* rsa) { + verifies = {verifies.calls + 1, type, digest, digestLength, signature, signatureLength, rsa}; + return 55; +} + +static void TestVerify() { + const auto spki = Hex(CavpSpki); + auto* rsa = Decode(spki, static_cast(spki.size())); + const auto digest = Hex(CavpDigest); + const auto signature = Hex(CavpSignature); + Require(Verify(DigestSha256, digest, signature, rsa) == 1); + auto tampered = signature; + tampered[100] ^= 0x10; + Require(Verify(DigestSha256, digest, tampered, rsa) == 0); + auto wrong = digest; + wrong[31] ^= 1; + Require(Verify(DigestSha256, wrong, signature, rsa) == 0); + Require(Verify(DigestSha256, Bytes(digest.begin(), digest.end() - 1), signature, rsa) == 0); + Require(Verify(DigestSha256, Concat({digest, {0}}), signature, rsa) == 0); + for (int type : {DigestSha1, 0, 1, 673, 675, -1, 956, DigestMd5Sha1}) Require(Verify(type, digest, signature, rsa) == 0); + Require(Verify(DigestSha256, digest, Bytes(signature.begin() + 1, signature.end()), rsa) == 0); + Require(Verify(DigestSha256, digest, Concat({{0}, signature}), rsa) == 0); + const auto ssl = Pattern(36, 7); + const auto sslSignature = Sign(ssl); + Require(Verify(DigestMd5Sha1, ssl, sslSignature, rsa) == 1); + Require(Verify(DigestMd5Sha1, Bytes(ssl.begin(), ssl.end() - 1), sslSignature, rsa) == 0); + auto otherSsl = ssl; + otherSsl[35] ^= 1; + Require(Verify(DigestMd5Sha1, otherSsl, sslSignature, rsa) == 0); + Require(Verify(DigestMd5Sha1, ssl, Sign(Bytes(ssl.begin(), ssl.end() - 1)), rsa) == 0); + Require(Verify(DigestSha256, ssl, sslSignature, rsa) == 0); + RequireThrows([&] { RSA_verify_nid_postfix(DigestMd5Sha1, nullptr, 36, sslSignature.data(), 256, rsa); }); + const auto sha256 = Hex("608648016503040201"); + Require(Verify(DigestSha256, digest, Sign(DigestInfo(sha256, Null, digest)), rsa) == 1); + Require(Verify(DigestSha256, digest, Sign(DigestInfo(sha256, {}, digest)), rsa) == 1); + Require(Verify(DigestSha256, digest, Sign(DigestInfo(sha256, {0x05, 0x81, 0x00}, digest)), rsa) == 1); + for (const auto& parameter : {Bytes {0x04, 0x00}, Bytes {0x05, 0x01, 0x00}, Bytes {0x30, 0x00}, Bytes {0x80, 0x00}, Bytes {0x01, 0x01, 0x00}, Concat({Null, Null})}) + Require(Verify(DigestSha256, digest, Sign(DigestInfo(sha256, parameter, digest)), rsa) == 0); + const auto algorithm = Encode(0x30, Concat({Encode(0x06, sha256), Null})); + Require(Verify(DigestSha256, digest, Sign(Concat({DigestInfo(sha256, Null, digest), {0x00}})), rsa) == 0); + Require(Verify(DigestSha256, digest, Sign(Encode(0x30, Concat({algorithm, Encode(0x04, digest), Null}))), rsa) == 0); + Require(Verify(DigestSha256, digest, Sign(Encode(0x30, algorithm)), rsa) == 0); + Require(Verify(DigestSha256, digest, Sign(Encode(0x30, Concat({algorithm, Encode(0x03, digest)}))), rsa) == 0); + Require(Verify(DigestSha256, digest, Sign(Encode(0x31, Concat({algorithm, Encode(0x04, digest)}))), rsa) == 0); + Require(Verify(DigestSha256, digest, Sign(Encode(0x04, digest)), rsa) == 0); + Require(Verify(0, digest, Sign(DigestInfo({}, Null, digest)), rsa) == 0); + Require(Verify(0, digest, Sign(DigestInfo({0x2A, 0x80}, Null, digest)), rsa) == 0); + const std::pair objects[] {{3, "2a864886f70d0202"}, {4, "2a864886f70d0205"}, {41, "2b0e030212"}, {64, "2b0e03021a"}, {95, "55080365"}, + {117, "2b24030201"}, {257, "2a864886f70d0204"}, {672, "608648016503040201"}, {673, "608648016503040202"}, {674, "608648016503040203"}, + {675, "608648016503040204"}, {804, "28cf06030037"}, {809, "2a8503020209"}, {941, "2a85030701010202"}, {942, "2a85030701010203"}}; + const auto hash = Pattern(20, 11); + for (std::size_t index = 0; index < std::size(objects); ++index) { + const auto encoded = Sign(DigestInfo(Hex(objects[index].second), Null, hash)); + Require(Verify(objects[index].first, hash, encoded, rsa) == 1); + Require(Verify(objects[(index + 1) % std::size(objects)].first, hash, encoded, rsa) == 0); + Require(Verify(1, hash, encoded, rsa) == 0); + } + const auto unknown = Sign(DigestInfo({0x2A, 0x03, 0x04}, Null, hash)); + for (int type : {-1, 956, 2000, DigestSha1, DigestSha256}) Require(Verify(type, hash, unknown, rsa) == 0); + const auto* defaultMethod = rsa->meth; + LibreSslRsaMethod method = *defaultMethod; + method.verify = reinterpret_cast(RecordVerify); + rsa->meth = &method; + verifies = {}; + Require(Verify(DigestSha256, digest, signature, rsa) == 1 && verifies.calls == 0); + rsa->flags |= RsaFlagSignVerify; + Require(Verify(5, wrong, tampered, rsa) == 55); + Require(verifies.calls == 1 && verifies.type == 5 && verifies.digest == wrong.data() && verifies.digestLength == 32); + Require(verifies.signature == tampered.data() && verifies.signatureLength == 256 && verifies.rsa == rsa); + method.verify = nullptr; + Require(Verify(DigestSha256, digest, signature, rsa) == 1 && verifies.calls == 1); + rsa->meth = nullptr; + RequireThrows([&] { Verify(DigestSha256, digest, signature, rsa); }); + rsa->flags &= ~RsaFlagSignVerify; + rsa->meth = defaultMethod; + RequireThrows([&] { Verify(DigestSha256, digest, signature, nullptr); }); + RequireThrows([&] { RSA_verify_nid_postfix(DigestSha256, nullptr, 32, signature.data(), 256, rsa); }); + Require(RSA_verify_nid_postfix(DigestSha256, nullptr, 32, tampered.data(), 256, rsa) == 0); + Require(RSA_verify_nid_postfix(DigestSha256, nullptr, 0, signature.data(), 256, rsa) == 0); + RequireThrows([&] { RSA_verify_nid_postfix(DigestSha256, digest.data(), 32, nullptr, 256, rsa); }); + Require(RSA_verify_nid_postfix(DigestSha256, digest.data(), 32, nullptr, 255, rsa) == 0); + RSA_free_nid_postfix(rsa); +} + int main() { + InitializeHeap(); TestFlags(); TestSize(); + TestNew(); + TestFree(); + TestPublicKeyInfo(); + TestPublicKeyInfoVariants(); + TestPublicDecrypt(); + TestPublicEncrypt(); + TestPublicKeyChecks(); + TestVerify(); std::puts("libSceLibreSsl RSA tests passed"); } diff --git a/core/libs/tests/GuestLibreSslSsl.cpp b/core/libs/tests/GuestLibreSslSsl.cpp new file mode 100644 index 0000000000..2c2863a664 --- /dev/null +++ b/core/libs/tests/GuestLibreSslSsl.cpp @@ -0,0 +1,146 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +struct TestSslInternal { + unsigned char beforeRwstate[156]; + int rwstate; + unsigned char beforeShutdown[8]; + int shutdown; + unsigned char rest[260]; +}; + +struct TestSsl { + int version; + const void* method; + void* rbio; + void* wbio; + void* bbio; + unsigned char beforeSession[88]; + void* session; + unsigned char beforeCtx[16]; + void* ctx; + std::int64_t verifyResult; + unsigned char beforeInternal[56]; + TestSslInternal* internal; +}; + +static_assert(sizeof(TestSslInternal) == 432 && offsetof(TestSslInternal, rwstate) == 156 && offsetof(TestSslInternal, shutdown) == 168); +static_assert(sizeof(TestSsl) == 232 && offsetof(TestSsl, session) == 128 && offsetof(TestSsl, ctx) == 152); +static_assert(offsetof(TestSsl, verifyResult) == 160 && offsetof(TestSsl, internal) == 224); + +static const LibreSslSsl* Ssl(const TestSsl* ssl) { + return reinterpret_cast(ssl); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL SSL check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestGetSslCtx() { + int context = 0; + int session = 0; + TestSsl ssl {}; + RequireThrows([] { SSL_get_SSL_CTX_nid_postfix(nullptr); }); + Require(SSL_get_SSL_CTX_nid_postfix(Ssl(&ssl)) == nullptr); + ssl.ctx = &context; + ssl.session = &session; + Require(SSL_get_SSL_CTX_nid_postfix(Ssl(&ssl)) == &context); +} + +static void TestGetRbio() { + int read = 0; + int write = 0; + TestSsl ssl {}; + RequireThrows([] { SSL_get_rbio_nid_postfix(nullptr); }); + Require(SSL_get_rbio_nid_postfix(Ssl(&ssl)) == nullptr); + ssl.rbio = &read; + ssl.wbio = &write; + Require(SSL_get_rbio_nid_postfix(Ssl(&ssl)) == &read); +} + +static void TestGetWbio() { + int read = 0; + int write = 0; + int buffer = 0; + TestSsl ssl {}; + RequireThrows([] { SSL_get_wbio_nid_postfix(nullptr); }); + Require(SSL_get_wbio_nid_postfix(Ssl(&ssl)) == nullptr); + ssl.rbio = &read; + ssl.wbio = &write; + ssl.bbio = &buffer; + Require(SSL_get_wbio_nid_postfix(Ssl(&ssl)) == &write); +} + +static void TestVersion() { + TestSsl ssl {}; + RequireThrows([] { SSL_version_nid_postfix(nullptr); }); + Require(SSL_version_nid_postfix(Ssl(&ssl)) == 0); + ssl.version = 0x0303; + Require(SSL_version_nid_postfix(Ssl(&ssl)) == 0x0303); + ssl.version = 0xFEFF; + Require(SSL_version_nid_postfix(Ssl(&ssl)) == 0xFEFF); +} + +static void TestVerifyResult() { + TestSsl ssl {}; + RequireThrows([] { SSL_get_verify_result_nid_postfix(nullptr); }); + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == 0); + ssl.verifyResult = 20; + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == 20); + ssl.verifyResult = INT64_C(0x100000002); + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == INT64_C(0x100000002)); + ssl.verifyResult = -1; + Require(SSL_get_verify_result_nid_postfix(Ssl(&ssl)) == -1); +} + +static void TestGetShutdown() { + TestSslInternal internal {}; + TestSsl ssl {}; + RequireThrows([] { SSL_get_shutdown_nid_postfix(nullptr); }); + RequireThrows([&] { SSL_get_shutdown_nid_postfix(Ssl(&ssl)); }); + ssl.internal = &internal; + Require(SSL_get_shutdown_nid_postfix(Ssl(&ssl)) == 0); + internal.rwstate = 3; + internal.shutdown = 1; + Require(SSL_get_shutdown_nid_postfix(Ssl(&ssl)) == 1); + internal.shutdown = 3; + Require(SSL_get_shutdown_nid_postfix(Ssl(&ssl)) == 3); +} + +static void TestWant() { + TestSslInternal internal {}; + TestSsl ssl {}; + RequireThrows([] { SSL_want_nid_postfix(nullptr); }); + RequireThrows([&] { SSL_want_nid_postfix(Ssl(&ssl)); }); + ssl.internal = &internal; + Require(SSL_want_nid_postfix(Ssl(&ssl)) == 0); + internal.rwstate = 1; + Require(SSL_want_nid_postfix(Ssl(&ssl)) == 1); + internal.shutdown = 2; + internal.rwstate = 3; + Require(SSL_want_nid_postfix(Ssl(&ssl)) == 3); +} + +int main() { + TestGetSslCtx(); + TestGetRbio(); + TestGetWbio(); + TestVersion(); + TestVerifyResult(); + TestGetShutdown(); + TestWant(); + return 0; +} diff --git a/core/libs/tests/GuestLibreSslSslCtx.cpp b/core/libs/tests/GuestLibreSslSslCtx.cpp new file mode 100644 index 0000000000..3e77a25e02 --- /dev/null +++ b/core/libs/tests/GuestLibreSslSslCtx.cpp @@ -0,0 +1,113 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +struct TestCtxInternal { + unsigned char beforeQuietShutdown[292]; + int quietShutdown; + unsigned char rest[168]; +}; + +struct TestCtx { + const void* method; + void* cipherList; + void* certStore; + std::int64_t sessionTimeout; + unsigned char beforeVerifyMode[16]; + int verifyMode; + unsigned char beforeCallback[44]; + void* defaultPasswdCallback; + void* defaultPasswdCallbackUserdata; + TestCtxInternal* internal; +}; + +static_assert(sizeof(TestCtxInternal) == 464 && offsetof(TestCtxInternal, quietShutdown) == 292); +static_assert(sizeof(TestCtx) == 120 && offsetof(TestCtx, verifyMode) == 48 && offsetof(TestCtx, defaultPasswdCallback) == 96); + +static LibreSslCtx* Context(TestCtx* ctx) { + return reinterpret_cast(ctx); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL SSL_CTX check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestGetVerifyMode() { + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_get_verify_mode_nid_postfix(nullptr); }); + Require(SSL_CTX_get_verify_mode_nid_postfix(Context(&ctx)) == 0); + ctx.verifyMode = 0x03; + Require(SSL_CTX_get_verify_mode_nid_postfix(Context(&ctx)) == 0x03); + ctx.verifyMode = -1; + Require(SSL_CTX_get_verify_mode_nid_postfix(Context(&ctx)) == -1); +} + +static void TestGetCertStore() { + int store = 0; + int ciphers = 0; + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_get_cert_store_nid_postfix(nullptr); }); + Require(SSL_CTX_get_cert_store_nid_postfix(Context(&ctx)) == nullptr); + ctx.cipherList = &ciphers; + ctx.certStore = &store; + ctx.sessionTimeout = -1; + Require(SSL_CTX_get_cert_store_nid_postfix(Context(&ctx)) == &store); +} + +static void TestSetQuietShutdown() { + TestCtxInternal internal {}; + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_set_quiet_shutdown_nid_postfix(nullptr, 1); }); + RequireThrows([&] { SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), 1); }); + ctx.internal = &internal; + SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), 1); + Require(internal.quietShutdown == 1); + SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), INT_MIN); + Require(internal.quietShutdown == INT_MIN); + SSL_CTX_set_quiet_shutdown_nid_postfix(Context(&ctx), 0); + Require(internal.quietShutdown == 0); + for (unsigned char byte : internal.beforeQuietShutdown) Require(byte == 0); + for (unsigned char byte : internal.rest) Require(byte == 0); +} + +static void TestSetPasswordCallback() { + int callback = 0; + TestCtx ctx {}; + RequireThrows([&] { SSL_CTX_set_default_passwd_cb_nid_postfix(nullptr, &callback); }); + SSL_CTX_set_default_passwd_cb_nid_postfix(Context(&ctx), &callback); + Require(ctx.defaultPasswdCallback == &callback && ctx.defaultPasswdCallbackUserdata == nullptr); + SSL_CTX_set_default_passwd_cb_nid_postfix(Context(&ctx), nullptr); + Require(ctx.defaultPasswdCallback == nullptr); +} + +static void TestSetPasswordUserdata() { + int userdata = 0; + TestCtx ctx {}; + RequireThrows([&] { SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(nullptr, &userdata); }); + SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(Context(&ctx), &userdata); + Require(ctx.defaultPasswdCallbackUserdata == &userdata && ctx.defaultPasswdCallback == nullptr && ctx.internal == nullptr); + SSL_CTX_set_default_passwd_cb_userdata_nid_postfix(Context(&ctx), nullptr); + Require(ctx.defaultPasswdCallbackUserdata == nullptr); +} + +int main() { + TestGetVerifyMode(); + TestGetCertStore(); + TestSetQuietShutdown(); + TestSetPasswordCallback(); + TestSetPasswordUserdata(); + return 0; +} diff --git a/core/libs/tests/GuestLibreSslSslCtxCallbacks.cpp b/core/libs/tests/GuestLibreSslSslCtxCallbacks.cpp new file mode 100644 index 0000000000..3d5ae8e840 --- /dev/null +++ b/core/libs/tests/GuestLibreSslSslCtxCallbacks.cpp @@ -0,0 +1,131 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +struct TestCallbacks { + void* slots[58]; +}; + +struct TestCtx { + unsigned char beforeInternal[112]; + TestCallbacks* internal; +}; + +static_assert(sizeof(TestCallbacks) == 464 && sizeof(TestCtx) == 120); + +static int Marker[4]; + +static bool OnlySlots(const TestCallbacks& callbacks, int first, void* firstValue, int second, void* secondValue) { + for (int slot = 0; slot < 58; ++slot) { + void* expected = slot == first ? firstValue : slot == second ? secondValue : nullptr; + if (callbacks.slots[slot] != expected) return false; + } + return true; +} + +static LibreSslCtx* Context(TestCtx* ctx) { + return reinterpret_cast(ctx); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL SSL_CTX callback check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestInfoCallback() { + TestCallbacks callbacks {}; + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_set_info_callback_nid_postfix(nullptr, &Marker[0]); }); + RequireThrows([&] { SSL_CTX_set_info_callback_nid_postfix(Context(&ctx), &Marker[0]); }); + ctx.internal = &callbacks; + SSL_CTX_set_info_callback_nid_postfix(Context(&ctx), &Marker[0]); + Require(OnlySlots(callbacks, 11, &Marker[0], -1, nullptr)); + SSL_CTX_set_info_callback_nid_postfix(Context(&ctx), nullptr); + Require(OnlySlots(callbacks, -1, nullptr, -1, nullptr)); +} + +static void TestNewSessionCallback() { + TestCallbacks callbacks {}; + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_sess_set_new_cb_nid_postfix(nullptr, &Marker[0]); }); + RequireThrows([&] { SSL_CTX_sess_set_new_cb_nid_postfix(Context(&ctx), &Marker[0]); }); + ctx.internal = &callbacks; + SSL_CTX_sess_set_new_cb_nid_postfix(Context(&ctx), &Marker[0]); + Require(OnlySlots(callbacks, 3, &Marker[0], -1, nullptr)); + SSL_CTX_sess_set_new_cb_nid_postfix(Context(&ctx), nullptr); + Require(OnlySlots(callbacks, -1, nullptr, -1, nullptr)); +} + +static void TestCertVerifyCallback() { + TestCallbacks callbacks {}; + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_set_cert_verify_callback_nid_postfix(nullptr, &Marker[0], &Marker[1]); }); + RequireThrows([&] { SSL_CTX_set_cert_verify_callback_nid_postfix(Context(&ctx), &Marker[0], &Marker[1]); }); + ctx.internal = &callbacks; + SSL_CTX_set_cert_verify_callback_nid_postfix(Context(&ctx), &Marker[0], &Marker[1]); + Require(OnlySlots(callbacks, 6, &Marker[0], 7, &Marker[1])); + SSL_CTX_set_cert_verify_callback_nid_postfix(Context(&ctx), nullptr, &Marker[2]); + Require(OnlySlots(callbacks, 6, nullptr, 7, &Marker[2])); + SSL_CTX_set_cert_verify_callback_nid_postfix(Context(&ctx), nullptr, nullptr); + Require(OnlySlots(callbacks, -1, nullptr, -1, nullptr)); +} + +static void TestClientCertCallback() { + TestCallbacks callbacks {}; + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_set_client_cert_cb_nid_postfix(nullptr, &Marker[0]); }); + RequireThrows([&] { SSL_CTX_set_client_cert_cb_nid_postfix(Context(&ctx), &Marker[0]); }); + ctx.internal = &callbacks; + SSL_CTX_set_client_cert_cb_nid_postfix(Context(&ctx), &Marker[0]); + Require(OnlySlots(callbacks, 8, &Marker[0], -1, nullptr)); + SSL_CTX_set_client_cert_cb_nid_postfix(Context(&ctx), nullptr); + Require(OnlySlots(callbacks, -1, nullptr, -1, nullptr)); +} + +static void TestNextProtoSelectCallback() { + TestCallbacks callbacks {}; + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_set_next_proto_select_cb_nid_postfix(nullptr, &Marker[0], &Marker[1]); }); + RequireThrows([&] { SSL_CTX_set_next_proto_select_cb_nid_postfix(Context(&ctx), &Marker[0], &Marker[1]); }); + ctx.internal = &callbacks; + SSL_CTX_set_next_proto_select_cb_nid_postfix(Context(&ctx), &Marker[0], &Marker[1]); + Require(OnlySlots(callbacks, 48, &Marker[0], 49, &Marker[1])); + SSL_CTX_set_next_proto_select_cb_nid_postfix(Context(&ctx), nullptr, &Marker[2]); + Require(OnlySlots(callbacks, 48, nullptr, 49, &Marker[2])); + SSL_CTX_set_next_proto_select_cb_nid_postfix(Context(&ctx), nullptr, nullptr); + Require(OnlySlots(callbacks, -1, nullptr, -1, nullptr)); +} + +static void TestAlpnSelectCallback() { + TestCallbacks callbacks {}; + TestCtx ctx {}; + RequireThrows([] { SSL_CTX_set_alpn_select_cb_nid_postfix(nullptr, &Marker[0], &Marker[1]); }); + RequireThrows([&] { SSL_CTX_set_alpn_select_cb_nid_postfix(Context(&ctx), &Marker[0], &Marker[1]); }); + ctx.internal = &callbacks; + SSL_CTX_set_alpn_select_cb_nid_postfix(Context(&ctx), &Marker[0], &Marker[1]); + Require(OnlySlots(callbacks, 50, &Marker[0], 51, &Marker[1])); + SSL_CTX_set_alpn_select_cb_nid_postfix(Context(&ctx), nullptr, &Marker[2]); + Require(OnlySlots(callbacks, 50, nullptr, 51, &Marker[2])); + SSL_CTX_set_alpn_select_cb_nid_postfix(Context(&ctx), nullptr, nullptr); + Require(OnlySlots(callbacks, -1, nullptr, -1, nullptr)); +} + +int main() { + TestInfoCallback(); + TestNewSessionCallback(); + TestCertVerifyCallback(); + TestClientCertCallback(); + TestNextProtoSelectCallback(); + TestAlpnSelectCallback(); + return 0; +} diff --git a/core/libs/tests/GuestLibreSslSslCtxFields.cpp b/core/libs/tests/GuestLibreSslSslCtxFields.cpp new file mode 100644 index 0000000000..9f1e2feeea --- /dev/null +++ b/core/libs/tests/GuestLibreSslSslCtxFields.cpp @@ -0,0 +1,440 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +constexpr std::size_t CtxSize = 120; +constexpr std::size_t InternalSize = 464; + +constexpr std::size_t CtxExtraCerts = 40; +constexpr std::size_t CtxVerifyMode = 48; +constexpr std::size_t CtxSidCtxLength = 52; +constexpr std::size_t CtxSidCtx = 56; +constexpr std::size_t CtxInternal = 112; + +constexpr std::size_t MinVersion = 0; +constexpr std::size_t MaxVersion = 2; +constexpr std::size_t Options = 8; +constexpr std::size_t Mode = 16; +constexpr std::size_t MsgCallback = 96; +constexpr std::size_t MsgCallbackArg = 104; +constexpr std::size_t DefaultVerifyCallback = 112; +constexpr std::size_t ServernameCallback = 128; +constexpr std::size_t ServernameArg = 136; +constexpr std::size_t TicketKeyCallback = 144; +constexpr std::size_t StatusCallback = 152; +constexpr std::size_t StatusArg = 160; +constexpr std::size_t SessionCacheSize = 176; +constexpr std::size_t SessionCacheMode = 200; +constexpr std::size_t StatsConnect = 204; +constexpr std::size_t StatsConnectRenegotiate = 208; +constexpr std::size_t StatsConnectGood = 212; +constexpr std::size_t StatsAccept = 216; +constexpr std::size_t StatsAcceptRenegotiate = 220; +constexpr std::size_t StatsAcceptGood = 224; +constexpr std::size_t StatsMiss = 228; +constexpr std::size_t StatsTimeout = 232; +constexpr std::size_t StatsCacheFull = 236; +constexpr std::size_t StatsHit = 240; +constexpr std::size_t StatsCbHit = 244; +constexpr std::size_t MaxCertList = 280; +constexpr std::size_t ReadAhead = 288; +constexpr std::size_t MaxSendFragment = 296; +constexpr std::size_t TicketKeys = 312; + +constexpr std::uint64_t ShouldNotHaveBeenCalled = 0x14FFF042; +constexpr std::uint64_t InvalidTicketKeysLength = 0x14FFF145; +constexpr std::uint64_t SessionIdContextTooLong = 0x14FFF111; + +static_assert(sizeof(LibreSslCtx) == CtxSize && sizeof(LibreSslCtxInternal) == InternalSize); +static_assert(StatsCbHit + 4 == 248 && TicketKeys + 48 == 360 && CtxSidCtx + 32 == 88); + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL SSL_CTX fields check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void* Pointer(std::uintptr_t value) { + return reinterpret_cast(value); +} + +template static void Store(unsigned char* base, std::size_t offset, TValue value) { + std::memcpy(base + offset, &value, sizeof value); +} + +template static TValue Load(const unsigned char* base, std::size_t offset) { + TValue value; + std::memcpy(&value, base + offset, sizeof value); + return value; +} + +struct Bytes { + alignas(8) unsigned char ctx[CtxSize]; + alignas(8) unsigned char internal[InternalSize]; +}; + +struct Fixture { + Bytes actual; + Bytes expected; + + Fixture() { + for (std::size_t i = 0; i < CtxSize; ++i) actual.ctx[i] = static_cast(i * 7 + 3); + for (std::size_t i = 0; i < InternalSize; ++i) actual.internal[i] = static_cast(i * 13 + 5); + Store(actual.ctx, CtxExtraCerts, nullptr); + Store(actual.ctx, CtxVerifyMode, 0); + Store(actual.ctx, CtxSidCtxLength, 0); + Store(actual.ctx, CtxInternal, actual.internal); + Store(actual.internal, MinVersion, 0x301); + Store(actual.internal, MaxVersion, 0x303); + Store(actual.internal, Options, 0x4); + Store(actual.internal, Mode, 0); + for (std::size_t offset : {MsgCallback, MsgCallbackArg, DefaultVerifyCallback, ServernameCallback, ServernameArg, TicketKeyCallback, StatusCallback, StatusArg}) + Store(actual.internal, offset, nullptr); + Store(actual.internal, SessionCacheSize, 20480); + Store(actual.internal, SessionCacheMode, 2); + for (std::size_t offset = StatsConnect; offset <= StatsCbHit; offset += 4) Store(actual.internal, offset, 0); + Store(actual.internal, MaxCertList, 102400); + Store(actual.internal, ReadAhead, 0); + Store(actual.internal, MaxSendFragment, 16384); + expected = actual; + } + + Fixture(const Fixture&) = delete; + Fixture& operator=(const Fixture&) = delete; + + LibreSslCtx* Context() { + return reinterpret_cast(actual.ctx); + } + + template void SetCtx(std::size_t offset, TValue value) { + Store(actual.ctx, offset, value); + Store(expected.ctx, offset, value); + } + + template void SetInternal(std::size_t offset, TValue value) { + Store(actual.internal, offset, value); + Store(expected.internal, offset, value); + } + + template void ExpectCtx(std::size_t offset, TValue value) { + Store(expected.ctx, offset, value); + } + + template void ExpectInternal(std::size_t offset, TValue value) { + Store(expected.internal, offset, value); + } + + void Verify(std::uint64_t error, std::source_location location) { + Require(std::memcmp(actual.ctx, expected.ctx, CtxSize) == 0, location); + Require(std::memcmp(actual.internal, expected.internal, InternalSize) == 0, location); + Require(ERR_get_error_nid_postfix() == error, location); + Require(ERR_get_error_nid_postfix() == 0, location); + } + + void Ctrl(int cmd, std::int64_t larg, void* parg, std::int64_t result, std::uint64_t error = 0, std::source_location location = std::source_location::current()) { + Require(SSL_CTX_ctrl_nid_postfix(Context(), cmd, larg, parg) == result, location); + Verify(error, location); + } + + void CallbackCtrl(int cmd, void* callback, std::int64_t result, std::uint64_t error = 0, std::source_location location = std::source_location::current()) { + Require(SSL_CTX_callback_ctrl_nid_postfix(Context(), cmd, callback) == result, location); + Verify(error, location); + } + + void SessionIdContext(const unsigned char* sidCtx, unsigned int length, int result, std::uint64_t error = 0, std::source_location location = std::source_location::current()) { + Require(SSL_CTX_set_session_id_context_nid_postfix(Context(), sidCtx, length) == result, location); + Verify(error, location); + } + + template void Throws(TAction action, std::source_location location = std::source_location::current()) { + RequireThrows(action, location); + Verify(0, location); + } +}; + +static void TestOptionsAndMode() { + Fixture f; + f.Ctrl(32, 0x4, nullptr, 0x4); + f.ExpectInternal(Options, 0x80000004); + f.Ctrl(32, 0x80000000, nullptr, 0x80000004); + f.ExpectInternal(Options, 0x80000000); + f.Ctrl(77, 0x4, nullptr, 0x80000000); + f.ExpectInternal(Mode, 0x2); + f.Ctrl(33, 0x2, nullptr, 0x2); + f.ExpectInternal(Mode, 0); + f.Ctrl(78, 0x2, nullptr, 0); + f.ExpectInternal(Options, UINT64_MAX); + f.Ctrl(32, -1, nullptr, -1); + f.ExpectInternal(Options, 0); + f.Ctrl(77, -1, nullptr, 0); + f.ExpectInternal(Mode, UINT64_MAX); + f.Ctrl(33, -1, nullptr, -1); + f.ExpectInternal(Mode, 0x8000000000000000); + f.Ctrl(78, INT64_MAX, nullptr, INT64_MIN); +} + +static void TestReadAhead() { + Fixture f; + f.Ctrl(40, 0, nullptr, 0); + f.ExpectInternal(ReadAhead, 1); + f.Ctrl(41, 1, nullptr, 0); + f.Ctrl(41, 0x100000001, nullptr, 1); + f.Ctrl(40, 0, nullptr, 1); + f.ExpectInternal(ReadAhead, 0); + f.Ctrl(41, 0x100000000, nullptr, 1); + f.Ctrl(40, 0, nullptr, 0); + f.ExpectInternal(ReadAhead, -1); + f.Ctrl(41, -1, nullptr, 0); + f.Ctrl(40, 0, nullptr, -1); +} + +static void TestMaxCertList() { + Fixture f; + f.Ctrl(50, 0, nullptr, 102400); + f.ExpectInternal(MaxCertList, 1234); + f.Ctrl(51, 1234, nullptr, 102400); + f.ExpectInternal(MaxCertList, -5); + f.Ctrl(51, -5, nullptr, 1234); + f.Ctrl(50, 0, nullptr, -5); +} + +static void TestSessionCache() { + Fixture f; + f.Ctrl(43, 0, nullptr, 20480); + f.ExpectInternal(SessionCacheSize, 7); + f.Ctrl(42, 7, nullptr, 20480); + f.ExpectInternal(SessionCacheSize, UINT64_MAX); + f.Ctrl(42, -1, nullptr, 7); + f.Ctrl(43, 0, nullptr, -1); + f.Ctrl(45, 0, nullptr, 2); + f.ExpectInternal(SessionCacheMode, 3); + f.Ctrl(44, 3, nullptr, 2); + f.ExpectInternal(SessionCacheMode, 2); + f.Ctrl(44, 0x100000002, nullptr, 3); + f.Ctrl(45, 0, nullptr, 2); +} + +static void TestMaxSendFragment() { + Fixture f; + f.Ctrl(52, 511, nullptr, 0); + f.ExpectInternal(MaxSendFragment, 512); + f.Ctrl(52, 512, nullptr, 1); + f.ExpectInternal(MaxSendFragment, 16384); + f.Ctrl(52, 16384, nullptr, 1); + f.Ctrl(52, 16385, nullptr, 0); + f.Ctrl(52, -1, nullptr, 0); + f.Ctrl(52, 0x100000200, nullptr, 0); +} + +static void TestStats() { + Fixture f; + for (int cmd = 21; cmd <= 31; ++cmd) f.Ctrl(cmd, 0, nullptr, 0); + const std::size_t offsets[] = {StatsConnect, StatsConnectRenegotiate, StatsConnectGood, StatsAccept, StatsAcceptRenegotiate, StatsAcceptGood, + StatsMiss, StatsTimeout, StatsCacheFull, StatsHit, StatsCbHit}; + for (int i = 0; i < 11; ++i) f.SetInternal(offsets[i], 101 + i); + f.Ctrl(21, 0, nullptr, 101); + f.Ctrl(22, 0, nullptr, 103); + f.Ctrl(23, 0, nullptr, 102); + f.Ctrl(24, 0, nullptr, 104); + f.Ctrl(25, 0, nullptr, 106); + f.Ctrl(26, 0, nullptr, 105); + f.Ctrl(27, 0, nullptr, 110); + f.Ctrl(28, 0, nullptr, 111); + f.Ctrl(29, 0, nullptr, 107); + f.Ctrl(30, 0, nullptr, 108); + f.Ctrl(31, 0, nullptr, 109); + f.SetInternal(StatsConnect, -1); + f.Ctrl(21, 0, nullptr, -1); +} + +static void TestCallbackArgs() { + Fixture f; + f.ExpectInternal(MsgCallbackArg, Pointer(0x2222)); + f.Ctrl(16, 0, Pointer(0x2222), 1); + f.ExpectInternal(ServernameArg, Pointer(0x3333)); + f.Ctrl(54, 0, Pointer(0x3333), 1); + f.ExpectInternal(StatusArg, Pointer(0x4444)); + f.Ctrl(64, 0, Pointer(0x4444), 1); + f.ExpectInternal(MsgCallbackArg, nullptr); + f.Ctrl(16, 5, nullptr, 1); +} + +static void TestTicketKeys() { + Fixture f; + f.Ctrl(58, 0, nullptr, 48); + f.Ctrl(59, 0, nullptr, 48); + f.Ctrl(58, 47, nullptr, 48); + f.Ctrl(59, 47, nullptr, 48); + unsigned char buffer[48]; + std::memset(buffer, 0xee, sizeof buffer); + f.Ctrl(58, 47, buffer, 0, InvalidTicketKeysLength); + for (unsigned char byte : buffer) Require(byte == 0xee); + f.Ctrl(59, 47, buffer, 0, InvalidTicketKeysLength); + f.Ctrl(59, 49, buffer, 0, InvalidTicketKeysLength); + f.Ctrl(58, 48, buffer, 1); + Require(std::memcmp(buffer, f.actual.internal + TicketKeys, 48) == 0); + for (int i = 0; i < 48; ++i) buffer[i] = static_cast(i); + for (int i = 0; i < 48; ++i) f.ExpectInternal(TicketKeys + i, static_cast(i)); + f.Ctrl(59, 48, buffer, 1); + unsigned char keys[48]; + std::memset(keys, 0xee, sizeof keys); + f.Ctrl(58, 48, keys, 1); + for (int i = 0; i < 48; ++i) Require(keys[i] == i); +} + +static void TestExtraChainCerts() { + Fixture f; + void* slot = Pointer(0x5555); + f.Ctrl(82, 0, &slot, 1); + Require(slot == nullptr); + f.SetCtx(CtxExtraCerts, Pointer(0x7777)); + f.Ctrl(82, 0, &slot, 1); + Require(slot == Pointer(0x7777)); + f.Throws([&] { SSL_CTX_ctrl_nid_postfix(f.Context(), 82, 0, nullptr); }); +} + +static void TestTemporaryKeys() { + Fixture f; + f.Ctrl(1, 0, nullptr, 0); + f.Ctrl(2, 0, nullptr, 0, ShouldNotHaveBeenCalled); + f.Ctrl(5, 0, nullptr, 0, ShouldNotHaveBeenCalled); + f.Ctrl(6, 0, nullptr, 0, ShouldNotHaveBeenCalled); + f.Ctrl(7, 0, nullptr, 0, ShouldNotHaveBeenCalled); +} + +static void TestOtherCtrlCommands() { + Fixture f; + for (int cmd : {0, 8, 13, 15, 17, 55, 65, 999, -1}) f.Ctrl(cmd, 0, nullptr, 0); +} + +static void TestCallbackCtrl() { + Fixture f; + f.ExpectInternal(MsgCallback, Pointer(0x6666)); + f.CallbackCtrl(15, Pointer(0x6666), 1); + f.ExpectInternal(ServernameCallback, Pointer(0x7777)); + f.CallbackCtrl(53, Pointer(0x7777), 1); + f.ExpectInternal(StatusCallback, Pointer(0x8888)); + f.CallbackCtrl(63, Pointer(0x8888), 1); + f.ExpectInternal(TicketKeyCallback, Pointer(0x9999)); + f.CallbackCtrl(72, Pointer(0x9999), 1); + f.CallbackCtrl(5, Pointer(0x1111), 0, ShouldNotHaveBeenCalled); + f.CallbackCtrl(0, Pointer(0x1111), 0); + f.CallbackCtrl(16, Pointer(0x1111), 0); + f.CallbackCtrl(999, Pointer(0x1111), 0); +} + +static void TestSetMsgCallback() { + Fixture f; + f.ExpectInternal(MsgCallback, Pointer(0xaaaa)); + SSL_CTX_set_msg_callback_nid_postfix(f.Context(), Pointer(0xaaaa)); + f.Verify(0, std::source_location::current()); + f.ExpectInternal(MsgCallback, nullptr); + SSL_CTX_set_msg_callback_nid_postfix(f.Context(), nullptr); + f.Verify(0, std::source_location::current()); +} + +static void TestSetVerify() { + Fixture f; + f.ExpectCtx(CtxVerifyMode, 3); + f.ExpectInternal(DefaultVerifyCallback, Pointer(0xbbbb)); + SSL_CTX_set_verify_nid_postfix(f.Context(), 3, Pointer(0xbbbb)); + f.Verify(0, std::source_location::current()); + f.ExpectCtx(CtxVerifyMode, -1); + f.ExpectInternal(DefaultVerifyCallback, nullptr); + SSL_CTX_set_verify_nid_postfix(f.Context(), -1, nullptr); + f.Verify(0, std::source_location::current()); + f.ExpectCtx(CtxVerifyMode, 0x7fffffff); + f.ExpectInternal(DefaultVerifyCallback, Pointer(0xcccc)); + SSL_CTX_set_verify_nid_postfix(f.Context(), 0x7fffffff, Pointer(0xcccc)); + f.Verify(0, std::source_location::current()); +} + +static void TestSetSessionIdContext() { + Fixture f; + const unsigned char abc[] = {'a', 'b', 'c'}; + unsigned char sequence[33]; + for (int i = 0; i < 33; ++i) sequence[i] = static_cast(i + 1); + const unsigned char q[] = {'q'}; + const unsigned char zz[] = {'z', 'z'}; + f.ExpectCtx(CtxSidCtxLength, 3); + for (int i = 0; i < 3; ++i) f.ExpectCtx(CtxSidCtx + i, abc[i]); + f.SessionIdContext(abc, 3, 1); + f.ExpectCtx(CtxSidCtxLength, 32); + for (int i = 0; i < 32; ++i) f.ExpectCtx(CtxSidCtx + i, sequence[i]); + f.SessionIdContext(sequence, 32, 1); + f.SessionIdContext(sequence, 33, 0, SessionIdContextTooLong); + f.SessionIdContext(sequence, UINT32_MAX, 0, SessionIdContextTooLong); + f.SessionIdContext(nullptr, 33, 0, SessionIdContextTooLong); + f.ExpectCtx(CtxSidCtxLength, 0); + f.SessionIdContext(q, 0, 1); + f.SessionIdContext(nullptr, 0, 1); + f.ExpectCtx(CtxSidCtxLength, 32); + f.SessionIdContext(sequence, 32, 1); + f.ExpectCtx(CtxSidCtxLength, 2); + f.ExpectCtx(CtxSidCtx, 'z'); + f.ExpectCtx(CtxSidCtx + 1, 'z'); + f.SessionIdContext(zz, 2, 1); + for (int i = 2; i < 32; ++i) Require(f.actual.ctx[CtxSidCtx + i] == i + 1); + f.Throws([&] { SSL_CTX_set_session_id_context_nid_postfix(f.Context(), nullptr, 1); }); +} + +static void TestNullContext() { + const unsigned char sidCtx[] = {1}; + RequireThrows([] { SSL_CTX_ctrl_nid_postfix(nullptr, 32, 1, nullptr); }); + RequireThrows([] { SSL_CTX_callback_ctrl_nid_postfix(nullptr, 15, nullptr); }); + RequireThrows([] { SSL_CTX_set_msg_callback_nid_postfix(nullptr, nullptr); }); + RequireThrows([] { SSL_CTX_set_verify_nid_postfix(nullptr, 1, nullptr); }); + RequireThrows([&] { SSL_CTX_set_session_id_context_nid_postfix(nullptr, sidCtx, 1); }); + Require(ERR_get_error_nid_postfix() == 0); +} + +static void TestNullInternal() { + Fixture f; + f.SetCtx(CtxInternal, nullptr); + unsigned char keys[48] {}; + for (int cmd = -1; cmd <= 120; ++cmd) { + f.Throws([&] { SSL_CTX_ctrl_nid_postfix(f.Context(), cmd, 48, keys); }); + f.Throws([&] { SSL_CTX_callback_ctrl_nid_postfix(f.Context(), cmd, Pointer(0x1111)); }); + } + f.Throws([&] { SSL_CTX_ctrl_nid_postfix(f.Context(), 999, 0, nullptr); }); + f.Throws([&] { SSL_CTX_callback_ctrl_nid_postfix(f.Context(), 999, nullptr); }); + f.Throws([&] { SSL_CTX_set_msg_callback_nid_postfix(f.Context(), Pointer(0xaaaa)); }); + f.Throws([&] { SSL_CTX_set_verify_nid_postfix(f.Context(), 3, Pointer(0xbbbb)); }); + const unsigned char abc[] = {'a', 'b', 'c'}; + f.ExpectCtx(CtxSidCtxLength, 3); + for (int i = 0; i < 3; ++i) f.ExpectCtx(CtxSidCtx + i, abc[i]); + f.SessionIdContext(abc, 3, 1); +} + +int main() { + ERR_clear_error_nid_postfix(); + TestOptionsAndMode(); + TestReadAhead(); + TestMaxCertList(); + TestSessionCache(); + TestMaxSendFragment(); + TestStats(); + TestCallbackArgs(); + TestTicketKeys(); + TestExtraChainCerts(); + TestTemporaryKeys(); + TestOtherCtrlCommands(); + TestCallbackCtrl(); + TestSetMsgCallback(); + TestSetVerify(); + TestSetSessionIdContext(); + TestNullContext(); + TestNullInternal(); + return 0; +} diff --git a/core/libs/tests/GuestLibreSslSslSession.cpp b/core/libs/tests/GuestLibreSslSslSession.cpp new file mode 100644 index 0000000000..55a3e23e08 --- /dev/null +++ b/core/libs/tests/GuestLibreSslSslSession.cpp @@ -0,0 +1,121 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +struct TestCertKey { + void* x509; + void* privatekey; + const void* digest; +}; + +struct TestCert { + TestCertKey* key; + TestCertKey pkeys[8]; + unsigned char rest[56]; +}; + +struct TestSession { + unsigned char beforeCipher[168]; + const void* cipher; + unsigned char rest[56]; +}; + +struct TestSsl { + unsigned char beforeCert[80]; + TestCert* cert; + unsigned char beforeSession[40]; + TestSession* session; + unsigned char rest[96]; +}; + +static_assert(sizeof(TestCertKey) == 24 && sizeof(TestCert) == 256); +static_assert(sizeof(TestSession) == 232 && offsetof(TestSession, cipher) == 168); +static_assert(sizeof(TestSsl) == 232 && offsetof(TestSsl, cert) == 80 && offsetof(TestSsl, session) == 128); + +static const LibreSslSsl* Connection(const TestSsl* ssl) { + return reinterpret_cast(ssl); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL SSL session check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestGetSession() { + TestSession session {}; + TestCert cert {}; + TestSsl ssl {}; + RequireThrows([] { SSL_get_session_nid_postfix(nullptr); }); + Require(SSL_get_session_nid_postfix(Connection(&ssl)) == nullptr); + ssl.cert = &cert; + ssl.session = &session; + Require(reinterpret_cast(SSL_get_session_nid_postfix(Connection(&ssl))) == &session); +} + +static void TestCurrentCipher() { + int cipher = 0; + TestSession session {}; + TestSsl ssl {}; + RequireThrows([] { SSL_get_current_cipher_nid_postfix(nullptr); }); + Require(SSL_get_current_cipher_nid_postfix(Connection(&ssl)) == nullptr); + ssl.session = &session; + Require(SSL_get_current_cipher_nid_postfix(Connection(&ssl)) == nullptr); + session.cipher = &cipher; + Require(SSL_get_current_cipher_nid_postfix(Connection(&ssl)) == &cipher); +} + +static void TestGetCertificate() { + int x509 = 0; + int other = 0; + int key = 0; + TestCert cert {}; + TestSsl ssl {}; + RequireThrows([] { SSL_get_certificate_nid_postfix(nullptr); }); + Require(SSL_get_certificate_nid_postfix(Connection(&ssl)) == nullptr); + ssl.cert = &cert; + RequireThrows([&] { SSL_get_certificate_nid_postfix(Connection(&ssl)); }); + cert.pkeys[0].x509 = &other; + cert.pkeys[2].x509 = &x509; + cert.pkeys[2].privatekey = &key; + cert.key = &cert.pkeys[1]; + Require(SSL_get_certificate_nid_postfix(Connection(&ssl)) == nullptr); + cert.key = &cert.pkeys[2]; + Require(SSL_get_certificate_nid_postfix(Connection(&ssl)) == &x509); +} + +static void TestGetPrivateKey() { + int x509 = 0; + int key = 0; + int other = 0; + TestCert cert {}; + TestSsl ssl {}; + RequireThrows([] { SSL_get_privatekey_nid_postfix(nullptr); }); + Require(SSL_get_privatekey_nid_postfix(Connection(&ssl)) == nullptr); + ssl.cert = &cert; + RequireThrows([&] { SSL_get_privatekey_nid_postfix(Connection(&ssl)); }); + cert.pkeys[0].privatekey = &other; + cert.pkeys[3].x509 = &x509; + cert.pkeys[3].privatekey = &key; + cert.key = &cert.pkeys[1]; + Require(SSL_get_privatekey_nid_postfix(Connection(&ssl)) == nullptr); + cert.key = &cert.pkeys[3]; + Require(SSL_get_privatekey_nid_postfix(Connection(&ssl)) == &key); +} + +int main() { + TestGetSession(); + TestCurrentCipher(); + TestGetCertificate(); + TestGetPrivateKey(); + return 0; +} diff --git a/core/libs/tests/GuestLibreSslSslSettings.cpp b/core/libs/tests/GuestLibreSslSslSettings.cpp new file mode 100644 index 0000000000..161b9f82f0 --- /dev/null +++ b/core/libs/tests/GuestLibreSslSslSettings.cpp @@ -0,0 +1,427 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include + +struct TestSslInternal { + unsigned char beforeOptions[8]; + std::uint64_t options; + std::uint64_t mode; + unsigned char beforeHandshakeFunc[32]; + void* handshakeFunc; + void* msgCallback; + void* msgCallbackArg; + unsigned char beforeVerifyCallback[8]; + void* verifyCallback; + unsigned char beforeShutdown[72]; + int shutdown; + unsigned char beforeReadAhead[40]; + int readAhead; + unsigned char beforeAeadReadCtx[24]; + void* aeadReadCtx; + void* aeadWriteCtx; + void* encWriteCtx; + void* writeHash; + unsigned char beforeMaxCertList[24]; + std::int64_t maxCertList; + unsigned char beforeState[108]; + int state; + int rstate; + unsigned char rest[12]; +}; + +struct TestSsl3Internal { + unsigned char beforeSendConnectionBinding[1012]; + int sendConnectionBinding; + unsigned char beforeAlpnSelected[8]; + const unsigned char* alpnSelected; + unsigned int alpnSelectedLength; + unsigned char rest[4]; +}; + +struct TestSsl3 { + unsigned char beforeInternal[136]; + TestSsl3Internal* internal; +}; + +struct TestDtls1Internal { + unsigned char beforeMtu[636]; + std::uint32_t mtu; + unsigned char rest[4]; +}; + +struct TestDtls1 { + unsigned char beforeInternal[32]; + TestDtls1Internal* internal; +}; + +struct TestMethodInternal { + int version; + unsigned char beforeAccept[28]; + void* accept; + void* connect; + unsigned char rest[112]; +}; + +struct TestMethod { + unsigned char beforeInternal[40]; + TestMethodInternal* internal; +}; + +struct TestSsl { + int version; + unsigned char beforeMethod[4]; + TestMethod* method; + unsigned char beforeServer[24]; + int server; + unsigned char beforeS3[4]; + TestSsl3* s3; + TestDtls1* d1; + unsigned char beforeVerifyMode[72]; + int verifyMode; + unsigned char beforeClientVersion[32]; + int clientVersion; + unsigned int maxSendFragment; + unsigned char beforeTlsextHostname[4]; + char* tlsextHostname; + unsigned char beforeEncReadCtx[16]; + void* encReadCtx; + void* readHash; + TestSslInternal* internal; +}; + +static_assert(sizeof(TestSslInternal) == 432 && offsetof(TestSslInternal, options) == 8 && offsetof(TestSslInternal, mode) == 16); +static_assert(offsetof(TestSslInternal, handshakeFunc) == 56 && offsetof(TestSslInternal, msgCallback) == 64 && offsetof(TestSslInternal, msgCallbackArg) == 72); +static_assert(offsetof(TestSslInternal, verifyCallback) == 88 && offsetof(TestSslInternal, shutdown) == 168 && offsetof(TestSslInternal, readAhead) == 212); +static_assert(offsetof(TestSslInternal, aeadReadCtx) == 240 && offsetof(TestSslInternal, aeadWriteCtx) == 248 && offsetof(TestSslInternal, encWriteCtx) == 256); +static_assert(offsetof(TestSslInternal, writeHash) == 264 && offsetof(TestSslInternal, maxCertList) == 296); +static_assert(offsetof(TestSslInternal, state) == 412 && offsetof(TestSslInternal, rstate) == 416); +static_assert(sizeof(TestSsl3Internal) == 1040 && offsetof(TestSsl3Internal, sendConnectionBinding) == 1012); +static_assert(offsetof(TestSsl3Internal, alpnSelected) == 1024 && offsetof(TestSsl3Internal, alpnSelectedLength) == 1032); +static_assert(sizeof(TestSsl3) == 144 && sizeof(TestDtls1) == 40 && offsetof(TestDtls1Internal, mtu) == 636); +static_assert(sizeof(TestMethodInternal) == 160 && offsetof(TestMethodInternal, accept) == 32 && offsetof(TestMethodInternal, connect) == 40); +static_assert(sizeof(TestMethod) == 48 && sizeof(TestSsl) == 232 && offsetof(TestSsl, method) == 8 && offsetof(TestSsl, server) == 40); +static_assert(offsetof(TestSsl, s3) == 48 && offsetof(TestSsl, d1) == 56 && offsetof(TestSsl, verifyMode) == 136); +static_assert(offsetof(TestSsl, clientVersion) == 172 && offsetof(TestSsl, maxSendFragment) == 176 && offsetof(TestSsl, tlsextHostname) == 184); +static_assert(offsetof(TestSsl, encReadCtx) == 208 && offsetof(TestSsl, readHash) == 216 && offsetof(TestSsl, internal) == 224); + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL SSL settings check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +template static void Fill(T& value, unsigned char seed) { + auto* bytes = reinterpret_cast(&value); + for (std::size_t index = 0; index < sizeof(T); ++index) bytes[index] = static_cast(seed + index); +} + +template static bool Same(const T& left, const T& right) { + return std::memcmp(&left, &right, sizeof(T)) == 0; +} + +struct Connection { + TestSsl ssl; + TestSslInternal internal; + + Connection() { + Fill(ssl, 0x10); + Fill(internal, 0x40); + ssl.method = nullptr; + ssl.s3 = nullptr; + ssl.d1 = nullptr; + ssl.encReadCtx = nullptr; + ssl.readHash = nullptr; + ssl.internal = &internal; + internal.aeadReadCtx = nullptr; + internal.aeadWriteCtx = nullptr; + internal.encWriteCtx = nullptr; + internal.writeHash = nullptr; + } + + LibreSslSsl* Ssl() { + return reinterpret_cast(&ssl); + } +}; + +static std::int64_t Control(Connection& connection, int command, std::int64_t argument, void* pointer = nullptr) { + return SSL_ctrl_nid_postfix(connection.Ssl(), command, argument, pointer); +} + +static void TestSetVerify() { + int first = 0; + int second = 0; + Connection connection; + Connection expected = connection; + RequireThrows([&] { SSL_set_verify_nid_postfix(nullptr, 1, &first); }); + SSL_set_verify_nid_postfix(connection.Ssl(), 3, &first); + expected.ssl.verifyMode = 3; + expected.internal.verifyCallback = &first; + Require(Same(connection.ssl, expected.ssl) && Same(connection.internal, expected.internal)); + SSL_set_verify_nid_postfix(connection.Ssl(), -1, nullptr); + expected.ssl.verifyMode = -1; + Require(Same(connection.ssl, expected.ssl) && Same(connection.internal, expected.internal)); + SSL_set_verify_nid_postfix(connection.Ssl(), 1, &second); + expected.ssl.verifyMode = 1; + expected.internal.verifyCallback = &second; + Require(Same(connection.ssl, expected.ssl) && Same(connection.internal, expected.internal)); + connection.ssl.internal = nullptr; + SSL_set_verify_nid_postfix(connection.Ssl(), 2, nullptr); + Require(connection.ssl.verifyMode == 2); + RequireThrows([&] { SSL_set_verify_nid_postfix(connection.Ssl(), 5, &first); }); + Require(connection.ssl.verifyMode == 2); +} + +static void TestGetAlpnSelected() { + static const unsigned char protocol[] = "h2"; + TestSsl3Internal stateInternal {}; + TestSsl3 state {}; + Connection connection; + const Connection expected = connection; + const unsigned char* data = reinterpret_cast(1); + unsigned int length = 77; + RequireThrows([&] { SSL_get0_alpn_selected_nid_postfix(nullptr, &data, &length); }); + RequireThrows([&] { SSL_get0_alpn_selected_nid_postfix(connection.Ssl(), nullptr, &length); }); + RequireThrows([&] { SSL_get0_alpn_selected_nid_postfix(connection.Ssl(), &data, nullptr); }); + SSL_get0_alpn_selected_nid_postfix(connection.Ssl(), &data, &length); + Require(data == nullptr && length == 0); + connection.ssl.s3 = &state; + RequireThrows([&] { SSL_get0_alpn_selected_nid_postfix(connection.Ssl(), &data, &length); }); + state.internal = &stateInternal; + Fill(stateInternal, 0x70); + stateInternal.alpnSelected = protocol; + stateInternal.alpnSelectedLength = 2; + data = nullptr; + SSL_get0_alpn_selected_nid_postfix(connection.Ssl(), &data, &length); + Require(data == protocol && length == 2); + stateInternal.alpnSelected = nullptr; + stateInternal.alpnSelectedLength = 0; + SSL_get0_alpn_selected_nid_postfix(connection.Ssl(), &data, &length); + Require(data == nullptr && length == 0); + connection.ssl.s3 = nullptr; + Require(Same(connection.ssl, expected.ssl) && Same(connection.internal, expected.internal)); +} + +static void TestSetConnectState() { + int accept = 0; + int connect = 0; + TestMethodInternal methodInternal {}; + TestMethod method {}; + Fill(methodInternal, 0x30); + methodInternal.accept = &accept; + methodInternal.connect = &connect; + method.internal = &methodInternal; + Connection connection; + connection.ssl.method = &method; + connection.ssl.server = 1; + connection.internal.shutdown = 3; + connection.internal.state = 0x1234; + Connection expected = connection; + RequireThrows([] { SSL_set_connect_state_nid_postfix(nullptr); }); + SSL_set_connect_state_nid_postfix(connection.Ssl()); + expected.ssl.server = 0; + expected.internal.shutdown = 0; + expected.internal.state = 0x5000; + expected.internal.handshakeFunc = &connect; + Require(Same(connection.ssl, expected.ssl) && Same(connection.internal, expected.internal)); + methodInternal.connect = nullptr; + connection.internal.handshakeFunc = &accept; + SSL_set_connect_state_nid_postfix(connection.Ssl()); + Require(connection.internal.handshakeFunc == nullptr); + connection.ssl.server = 1; + method.internal = nullptr; + RequireThrows([&] { SSL_set_connect_state_nid_postfix(connection.Ssl()); }); + connection.ssl.method = nullptr; + RequireThrows([&] { SSL_set_connect_state_nid_postfix(connection.Ssl()); }); + connection.ssl.method = &method; + method.internal = &methodInternal; + connection.ssl.internal = nullptr; + RequireThrows([&] { SSL_set_connect_state_nid_postfix(connection.Ssl()); }); + Require(connection.ssl.server == 1); +} + +static void TestControlReadAhead() { + Connection connection; + Connection expected = connection; + RequireThrows([] { SSL_ctrl_nid_postfix(nullptr, 40, 0, nullptr); }); + connection.internal.readAhead = -1; + expected.internal.readAhead = -1; + Require(Control(connection, 40, 5) == -1); + Require(Control(connection, 41, INT64_C(0x123456789ABCDEF0)) == -1); + expected.internal.readAhead = static_cast(0x9ABCDEF0u); + Require(Same(connection.ssl, expected.ssl) && Same(connection.internal, expected.internal)); + Require(Control(connection, 40, 0) == INT64_C(-1698898192)); + Require(Control(connection, 41, 1) == INT64_C(-1698898192)); + Require(Control(connection, 40, 0) == 1); + connection.ssl.internal = nullptr; + RequireThrows([&] { Control(connection, 40, 0); }); + RequireThrows([&] { Control(connection, 41, 0); }); +} + +static void TestControlMessageCallbackArgument() { + int argument = 0; + Connection connection; + Connection expected = connection; + Require(Control(connection, 16, 9, &argument) == 1); + expected.internal.msgCallbackArg = &argument; + Require(Same(connection.ssl, expected.ssl) && Same(connection.internal, expected.internal)); + Require(Control(connection, 16, 0, nullptr) == 1); + Require(connection.internal.msgCallbackArg == nullptr); + connection.ssl.internal = nullptr; + RequireThrows([&] { Control(connection, 16, 0, &argument); }); +} + +static void TestControlOptionsAndMode() { + Connection connection; + connection.internal.options = 0x4; + connection.internal.mode = 0; + Connection expected = connection; + Require(Control(connection, 32, 0x10) == 0x14); + Require(Control(connection, 32, INT64_C(0x100000000)) == INT64_C(0x100000014)); + Require(Control(connection, 77, 0x4) == INT64_C(0x100000010)); + Require(Control(connection, 32, 0) == INT64_C(0x100000010)); + Require(Control(connection, 32, -1) == -1); + Require(Control(connection, 77, -1) == 0); + Require(Control(connection, 33, 0x2) == 0x2); + Require(Control(connection, 33, INT64_C(0x800000000)) == INT64_C(0x800000002)); + Require(Control(connection, 78, 0x2) == INT64_C(0x800000000)); + Require(Control(connection, 78, -1) == 0); + Require(Control(connection, 33, 0x5) == 0x5); + expected.internal.options = 0; + expected.internal.mode = 0x5; + Require(Same(connection.ssl, expected.ssl) && Same(connection.internal, expected.internal)); + connection.ssl.internal = nullptr; + RequireThrows([&] { Control(connection, 32, 1); }); + RequireThrows([&] { Control(connection, 77, 1); }); + RequireThrows([&] { Control(connection, 33, 1); }); + RequireThrows([&] { Control(connection, 78, 1); }); +} + +static void TestControlMaxCertList() { + Connection connection; + connection.internal.maxCertList = 102400; + Connection expected = connection; + Require(Control(connection, 50, 7) == 102400); + Require(Control(connection, 51, INT64_C(0x123456789)) == 102400); + Require(Control(connection, 50, 0) == INT64_C(0x123456789)); + Require(Control(connection, 51, -5) == INT64_C(0x123456789)); + expected.internal.maxCertList = -5; + Require(Same(connection.ssl, expected.ssl) && Same(connection.internal, expected.internal)); + connection.ssl.internal = nullptr; + RequireThrows([&] { Control(connection, 50, 0); }); + RequireThrows([&] { Control(connection, 51, 0); }); +} + +static void TestControlMtu() { + TestMethodInternal methodInternal {}; + TestMethod method {}; + TestDtls1Internal dtlsInternal {}; + TestDtls1 dtls {}; + Fill(dtlsInternal, 0x50); + method.internal = &methodInternal; + methodInternal.version = 0x0303; + Connection connection; + connection.ssl.internal = nullptr; + Connection expected = connection; + Require(Control(connection, 17, 227) == 0); + Require(Control(connection, 17, -1) == 0); + Require(Control(connection, 17, INT64_MIN) == 0); + Require(SSL_ctrl_nid_postfix(nullptr, 17, 227, nullptr) == 0); + Require(SSL_ctrl_nid_postfix(nullptr, 17, -1, nullptr) == 0); + RequireThrows([] { SSL_ctrl_nid_postfix(nullptr, 17, 228, nullptr); }); + RequireThrows([&] { Control(connection, 17, 228); }); + connection.ssl.method = &method; + expected.ssl.method = &method; + Require(Control(connection, 17, 228) == 0); + Require(Control(connection, 17, 1500) == 0); + Require(Same(connection.ssl, expected.ssl)); + methodInternal.version = 0xFEFF; + RequireThrows([&] { Control(connection, 17, 1500); }); + connection.ssl.d1 = &dtls; + RequireThrows([&] { Control(connection, 17, 1500); }); + dtls.internal = &dtlsInternal; + TestDtls1Internal dtlsExpected = dtlsInternal; + Require(Control(connection, 17, 227) == 0); + Require(Same(dtlsInternal, dtlsExpected)); + Require(Control(connection, 17, 228) == 228); + dtlsExpected.mtu = 228; + Require(Same(dtlsInternal, dtlsExpected)); + Require(Control(connection, 17, INT64_C(0x100000500)) == INT64_C(0x100000500)); + dtlsExpected.mtu = 0x500; + Require(Same(dtlsInternal, dtlsExpected)); + method.internal = nullptr; + RequireThrows([&] { Control(connection, 17, 1500); }); +} + +static void TestControlMaxSendFragment() { + Connection connection; + connection.ssl.internal = nullptr; + connection.ssl.maxSendFragment = 16384; + Connection expected = connection; + for (std::int64_t argument : {INT64_C(511), INT64_C(16385), INT64_C(0), INT64_C(-1), INT64_C(0x100000200), INT64_C(1) << 60}) { + Require(Control(connection, 52, argument) == 0); + Require(Same(connection.ssl, expected.ssl)); + } + Require(SSL_ctrl_nid_postfix(nullptr, 52, 511, nullptr) == 0); + Require(SSL_ctrl_nid_postfix(nullptr, 52, 16385, nullptr) == 0); + RequireThrows([] { SSL_ctrl_nid_postfix(nullptr, 52, 512, nullptr); }); + Require(Control(connection, 52, 512) == 1); + expected.ssl.maxSendFragment = 512; + Require(Same(connection.ssl, expected.ssl)); + Require(Control(connection, 52, 16384) == 1); + expected.ssl.maxSendFragment = 16384; + Require(Same(connection.ssl, expected.ssl)); + Require(Control(connection, 52, 4096) == 1); + expected.ssl.maxSendFragment = 4096; + Require(Same(connection.ssl, expected.ssl)); +} + +static void TestControlRenegotiationInfoSupport() { + TestSsl3Internal stateInternal {}; + TestSsl3 state {}; + Connection connection; + connection.ssl.internal = nullptr; + Connection expected = connection; + RequireThrows([] { SSL_ctrl_nid_postfix(nullptr, 76, 0, nullptr); }); + Require(Control(connection, 76, 1) == 0); + connection.ssl.s3 = &state; + RequireThrows([&] { Control(connection, 76, 0); }); + state.internal = &stateInternal; + Fill(stateInternal, 0x60); + stateInternal.sendConnectionBinding = 1; + Require(Control(connection, 76, 0) == 1); + stateInternal.sendConnectionBinding = -1; + Require(Control(connection, 76, 0) == -1); + stateInternal.sendConnectionBinding = 0; + Require(Control(connection, 76, 0) == 0); + connection.ssl.s3 = nullptr; + Require(Same(connection.ssl, expected.ssl)); +} + +static void TestControlNullConnection() { + for (int command : {16, 32, 33, 50, 51, 77, 78}) RequireThrows([&] { SSL_ctrl_nid_postfix(nullptr, command, 1, nullptr); }); +} + +int main() { + TestSetVerify(); + TestGetAlpnSelected(); + TestSetConnectState(); + TestControlReadAhead(); + TestControlMessageCallbackArgument(); + TestControlOptionsAndMode(); + TestControlMaxCertList(); + TestControlMtu(); + TestControlMaxSendFragment(); + TestControlRenegotiationInfoSupport(); + TestControlNullConnection(); + return 0; +} diff --git a/core/libs/tests/GuestLibreSslUiString.cpp b/core/libs/tests/GuestLibreSslUiString.cpp new file mode 100644 index 0000000000..bc7512950a --- /dev/null +++ b/core/libs/tests/GuestLibreSslUiString.cpp @@ -0,0 +1,91 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +struct TestUi { + const void* meth; + void* strings; + void* userData; + void* exData; + int flags; +}; + +struct TestUiString { + int type; + const char* outString; + int inputFlags; + char* resultBuf; + unsigned char data[24]; + int flags; +}; + +static_assert(sizeof(TestUi) == 40 && offsetof(TestUi, userData) == 16); +static_assert(sizeof(TestUiString) == 64 && offsetof(TestUiString, inputFlags) == 16); + +static const LibreSslUi* Ui(const TestUi* ui) { + return reinterpret_cast(ui); +} + +static const LibreSslUiString* UiString(const TestUiString* string) { + return reinterpret_cast(string); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL UI string check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestUserData() { + int data = 0; + int strings = 0; + TestUi ui {}; + RequireThrows([] { UI_get0_user_data_nid_postfix(nullptr); }); + Require(UI_get0_user_data_nid_postfix(Ui(&ui)) == nullptr); + ui.strings = &strings; + ui.userData = &data; + ui.flags = 0x0101; + Require(UI_get0_user_data_nid_postfix(Ui(&ui)) == &data); +} + +static void TestStringType() { + TestUiString string {}; + Require(UI_get_string_type_nid_postfix(nullptr) == 0); + Require(UI_get_string_type_nid_postfix(UiString(&string)) == 0); + string.inputFlags = 1; + string.flags = 1; + for (int type = 1; type <= 5; ++type) { + string.type = type; + Require(UI_get_string_type_nid_postfix(UiString(&string)) == type); + } +} + +static void TestInputFlags() { + TestUiString string {}; + Require(UI_get_input_flags_nid_postfix(nullptr) == 0); + Require(UI_get_input_flags_nid_postfix(UiString(&string)) == 0); + string.type = 2; + string.flags = 1; + string.inputFlags = 0x01; + Require(UI_get_input_flags_nid_postfix(UiString(&string)) == 0x01); + string.inputFlags = 0x8001; + Require(UI_get_input_flags_nid_postfix(UiString(&string)) == 0x8001); + string.inputFlags = -1; + Require(UI_get_input_flags_nid_postfix(UiString(&string)) == -1); +} + +int main() { + TestUserData(); + TestStringType(); + TestInputFlags(); + return 0; +} diff --git a/core/libs/tests/GuestLibreSslX509Name.cpp b/core/libs/tests/GuestLibreSslX509Name.cpp new file mode 100644 index 0000000000..cbb9627c50 --- /dev/null +++ b/core/libs/tests/GuestLibreSslX509Name.cpp @@ -0,0 +1,265 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +struct TestStack { + int num; + char** data; + int sorted; + int numAlloc; + void* comp; +}; + +struct TestNameEntry { + void* object; + void* value; + int set; + int size; +}; + +struct TestName { + TestStack* entries; + int modified; + void* bytes; + unsigned char* canonEnc; + int canonEnclen; +}; + +struct TestObject { + const char* shortName; + const char* longName; + int nid; + int length; + const unsigned char* data; + int flags; +}; + +static_assert(sizeof(TestStack) == 32 && offsetof(TestStack, data) == 8); +static_assert(sizeof(TestNameEntry) == 24 && offsetof(TestNameEntry, value) == 8); +static_assert(sizeof(TestName) == 40); + +static_assert(sizeof(TestObject) == 40 && offsetof(TestObject, length) == 20 && offsetof(TestObject, data) == 24); + +static const LibreSslX509Name* Name(const TestName* name) { + return reinterpret_cast(name); +} + +static const LibreSslX509NameEntry* Entry(const TestNameEntry* entry) { + return reinterpret_cast(entry); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL X509_NAME check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestGetEntry() { + TestNameEntry first {}; + TestNameEntry second {}; + char* slots[] {reinterpret_cast(&first), reinterpret_cast(&second), nullptr}; + TestStack entries {2, slots, 0, 3, nullptr}; + TestName name {}; + Require(X509_NAME_get_entry_nid_postfix(nullptr, 0) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 0) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), -1) == nullptr); + name.entries = &entries; + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 0) == Entry(&first)); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 1) == Entry(&second)); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 2) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), -1) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), INT_MIN) == nullptr); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), INT_MAX) == nullptr); + entries.num = 0; + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 0) == nullptr); + entries.num = 3; + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 2) == nullptr); + entries.data = nullptr; + RequireThrows([&] { X509_NAME_get_entry_nid_postfix(Name(&name), 0); }); + Require(X509_NAME_get_entry_nid_postfix(Name(&name), 3) == nullptr); +} + +static void TestEntryGetData() { + int value = 0; + TestNameEntry entry {}; + Require(X509_NAME_ENTRY_get_data_nid_postfix(nullptr) == nullptr); + Require(X509_NAME_ENTRY_get_data_nid_postfix(Entry(&entry)) == nullptr); + entry.value = &value; + Require(X509_NAME_ENTRY_get_data_nid_postfix(Entry(&entry)) == &value); + entry.object = &entry; + entry.set = 7; + Require(X509_NAME_ENTRY_get_data_nid_postfix(Entry(&entry)) == &value); +} + +static const unsigned char CountryOid[] {0x55, 0x04, 0x06}; +static const unsigned char OrganizationOid[] {0x55, 0x04, 0x0a}; +static const unsigned char CommonNameOid[] {0x55, 0x04, 0x03}; +static const unsigned char UnitOid[] {0x55, 0x04, 0x0b}; +static const unsigned char EmailOid[] {0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01}; +static const unsigned char PrivateOid[] {0x2a, 0x03, 0x04}; +static const unsigned char LocalityOid[] {0x55, 0x04, 0x07}; +static const unsigned char UnstructuredNameOid[] {0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x02}; + +struct DecodedName { + TestObject objects[8]; + Asn1String values[8]; + LibreSslX509NameEntry entries[8]; + char* slots[8]; + LibreSslStack stack; + LibreSslX509Name name; + + DecodedName() { + const struct { const unsigned char* oid; int oidLength; const char* text; int textLength; } fields[] { + {CountryOid, 3, "US", 2}, + {OrganizationOid, 3, "Example", 7}, + {CommonNameOid, 3, "first", 5}, + {UnitOid, 3, "Unit", 4}, + {CommonNameOid, 3, "second", 6}, + {EmailOid, 9, "a@b.c", 5}, + {PrivateOid, 3, "x", 1}, + {LocalityOid, 3, "a\0b", 3}, + }; + for (int index = 0; index < 8; ++index) { + objects[index] = {nullptr, nullptr, 0, fields[index].oidLength, fields[index].oid, 0}; + values[index] = {fields[index].textLength, 12, reinterpret_cast(const_cast(fields[index].text)), 0}; + entries[index] = {&objects[index], &values[index], index, 0}; + slots[index] = reinterpret_cast(&entries[index]); + } + stack = {8, slots, 0, 8, nullptr}; + name = {&stack, 0, nullptr, nullptr, 0}; + } + + DecodedName(const DecodedName&) = delete; +}; + +static void TestIndexByNid() { + DecodedName decoded; + const auto* name = &decoded.name; + const int commonName[][2] {{-1, 2}, {-5, 2}, {INT_MIN, 2}, {0, 2}, {2, 4}, {4, -1}, {7, -1}, {8, -1}, {100, -1}}; + for (const auto& [lastpos, index] : commonName) Require(X509_NAME_get_index_by_NID_nid_postfix(name, 13, lastpos) == index); + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 14, -1) == 0); + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 14, 0) == -1); + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 48, -1) == 5); + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 48, 4) == 5); + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 48, 7) == -1); + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 15, -1) == 7); + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 15, 7) == -1); + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 16, -1) == -1); + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 0, -1) == -1); + Require(X509_NAME_get_index_by_NID_nid_postfix(nullptr, 13, -1) == -1); + decoded.objects[6].nid = 13; + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 13, 4) == -1); + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 458, -1) == -1); + decoded.objects[4] = {nullptr, nullptr, 0, 9, UnstructuredNameOid, 0}; + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 48, -1) == 5); + decoded.objects[6] = {nullptr, nullptr, 0, 0, nullptr, 0}; + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 0, -1) == 6); + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 0, 6) == -1); + LibreSslStack empty {0, nullptr, 0, 0, nullptr}; + LibreSslX509Name emptyName {&empty, 0, nullptr, nullptr, 0}; + LibreSslX509Name noEntries {}; + Require(X509_NAME_get_index_by_NID_nid_postfix(&emptyName, 13, -1) == -1); + Require(X509_NAME_get_index_by_NID_nid_postfix(&noEntries, 13, -1) == -1); + RequireThrows([&] { X509_NAME_get_index_by_NID_nid_postfix(name, 13, INT_MAX); }); + decoded.objects[2].data = nullptr; + RequireThrows([&] { X509_NAME_get_index_by_NID_nid_postfix(name, 13, -1); }); + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 14, -1) == 0); + decoded.entries[1].object = nullptr; + RequireThrows([&] { X509_NAME_get_index_by_NID_nid_postfix(name, 13, -1); }); + decoded.slots[1] = nullptr; + RequireThrows([&] { X509_NAME_get_index_by_NID_nid_postfix(name, 13, -1); }); + Require(X509_NAME_get_index_by_NID_nid_postfix(name, 14, -1) == 0); +} + +static bool BufferHolds(const char* buffer, const char* prefix, std::size_t length) { + if (std::memcmp(buffer, prefix, length) != 0) return false; + for (std::size_t index = length; index < 64; ++index) { + if (buffer[index] != 0x7f) return false; + } + return true; +} + +static void TestTextByNid() { + DecodedName decoded; + const auto* name = &decoded.name; + char buffer[64]; + const struct { int len; int result; const char* prefix; std::size_t length; } cases[] { + {64, 5, "first", 6}, {6, 5, "first", 6}, {5, 4, "firs", 5}, {3, 2, "fi", 3}, {2, 1, "f", 2}, {1, 0, "", 1}, + {0, -1, "", 0}, {-5, -6, "", 0}, + }; + for (const auto& item : cases) { + std::memset(buffer, 0x7f, sizeof(buffer)); + Require(X509_NAME_get_text_by_NID_nid_postfix(name, 13, buffer, item.len) == item.result); + Require(BufferHolds(buffer, item.prefix, item.length)); + } + std::memset(buffer, 0x7f, sizeof(buffer)); + Require(X509_NAME_get_text_by_NID_nid_postfix(name, 13, nullptr, 64) == 5); + Require(X509_NAME_get_text_by_NID_nid_postfix(name, 13, nullptr, 0) == 5); + Require(X509_NAME_get_text_by_NID_nid_postfix(name, 16, buffer, 64) == -1); + Require(X509_NAME_get_text_by_NID_nid_postfix(nullptr, 13, buffer, 64) == -1); + Require(X509_NAME_get_text_by_NID_nid_postfix(name, 16, buffer, INT_MIN) == -1); + Require(BufferHolds(buffer, "", 0)); + Require(X509_NAME_get_text_by_NID_nid_postfix(name, 15, buffer, 64) == 3); + Require(BufferHolds(buffer, "a\0b", 4)); + Require(X509_NAME_get_text_by_NID_nid_postfix(name, 48, buffer, 64) == 5); + Require(BufferHolds(buffer, "a@b.c", 6)); + RequireThrows([&] { X509_NAME_get_text_by_NID_nid_postfix(name, 13, buffer, INT_MIN); }); + decoded.values[2].data = nullptr; + RequireThrows([&] { X509_NAME_get_text_by_NID_nid_postfix(name, 13, buffer, 64); }); + std::memset(buffer, 0x7f, sizeof(buffer)); + Require(X509_NAME_get_text_by_NID_nid_postfix(name, 13, buffer, 1) == 0); + Require(BufferHolds(buffer, "", 1)); + decoded.entries[2].value = nullptr; + RequireThrows([&] { X509_NAME_get_text_by_NID_nid_postfix(name, 13, buffer, 64); }); +} + +static void TestDynamicNid() { + OBJ_cleanup_nid_postfix(); + ERR_clear_error_nid_postfix(); + const int nid = OBJ_create_nid_postfix("1.2.3.4", "testName", "Test Name"); + Require(nid >= 956); + DecodedName decoded; + bool indexAccepted = false; + try { + const int index = X509_NAME_get_index_by_NID_nid_postfix(&decoded.name, nid, -1); + indexAccepted = index == 6; + if (!indexAccepted) std::fprintf(stderr, "dynamic X509_NAME index: expected 6 or not implemented, got %d\n", index); + } catch (const std::runtime_error& error) { + Require(std::strstr(error.what(), "X509_NAME_get_index_by_NID") && std::strstr(error.what(), "not implemented")); + indexAccepted = true; + } + bool textAccepted = false; + char buffer[2] {}; + try { + const int length = X509_NAME_get_text_by_NID_nid_postfix(&decoded.name, nid, buffer, sizeof(buffer)); + textAccepted = length == 1 && std::strcmp(buffer, "x") == 0; + if (!textAccepted) std::fprintf(stderr, "dynamic X509_NAME text: expected x with length 1 or not implemented, got length %d\n", length); + } catch (const std::runtime_error& error) { + Require(std::strstr(error.what(), "X509_NAME_get_text_by_NID") && std::strstr(error.what(), "not implemented")); + textAccepted = true; + } + OBJ_cleanup_nid_postfix(); + ERR_clear_error_nid_postfix(); + Require(indexAccepted); + Require(textAccepted); +} + +int main() { + TestGetEntry(); + TestEntryGetData(); + TestIndexByNid(); + TestTextByNid(); + TestDynamicNid(); + return 0; +} diff --git a/core/libs/tests/GuestLocale.cpp b/core/libs/tests/GuestLocale.cpp index 78280f321e..a14f94e057 100644 --- a/core/libs/tests/GuestLocale.cpp +++ b/core/libs/tests/GuestLocale.cpp @@ -33,6 +33,8 @@ extern std::uintptr_t _ZTVSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIc extern std::uintptr_t _ZTVSt5ctypeIcE_nid_postfix[14]; extern std::uintptr_t _ZTVSt5ctypeIwE_nid_postfix[18]; extern std::uintptr_t _ZTVSt7codecvtIcc9_MbstatetE_nid_postfix[13]; +extern std::uintptr_t _ZTVSt8numpunctIcE_nid_postfix[11]; +extern std::uintptr_t _ZTVSt8messagesIcE_nid_postfix[9]; } @@ -103,6 +105,24 @@ static void CheckCollateVtableObject() { Require(vtable->hash(&facet, "abc", "abc" + 3) == -1792535898324117685LL); } +static void CheckNumpunctVtableObject() { + for (std::uintptr_t& slot : _ZTVSt8numpunctIcE_nid_postfix) { + Require(slot == 0); + slot = 0x1111; + Require(slot == 0x1111); + slot = 0; + } +} + +static void CheckMessagesVtableObject() { + for (std::uintptr_t& slot : _ZTVSt8messagesIcE_nid_postfix) { + Require(slot == 0); + slot = 0x1111; + Require(slot == 0x1111); + slot = 0; + } +} + static void CheckCodecvtVtableObject() { Require(sizeof(_ZTVSt7codecvtIcc9_MbstatetE_nid_postfix) == kCodecvtCharVtableSlots * sizeof(std::uintptr_t)); for (std::uintptr_t& slot : _ZTVSt7codecvtIcc9_MbstatetE_nid_postfix) { @@ -553,6 +573,16 @@ static void CheckCtypeVtableObject() { } } +static void CheckTimePutVtableObject() { + Require(sizeof(_ZTVSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix) == kTimePutCharVtableSlots * sizeof(std::uintptr_t)); + for (std::uintptr_t& slot : _ZTVSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix) { + Require(slot == 0); + slot = 0x1111; + Require(slot == 0x1111); + slot = 0; + } +} + static void CheckNumPutVtableObject() { Require(sizeof(_ZTVSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix) == kNumPutCharVtableSlots * sizeof(std::uintptr_t)); for (std::uintptr_t& slot : _ZTVSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix) { @@ -590,6 +620,16 @@ static void CheckTimeGetWideVtableObject() { } } +static void CheckMoneyGetVtableObject() { + Require(sizeof(_ZTVSt9money_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix) == kMoneyGetCharVtableSlots * sizeof(std::uintptr_t)); + for (std::uintptr_t& slot : _ZTVSt9money_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix) { + Require(slot == 0); + slot = 0x1111; + Require(slot == 0x1111); + slot = 0; + } +} + int main() { std::array heapApi{}; heapApi[0] = reinterpret_cast(&TestHeapAllocate); @@ -621,6 +661,10 @@ int main() { CheckCtypeVtableObject(); CheckWideCtypeVtableObject(); CheckCodecvtVtableObject(); + CheckTimePutVtableObject(); + CheckMoneyGetVtableObject(); + CheckNumpunctVtableObject(); + CheckMessagesVtableObject(); _Locksyslock_nid_postfix(); _Locksyslock_nid_postfix(); _Unlocksyslock_nid_postfix(); diff --git a/core/libs/tests/GuestMainStackMove.cpp b/core/libs/tests/GuestMainStackMove.cpp index 1030bb792a..cd775b4d1d 100644 --- a/core/libs/tests/GuestMainStackMove.cpp +++ b/core/libs/tests/GuestMainStackMove.cpp @@ -1,5 +1,6 @@ #include "prx/libc/include/GuestArena.hpp" #include +#include #include #include #include @@ -20,6 +21,7 @@ constexpr std::size_t NeighbourBytes = std::size_t {1} << 20; std::uintptr_t originalStack = 0; std::string originalArgument; +std::string originalPath; bool originalInArena = false; mach_vm_address_t neighbour = 0; @@ -67,17 +69,30 @@ bool InArena(std::uintptr_t address) { void CheckMainThread(void*) { int local = 0; Require(pthread_main_np() != 0, "the check runs on the main thread"); + char path[4096]; + std::uint32_t pathBytes = sizeof(path); + Require(_NSGetExecutablePath(path, &pathBytes) == 0, "the executable path is readable after the move"); + Require(originalPath == path, "the executable path survived the move"); const auto address = reinterpret_cast(&local); if (originalInArena) { Require(!InArena(address), "the main thread stack is outside the guest arena"); Require(address != originalStack, "the main thread stack moved"); - const auto old = reinterpret_cast(originalStack & ~(MappingAlignment - 1)); + const auto kept = originalStack & ~(MappingAlignment - 1); + const auto released = reinterpret_cast(kept - MappingBytes); try { - GuestArena::GuestArenaMarkUsed_nid_postfix(old, MappingBytes); + GuestArena::GuestArenaMarkUsed_nid_postfix(released, MappingBytes); } catch (const std::runtime_error&) { Require(false, "a fixed guest mapping fits where the main thread stack was"); } - GuestArena::GuestArenaRelease_nid_postfix(old, MappingBytes); + GuestArena::GuestArenaRelease_nid_postfix(released, MappingBytes); + bool refused = false; + try { + GuestArena::GuestArenaMarkUsed_nid_postfix(reinterpret_cast(kept), MappingBytes); + } catch (const std::runtime_error&) { + refused = true; + } + if (!refused) GuestArena::GuestArenaRelease_nid_postfix(reinterpret_cast(kept), MappingBytes); + Require(refused, "the process start block stays mapped where dyld points"); } if (neighbour != 0) { mach_vm_address_t begin = 0, end = 0; @@ -100,6 +115,9 @@ int main(int, char** argv) { originalStack = reinterpret_cast(&local); originalInArena = InArena(originalStack); originalArgument = argv[0]; + char path[4096]; + std::uint32_t pathBytes = sizeof(path); + if (_NSGetExecutablePath(path, &pathBytes) == 0) originalPath = path; if (originalInArena) neighbour = MapStackNeighbour(originalStack); Aps5StartGuest_nid_no_patch(Guest, nullptr); } diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index c153826d34..3936b662ef 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -796,14 +796,14 @@ static void CheckFmodfErrno() { const RestoreFloatingPointState saved; const struct { std::uint32_t dividend; std::uint32_t divisor; std::uint32_t expected; bool invalid; bool nan; } cases[] = { {0x3f800000u, 0x00000000u, 0u, true, true}, {0x3f800000u, 0x80000000u, 0u, true, true}, - {0x7f800000u, 0x40000000u, 0u, true, true}, {0x7fc00000u, 0x40000000u, 0u, false, true}, + {0x7f800000u, 0x40000000u, 0u, true, true}, {0x7fc00000u, 0x40000000u, 0u, true, true}, {0x3f800000u, 0x7f800000u, 0x3f800000u, false, false}, {0xbf800000u, 0x7f800000u, 0xbf800000u, false, false}, {0x41280000u, 0x40000000u, 0x3f000000u, false, false}, {0xc1280000u, 0x40000000u, 0xbf000000u, false, false}, {0x41280000u, 0xc0000000u, 0x3f000000u, false, false}, {0xc1280000u, 0xc0000000u, 0xbf000000u, false, false}, {0x00000000u, 0x40000000u, 0x00000000u, false, false}, {0x80000000u, 0x40000000u, 0x80000000u, false, false}, {0x00000000u, 0x80000000u, 0u, true, true}, - {0x3f800000u, 0x7fc00001u, 0u, false, true}, - {0x00000000u, 0x7fc00001u, 0u, false, true}, + {0x3f800000u, 0x7fc00001u, 0u, true, true}, + {0x00000000u, 0x7fc00001u, 0u, true, true}, {0x7f800001u, 0x40000000u, 0u, true, true}, {0x3f800000u, 0x7f800001u, 0u, true, true}, {0x00000003u, 0x00000002u, 0x00000001u, false, false}, @@ -817,7 +817,7 @@ static void CheckFmodfErrno() { const std::uint32_t bits = std::bit_cast(result); const int invalid = std::fetestexcept(FE_INVALID) != 0; const bool nan = std::isnan(result); - if (nan != test.nan || (!nan && bits != test.expected) || invalid != test.invalid || *__error_nid_postfix() != 73) { + if (nan != test.nan || (!nan && bits != test.expected) || invalid != test.invalid || *__error_nid_postfix() != (test.nan ? EDOM : 73)) { std::fprintf(stderr, "Guest fmodf failed for %08x and %08x: got %08x, invalid %d, errno %d\n", test.dividend, test.divisor, bits, invalid, *__error_nid_postfix()); std::abort(); @@ -827,7 +827,7 @@ static void CheckFmodfErrno() { std::feraiseexcept(FE_DIVBYZERO); *__error_nid_postfix() = 73; (void)fmodf_nid_postfix(1.0f, 0.0f); - if (*__error_nid_postfix() != 73 || std::fetestexcept(FE_DIVBYZERO) == 0) { + if (*__error_nid_postfix() != EDOM || std::fetestexcept(FE_DIVBYZERO) == 0) { std::fprintf(stderr, "Guest fmodf dropped the floating-point environment\n"); std::abort(); } @@ -908,7 +908,7 @@ static void CheckNearbyintl() { static void CheckFmodErrno() { errno = 0; (void)fmod_nid_postfix(1.0, 0.0); - Require(errno == 0); + Require(errno == EDOM); Require(fmod_nid_postfix(1e300, 3.0) == std::fmod(1e300, 3.0)); } @@ -921,6 +921,41 @@ static long double MakeExtended(std::uint64_t mantissa, std::uint16_t signExpone return value; } +static void CheckLogEdgeFlags() { + for (const double input : {-1.0, -std::numeric_limits::infinity(), -std::numeric_limits::quiet_NaN()}) { + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + Require(std::isnan(log_nid_postfix(input)) && errno == EDOM && std::fetestexcept(FE_INVALID) != 0); + } +} + +static void CheckLogConsoleBits() { + constexpr struct { + std::uint64_t input; + std::uint64_t expected; + int error; + } cases[] = { + {0x0000000000000000ull, 0xfff0000000000000ull, ERANGE}, {0x8000000000000000ull, 0xfff0000000000000ull, ERANGE}, + {0x0000000000000001ull, 0xc0874385446d71c3ull, 0}, {0x000fffffffffffffull, 0xc086232bdd7abcd2ull, 0}, + {0x0010000000000000ull, 0xc086232bdd7abcd2ull, 0}, {0x3ff0000000000000ull, 0x0000000000000000ull, 0}, + {0x3fefffffffffffffull, 0xbca0000000000000ull, 0}, {0x3ff0000000000001ull, 0x3cafffffffffffffull, 0}, + {0x3ff00000fffffffeull, 0x3eaffffeffc00aafull, 0}, {0x3ff6a09e00000000ull, 0x3fd62e41dd1274ceull, 0}, + {0x3fe6a09e00000000ull, 0xbfd62e4420e1ff10ull, 0}, {0x4000000000000000ull, 0x3fe62e42fefa39efull, 0}, + {0x4024000000000000ull, 0x40026bb1bbb55516ull, 0}, {0x7fefffffffffffffull, 0x40862e42fefa39efull, 0}, + {0x7ff0000000000000ull, 0x7ff0000000000000ull, 0}, {0xbff0000000000000ull, 0xfff8000000000000ull, EDOM}, + {0xfff0000000000000ull, 0xfff8000000000000ull, EDOM}, {0xbfe0000000000000ull, 0xfff8000000000000ull, EDOM}, + {0x7ff8000000000001ull, 0x7ff8000000000001ull, 0}, {0xfff8000000000002ull, 0xfff8000000000002ull, EDOM}, + {0x3fcbf0cfff3ac077ull, 0xbff859e4e1f045dcull, 0}, {0x3f08b0c25bca18b0ull, 0xc023ed400c16d9eeull, 0}, + {0x402d5e682f6c8da9ull, 0x40057e89190b1e8eull, 0}, {0x40881f30b22704c7ull, 0x401a986d082edef0ull, 0}, + {0x4025398ccfd724ccull, 0x4002e56c809c7c6aull, 0}, {0x401231605052b9ceull, 0x3ff83c5b860d2a7eull, 0}, + }; + for (const auto& item : cases) { + errno = 1234; + Require(std::bit_cast(log_nid_postfix(std::bit_cast(item.input))) == item.expected); + Require(errno == (item.error != 0 ? item.error : 1234)); + } +} + static void CheckPowRangeFlags() { constexpr int over = FE_OVERFLOW | FE_INEXACT, under = FE_UNDERFLOW | FE_INEXACT; constexpr struct { @@ -1471,6 +1506,89 @@ static void CheckExpm1ScalingRounding() { } _mm_setcsr(savedCsr); } +static void CheckAtanhDomainEnvironment() { + const RestoreFloatingPointState saved; + const unsigned savedCsr = _mm_getcsr(); + const struct { + std::uint64_t input; + std::uint64_t result; + unsigned raised; + int error; + } cases[] = { + {0x3ff0000000000000ull, 0x7ff0000000000000ull, 4u, ERANGE}, + {0xbff0000000000000ull, 0xfff0000000000000ull, 4u, ERANGE}, + {0x3ff0000000000001ull, 0xfff8000000000000ull, 1u, EDOM}, + {0xbff0000000000001ull, 0xfff8000000000000ull, 1u, EDOM}, + {0x4000000000000000ull, 0xfff8000000000000ull, 1u, EDOM}, + {0xc000000000000000ull, 0xfff8000000000000ull, 1u, EDOM}, + {0x7ff0000000000000ull, 0xfff8000000000000ull, 1u, EDOM}, + {0xfff0000000000000ull, 0xfff8000000000000ull, 1u, EDOM}, + {0x7ff8000000001234ull, 0x7ff8000000001234ull, 1u, EDOM}, + {0xfff8000000001234ull, 0xfff8000000001234ull, 1u, EDOM}, + {0x7ff0000000000001ull, 0x7ff8000000000001ull, 1u, EDOM}, + {0xfff0000000000001ull, 0xfff8000000000001ull, 1u, EDOM}, + }; + const int rounding[] = {FE_TONEAREST, FE_DOWNWARD, FE_UPWARD, FE_TOWARDZERO}; + double (APS5_VABI * volatile call)(double) = atanh_nid_postfix; + for (unsigned mode = 0; mode != 4; ++mode) { + for (unsigned denorm = 0; denorm != 4; ++denorm) { + const unsigned control = 0x1f80u | (mode << 13u) | ((denorm & 1u) ? 0x40u : 0u) | ((denorm & 2u) ? 0x8000u : 0u); + for (std::uint16_t sticky : {0u, 5u, 32u}) { + for (const auto& item : cases) { + Require(std::fesetenv(FE_DFL_ENV) == 0 && std::fesetround(rounding[mode]) == 0); + Require(fesetexceptflag_nid_postfix(&sticky, 0x3f) == 0); + _mm_setcsr(control | sticky); + GuestFloatingPointEnvironment before; + Require(fegetenv_nid_postfix(&before) == 0 && (before.status & 0x3fu) == sticky); + errno = 1234; + const auto result = std::bit_cast(call(std::bit_cast(item.input))); + const int error = errno; + GuestFloatingPointEnvironment after; + Require(fegetenv_nid_postfix(&after) == 0); + if (result != item.result || after.mxcsr != (control | sticky | item.raised) || (after.status & 0x3fu) != (sticky | item.raised) || after.control != before.control || error != item.error) { + std::fprintf(stderr, "atanh domain: input=%016llx mode=%u denorm=%u sticky=%u bits=%016llx csr=%x x87=%x errno=%d\n", static_cast(item.input), mode, denorm, sticky, static_cast(result), after.mxcsr, after.status, error); + Require(false); + } + } + } + } + } + _mm_setcsr(savedCsr); +} + +static void CheckAtanhConsoleBits() { + constexpr struct { + std::uint64_t input; + std::uint64_t expected; + int error; + } cases[] = { + {0x0000000000000000ull, 0x0000000000000000ull, 0}, {0x8000000000000000ull, 0x8000000000000000ull, 0}, + {0x3e2fffffffffffffull, 0x3e2fffffffffffffull, 0}, {0x3e30000000000000ull, 0x3e30000000000000ull, 0}, + {0xbe30000000000000ull, 0xbe30000000000000ull, 0}, {0x3fdfffffffffffffull, 0x3fe193ea7aad030aull, 0}, + {0x3fe0000000000000ull, 0x3fe193ea7aad030aull, 0}, {0xbfe0000000000000ull, 0xbfe193ea7aad030aull, 0}, + {0x3fefffffffffffffull, 0x4032b708872320e2ull, 0}, {0xbfefffffffffffffull, 0xc032b708872320e2ull, 0}, + {0x3ff0000000000000ull, 0x7ff0000000000000ull, ERANGE}, {0xbff0000000000000ull, 0xfff0000000000000ull, ERANGE}, + {0x3ff0000000000001ull, 0xfff8000000000000ull, EDOM}, {0xc000000000000000ull, 0xfff8000000000000ull, EDOM}, + {0x7ff0000000000000ull, 0xfff8000000000000ull, EDOM}, {0xfff0000000000000ull, 0xfff8000000000000ull, EDOM}, + {0x7ff8000000000001ull, 0x7ff8000000000001ull, EDOM}, {0xfff8000000000002ull, 0xfff8000000000002ull, EDOM}, + {0x3f03a67d5567227bull, 0x3f03a67d558ea76eull, 0}, {0x3faa28c4621dd8d7ull, 0x3faa2e9a77f5a83full, 0}, + {0x3f4742fce55398bbull, 0x3f4742fd26e249ceull, 0}, {0x3f2cf9c68a297d4aull, 0x3f2cf9c69214cc60ull, 0}, + {0x3fc796d8c6ddd135ull, 0x3fc7dca409c1ba11ull, 0}, {0x3f73b85b718bdc38ull, 0x3f73b8656ddda145ull, 0}, + }; + for (const auto& item : cases) { + errno = 1234; + Require(std::bit_cast(atanh_nid_postfix(std::bit_cast(item.input))) == item.expected); + Require(errno == (item.error != 0 ? item.error : 1234)); + } +} +static void CheckAtanhEdgeFlags() { + for (const double input : {2.0, -2.0, std::numeric_limits::infinity(), std::numeric_limits::quiet_NaN(), -std::numeric_limits::quiet_NaN()}) { + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + Require(std::isnan(atanh_nid_postfix(input)) && errno == EDOM && std::fetestexcept(FE_INVALID) != 0); + } +} + static void CheckHypotConsoleBits() { constexpr struct { std::uint64_t x; @@ -1508,6 +1626,158 @@ static void CheckHypotConsoleBits() { Require(std::bit_cast(hypot_nid_postfix(std::bit_cast(item.y), std::bit_cast(item.x))) == item.expected || std::isnan(std::bit_cast(item.expected))); } } +static void CheckFmodEdgeFlags() { + const double nan = std::numeric_limits::quiet_NaN(); + const double infinity = std::numeric_limits::infinity(); + constexpr double one = 1.0; + const std::pair cases[] = {{0.0, nan}, {one, -nan}, {nan, one}, {-nan, 0.0}, {one, 0.0}, {infinity, one}, {-infinity, -2.0}}; + for (const auto& [x, y] : cases) { + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + Require(std::isnan(fmod_nid_postfix(x, y)) && errno == EDOM && std::fetestexcept(FE_INVALID) != 0); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + Require(std::isnan(fmodf_nid_postfix(static_cast(x), static_cast(y))) && errno == EDOM && std::fetestexcept(FE_INVALID) != 0); + } +} + +static void CheckFmodConsoleBits() { + constexpr struct { + std::uint64_t x; + std::uint64_t y; + std::uint64_t expected; + int expectedErrno; + } cases[] = { + {0x3ff0000000000000ull, 0x0000000000000000ull, 0xfff8000000000000ull, EDOM}, + {0xbff0000000000000ull, 0x8000000000000000ull, 0xfff8000000000000ull, EDOM}, + {0x7ff0000000000000ull, 0x4000000000000000ull, 0xfff8000000000000ull, EDOM}, + {0xfff0000000000000ull, 0x7ff0000000000000ull, 0xfff8000000000000ull, EDOM}, + {0x7ff8000000000123ull, 0x4000000000000000ull, 0x7ff8000000000123ull, EDOM}, + {0xfff8000000000123ull, 0x0000000000000000ull, 0xfff8000000000123ull, EDOM}, + {0x3ff0000000000000ull, 0x7ff8000000000456ull, 0x7ff8000000000456ull, EDOM}, + {0x7ff8000000000123ull, 0xfff8000000000456ull, 0xfff8000000000456ull, EDOM}, + {0x7ff4000000000000ull, 0x4000000000000000ull, 0x7ffc000000000000ull, EDOM}, + {0x3ff0000000000000ull, 0x7ff4000000000000ull, 0x7ffc000000000000ull, EDOM}, + {0x7ff0000000000000ull, 0x7ff8000000000456ull, 0x7ff8000000000456ull, EDOM}, + {0x3ff0000000000000ull, 0x7ff0000000000000ull, 0x3ff0000000000000ull, 0}, + {0x4016000000000000ull, 0x4000000000000000ull, 0x3ff8000000000000ull, 0}, + {0x0000000000000000ull, 0x8000000000000000ull, 0xfff8000000000000ull, EDOM}, + {0x0000000000000003ull, 0x0000000000000002ull, 0x0000000000000001ull, 0}, + }; + for (const auto& item : cases) { + errno = 1234; + Require(std::bit_cast(fmod_nid_postfix(std::bit_cast(item.x), std::bit_cast(item.y))) == item.expected); + Require(errno == (item.expectedErrno != 0 ? item.expectedErrno : 1234)); + } + constexpr struct { + std::uint32_t x; + std::uint32_t y; + std::uint32_t expected; + int expectedErrno; + } floatCases[] = { + {0x3f800000u, 0x00000000u, 0xffc00000u, EDOM}, + {0xbf800000u, 0x80000000u, 0xffc00000u, EDOM}, + {0x7f800000u, 0x40000000u, 0xffc00000u, EDOM}, + {0xff800000u, 0x7f800000u, 0xffc00000u, EDOM}, + {0x7fc00123u, 0x40000000u, 0x7fc00123u, EDOM}, + {0xffc00123u, 0x00000000u, 0xffc00123u, EDOM}, + {0x3f800000u, 0x7fc00456u, 0x7fc00456u, EDOM}, + {0x7fc00123u, 0xffc00456u, 0xffc00456u, EDOM}, + {0x7fa00000u, 0x40000000u, 0x7fe00000u, EDOM}, + {0x3f800000u, 0x7fa00000u, 0x7fe00000u, EDOM}, + {0x7f800000u, 0x7fc00456u, 0x7fc00456u, EDOM}, + {0x3f800000u, 0x7f800000u, 0x3f800000u, 0}, + {0x40b00000u, 0x40000000u, 0x3fc00000u, 0}, + {0x00000000u, 0x80000000u, 0xffc00000u, EDOM}, + {0x00000003u, 0x00000002u, 0x00000001u, 0}, + }; + for (const auto& item : floatCases) { + errno = 1234; + Require(std::bit_cast(fmodf_nid_postfix(std::bit_cast(item.x), std::bit_cast(item.y))) == item.expected); + Require(errno == (item.expectedErrno != 0 ? item.expectedErrno : 1234)); + } +} +static void CheckHypotfConsoleBits() { + constexpr struct { + std::uint32_t x; + std::uint32_t y; + std::uint32_t expected; + } cases[] = { + {0x40400000u, 0xc0800000u, 0x40a00000u}, + {0x00000000u, 0x80000000u, 0x00000000u}, + {0x3f800000u, 0x00000000u, 0x3f800000u}, + {0x00000001u, 0x00000001u, 0x00000001u}, + {0x007fffffu, 0x00000003u, 0x007fffffu}, + {0x26800000u, 0x267fffffu, 0x26b504f3u}, + {0x3f800000u, 0x307fffffu, 0x3f800000u}, + {0x3f800000u, 0x30800001u, 0x3f800000u}, + {0x58800001u, 0x58800000u, 0x58b504f4u}, + {0x7f000000u, 0x7f000000u, 0x7f3504f3u}, + {0x7f3504f3u, 0x7f3504f3u, 0x7f7fffffu}, + {0x7f3504f4u, 0xff3504f4u, 0x7f800000u}, + {0x7f7fffffu, 0x73000000u, 0x7f7fffffu}, + {0x7f800000u, 0x7fc00123u, 0x7f800000u}, + {0xffc00123u, 0xff800000u, 0x7f800000u}, + {0x7fc00123u, 0x3f800000u, 0x7fc00123u}, + {0xffc00002u, 0x7fc00001u, 0x7fc00002u}, + {0x7fc00001u, 0xffc00002u, 0x7fc00001u}, + {0xbc059eb6u, 0x39413c60u, 0x3c05a772u}, + {0x3f700f06u, 0x452dc68bu, 0x452dc68bu}, + {0xb9b28c49u, 0xb9416ea5u, 0x39cb0f61u}, + {0x4056e15fu, 0x4379cdefu, 0x4379d3b5u}, + {0x3f750592u, 0x3a7fa6a3u, 0x3f75059bu}, + {0xc3a8012eu, 0x47264372u, 0x472644c5u}, + }; + for (const auto& item : cases) { + errno = 1234; + Require(std::bit_cast(hypotf_nid_postfix(std::bit_cast(item.x), std::bit_cast(item.y))) == item.expected && errno == 1234); + Require(std::bit_cast(hypotf_nid_postfix(std::bit_cast(item.y), std::bit_cast(item.x))) == item.expected || std::isnan(std::bit_cast(item.expected))); + } +} +static void CheckTrigConsoleBits() { + constexpr struct { + std::uint64_t input; + std::uint64_t sine; + std::uint64_t cosine; + std::uint64_t tangent; + int error; + } cases[] = { + {0x0000000000000000ull, 0x0000000000000000ull, 0x3ff0000000000000ull, 0x0000000000000000ull, 0}, + {0x8000000000000000ull, 0x8000000000000000ull, 0x3ff0000000000000ull, 0x8000000000000000ull, 0}, + {0x3e3fffffffffffffull, 0x3e3fffffffffffffull, 0x3ff0000000000000ull, 0x3e3fffffffffffffull, 0}, + {0x3fe921fb54442d18ull, 0x3fe6a09e667f3bccull, 0x3fe6a09e667f3bcdull, 0x3fefffffffffffffull, 0}, + {0x3ff921fb54442d18ull, 0x3ff0000000000000ull, 0x3c91a62633145c07ull, 0x434d02967c31cdb5ull, 0}, + {0x400921fb54442d18ull, 0x3ca1a62633145c07ull, 0xbff0000000000000ull, 0xbca1a62633145c07ull, 0}, + {0xc00921fb54442d18ull, 0xbca1a62633145c07ull, 0xbff0000000000000ull, 0x3ca1a62633145c07ull, 0}, + {0x401921fb54442d18ull, 0xbcb1a62633145c07ull, 0x3ff0000000000000ull, 0xbcb1a62633145c07ull, 0}, + {0x4012d97c7f3321d2ull, 0xbff0000000000000ull, 0xbcaa79394c9e8a0aull, 0x4333570efd768923ull, 0}, + {0x412e848000000000ull, 0xbfd6664b2568d867ull, 0x3fedf9df9906d32cull, 0xbfd7e9768ab734c0ull, 0}, + {0x4139000000000000ull, 0x3fd065dff07cc910ull, 0xbfeeee8b0ce93021ull, 0xbfd0f6d7d15bc5b1ull, 0}, + {0x7e37e43c8800759cull, 0xbfea2c16b010e385ull, 0xbfe2699022adc4c1ull, 0x3ff6be411f37ac77ull, 0}, + {0x7fefffffffffffffull, 0x3f7452fc98b34e97ull, 0xbfefffe62ecfab75ull, 0xbf74530cfe729484ull, 0}, + {0xffefffffffffffffull, 0xbf7452fc98b34e97ull, 0xbfefffe62ecfab75ull, 0x3f74530cfe729484ull, 0}, + {0x7ff0000000000000ull, 0xfff8000000000000ull, 0xfff8000000000000ull, 0xfff8000000000000ull, EDOM}, + {0xfff0000000000000ull, 0xfff8000000000000ull, 0xfff8000000000000ull, 0xfff8000000000000ull, EDOM}, + {0x7ff0000054442d18ull, 0x7ff8000054442d18ull, 0x7ff8000054442d18ull, 0x7ff8000054442d18ull, EDOM}, + {0x7ff8000000000001ull, 0x7ff8000000000001ull, 0x7ff8000000000001ull, 0x7ff8000000000001ull, 0}, + {0x3fdada3603b109ebull, 0x3fda124ac129e754ull, 0x3fed397712faa550ull, 0x3fdc8c2adf939d0aull, 0}, + {0xbfba382e79c9f161ull, 0xbfba2c73e99f121cull, 0x3fefd51240491c33ull, 0xbfba4fc011ff9a31ull, 0}, + {0x4079ab389c31a606ull, 0x3fe7fbf5be09b71cull, 0xbfe52f138550db6full, 0xbff21d74a8b63e76ull, 0}, + {0xbfd6066a236944ecull, 0xbfd597c69592a402ull, 0x3fee1faa6b709b8bull, 0xbfd6f0152b51d55cull, 0}, + {0xbfcdf9356ad39d8full, 0xbfcdb34648619610ull, 0x3fef206c15f26ee6ull, 0xbfce889b662f705full, 0}, + {0x40557a01c7c97d05ull, 0xbfec45eb63779e2eull, 0xbfddf9b1098520ebull, 0x3ffe2ecb59bfb87eull, 0}, + }; + for (const auto& item : cases) { + const double x = std::bit_cast(item.input); + const int expected = item.error != 0 ? item.error : 1234; + errno = 1234; + Require(std::bit_cast(sin_nid_postfix(x)) == item.sine && errno == expected); + errno = 1234; + Require(std::bit_cast(cos_nid_postfix(x)) == item.cosine && errno == expected); + errno = 1234; + Require(std::bit_cast(tan_nid_postfix(x)) == item.tangent && errno == expected); + } +} @@ -3227,6 +3497,8 @@ int main() { CheckCbrtConsoleBits(); CheckPowConsoleBits(); CheckPowRangeFlags(); + CheckLogConsoleBits(); + CheckLogEdgeFlags(); CheckLog10ConsoleBits(); CheckLog10EdgeFlags(); CheckLogbConsoleBits(); @@ -3244,6 +3516,13 @@ int main() { CheckNextafterEnvironment(nextafterf_nid_postfix, 0x80000000u, 0x3f800000u, 0x00800000u, 0x7f7fffffu, 0x7f800000u); CheckNextafterEnvironment(nextafter_nid_postfix, std::uint64_t{0x8000000000000000ull}, std::uint64_t{0x3ff0000000000000ull}, std::uint64_t{0x0010000000000000ull}, std::uint64_t{0x7fefffffffffffffull}, std::uint64_t{0x7ff0000000000000ull}); CheckExpm1ScalingRounding(); + CheckFmodConsoleBits(); + CheckFmodEdgeFlags(); + CheckHypotfConsoleBits(); + CheckTrigConsoleBits(); + CheckAtanhConsoleBits(); + CheckAtanhDomainEnvironment(); + CheckAtanhEdgeFlags(); CheckHypotConsoleBits(); CheckPowConsoleBits(); CheckExpConsoleBits(); diff --git a/core/libs/tests/GuestMove.cpp b/core/libs/tests/GuestMove.cpp index a8a16a5ba2..e912f04f60 100644 --- a/core/libs/tests/GuestMove.cpp +++ b/core/libs/tests/GuestMove.cpp @@ -78,6 +78,16 @@ void RequireNotInit(std::int32_t handle) { Require(sceMoveSetVibration(handle, 0xFF) == kNotInit); Require(sceMoveSetLightSphere(handle, 0xFF, 0, 0) == kNotInit); Require(sceMoveResetLightSphere(handle) == kNotInit); + Require(sceMoveGetBluetoothAddress() == kNotInit); + Require(sceMoveGetCalibrationInfo() == kNotInit); + Require(sceMoveGetDeviceId() == kNotInit); + Require(sceMoveIsReproductionModel() == kNotInit); + Require(sceMoveIsValidHandle() == 0); + Require(sceMoveReadStateRecentForTracker() == kNotInit); + Require(sceMoveSetCalibrationMode() == kNotInit); + Require(sceMoveSetExtensionPortOutput() == kNotInit); + Require(sceMoveSetLightSphereForTracker() == kNotInit); + Require(sceMoveWriteCalibrationInfo() == kNotInit); Require(sceMoveTerm() == kNotInit); Require(buffer.Untouched()); Require(count == -7); diff --git a/core/libs/tests/GuestMoveTracker.cpp b/core/libs/tests/GuestMoveTracker.cpp index 0d141bf511..4fbe7c3434 100644 --- a/core/libs/tests/GuestMoveTracker.cpp +++ b/core/libs/tests/GuestMoveTracker.cpp @@ -17,6 +17,8 @@ int main() { std::array processing{kFill, kFill}; Require(sceMoveTrackerGetWorkingMemorySize(nullptr, nullptr) == kErrInvalidArg); + Require(sceMoveTrackerGetWorkingMemorySize(reinterpret_cast(0x10), processing.data()) == kErrInvalidArg); + Require(sceMoveTrackerGetWorkingMemorySize(total.data(), reinterpret_cast(0x10)) == kErrInvalidArg); Require(sceMoveTrackerGetWorkingMemorySize(total.data(), nullptr) == kErrInvalidArg); Require(sceMoveTrackerGetWorkingMemorySize(nullptr, processing.data()) == kErrInvalidArg); Require(total[0] == kFill && total[1] == kFill && processing[0] == kFill && processing[1] == kFill); diff --git a/core/libs/tests/GuestNKWebKitSqlite.cpp b/core/libs/tests/GuestNKWebKitSqlite.cpp new file mode 100644 index 0000000000..bfe89e0b6a --- /dev/null +++ b/core/libs/tests/GuestNKWebKitSqlite.cpp @@ -0,0 +1,426 @@ +#include "prx/libSceNKWebKitRequirements/include/Sqlite.hpp" +#include "prx/libc/include/General.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int SQLITE_OK = 0; +constexpr int SQLITE_ERROR = 1; +constexpr int SQLITE_BUSY = 5; +constexpr int SQLITE_INTERRUPT = 9; +constexpr int SQLITE_MISUSE = 21; +constexpr int SQLITE_AUTH = 23; +constexpr int SQLITE_ROW = 100; +constexpr int SQLITE_DONE = 101; +constexpr int SQLITE_CONSTRAINT_UNIQUE = 2067; +constexpr int SQLITE_OPEN_READWRITE = 0x2; +constexpr int SQLITE_OPEN_CREATE = 0x4; +constexpr int SQLITE_OPEN_URI = 0x40; +constexpr int SQLITE_OPEN_MEMORY = 0x80; +constexpr int SQLITE_UTF8 = 1; +constexpr int SQLITE_UTF16LE = 2; +constexpr int SQLITE_INTEGER = 1; +constexpr int SQLITE_FLOAT = 2; +constexpr int SQLITE_TEXT = 3; +constexpr int SQLITE_BLOB = 4; +constexpr int SQLITE_NULL = 5; +constexpr int SQLITE_DENY = 1; +constexpr int SQLITE_DELETE = 9; +constexpr int SQLITE_CHECKPOINT_PASSIVE = 0; +const auto SQLITE_STATIC = reinterpret_cast(std::intptr_t{0}); +const auto SQLITE_TRANSIENT = reinterpret_cast(std::intptr_t{-1}); + +void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "NKWebKit SQLite assertion failed: %s\n", message); + std::abort(); +} + +int destroyedBuffers = 0; +int destroyedCollations = 0; +int functionCalls = 0; +int stepCalls = 0; +int finalCalls = 0; +int busyCalls = 0; +int walCalls = 0; +double lastFunctionValue = 0; +void* lastFunctionApplication = nullptr; +int walFrames = 0; +std::string walSchema; +sqlite3* interruptTarget = nullptr; + +void APS5_VABI FreeBuffer(void* buffer) { + ++destroyedBuffers; + std::memset(buffer, 0x5A, 8); + std::free(buffer); +} + +int APS5_VABI ReverseCompare(void* argument, int leftSize, const void* left, int rightSize, const void* right) { + Require(*static_cast(argument) == 0x51, "collation argument"); + const int common = leftSize < rightSize ? leftSize : rightSize; + const int order = std::memcmp(left, right, static_cast(common)); + return order ? -order : rightSize - leftSize; +} + +void APS5_VABI DestroyCollation(void* argument) { + Require(*static_cast(argument) == 0x51, "collation destroy argument"); + ++destroyedCollations; +} + +void APS5_VABI Probe(sqlite3_context* context, int count, sqlite3_value** values) { + ++functionCalls; + Require(count == 1, "function argument count"); + lastFunctionApplication = sqlite3_user_data_nid_postfix(context); + if (sqlite3_value_type_nid_postfix(values[0]) == SQLITE_TEXT) { + sqlite3_result_error_nid_postfix(context, "probe rejects text", -1); + return; + } + lastFunctionValue = sqlite3_value_double_nid_postfix(values[0]); +} + +void APS5_VABI Interrupt(sqlite3_context*, int, sqlite3_value**) { + sqlite3_interrupt_nid_postfix(interruptTarget); +} + +void APS5_VABI CountStep(sqlite3_context* context, int, sqlite3_value**) { + Require(sqlite3_user_data_nid_postfix(context) == &stepCalls, "aggregate application"); + ++stepCalls; +} + +void APS5_VABI CountFinal(sqlite3_context*) { + ++finalCalls; +} + +int APS5_VABI DenyDelete(void* argument, int action, const char*, const char*, const char*, const char*) { + Require(*static_cast(argument) == 0xA7, "authorizer argument"); + return action == SQLITE_DELETE ? SQLITE_DENY : SQLITE_OK; +} + +int APS5_VABI AllowAll(void*, int, const char*, const char*, const char*, const char*) { + return SQLITE_OK; +} + +int APS5_VABI GiveUp(void* argument, int count) { + Require(*static_cast(argument) == 0xB5, "busy argument"); + Require(count == busyCalls, "busy count"); + ++busyCalls; + return 0; +} + +int APS5_VABI RecordWal(void* argument, sqlite3*, const char* schema, int frames) { + Require(*static_cast(argument) == 0xC3, "wal argument"); + ++walCalls; + walSchema = schema; + walFrames = frames; + return SQLITE_OK; +} + +sqlite3* Open(const std::string& name) { + sqlite3* db = nullptr; + const int result = sqlite3_open_v2_nid_postfix(name.c_str(), &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + nullptr); + if (result != SQLITE_OK) { + std::fprintf(stderr, "open %s: %d %s\n", name.c_str(), result, db ? sqlite3_errmsg_nid_postfix(db) : ""); + } + Require(result == SQLITE_OK, "open"); + Require(db != nullptr, "open handle"); + return db; +} + +sqlite3_stmt* Prepare(sqlite3* db, const char* sql) { + sqlite3_stmt* statement = nullptr; + const char* tail = nullptr; + Require(sqlite3_prepare_v2_nid_postfix(db, sql, -1, &statement, &tail) == SQLITE_OK, sql); + Require(statement && tail && *tail == '\0', "prepare tail"); + return statement; +} + +void Execute(sqlite3* db, const char* sql) { + sqlite3_stmt* statement = Prepare(db, sql); + int result = SQLITE_ROW; + while (result == SQLITE_ROW) { + result = sqlite3_step_nid_postfix(statement); + } + Require(result == SQLITE_DONE, sql); + Require(sqlite3_finalize_nid_postfix(statement) == SQLITE_OK, "finalize"); +} + +void CheckStatements(sqlite3* db) { + Execute(db, "CREATE TABLE t(a INTEGER, b REAL, c TEXT, d BLOB)"); + sqlite3_stmt* insert = Prepare(db, "INSERT INTO t VALUES(?, ?, ?, ?)"); + Require(sqlite3_bind_parameter_count_nid_postfix(insert) == 4, "parameter count"); + Require(sqlite3_stmt_busy_nid_postfix(insert) == 0, "idle statement"); + Require(sqlite3_bind_int64_nid_postfix(insert, 1, 0x123456789AB) == SQLITE_OK, "bind int64"); + Require(sqlite3_bind_double_nid_postfix(insert, 2, 2.5) == SQLITE_OK, "bind double"); + auto* text = static_cast(std::malloc(16)); + std::memcpy(text, u"hé€", 8); + Require(sqlite3_bind_text16_nid_postfix(insert, 3, text, 6, FreeBuffer) == SQLITE_OK, "bind text16"); + Require(destroyedBuffers == 1, "text16 destructor runs after the copy"); + auto* blob = static_cast(std::malloc(8)); + std::memcpy(blob, "\x01\x02\x03\x00\x05\x06\x07\x08", 8); + Require(sqlite3_bind_blob_nid_postfix(insert, 4, blob, 8, FreeBuffer) == SQLITE_OK, "bind blob"); + Require(destroyedBuffers == 2, "blob destructor runs after the copy"); + Require(sqlite3_step_nid_postfix(insert) == SQLITE_DONE, "insert"); + Require(sqlite3_last_insert_rowid_nid_postfix(db) == 1, "last rowid"); + Require(sqlite3_reset_nid_postfix(insert) == SQLITE_OK, "reset"); + Require(sqlite3_clear_bindings_nid_postfix(insert) == SQLITE_OK, "clear bindings"); + static const unsigned char fixed[3] = {9, 8, 7}; + Require(sqlite3_bind_int_nid_postfix(insert, 1, -7) == SQLITE_OK, "bind int"); + Require(sqlite3_bind_null_nid_postfix(insert, 2) == SQLITE_OK, "bind null"); + Require(sqlite3_bind_text16_nid_postfix(insert, 3, u"z", -1, SQLITE_TRANSIENT) == SQLITE_OK, "transient"); + Require(sqlite3_bind_blob_nid_postfix(insert, 4, fixed, 3, SQLITE_STATIC) == SQLITE_OK, "static"); + Require(destroyedBuffers == 2, "static data is not released"); + Require(sqlite3_bind_blob_nid_postfix(insert, 5, std::malloc(8), 8, FreeBuffer) != SQLITE_OK, "bad index"); + Require(destroyedBuffers == 3, "destructor runs when the bind fails"); + Require(sqlite3_step_nid_postfix(insert) == SQLITE_DONE, "second insert"); + Require(sqlite3_total_changes_nid_postfix(db) == 2, "total changes"); + Require(sqlite3_get_autocommit_nid_postfix(db) == 1, "autocommit"); + Require(sqlite3_next_stmt_nid_postfix(db, nullptr) == insert, "next statement"); + Require(sqlite3_finalize_nid_postfix(insert) == SQLITE_OK, "finalize insert"); + + const char* query = "SELECT a, b, c, d FROM t ORDER BY rowid"; + sqlite3_stmt* select = Prepare(db, query); + Require(std::strcmp(sqlite3_sql_nid_postfix(select), query) == 0, "statement text"); + Require(sqlite3_data_count_nid_postfix(select) == 0, "no row yet"); + Require(sqlite3_step_nid_postfix(select) == SQLITE_ROW, "first row"); + Require(sqlite3_stmt_busy_nid_postfix(select) != 0, "busy statement"); + Require(sqlite3_data_count_nid_postfix(select) == 4, "data count"); + Require(sqlite3_column_type_nid_postfix(select, 0) == SQLITE_INTEGER, "integer type"); + Require(sqlite3_column_int64_nid_postfix(select, 0) == 0x123456789AB, "column int64"); + Require(sqlite3_column_int_nid_postfix(select, 0) == static_cast(0x456789AB), "column int"); + Require(sqlite3_column_type_nid_postfix(select, 1) == SQLITE_FLOAT, "float type"); + Require(sqlite3_column_double_nid_postfix(select, 1) == 2.5, "column double"); + Require(sqlite3_column_type_nid_postfix(select, 2) == SQLITE_TEXT, "text type"); + const auto* column = static_cast(sqlite3_column_text16_nid_postfix(select, 2)); + Require(std::memcmp(column, u"hé€", 8) == 0, "column text16"); + Require(sqlite3_column_bytes16_nid_postfix(select, 2) == 6, "column bytes16"); + Require(sqlite3_column_type_nid_postfix(select, 3) == SQLITE_BLOB, "blob type"); + Require(sqlite3_column_bytes_nid_postfix(select, 3) == 8, "column bytes"); + Require(std::memcmp(sqlite3_column_blob_nid_postfix(select, 3), "\x01\x02\x03\x00\x05\x06\x07\x08", 8) == 0, + "column blob"); + Require(std::strcmp(sqlite3_column_decltype_nid_postfix(select, 0), "INTEGER") == 0, "decltype"); + Require(std::memcmp(sqlite3_column_name16_nid_postfix(select, 2), u"c", 4) == 0, "column name16"); + sqlite3_value* value = sqlite3_column_value_nid_postfix(select, 1); + Require(sqlite3_value_type_nid_postfix(value) == SQLITE_FLOAT, "value type"); + Require(sqlite3_value_double_nid_postfix(value) == 2.5, "value double"); + Require(std::memcmp(sqlite3_value_text16_nid_postfix(value), u"2.5", 8) == 0, "value text16"); + Require(sqlite3_step_nid_postfix(select) == SQLITE_ROW, "second row"); + Require(sqlite3_column_int_nid_postfix(select, 0) == -7, "negative int"); + Require(sqlite3_column_type_nid_postfix(select, 1) == SQLITE_NULL, "null type"); + Require(std::memcmp(sqlite3_column_text16_nid_postfix(select, 2), u"z", 4) == 0, "transient text"); + Require(std::memcmp(sqlite3_column_blob_nid_postfix(select, 3), fixed, 3) == 0, "static blob"); + Require(sqlite3_step_nid_postfix(select) == SQLITE_DONE, "end of rows"); + Require(sqlite3_finalize_nid_postfix(select) == SQLITE_OK, "finalize select"); +} + +void CheckFunctions(sqlite3* db) { + static int application = 0x42; + Require(sqlite3_create_function_nid_postfix(db, "probe", 1, SQLITE_UTF8, &application, Probe, nullptr, + nullptr) == SQLITE_OK, "create function"); + Execute(db, "SELECT probe(4.25)"); + Require(functionCalls == 1 && lastFunctionValue == 4.25, "function value"); + Require(lastFunctionApplication == &application, "function application"); + sqlite3_stmt* rejected = Prepare(db, "SELECT probe('x')"); + Require(sqlite3_step_nid_postfix(rejected) == SQLITE_ERROR, "function error"); + Require(std::strcmp(sqlite3_errmsg_nid_postfix(db), "probe rejects text") == 0, "function message"); + Require(sqlite3_errcode_nid_postfix(db) == SQLITE_ERROR, "function error code"); + Require(sqlite3_finalize_nid_postfix(rejected) == SQLITE_ERROR, "finalize after error"); + + Require(sqlite3_create_function_nid_postfix(db, "counted", 1, SQLITE_UTF8, &stepCalls, nullptr, CountStep, + CountFinal) == SQLITE_OK, "create aggregate"); + Execute(db, "SELECT counted(a) FROM t"); + Require(stepCalls == 2 && finalCalls == 1, "aggregate calls"); + Require(sqlite3_create_function_nid_postfix(db, "counted", 1, SQLITE_UTF8, nullptr, nullptr, nullptr, + nullptr) == SQLITE_OK, "delete aggregate"); + sqlite3_stmt* missing = nullptr; + Require(sqlite3_prepare_v2_nid_postfix(db, "SELECT counted(a) FROM t", -1, &missing, nullptr) == SQLITE_ERROR, + "deleted aggregate"); + Require(missing == nullptr, "no statement for deleted aggregate"); + + interruptTarget = db; + Require(sqlite3_create_function_nid_postfix(db, "stop", 0, SQLITE_UTF8, nullptr, Interrupt, nullptr, + nullptr) == SQLITE_OK, "create interrupt"); + sqlite3_stmt* endless = Prepare(db, + "WITH RECURSIVE n(i) AS (SELECT 1 UNION ALL SELECT i + 1 FROM n) SELECT stop() FROM n"); + int result = SQLITE_ROW; + while (result == SQLITE_ROW) { + result = sqlite3_step_nid_postfix(endless); + } + Require(result == SQLITE_INTERRUPT, "interrupt"); + sqlite3_finalize_nid_postfix(endless); +} + +void CheckCollation(sqlite3* db) { + static int argument = 0x51; + Require(sqlite3_create_collation_v2_nid_postfix(db, "reverse", SQLITE_UTF8, &argument, ReverseCompare, + DestroyCollation) == SQLITE_OK, "create collation"); + Execute(db, "CREATE TABLE words(w TEXT)"); + Execute(db, "INSERT INTO words VALUES('alpha'), ('gamma'), ('beta')"); + sqlite3_stmt* ordered = Prepare(db, "SELECT w FROM words ORDER BY w COLLATE reverse"); + const char16_t* expected[] = {u"gamma", u"beta", u"alpha"}; + for (const char16_t* word : expected) { + Require(sqlite3_step_nid_postfix(ordered) == SQLITE_ROW, "collation row"); + const auto* text = static_cast(sqlite3_column_text16_nid_postfix(ordered, 0)); + Require(std::u16string(text) == word, "collation order"); + } + Require(sqlite3_finalize_nid_postfix(ordered) == SQLITE_OK, "finalize collation"); + Require(sqlite3_create_collation_v2_nid_postfix(db, "reverse", SQLITE_UTF16LE, &argument, ReverseCompare, + DestroyCollation) == SQLITE_OK, "second encoding"); + Require(sqlite3_create_collation_v2_nid_postfix(db, "invalid", 99, &argument, ReverseCompare, DestroyCollation) == + SQLITE_MISUSE, "invalid collation encoding"); + Require(destroyedCollations == 0, "collations live until close"); +} + +void CheckAuthorizer(sqlite3* db) { + static int argument = 0xA7; + Require(sqlite3_set_authorizer_nid_postfix(db, DenyDelete, &argument) == SQLITE_OK, "set authorizer"); + sqlite3_stmt* denied = nullptr; + Require(sqlite3_prepare_v2_nid_postfix(db, "DELETE FROM words", -1, &denied, nullptr) == SQLITE_AUTH, + "authorizer denies"); + Require(denied == nullptr, "no denied statement"); + Require(std::strcmp(sqlite3_errstr_nid_postfix(SQLITE_AUTH), "authorization denied") == 0, "errstr"); + Require(sqlite3_set_authorizer_nid_postfix(db, nullptr, nullptr) == SQLITE_OK, "clear authorizer"); + Execute(db, "DELETE FROM words WHERE w = 'beta'"); +} + +void CheckExtendedCodes(sqlite3* db) { + Execute(db, "CREATE TABLE unique_values(v INTEGER UNIQUE)"); + Execute(db, "INSERT INTO unique_values VALUES(1)"); + Require(sqlite3_extended_result_codes_nid_postfix(db, 1) == SQLITE_OK, "extended codes"); + sqlite3_stmt* duplicate = Prepare(db, "INSERT INTO unique_values VALUES(1)"); + Require(sqlite3_step_nid_postfix(duplicate) == SQLITE_CONSTRAINT_UNIQUE, "extended step code"); + Require(sqlite3_errcode_nid_postfix(db) == SQLITE_CONSTRAINT_UNIQUE, "extended errcode"); + sqlite3_finalize_nid_postfix(duplicate); +} + +void CheckFileDatabase(const std::filesystem::path& directory) { + const std::string name = "/wal.db"; + sqlite3* writer = Open(name); + Execute(writer, "PRAGMA journal_mode = WAL"); + Execute(writer, "CREATE TABLE log(v INTEGER)"); + static int walArgument = 0xC3; + void* previous = sqlite3_wal_hook_nid_postfix(writer, RecordWal, &walArgument); + Require(reinterpret_cast(previous) == 1000, "default autocheckpoint hook"); + Execute(writer, "INSERT INTO log VALUES(1)"); + Require(walCalls == 1 && walSchema == "main" && walFrames > 0, "wal hook"); + Require(sqlite3_wal_hook_nid_postfix(writer, RecordWal, &walArgument) == &walArgument, "previous wal argument"); + int logFrames = -1; + int checkpointed = -1; + Require(sqlite3_wal_checkpoint_v2_nid_postfix(writer, "main", SQLITE_CHECKPOINT_PASSIVE, &logFrames, + &checkpointed) == SQLITE_OK, "checkpoint"); + Require(logFrames >= 1 && checkpointed == logFrames, "checkpoint frames"); + + sqlite3* reader = Open(name); + static int busyArgument = 0xB5; + Require(sqlite3_busy_handler_nid_postfix(reader, GiveUp, &busyArgument) == SQLITE_OK, "busy handler"); + Execute(writer, "BEGIN EXCLUSIVE"); + Require(sqlite3_get_autocommit_nid_postfix(writer) == 0, "transaction open"); + sqlite3_stmt* blocked = Prepare(reader, "INSERT INTO log VALUES(2)"); + Require(sqlite3_step_nid_postfix(blocked) == SQLITE_BUSY, "busy"); + Require(busyCalls == 1, "busy handler called"); + sqlite3_finalize_nid_postfix(blocked); + Require(sqlite3_busy_timeout_nid_postfix(reader, 10) == SQLITE_OK, "busy timeout"); + Execute(writer, "COMMIT"); + Execute(reader, "INSERT INTO log VALUES(2)"); + Require(busyCalls == 1, "timeout replaces the guest handler"); + Require(sqlite3_close_nid_postfix(reader) == SQLITE_OK, "close reader"); + Require(sqlite3_close_nid_postfix(writer) == SQLITE_OK, "close writer"); +} + +} + +int main() { + Require(sqlite3_initialize_nid_postfix() == SQLITE_OK, "initialize"); + sqlite3* db = Open(":memory:"); + CheckStatements(db); + CheckFunctions(db); + CheckCollation(db); + CheckAuthorizer(db); + CheckExtendedCodes(db); + Require(sqlite3_close_nid_postfix(db) == SQLITE_OK, "close"); + Require(destroyedCollations == 2, "collation destructors on close"); + sqlite3* memory = nullptr; + Require(sqlite3_open_v2_nid_postfix("file::memory:", &memory, SQLITE_OPEN_READWRITE | SQLITE_OPEN_URI, nullptr) == + SQLITE_OK, "memory URI"); + Execute(memory, "CREATE TABLE t(v)"); + Require(sqlite3_close_nid_postfix(memory) == SQLITE_OK, "close memory URI"); + + std::random_device random; + const auto directory = std::filesystem::temp_directory_path() / ("nk_sqlite_" + std::to_string(random())); + std::filesystem::create_directories(directory); + std::filesystem::current_path(directory); + constexpr int uriFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI; + for (const char* name : {"file:mode=memory.db?mode=rwc", "file:mode=memory.db", "file::memory:x", "file:later.db?mode=memory&mode=rwc", "file:encoded.db?mode=memory&mod%65=rwc", "file:m1.db?mode=rwc", "file:plain.db"}) { + for (const int flags : {uriFlags, uriFlags | SQLITE_OPEN_MEMORY}) { + sqlite3* host = nullptr; + try { + sqlite3_open_v2_nid_postfix(name, &host, flags, nullptr); + } catch (const std::exception&) { + } + if (host != nullptr) sqlite3_close_nid_postfix(host); + Require(std::filesystem::is_empty(directory), "a URI that only mentions mode=memory in its path creates no host file"); + } + } + for (const char* name : {"file:one?mode=memory", "file:two?cache=shared&mode=memory", "file::memory:?cache=private", "file:three?mode=rwc&mode=memory"}) { + sqlite3* inMemory = nullptr; + Require(sqlite3_open_v2_nid_postfix(name, &inMemory, uriFlags, nullptr) == SQLITE_OK, "memory URI parameter"); + Execute(inMemory, "CREATE TABLE t(v)"); + Require(sqlite3_close_nid_postfix(inMemory) == SQLITE_OK, "close memory URI parameter"); + Require(std::filesystem::is_empty(directory), "a memory URI creates no host file"); + } + const auto refused = [](sqlite3* connection, const char* sql) { + sqlite3_stmt* statement = nullptr; + const char* tail = nullptr; + int result = sqlite3_prepare_v2_nid_postfix(connection, sql, -1, &statement, &tail); + if (result == SQLITE_OK) { + do { + result = sqlite3_step_nid_postfix(statement); + } while (result == SQLITE_ROW); + } + if (statement != nullptr) sqlite3_finalize_nid_postfix(statement); + return result != SQLITE_OK && result != SQLITE_DONE; + }; + sqlite3* guard = Open(":memory:"); + Require(refused(guard, "ATTACH DATABASE 'attached.db' AS other"), "ATTACH is refused on an in-memory connection"); + Require(refused(guard, "VACUUM INTO 'vacuumed.db'"), "VACUUM INTO is refused on an in-memory connection"); + Require(std::filesystem::is_empty(directory), "ATTACH and VACUUM INTO create no host file"); + Execute(guard, "VACUUM"); + Require(sqlite3_close_nid_postfix(guard) == SQLITE_OK, "close in-memory guard"); + sqlite3* limited = nullptr; + Require(sqlite3_open_v2_nid_postfix("limited.db", &limited, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr) == SQLITE_OK, "open limited.db"); + Require(refused(limited, "ATTACH DATABASE 'attached.db' AS other"), "ATTACH is refused on a file connection"); + Require(refused(limited, "VACUUM INTO 'vacuumed.db'"), "VACUUM INTO is refused on a file connection"); + Require(!std::filesystem::exists(directory / "attached.db") && !std::filesystem::exists(directory / "vacuumed.db"), "ATTACH and VACUUM INTO create no host file from a file connection"); + Execute(limited, "VACUUM"); + Execute(limited, "ATTACH DATABASE '' AS scratch"); + Execute(limited, "DETACH DATABASE scratch"); + Require(sqlite3_set_authorizer_nid_postfix(limited, AllowAll, nullptr) == SQLITE_OK, "set a permissive guest authorizer"); + Require(refused(limited, "ATTACH DATABASE 'attached.db' AS other"), "ATTACH is refused with a guest authorizer set"); + Require(refused(limited, "VACUUM INTO 'vacuumed.db'"), "VACUUM INTO is refused with a guest authorizer set"); + Require(sqlite3_set_authorizer_nid_postfix(limited, nullptr, nullptr) == SQLITE_OK, "clear the guest authorizer"); + Require(refused(limited, "ATTACH DATABASE 'attached.db' AS other"), "ATTACH is refused after the guest clears its authorizer"); + Require(refused(limited, "VACUUM INTO 'vacuumed.db'"), "VACUUM INTO is refused after the guest clears its authorizer"); + Require(!std::filesystem::exists(directory / "attached.db") && !std::filesystem::exists(directory / "vacuumed.db"), "no host file after the guest sets and clears an authorizer"); + Require(sqlite3_close_nid_postfix(limited) == SQLITE_OK, "close limited.db"); + std::filesystem::remove(directory / "limited.db"); + CheckFileDatabase(directory); + Require(std::filesystem::is_regular_file(directory / "wal.db"), "guest path maps into the root"); + const auto written = WrittenPaths_nid_no_patch(); + for (const char* name : {"wal.db", "wal.db-wal", "wal.db-shm", "wal.db-journal"}) { + Require(std::any_of(written.begin(), written.end(), [&](const std::filesystem::path& path) { + return path.filename() == name && std::filesystem::equivalent(path.parent_path(), directory); + }), "database files recorded for sceKernelSync"); + } + std::filesystem::current_path(directory.parent_path()); + std::filesystem::remove_all(directory); + return 0; +} diff --git a/core/libs/tests/GuestNetThread.cpp b/core/libs/tests/GuestNetThread.cpp index abdc2c22d8..66f662a6a7 100644 --- a/core/libs/tests/GuestNetThread.cpp +++ b/core/libs/tests/GuestNetThread.cpp @@ -68,6 +68,10 @@ int main() { Require(sceNetEmulationGet(nullptr, 0) == (NetError | Eopnotsupp) && *sceNetErrnoLoc() == Eopnotsupp, "sceNetEmulationGet did not return EOPNOTSUPP"); *sceNetErrnoLoc() = 0; Require(sceNetEmulationSet(nullptr, 0) == (NetError | Eopnotsupp) && *sceNetErrnoLoc() == Eopnotsupp, "sceNetEmulationSet did not return EOPNOTSUPP"); + *sceNetErrnoLoc() = 0; + Require(sceNetEmulationDebugSettingsGet(nullptr, 0) == (NetError | Eopnotsupp) && *sceNetErrnoLoc() == Eopnotsupp, "sceNetEmulationDebugSettingsGet did not return EOPNOTSUPP"); + *sceNetErrnoLoc() = 0; + Require(sceNetEmulationDebugSettingsSet(nullptr, 0) == (NetError | Eopnotsupp) && *sceNetErrnoLoc() == Eopnotsupp, "sceNetEmulationDebugSettingsSet did not return EOPNOTSUPP"); std::puts("net thread tests passed"); return 0; } diff --git a/core/libs/tests/GuestNpAuth.cpp b/core/libs/tests/GuestNpAuth.cpp index 31efb7ba57..3681d0bf85 100644 --- a/core/libs/tests/GuestNpAuth.cpp +++ b/core/libs/tests/GuestNpAuth.cpp @@ -120,6 +120,11 @@ int main() { int asyncWait = 0; Require(sceNpAuthWaitAsync(asyncReq, &asyncWait) == 0); Require(asyncWait == signedOut); + Require(sceNpAuthPollAsync(asyncReq, reinterpret_cast(0x10)) == invalidArgument); + Require(sceNpAuthWaitAsync(asyncReq, reinterpret_cast(0x10)) == invalidArgument); + asyncWait = 0; + Require(sceNpAuthWaitAsync(asyncReq, &asyncWait) == 0); + Require(asyncWait == signedOut); Require(sceNpAuthGetAuthorizationCodeV3(asyncReq2, param, authCode, &issuerId) == 0); Require(sceNpAuthGetAuthorizationCodeV3(asyncReq2, param, authCode, &issuerId) == invalidArgument); diff --git a/core/libs/tests/GuestNpCommerceDialog.cpp b/core/libs/tests/GuestNpCommerceDialog.cpp index 12cd7fa4bf..d8c338121c 100644 --- a/core/libs/tests/GuestNpCommerceDialog.cpp +++ b/core/libs/tests/GuestNpCommerceDialog.cpp @@ -27,6 +27,7 @@ constexpr std::int32_t resultUserCanceled = 1; void RequireCanceledResult(std::uint64_t userData, bool clearsReserved) { NpCommerceDialogResult result; std::memset(&result, 0x33, sizeof(result)); + Require(sceNpCommerceDialogGetResult(reinterpret_cast(0x10)) == static_cast(0x80B8000Au)); Require(sceNpCommerceDialogGetResult(&result) == resultUserCanceled); Require(result.result == resultUserCanceled); Require(result.authorized == 0); diff --git a/core/libs/tests/GuestNpCommonRuntime.cpp b/core/libs/tests/GuestNpCommonRuntime.cpp index 2d27cc417a..5c1ff58827 100644 --- a/core/libs/tests/GuestNpCommonRuntime.cpp +++ b/core/libs/tests/GuestNpCommonRuntime.cpp @@ -35,6 +35,8 @@ constexpr int NpOutOfRange = static_cast(0x8055800E); constexpr int NpInvalidArgument = static_cast(0x80558009); constexpr int NpEventFlagBusy = static_cast(0x8055800C); constexpr int NpSemaBusy = static_cast(0x8055800D); +constexpr int NpObjectAlreadyInitialized = static_cast(0x80559E03); +constexpr int NpCondTimedOut = static_cast(0x8055800B); constexpr int NpStreamBufferTooSmall = static_cast(0x80559E0D); constexpr int NpTitleIdInvalid = static_cast(0x80559E11); constexpr int NpUtilParseOutOfRange = static_cast(0x8055060A); @@ -373,6 +375,101 @@ void TestObjectsThroughStoredAllocator() { "a failed array allocation returns null"); } +struct CondObjectWaiter { + NpMutexObject* mutex; + NpCondObject* cond; + std::atomic stage {0}; + int result = -1; +}; + +void* APS5_VABI WaitOnCond(void* arg) { + auto* waiter = static_cast(arg); + _ZN3sce2np5Mutex4LockEv(waiter->mutex); + waiter->stage = 1; + while (waiter->stage == 1) waiter->result = _ZN3sce2np4Cond4WaitEj(waiter->cond, 5000000); + _ZN3sce2np5Mutex6UnlockEv(waiter->mutex); + return nullptr; +} + +bool WakeWaiter(NpMutexObject* mutex, NpCondObject* cond, bool targeted) { + CondObjectWaiter waiter {mutex, cond}; + Pthread thread {}; + if (sceNpCreateThread(&thread, WaitOnCond, &waiter, 0, 0x10000, 0, "NpCondWaiter") != 0) return false; + while (waiter.stage != 1) std::this_thread::yield(); + _ZN3sce2np5Mutex4LockEv(mutex); + waiter.stage = 2; + const int signalled = targeted ? _ZN3sce2np4Cond8SignalToEP7pthread(cond, thread) : _ZN3sce2np4Cond6SignalEv(cond); + _ZN3sce2np5Mutex6UnlockEv(mutex); + void* value = nullptr; + return signalled == 0 && sceNpJoinThread(thread, &value) == 0 && waiter.result == 0; +} + +template +bool RefusedBeforeInit(Call call) { + try { + call(); + } catch (const std::invalid_argument&) { + return true; + } + return false; +} + +void TestMutexAndCondObjects() { + NpMutexObject mutex {}; + _ZN3sce2np5MutexC1Ev(&mutex); + Require(mutex.vtable != nullptr && !mutex.isInit, "a constructed mutex is not initialized"); + Require(RefusedBeforeInit([&] { _ZN3sce2np5Mutex4LockEv(&mutex); }) && RefusedBeforeInit([&] { _ZN3sce2np5Mutex7TryLockEv(&mutex); }) + && RefusedBeforeInit([&] { _ZN3sce2np5Mutex6UnlockEv(&mutex); }), "locking asserts that the mutex is initialized"); + Require(_ZN3sce2np5Mutex4InitEPKcj(&mutex, "NpMutexObject", 0) == 0 && mutex.isInit, "a mutex is initialized"); + Require(_ZN3sce2np5Mutex4InitEPKcj(&mutex, "NpMutexObject", 0) == NpObjectAlreadyInitialized, "a second Init is refused"); + Require(_ZN3sce2np5Mutex7TryLockEv(&mutex) && !_ZN3sce2np5Mutex7TryLockEv(&mutex), "a held plain mutex cannot be taken again"); + _ZN3sce2np5Mutex6UnlockEv(&mutex); + + NpMutexObject recursive {}; + _ZN3sce2np5MutexC1Ev(&recursive); + Require(_ZN3sce2np5Mutex4InitEPKcj(&recursive, "NpMutexRecursive", 3) == 0, "a recursive mutex is initialized"); + Require(_ZN3sce2np5Mutex7TryLockEv(&recursive) && _ZN3sce2np5Mutex7TryLockEv(&recursive), "bit 0 of the flags makes the mutex recursive"); + _ZN3sce2np5Mutex6UnlockEv(&recursive); + _ZN3sce2np5Mutex6UnlockEv(&recursive); + _ZN3sce2np5Mutex7DestroyEv(&recursive); + _ZN3sce2np5Mutex7DestroyEv(&recursive); + Require(!recursive.isInit, "Destroy leaves the mutex uninitialized and may be repeated"); + + NpCondObject cond {}; + _ZN3sce2np4CondC1Ev(&cond); + Require(cond.vtable != nullptr && !_ZNK3sce2np4Cond6IsInitEv(&cond), "a constructed condition is not initialized"); + Require(_ZN3sce2np4Cond4InitEPKcPNS0_5MutexE(&cond, "NpCondObject", &mutex) == 0 && _ZNK3sce2np4Cond6IsInitEv(&cond) && cond.mutex == &mutex.mutex, + "a condition keeps the kernel mutex of its sce::np::Mutex"); + Require(_ZN3sce2np4Cond4InitEPKcPNS0_5MutexE(&cond, "NpCondObject", &mutex) == NpObjectAlreadyInitialized, "a second Init is refused"); + _ZN3sce2np5Mutex4LockEv(&mutex); + Require(_ZN3sce2np4Cond4WaitEj(&cond, 2000) == NpCondTimedOut, "a timed wait reports the timeout"); + _ZN3sce2np5Mutex6UnlockEv(&mutex); + Require(_ZN3sce2np4Cond6SignalEv(&cond) == 0 && _ZN3sce2np4Cond9SignalAllEv(&cond) == 0, "signalling without waiters succeeds"); + Require(WakeWaiter(&mutex, &cond, false), "Signal wakes a waiter"); + Require(WakeWaiter(&mutex, &cond, true), "SignalTo wakes the given waiter"); + _ZN3sce2np4Cond7DestroyEv(&cond); + Require(!_ZNK3sce2np4Cond6IsInitEv(&cond) && cond.cond == nullptr, "Destroy clears the condition"); + Require(_ZN3sce2np4Cond4InitEPKcPNS0_5MutexE(&cond, "NpCondNullMutex", nullptr) == 0 && _ZNK3sce2np4Cond6IsInitEv(&cond), + "Init with a null mutex stores its member address and counts as initialized"); + _ZN3sce2np4Cond7DestroyEv(&cond); + _ZN3sce2np5Mutex7DestroyEv(&mutex); + + AllocatorRecord record {}; + NpAllocator allocator {RecordAllocate, nullptr, RecordRelease, &record}; + using MutexDestructor = void APS5_VABI (*)(NpMutexObject*); + using CondDestructor = void APS5_VABI (*)(NpCondObject*); + auto* ownedMutex = static_cast(_ZN3sce2np6ObjectnwEmR14SceNpAllocator(sizeof(NpMutexObject), &allocator)); + _ZN3sce2np5MutexC1Ev(ownedMutex); + Require(_ZN3sce2np5Mutex4InitEPKcj(ownedMutex, "NpMutexOwned", 0) == 0, "an allocated mutex is initialized"); + reinterpret_cast(ownedMutex->vtable[1])(ownedMutex); + Require(record.released == record.block.data(), "the mutex deleting destructor frees through the stored allocator"); + record.released = nullptr; + auto* ownedCond = static_cast(_ZN3sce2np6ObjectnwEmR14SceNpAllocator(sizeof(NpCondObject), &allocator)); + _ZN3sce2np4CondC1Ev(ownedCond); + reinterpret_cast(ownedCond->vtable[1])(ownedCond); + Require(record.released == record.block.data(), "the condition deleting destructor frees through the stored allocator"); +} + struct ExRecord { std::array block {}; int allocations = 0; @@ -618,7 +715,7 @@ void TestTitleSecretObjects() { auto* owned = static_cast(_ZN3sce2np6ObjectnwEmR14SceNpAllocator(sizeof(NpTitleSecretObject), &allocator)); _ZN3sce2np13NpTitleSecretC1Ev(owned); using Destructor = void APS5_VABI (*)(NpTitleSecretObject*); - reinterpret_cast(owned->vtable[1])(owned); + reinterpret_cast(const_cast(owned->vtable[1]))(owned); Require(record.released == record.block.data(), "the deleting destructor frees through the stored allocator"); } @@ -1226,6 +1323,8 @@ void TestBase64() { Require(sceNpBase64UrlGetDecodeSize("+w==", 4) == NpInvalidCharacter, "base64url refuses the standard alphabet"); Require(sceNpBase64UrlDecoder("-wA", 3, bytes, 2) == 2 && bytes[0] == 0xFB && bytes[1] == 0, "base64url decodes a short tail"); Require(sceNpBase64UrlDecoder("-w", 2, bytes, 0) == NpInvalidFormat, "base64url decoding refuses a short buffer"); + Require(sceNpBase64UrlGetDecodeSize(nullptr, 0) == NpInvalidCharacter && sceNpBase64UrlDecoder(nullptr, 0, bytes, sizeof(bytes)) == NpInvalidCharacter, + "base64url decoding of a null source with no length behaves like an empty one"); } void TestNumbers() { @@ -1569,6 +1668,7 @@ int main() { TestObjects(); TestAllocatorEx(); TestObjectsThroughStoredAllocator(); + TestMutexAndCondObjects(); TestAllocatorExRecorded(); TestRingBuf(); TestMemoryStreams(); diff --git a/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp b/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp index e9a056947e..7e584b6f50 100644 --- a/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp +++ b/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp @@ -304,6 +304,70 @@ static void TestVectorPushBackReleasesElements() { Require(deletedObject == object); } +static void TestVectorCopyFromSelfPreservesElements() { + int context = 0; + alignas(8) unsigned char objectA[ObjectSize]; + alignas(8) unsigned char objectB[ObjectSize]; + std::memset(objectA, 0x5a, sizeof(objectA)); + std::memset(objectB, 0x5a, sizeof(objectB)); + WriteCount(objectA, 0, 1); + WriteCount(objectB, 0, 1); + deleterCalls = 0; + deletedObject = nullptr; + + auto* sourceElements = static_cast(std::calloc(2, sizeof(IntrusivePtr))); + Require(sourceElements != nullptr); + sourceElements[0] = IntrusivePtr{objectA, RecordDelete, &context}; + sourceElements[1] = IntrusivePtr{objectB, RecordDelete, &context}; + alignas(8) unsigned char sourceBody[0x50]; + std::memset(sourceBody, 0, sizeof(sourceBody)); + const std::uint64_t magic = 0x434e507056656342ULL; + const std::uint64_t two = 2; + std::memcpy(sourceBody, &magic, sizeof(magic)); + WriteCount(sourceBody, VectorReferenceCountOffset, 1); + WriteCount(sourceBody, 0x34, 1); + std::memcpy(sourceBody + 0x38, &sourceElements, sizeof(sourceElements)); + std::memcpy(sourceBody + 0x40, &two, sizeof(two)); + std::memcpy(sourceBody + 0x48, &two, sizeof(two)); + IntrusivePtr source{sourceBody, RecordDelete, &context}; + + IntrusivePtr vector; + _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEEC1EPNS2_10LibContextE(&vector, &context); + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEE8copyFromERKS9_(&vector, &source) == 0); + Require(_ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEE4sizeEv(&vector) == 2); + Require(ReadCount(objectA, 0) == 2 && ReadCount(objectB, 0) == 2); + + IntrusivePtr* elements = nullptr; + std::memcpy(&elements, static_cast(vector.object) + 0x38, sizeof(elements)); + Require(elements != nullptr && elements != sourceElements); + Require(elements[0].object == objectA && elements[0].deleter == RecordDelete && elements[0].libContext == &context); + Require(elements[1].object == objectB && elements[1].deleter == RecordDelete && elements[1].libContext == &context); + + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEE8copyFromERKS9_(&vector, &vector) == 0); + Require(_ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEE4sizeEv(&vector) == 2); + IntrusivePtr* elementsAfter = nullptr; + std::memcpy(&elementsAfter, static_cast(vector.object) + 0x38, sizeof(elementsAfter)); + Require(elementsAfter == elements); + Require(elements[0].object == objectA && elements[1].object == objectB); + Require(ReadCount(objectA, 0) == 2 && ReadCount(objectB, 0) == 2); + Require(deleterCalls == 0); + + IntrusivePtr alias{vector.object, vector.deleter, vector.libContext}; + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEE8copyFromERKS9_(&alias, &vector) == 0); + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEE8copyFromERKS9_(&vector, &alias) == 0); + Require(_ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEE4sizeEv(&vector) == 2); + std::memcpy(&elementsAfter, static_cast(vector.object) + 0x38, sizeof(elementsAfter)); + Require(elementsAfter == elements); + Require(elements[0].object == objectA && elements[1].object == objectB); + Require(ReadCount(objectA, 0) == 2 && ReadCount(objectB, 0) == 2); + Require(deleterCalls == 0); + + _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V127ResponsePlayerSessionPlayerEEEED1Ev(&vector); + Require(ReadCount(objectA, 0) == 1 && ReadCount(objectB, 0) == 1); + Require(deleterCalls == 0); + std::free(sourceElements); +} + static void TestAdoptedStringVectorCountsReferences() { int context = 0; alignas(8) unsigned char body[ObjectSize]; @@ -366,6 +430,53 @@ static void TestScalarVectorAssignment() { Require(deleterCalls == 0); } +static void TestVectorFunctionsAcceptTheHandleObject() { + int context = 0; + + IntrusivePtr vector; + _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_7Matches2V116RequestMatchTeamEEEEC1EPNS2_10LibContextE(&vector, &context); + unsigned char object[ObjectSize]; + std::memset(object, 0x5a, sizeof(object)); + WriteCount(object, 0, 1); + IntrusivePtr filled{object, RecordDelete, &context}; + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_7Matches2V116RequestMatchTeamEEEE8pushBackERKS8_(&vector, &filled) == 0); + Require(_ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_7Matches2V116RequestMatchTeamEEEE4sizeEv(&vector) == 1); + Require(_ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_7Matches2V116RequestMatchTeamEEEE4sizeEv(vector.object) == 1); + _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_7Matches2V116RequestMatchTeamEEEED1Ev(&vector); + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_7Matches2V116RequestMatchTeamEED1Ev(&filled); + + alignas(8) unsigned char body[0x50] = {}; + const std::uint64_t magic = 0x434e507056656342ULL; + std::memcpy(body, &magic, sizeof(magic)); + WriteCount(body, VectorReferenceCountOffset, 1); + *reinterpret_cast(body + 0x34) = 1; + IntrusivePtr elements[2] = {}; + *reinterpret_cast(body + 0x38) = elements; + *reinterpret_cast(body + 0x40) = 2; + *reinterpret_cast(body + 0x48) = 2; + IntrusivePtr handle{body, nullptr, &context}; + + const auto presenceBegin = reinterpret_cast(_ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE5beginEv); + const auto presenceEnd = reinterpret_cast(_ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE3endEv); + const auto presenceNext = reinterpret_cast(_ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEEppEv); + const auto presenceNotEqual = reinterpret_cast(_ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEEneERKS9_); + + unsigned char first[0x10] = {}, last[0x10] = {}; + Require(presenceBegin(first, body) == first); + Require(presenceEnd(last, body) == last); + Require(presenceNotEqual(first, last)); + Require(presenceNext(first) == first); + Require(presenceNotEqual(first, last)); + Require(presenceNext(first) == first); + Require(!presenceNotEqual(first, last)); + + Require(presenceBegin(first, &handle) == first); + Require(presenceEnd(last, &handle) == last); + Require(presenceNext(first) == first); + Require(presenceNext(first) == first); + Require(!presenceNotEqual(first, last)); +} + static void TestVectorAliasedGrowth() { unsigned cases = 0, moves = 0; for (unsigned sourceIndex = 0; sourceIndex < 4; ++sourceIndex) { @@ -433,7 +544,47 @@ static void TestUserProfileVectorAssign() { } static void TestUserProfileModelAndTransaction() { - Require(_ZN3sce2Np9CppWebApi11UserProfile2V112BasicProfileC1EPNS1_6Common10LibContextE() == 0); + std::uint8_t profile[0xd0]; + std::memset(profile, 0xa5, sizeof(profile)); + std::uint64_t context = 0; + Require(_ZN3sce2Np9CppWebApi11UserProfile2V112BasicProfileC1EPNS1_6Common10LibContextE(profile, &context) == 0); + Require(_ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile13onlineIdIsSetEv(profile) == false); + Require(_ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile19personalDetailIsSetEv(profile) == false); + Require(_ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile25isOfficiallyVerifiedIsSetEv(profile) == false); + const bool verified = true; + Require(_ZN3sce2Np9CppWebApi11UserProfile2V112BasicProfile23setIsOfficiallyVerifiedERKb(profile, &verified) == 0); + Require(_ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile25isOfficiallyVerifiedIsSetEv(profile)); + unsigned char onlineId[0x14]; + std::memset(onlineId, 0x61, sizeof(onlineId)); + Require(_ZN3sce2Np9CppWebApi11UserProfile2V112BasicProfile11setOnlineIdERK13SceNpOnlineId(profile, onlineId) == 0); + Require(_ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile13onlineIdIsSetEv(profile)); + unsigned char onlineIdOut[0x14]; + std::memset(onlineIdOut, 0, sizeof(onlineIdOut)); + Require(_ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile11getOnlineIdEv(onlineIdOut, profile) == onlineIdOut); + Require(std::memcmp(onlineIdOut, onlineId, sizeof(onlineId)) == 0); + Require(_ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile23getIsOfficiallyVerifiedEv(profile)); + IntrusivePtr personalDetail{reinterpret_cast(0xfeed), RecordDelete, &context}; + Require(_ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile17getPersonalDetailEv(&personalDetail, profile) == &personalDetail); + Require(personalDetail.object == nullptr && personalDetail.deleter == nullptr && personalDetail.libContext == nullptr); + Require(_ZN3sce2Np9CppWebApi11UserProfile2V112BasicProfileD1Ev(profile) == profile); + + alignas(8) std::uint8_t presence[0x28]; + std::memset(presence, 0xa5, sizeof(presence)); + Require(_ZN3sce2Np9CppWebApi11UserProfile2V113BasicPresenceC1EPNS1_6Common10LibContextE(presence, &context) == 0); + Require(_ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence17onlineStatusIsSetEv(presence) == false); + Require(_ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence14inContextIsSetEv(presence) == false); + const std::uint64_t accountId = 0x1234; + Require(_ZN3sce2Np9CppWebApi11UserProfile2V113BasicPresence12setAccountIdERKm(presence, &accountId) == 0); + Require(_ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence12getAccountIdEv(presence) == 0x1234); + Require(_ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence14accountIdIsSetEv(presence)); + const bool inContext = true; + Require(_ZN3sce2Np9CppWebApi11UserProfile2V113BasicPresence12setInContextERKb(presence, &inContext) == 0); + Require(_ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence12getInContextEv(presence)); + const std::uint32_t status = 2; + Require(_ZN3sce2Np9CppWebApi11UserProfile2V113BasicPresence15setOnlineStatusERKNS3_12OnlineStatusE(presence, &status) == 0); + Require(_ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence17onlineStatusIsSetEv(presence)); + Require(_ZNK3sce2Np9CppWebApi11UserProfile2V113BasicPresence15getOnlineStatusEv(presence) == 2); + Require(_ZN3sce2Np9CppWebApi11UserProfile2V113BasicPresenceD1Ev(presence) == presence); std::uint64_t transaction[0x20]; std::memset(transaction, 0xa5, sizeof(transaction)); @@ -484,6 +635,109 @@ static void TestUserProfileBoolAndSize() { _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEED1Ev(&vector); } +static void TestResidualAccessors() { + int context = 0; + alignas(8) unsigned char object[ObjectSize]; + deleterCalls = 0; + deletedObject = nullptr; + + alignas(8) unsigned char response[0x210]; + std::memset(object, 0x5a, sizeof(object)); + WriteCount(object, VectorReferenceCountOffset, 6); + std::memset(response, 0, sizeof(response)); + IntrusivePtr member{object, RecordDelete, &context}; + std::memcpy(response + 0x30, &member, sizeof(member)); + IntrusivePtr out{reinterpret_cast(0xfeed), RecordDelete, &context}; + Require(_ZN3sce2Np9CppWebApi11UserProfile2V125GetBasicPresencesResponse17getBasicPresencesEv(&out, response) == &out); + Require(out.object == object && out.deleter == RecordDelete && out.libContext == &context); + Require(ReadCount(object, VectorReferenceCountOffset) == 7); + Require(OnlyCountChanged(object, VectorReferenceCountOffset)); + + out = {nullptr, nullptr, nullptr}; + Require(_ZN3sce2Np9CppWebApi14SessionManager2V129GetPlayerSessionsResponseBody17getPlayerSessionsEv(&out, response) == &out); + Require(out.object == object && ReadCount(object, VectorReferenceCountOffset) == 8); + + std::memset(response + 0x30, 0, sizeof(member)); + std::memcpy(response + 0x10, &member, sizeof(member)); + out = {nullptr, nullptr, nullptr}; + Require(_ZN3sce2Np9CppWebApi14SessionManager2V133ResponsePlayerSessionMemberPlayer10getPlayersEv(&out, response) == &out); + Require(out.object == object && ReadCount(object, VectorReferenceCountOffset) == 9); + + std::memset(object, 0x5a, sizeof(object)); + WriteCount(object, 0, 4); + std::memset(response, 0, sizeof(response)); + std::memcpy(response + 0x30, &member, sizeof(member)); + out = {nullptr, nullptr, nullptr}; + Require(_ZNK3sce2Np9CppWebApi14SessionManager2V113PlayerSession9getMemberEv(&out, response) == &out); + Require(out.object == object && ReadCount(object, 0) == 5); + Require(OnlyCountChanged(object, 0)); + + std::memset(response, 0, sizeof(response)); + std::memcpy(response + 0xf8, &member, sizeof(member)); + out = {nullptr, nullptr, nullptr}; + Require(_ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead9getLeaderEv(&out, response) == &out); + Require(out.object == object && ReadCount(object, 0) == 6); + + std::memset(object, 0x5a, sizeof(object)); + WriteCount(object, StringVectorReferenceCountOffset, 2); + std::memset(response, 0, sizeof(response)); + std::memcpy(response + 0x188, &member, sizeof(member)); + out = {nullptr, nullptr, nullptr}; + Require(_ZN3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead28getExclusiveLeaderPrivilegesEv(&out, response) == &out); + Require(out.object == object && ReadCount(object, StringVectorReferenceCountOffset) == 3); + Require(OnlyCountChanged(object, StringVectorReferenceCountOffset)); + + std::memset(response, 0x11, sizeof(response)); + Require(_ZN3sce2Np9CppWebApi11UserProfile2V118GetFriendsResponse10getFriendsEv(response) == response + 0x30); + Require(_ZN3sce2Np9CppWebApi11UserProfile2V124GetBlockingUsersResponse9getBlocksEv(response) == response + 0x30); + Require(_ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead14getCustomData1Ev(response) == response + 0x1c8); + Require(_ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead14getCustomData2Ev(response) == response + 0x1e8); + Require(_ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead16customData2IsSetEv(response)); + Require(_ZNK3sce2Np9CppWebApi14SessionManager2V127ResponsePlayerSessionPlayer11getPlatformEv(response) == response + 0x18); + Require(_ZNK3sce2Np9CppWebApi15ProfanityFilter2V223FilterProfanityResponse10getMessageEv(response) == response + 0x38); + Require(_ZNK3sce2Np9CppWebApi11UserProfile2V114PersonalDetail14getDisplayNameEv(response) == response + 0x88); + const std::uint64_t playerAccount = 0xfeed1234; + std::memcpy(response + 0x10, &playerAccount, sizeof(playerAccount)); + Require(_ZNK3sce2Np9CppWebApi14SessionManager2V127ResponsePlayerSessionPlayer12getAccountIdEv(response) == playerAccount); + + std::memset(response, 0, sizeof(response)); + IntrusivePtr headerOut{}; + Require(_ZN3sce2Np9CppWebApi6Common18ResponseHeaderBase14getHeaderValueEPKcRNS2_12IntrusivePtrINS2_6StringEEE(reinterpret_cast(response), "Content-Type", &headerOut) == static_cast(0x80553512)); + Require(_ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi26getMultiDataStatusesByUserEiRKNS4_37ParameterToGetMultiDataStatusesByUserERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_32GetMultiDataStatusesResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE() == static_cast(0x80552907)); + + std::uint64_t transactionSource[2] = {0xdeadbeefcafe, 0x0123456789ab}; + std::uint64_t transactionTarget[2] = {0, 0}; + Require(_ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEENS4_INS2_18ResponseHeaderBaseEEEEaSERKSB_(transactionTarget, transactionSource) == transactionTarget); + Require(transactionTarget[0] == transactionSource[0] && transactionTarget[1] == transactionSource[1]); + + alignas(8) unsigned char element[ObjectSize]; + std::memset(element, 0x5a, sizeof(element)); + WriteCount(element, 0, 1); + IntrusivePtr vector{}; + _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEEC1EPNS2_10LibContextE(&vector, &context); + IntrusivePtr owner{element, RecordDelete, &context}; + for (unsigned index = 0; index < 3; ++index) + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEE8pushBackERKS8_(&vector, &owner) == 0); + IntrusivePtr begin{}; + IntrusivePtr end{}; + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEE5beginEv(&begin, &vector) == &begin); + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEE3endEv(&end, &vector) == &end); + IntrusivePtr wrapper{&vector, nullptr, &context}; + auto* dereferenced = _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V53SkuEEEEEEptEv(&wrapper); + Require(dereferenced == &vector); + Require(_ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_13InGameCatalog2V53SkuEEEE4sizeEv(dereferenced) == 3); + IntrusivePtr step = begin; + unsigned visited = 0; + while (_ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEEneERKS9_(&step, &end)) { + Require(_ZN3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEEppEv(&step) == &step); + ++visited; + } + Require(visited == 3); + _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEED1Ev(&vector); + Require(ReadCount(element, 0) == 1 && deleterCalls == 0); + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEED1Ev(&owner); +} + int main(int argc, char** argv) { if (argc == 2 && std::strcmp(argv[1], "alias-vector") == 0) { TestVectorAliasedGrowth(); return 0; } Require(sizeof(IntrusivePtr) == 0x18); @@ -611,11 +865,14 @@ int main(int argc, char** argv) { TestMatchmakingSharedOwnership(); TestMatchTeamVectorSize(); TestVectorPushBackReleasesElements(); + TestVectorCopyFromSelfPreservesElements(); TestAdoptedStringVectorCountsReferences(); TestScalarVectorAssignment(); + TestVectorFunctionsAcceptTheHandleObject(); TestUserProfileVectorAssign(); TestUserProfileModelAndTransaction(); TestUserProfilePublicProfileRequest(); TestUserProfileBoolAndSize(); + TestResidualAccessors(); return 0; } diff --git a/core/libs/tests/GuestNpCppWebApiMatches.cpp b/core/libs/tests/GuestNpCppWebApiMatches.cpp index 10f6fe2c62..112cabe714 100644 --- a/core/libs/tests/GuestNpCppWebApiMatches.cpp +++ b/core/libs/tests/GuestNpCppWebApiMatches.cpp @@ -247,5 +247,71 @@ int main() { Require(_ZN3sce2Np9CppWebApi7Matches2V18MatchApi22ParameterToCreateMatch10initializeEPNS1_6Common10LibContextENS6_12IntrusivePtrINS3_18CreateMatchRequestEEE(createParameter.data(), &context, &joinBody) == 0); Require(_ZN3sce2Np9CppWebApi7Matches2V18MatchApi22ParameterToCreateMatch9terminateEv(createParameter.data()) == 0); + IntrusivePtr created{}; + Require(_ZN3sce2Np9CppWebApi7Matches2V125CreateMatchRequestFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_18CreateMatchRequestEEE(nullptr, "room", &created) == static_cast(0x80553503)); + Require(created.object == nullptr); + Require(_ZN3sce2Np9CppWebApi7Matches2V125CreateMatchRequestFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_18CreateMatchRequestEEE(&context, "room", nullptr) == static_cast(0x80553503)); + Require(_ZN3sce2Np9CppWebApi7Matches2V125CreateMatchRequestFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_18CreateMatchRequestEEE(&context, nullptr, &created) == static_cast(0x80553503)); + Require(_ZN3sce2Np9CppWebApi7Matches2V125CreateMatchRequestFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_18CreateMatchRequestEEE(&context, "room", &created) == 0); + Require(created.object != nullptr && created.deleter != nullptr && created.libContext == &context); + Require(Read(static_cast(created.object), 0) == 1); + Require(Read(static_cast(created.object), 24 + 0x08) != nullptr && std::strcmp(Read(static_cast(created.object), 24 + 0x08), "room") == 0); + created.deleter(created.object); + created = {}; + + Require(_ZN3sce2Np9CppWebApi7Matches2V120RemovedPlayerFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_13RemovedPlayerEEE(&context, "player", &created) == 0); + Require(created.object != nullptr && created.libContext == &context); + Require(Read(static_cast(created.object), 16 + 0x08) != nullptr && std::strcmp(Read(static_cast(created.object), 16 + 0x08), "player") == 0); + created.deleter(created.object); + created = {}; + + Require(_ZN3sce2Np9CppWebApi7Matches2V118AddedPlayerFactory6createEPNS1_6Common10LibContextEPKcNS3_10PlayerTypeEPNS5_12IntrusivePtrINS3_11AddedPlayerEEE(&context, "player", 7, &created) == 0); + Require(created.object != nullptr); + Require(Read(static_cast(created.object), 16 + 0x08) != nullptr && std::strcmp(Read(static_cast(created.object), 16 + 0x08), "player") == 0); + Require(Read(static_cast(created.object), 0x30) == 7); + created.deleter(created.object); + created = {}; + + Require(_ZN3sce2Np9CppWebApi7Matches2V126RequestMatchResultsFactory6createEPNS1_6Common10LibContextENS3_14ResultsVersionEPNS5_12IntrusivePtrINS3_19RequestMatchResultsEEE(&context, 3, &created) == 0); + Require(created.object != nullptr); + Require(Read(static_cast(created.object), 0x10) == 3); + created.deleter(created.object); + created = {}; + + Require(_ZN3sce2Np9CppWebApi7Matches2V131UpdateMatchStatusRequestFactory6createEPNS1_6Common10LibContextENS3_12UpdateStatusEPNS5_12IntrusivePtrINS3_24UpdateMatchStatusRequestEEE(&context, 2, &created) == 0); + Require(Read(static_cast(created.object), 0x18) == 2); + created.deleter(created.object); + created = {}; + + Require(_ZN3sce2Np9CppWebApi7Matches2V126RequestInGameRosterFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_19RequestInGameRosterEEE(&context, &created) == 0); + Require(created.object != nullptr && created.deleter != nullptr && created.libContext == &context); + created.deleter(created.object); + created = {}; + + alignas(8) std::array membersBody{}; + Write(membersBody.data(), 0x00, 0x434e507056656342ULL); + Write(membersBody.data(), 0x30, 1); + IntrusivePtr membersVector{}; + membersVector.object = membersBody.data(); + membersVector.libContext = &context; + Require(_ZN3sce2Np9CppWebApi7Matches2V123RequestMatchTeamFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_16RequestMatchTeamEEE(&context, "team", &created) == 0); + Require(_ZN3sce2Np9CppWebApi7Matches2V116RequestMatchTeam10setMembersERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_13RequestMemberEEEEE(created.object, &membersVector) == 0); + Require(Read(membersBody.data(), 0x30) == 2); + created.deleter(created.object); + Require(Read(membersBody.data(), 0x30) == 1); + created = {}; + + alignas(8) std::array rosterObject{}; + Write(rosterObject.data(), 0, 1); + IntrusivePtr rosterHandle{}; + rosterHandle.object = rosterObject.data(); + rosterHandle.libContext = &context; + Require(_ZN3sce2Np9CppWebApi7Matches2V125CreateMatchRequestFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_18CreateMatchRequestEEE(&context, "room", &created) == 0); + Require(_ZN3sce2Np9CppWebApi7Matches2V118CreateMatchRequest15setInGameRosterERKNS1_6Common12IntrusivePtrINS3_19RequestInGameRosterEEE(created.object, &rosterHandle) == 0); + Require(Read(rosterObject.data(), 0) == 2); + created.deleter(created.object); + Require(Read(rosterObject.data(), 0) == 1); + created = {}; + return 0; } diff --git a/core/libs/tests/GuestNpCppWebApiResponseHeader.cpp b/core/libs/tests/GuestNpCppWebApiResponseHeader.cpp new file mode 100644 index 0000000000..10334207b5 --- /dev/null +++ b/core/libs/tests/GuestNpCppWebApiResponseHeader.cpp @@ -0,0 +1,75 @@ +#include "prx/libSceNpCppWebApi/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool value, std::source_location at = std::source_location::current()) { + if (!value) { + std::fprintf(stderr, "requirement failed at %s:%u\n", at.file_name(), at.line()); + std::abort(); + } +} + +constexpr std::size_t kHeaderSize = 0x10; +constexpr std::size_t kGuardSize = 0x10; +constexpr std::size_t kReferenceCountOffset = 0x00; +constexpr std::size_t kHeadersOffset = 0x08; +constexpr int kBodyMissing = static_cast(0x80553512); +constexpr unsigned char kFill = 0xA5; + +struct Storage { + alignas(8) unsigned char bytes[kHeaderSize + kGuardSize]; +}; + +struct IntrusivePtrStorage { + alignas(8) unsigned char bytes[sizeof(IntrusivePtr) + kGuardSize]; +}; + +template +TValue Load(const Storage& storage, std::size_t offset) { + TValue value; + std::memcpy(&value, storage.bytes + offset, sizeof(value)); + return value; +} + +bool GuardIntact(const Storage& storage) { + for (std::size_t index = kHeaderSize; index < sizeof(storage.bytes); ++index) { + if (storage.bytes[index] != kFill) return false; + } + return true; +} + +void TestEmptyHeaderHasNoValue() { + Storage storage; + IntrusivePtrStorage value; + std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + std::memset(value.bytes, kFill, sizeof(value.bytes)); + + _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBaseC1Ev(reinterpret_cast(storage.bytes)); + Require(Load(storage, kReferenceCountOffset) == 0); + Require(Load(storage, kHeadersOffset) == nullptr); + Require(GuardIntact(storage)); + + Require(_ZN3sce2Np9CppWebApi6Common18ResponseHeaderBase14getHeaderValueEPKcRNS2_12IntrusivePtrINS2_6StringEEE( + reinterpret_cast(storage.bytes), "Content-Type", + reinterpret_cast(value.bytes)) == kBodyMissing); + Require(value.bytes[0] == kFill && value.bytes[sizeof(IntrusivePtr)] == kFill); + + _ZN3sce2Np9CppWebApi6Common18ResponseHeaderBaseD1Ev(reinterpret_cast(storage.bytes)); + Require(Load(storage, kReferenceCountOffset) == 0); + Require(Load(storage, kHeadersOffset) == nullptr); + Require(GuardIntact(storage)); +} + +} + +int main() { + TestEmptyHeaderHasNoValue(); + return 0; +} diff --git a/core/libs/tests/GuestNpCppWebApiSignedOut.cpp b/core/libs/tests/GuestNpCppWebApiSignedOut.cpp index 5d7ea3cf4f..6fac1aec91 100644 --- a/core/libs/tests/GuestNpCppWebApiSignedOut.cpp +++ b/core/libs/tests/GuestNpCppWebApiSignedOut.cpp @@ -144,11 +144,13 @@ static const RequestFlow flows[] = { using Lifecycle = void (APS5_VABI*)(void*); using Finish = int (APS5_VABI*)(void*); using Identifier = std::int64_t (APS5_VABI*)(); +using Response = int (APS5_VABI*)(const void*, void*); struct TransactionLifecycle { Lifecycle construct; Lifecycle destroy; Finish finish; + Response getResponse; }; static const TransactionLifecycle lifecycles[] = { @@ -156,11 +158,13 @@ static const TransactionLifecycle lifecycles[] = { _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS2_6VectorINS4_INS1_13InGameCatalog2V59ContainerEEEEEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev, _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS2_6VectorINS4_INS1_13InGameCatalog2V59ContainerEEEEEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev, _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS2_6VectorINS4_INS1_13InGameCatalog2V59ContainerEEEEEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv, + _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS2_6VectorINS4_INS1_13InGameCatalog2V59ContainerEEEEEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERSB_, }, { _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V132GetMultiDataStatusesResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev, _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V132GetMultiDataStatusesResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev, _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_17TitleCloudStorage2V132GetMultiDataStatusesResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE6finishEv, + _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_17TitleCloudStorage2V132GetMultiDataStatusesResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_, }, }; @@ -171,6 +175,10 @@ static const Call aborts[] = { _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V150PostPlayerSessionsSessionIdInvitationsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv, _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv, _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V161PostPlayerSessionsSessionIdJoinableSpecifiedUsersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv, + _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetPublicProfilesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5abortEv, }; static const Identifier identifiers[] = { @@ -179,6 +187,10 @@ static const Identifier identifiers[] = { _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv, _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V152PostPlayerSessionsSessionIdMemberPlayersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv, _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_14SessionManager2V161PostPlayerSessionsSessionIdJoinableSpecifiedUsersResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv, + _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V118GetFriendsResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv, + _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv, + _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv, + _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetPublicProfilesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5getIdEv, }; static void TestNeverStartedTransactions() { @@ -189,6 +201,12 @@ static void TestNeverStartedTransactions() { for (std::size_t index = 0; index < sizeof(transaction); ++index) Require(transaction[index] == (index < 0x10 ? 0 : 0x5a)); Require(lifecycle.finish(transaction) == TransactionNotStarted); + alignas(16) unsigned char response[0x18]; + std::memset(response, 0x5a, sizeof(response)); + Require(lifecycle.getResponse(transaction, response) == TransactionNotStarted); + for (const unsigned char byte : response) Require(byte == 0x5a); + for (std::size_t index = 0; index < sizeof(transaction); ++index) + Require(transaction[index] == (index < 0x10 ? 0 : 0x5a)); lifecycle.destroy(transaction); for (std::size_t index = 0; index < sizeof(transaction); ++index) Require(transaction[index] == (index < 0x10 ? 0 : 0x5a)); diff --git a/core/libs/tests/GuestNpCppWebApiString.cpp b/core/libs/tests/GuestNpCppWebApiString.cpp index 92b90704c1..a86e7f9fac 100644 --- a/core/libs/tests/GuestNpCppWebApiString.cpp +++ b/core/libs/tests/GuestNpCppWebApiString.cpp @@ -507,6 +507,58 @@ unsigned TestVectorAliased() { } +void TestVectorCopyFromStrings() { + std::uint64_t sourceContext = 1; + std::uint64_t targetContext = 2; + VectorStorage source; + VectorStorage target; + Storage element; + std::memset(source.bytes, kFill, sizeof(source.bytes)); + std::memset(target.bytes, kFill, sizeof(target.bytes)); + std::memset(element.bytes, kFill, sizeof(element.bytes)); + + _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEEC1EPNS2_10LibContextE(reinterpret_cast(source.bytes), &sourceContext); + _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEEC1EPNS2_10LibContextE(reinterpret_cast(target.bytes), &targetContext); + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(reinterpret_cast(element.bytes), &sourceContext); + const char* payload = "copied string element"; + Require(_ZN3sce2Np9CppWebApi6Common6String6appendEPKc(reinterpret_cast(element.bytes), payload) == 0); + for (unsigned index = 0; index < 3; ++index) + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE8pushBackERKS4_(reinterpret_cast(source.bytes), element.bytes) == 0); + + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE8copyFromERKS5_(reinterpret_cast(target.bytes), reinterpret_cast(source.bytes)) == 0); + void* sourceBody = LoadBody(source.bytes, kObjectOffset); + void* targetBody = LoadBody(target.bytes, kObjectOffset); + Require(targetBody != nullptr && targetBody != sourceBody); + Require(LoadBody(targetBody, kBodyElementKindOffset) == 2); + Require(LoadBody(targetBody, kBodySizeOffset) == 3); + const auto* sourceElements = LoadBody(sourceBody, kBodyElementsOffset); + const auto* targetElements = LoadBody(targetBody, kBodyElementsOffset); + for (unsigned index = 0; index < 3; ++index) { + const char* sourceBuffer = LoadBody(sourceElements + index * kStringSize, kBufferOffset); + const char* targetBuffer = LoadBody(targetElements + index * kStringSize, kBufferOffset); + Require(targetBuffer != nullptr && targetBuffer != sourceBuffer); + Require(std::strcmp(targetBuffer, payload) == 0); + Require(LoadBody(targetElements + index * kStringSize, kReferenceCountOffset) == 0); + } + + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE8copyFromERKS5_(reinterpret_cast(target.bytes), reinterpret_cast(source.bytes)) == 0); + Require(LoadBody(targetBody, kBodySizeOffset) == 3); + const auto* reloaded = LoadBody(targetBody, kBodyElementsOffset); + for (unsigned index = 0; index < 3; ++index) + Require(std::strcmp(LoadBody(reloaded + index * kStringSize, kBufferOffset), payload) == 0); + + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE8copyFromERKS5_(reinterpret_cast(target.bytes), reinterpret_cast(target.bytes)) == 0); + Require(LoadBody(targetBody, kBodySizeOffset) == 3); + const auto* selfCopied = LoadBody(targetBody, kBodyElementsOffset); + for (unsigned index = 0; index < 3; ++index) + Require(std::strcmp(LoadBody(selfCopied + index * kStringSize, kBufferOffset), payload) == 0); + + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(reinterpret_cast(element.bytes)); + _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEED1Ev(reinterpret_cast(source.bytes)); + _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEED1Ev(reinterpret_cast(target.bytes)); + Require(GuardIntact(element) && VectorGuardIntact(source) && VectorGuardIntact(target)); +} + int main(int argc, char** argv) { if (argc == 2 && std::strcmp(argv[1], "alias-append") == 0) return TestAppendAliased() ? 1 : 0; if (argc == 2 && std::strcmp(argv[1], "alias-vector") == 0) return TestVectorAliased(); @@ -517,6 +569,7 @@ int main(int argc, char** argv) { TestVectorPushBackCopiesStringContent(); TestVectorIterationVisitsStringsInOrder(); TestVectorIterationRejectsOtherBodies(); + TestVectorCopyFromStrings(); TestStringEmptyAndEquals(); TestStringCopyFromAndObservers(); TestIntrusivePtrVectorPushBack(); diff --git a/core/libs/tests/GuestNpCppWebApiTitleCloudStorage.cpp b/core/libs/tests/GuestNpCppWebApiTitleCloudStorage.cpp index da9a621cb3..bfefd1d03d 100644 --- a/core/libs/tests/GuestNpCppWebApiTitleCloudStorage.cpp +++ b/core/libs/tests/GuestNpCppWebApiTitleCloudStorage.cpp @@ -187,5 +187,10 @@ int main() { } TestInitializers(); TestSetters(); + alignas(8) IntrusivePtr emptyVector{}; + CommonIterator iterator{}; + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEE5beginEv(&iterator, &emptyVector) == &iterator); + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_17TitleCloudStorage2V110DataStatusEEEE3endEv(&iterator, &emptyVector) == &iterator); + Require(iterator.element == nullptr); return 0; } diff --git a/core/libs/tests/GuestNpSessionSignaling.cpp b/core/libs/tests/GuestNpSessionSignaling.cpp index 2ad4f01265..3077cc6038 100644 --- a/core/libs/tests/GuestNpSessionSignaling.cpp +++ b/core/libs/tests/GuestNpSessionSignaling.cpp @@ -31,11 +31,29 @@ int main() { Require(sceNpSessionSignalingTerminate() == NotInitialized, "terminate before initialize"); Require(sceNpSessionSignalingCreateContext2(nullptr, nullptr) == NotInitialized, "initialization check precedes context arguments"); Require(sceNpSessionSignalingGetConnectionStatus(0, 0, nullptr, nullptr, nullptr) == NotInitialized, "status before initialize"); + Require(sceNpSessionSignalingGetMemoryInfo(nullptr) == NotInitialized, "memory info before initialize"); + Require(sceNpSessionSignalingGetConnectionStatistics(nullptr) == NotInitialized, "statistics before initialize"); + Require(sceNpSessionSignalingGetGroupFromPeerAddress(0, nullptr, nullptr) == NotInitialized, "group from peer before initialize"); + Require(sceNpSessionSignalingGetCustomProperty(0, nullptr) == NotInitialized, "custom property before initialize"); + Require(sceNpSessionSignalingSetCustomProperty(0, nullptr, nullptr) == NotInitialized, "set custom property before initialize"); + Require(sceNpSessionSignalingRegisterInternalCallback(0, nullptr, nullptr) == NotInitialized, "register callback before initialize"); Require(sceNpSessionSignalingInitialize(nullptr) == InvalidArgument, "null initialize parameter"); Require(sceNpSessionSignalingInitialize(reinterpret_cast(initializeParam)) == InvalidArgument, "zero pool size"); const std::uint64_t poolSize = 4 * 1024 * 1024; std::memcpy(initializeParam + 8, &poolSize, sizeof(poolSize)); Require(sceNpSessionSignalingInitialize(reinterpret_cast(initializeParam)) == 0, "initialize failed"); + SessionSignalingMemoryInfo memoryInfo{}; + Require(sceNpSessionSignalingGetMemoryInfo(nullptr) == InvalidArgument, "null memory info"); + Require(sceNpSessionSignalingGetMemoryInfo(&memoryInfo) == 0, "memory info failed"); + Require(memoryInfo.poolSize == poolSize && memoryInfo.usedSize == 0 && memoryInfo.peakUsedSize == 0, "memory info contents"); + SessionSignalingConnectionStatistics statistics{}; + statistics.peakConnectionCount = 1; + statistics.connectionCount = 1; + statistics.connectingCount = 1; + statistics.connectedCount = 1; + Require(sceNpSessionSignalingGetConnectionStatistics(nullptr) == InvalidArgument, "null statistics"); + Require(sceNpSessionSignalingGetConnectionStatistics(&statistics) == 0, "statistics failed"); + Require(statistics.peakConnectionCount == 0 && statistics.connectionCount == 0 && statistics.connectingCount == 0 && statistics.connectedCount == 0, "statistics contents"); Require(sceNpSessionSignalingInitialize(nullptr) == AlreadyInitialized, "initialization check precedes initialize arguments"); alignas(8) unsigned char contextParam[56] = {}; std::uint32_t contextId = 0; @@ -62,6 +80,22 @@ int main() { Require(sceNpSessionSignalingDestroyContext(firstId) == ContextNotFound, "destroy stale context"); Require(sceNpSessionSignalingCreateContext2(reinterpret_cast(contextParam), &contextId) == 0, "reuse context slot"); Require(contextId != firstId, "reused context kept stale id"); + std::uint32_t previousProperty = 0; + int callbackMarker = 0; + int userDataMarker = 0; + Require(sceNpSessionSignalingRegisterInternalCallback(contextId, &callbackMarker, &userDataMarker) == 0, "register callback before context reuse"); + Require(sceNpSessionSignalingDestroyContext(contextId) == 0, "destroy context before reuse two"); + const std::int32_t secondReuseUser = 11; + std::memcpy(contextParam, &secondReuseUser, sizeof(secondReuseUser)); + Require(sceNpSessionSignalingCreateContext2(reinterpret_cast(contextParam), &contextId) == 0, "reuse context slot two"); + Require(sceNpSessionSignalingGetCustomProperty(contextId, &previousProperty) == 0 && previousProperty == 0, "reused context two kept state"); + Require(sceNpSessionSignalingRegisterInternalCallback(otherContexts[0], &callbackMarker, &userDataMarker) == 0, "register callback before context one reuse"); + Require(sceNpSessionSignalingDestroyContext(otherContexts[0]) == 0, "destroy context before reuse one"); + const std::int32_t thirdReuseUser = 12; + std::memcpy(contextParam, &thirdReuseUser, sizeof(thirdReuseUser)); + std::uint32_t reusedContext = 0; + Require(sceNpSessionSignalingCreateContext(reinterpret_cast(contextParam), &reusedContext) == 0, "reuse context slot one"); + Require(sceNpSessionSignalingGetCustomProperty(reusedContext, &previousProperty) == 0 && previousProperty == 0, "reused context one kept state"); std::uint32_t requestId = 0xa5a5a5a5u; Require(sceNpSessionSignalingRequestPrepare(firstId, &requestId) == ContextNotFound, "prepare stale context"); @@ -122,12 +156,24 @@ int main() { Require(sceNpSessionSignalingGetConnectionFromPeerAddress2(netContextId, 1, nullptr, reinterpret_cast(&found)) == InvalidArgument, "null group peer address"); Require(sceNpSessionSignalingGetConnectionFromPeerAddress2(netContextId, 1, reinterpret_cast(peer), nullptr) == InvalidArgument, "null connection id from group peer address"); Require(sceNpSessionSignalingGetConnectionFromPeerAddress2(netContextId, 1, reinterpret_cast(peer), reinterpret_cast(&found)) == GroupNotFound, "connection from group peer address without a session"); + Require(sceNpSessionSignalingGetGroupFromPeerAddress(netContextId, nullptr, reinterpret_cast(&found)) == InvalidArgument, "null group peer address"); + Require(sceNpSessionSignalingGetGroupFromPeerAddress(netContextId, reinterpret_cast(peer), nullptr) == InvalidArgument, "null group id from peer address"); + Require(sceNpSessionSignalingGetGroupFromPeerAddress(firstId, reinterpret_cast(peer), reinterpret_cast(&found)) == ContextNotFound, "group from peer address stale context"); + Require(sceNpSessionSignalingGetGroupFromPeerAddress(netContextId, reinterpret_cast(peer), reinterpret_cast(&found)) == GroupNotFound, "group from peer address without a session"); Require(sceNpSessionSignalingGetGroupInfo(firstId, 1, reinterpret_cast(info)) == ContextNotFound, "group info stale context"); Require(sceNpSessionSignalingGetGroupFromSessionId(firstId, reinterpret_cast(sessionId), reinterpret_cast(&found)) == ContextNotFound, "group from session id stale context"); Require(sceNpSessionSignalingGetConnectionFromNetAddress(firstId, 0x0100007fu, 3658, reinterpret_cast(&found)) == ContextNotFound, "connection from net address stale context"); Require(sceNpSessionSignalingGetConnectionFromNetAddress2(firstId, 1, 0x0100007fu, 3658, reinterpret_cast(&found)) == ContextNotFound, "connection from group net address stale context"); Require(sceNpSessionSignalingGetConnectionFromPeerAddress(firstId, reinterpret_cast(peer), reinterpret_cast(&found)) == ContextNotFound, "connection from peer address stale context"); Require(found == 0x5a5a5a5a, "a failed lookup wrote an id"); + std::uint32_t property = 0; + Require(sceNpSessionSignalingGetCustomProperty(firstId, &property) == ContextNotFound, "custom property stale context"); + Require(sceNpSessionSignalingGetCustomProperty(contextId, nullptr) == 0, "null custom property output"); + Require(sceNpSessionSignalingGetCustomProperty(contextId, &property) == 0 && property == 0, "default custom property"); + Require(sceNpSessionSignalingSetCustomProperty(contextId, nullptr, nullptr) == InvalidArgument, "null custom property value"); + Require(sceNpSessionSignalingSetCustomProperty(firstId, &property, nullptr) == ContextNotFound, "set custom property stale context"); + Require(sceNpSessionSignalingRegisterInternalCallback(firstId, nullptr, nullptr) == ContextNotFound, "register callback stale context"); + Require(sceNpSessionSignalingRegisterInternalCallback(contextId, nullptr, nullptr) == 0, "register callback"); const char* const activateSessionId = reinterpret_cast(sessionId); SessionSignalingSessionParam sessionParam{1, 0}; std::uint32_t activatedGroup = 0xa5a5a5a5u; @@ -184,4 +230,9 @@ int main() { Require(sceNpSessionSignalingRequestPrepare(contextId, &requestId) == ContextNotFound, "reinitialize retained a context"); Require(sceNpSessionSignalingTerminate() == 0, "final terminate"); Require(sceNpSessionSignalingCreateContext(reinterpret_cast(1), &contextId) == NotInitialized, "create context post-terminate"); + Require(sceNpSessionSignalingInitializeWithPort(nullptr, 0x217b, 0) == InvalidArgument, "null initialize with port parameter"); + Require(sceNpSessionSignalingInitializeWithPort(reinterpret_cast(initializeParam), 0x217b, 0) == 0, "initialize with port failed"); + Require(sceNpSessionSignalingInitializeWithPort(reinterpret_cast(initializeParam), 0x217b, 0) == AlreadyInitialized, "initialize with port after initialize"); + Require(sceNpSessionSignalingGetMemoryInfo(&memoryInfo) == 0 && memoryInfo.poolSize == poolSize && memoryInfo.usedSize == 0 && memoryInfo.peakUsedSize == 0, "memory info after initialize with port"); + Require(sceNpSessionSignalingTerminate() == 0, "final terminate after initialize with port"); } diff --git a/core/libs/tests/GuestNpUtilityOffline.cpp b/core/libs/tests/GuestNpUtilityOffline.cpp index f9604b54ae..721b099e6b 100644 --- a/core/libs/tests/GuestNpUtilityOffline.cpp +++ b/core/libs/tests/GuestNpUtilityOffline.cpp @@ -120,6 +120,60 @@ void CheckWordFilter() { Require(sceNpLookupDeleteTitleCtx(context) == InvalidId, "sceNpLookupDeleteTitleCtx", "a word filter context was found among the lookup contexts"); } +void CheckServiceChecker2() { + constexpr int AlreadyInitialized = static_cast(0x8055B301u); + constexpr int NotInitialized = static_cast(0x8055B302u); + constexpr int RequestNotFound = static_cast(0x8055B304u); + constexpr int CheckerInvalidArgument = static_cast(0x8055B305u); + int request = 0; + Require(sceNpServiceChecker2IntCreateRequest(&request) == NotInitialized, "sceNpServiceChecker2IntCreateRequest", "a request was created before initialization"); + Require(sceNpServiceChecker2IntInitialize() == 0, "sceNpServiceChecker2IntInitialize", "initialization failed"); + Require(sceNpServiceChecker2IntInitialize() == AlreadyInitialized, "sceNpServiceChecker2IntInitialize", "a second initialization was taken"); + Require(sceNpServiceChecker2IntCreateRequest(nullptr) == CheckerInvalidArgument, "sceNpServiceChecker2IntCreateRequest", "a null output was taken"); + Require(sceNpServiceChecker2IntCreateRequest(&request) == 0 && request == 1, "sceNpServiceChecker2IntCreateRequest", "the first request is not id 1"); + std::uint32_t availability = 0xA5A5A5A5; + Require(sceNpServiceChecker2IntGetServiceAvailability(request, nullptr, &availability) == CheckerInvalidArgument, "sceNpServiceChecker2IntGetServiceAvailability", "a null NpId was taken"); + Require(sceNpServiceChecker2IntGetServiceAvailability(request, selfNpId, nullptr) == CheckerInvalidArgument, "sceNpServiceChecker2IntGetServiceAvailability", "a null output was taken"); + Require(sceNpServiceChecker2IntGetServiceAvailability(request + 100, selfNpId, &availability) == RequestNotFound, "sceNpServiceChecker2IntGetServiceAvailability", "an unknown request was taken"); + Require(sceNpServiceChecker2IntGetServiceAvailability(request, selfNpId, &availability) == SignedOut, "sceNpServiceChecker2IntGetServiceAvailability", "the lookup did not return SIGNED_OUT"); + Require(availability == 0xA5A5A5A5, "sceNpServiceChecker2IntGetServiceAvailability", "an availability was written"); + Require(sceNpServiceChecker2IntGetServiceAvailabilityA(request, -1, &availability) == CheckerInvalidArgument, "sceNpServiceChecker2IntGetServiceAvailabilityA", "user -1 was taken"); + Require(sceNpServiceChecker2IntGetServiceAvailabilityA(request, 1, nullptr) == CheckerInvalidArgument, "sceNpServiceChecker2IntGetServiceAvailabilityA", "a null output was taken"); + Require(sceNpServiceChecker2IntGetServiceAvailabilityA(request + 100, 1, &availability) == RequestNotFound, "sceNpServiceChecker2IntGetServiceAvailabilityA", "an unknown request was taken"); + Require(sceNpServiceChecker2IntGetServiceAvailabilityA(request, 1, &availability) == SignedOut, "sceNpServiceChecker2IntGetServiceAvailabilityA", "the lookup did not return SIGNED_OUT"); + Require(availability == 0xA5A5A5A5, "sceNpServiceChecker2IntGetServiceAvailabilityA", "an availability was written"); + int second = 0; + Require(sceNpServiceChecker2IntCreateRequest(&second) == 0 && second == 2, "sceNpServiceChecker2IntCreateRequest", "the second request is not id 2"); + Require(sceNpServiceChecker2IntDestroyRequest(second) == 0, "sceNpServiceChecker2IntDestroyRequest", "destroying the request failed"); + Require(sceNpServiceChecker2IntGetServiceAvailability(second, selfNpId, &availability) == RequestNotFound, "sceNpServiceChecker2IntDestroyRequest", "a destroyed request was found"); + Require(sceNpServiceChecker2IntAbortRequest(request) == 0, "sceNpServiceChecker2IntAbortRequest", "aborting the request failed"); + Require(sceNpServiceChecker2IntAbortRequest(request + 100) == RequestNotFound, "sceNpServiceChecker2IntAbortRequest", "an unknown abort request was accepted"); + Require(sceNpServiceChecker2IntDestroyRequest(request + 100) == 0, "sceNpServiceChecker2IntDestroyRequest", "the destroy wrapper rejected an unknown request"); + const std::uint32_t serviceTypes[1] = {0x5}; + Require(sceNpServiceChecker2IntIsSetServiceType(serviceTypes, 0) == 1, "sceNpServiceChecker2IntIsSetServiceType", "bit 0 was not set"); + Require(sceNpServiceChecker2IntIsSetServiceType(serviceTypes, 1) == 0, "sceNpServiceChecker2IntIsSetServiceType", "bit 1 was set"); + Require(sceNpServiceChecker2IntIsSetServiceType(serviceTypes, 2) == 1, "sceNpServiceChecker2IntIsSetServiceType", "bit 2 was not set"); + Require(sceNpServiceChecker2IntIsSetServiceType(serviceTypes, 10) == 0, "sceNpServiceChecker2IntIsSetServiceType", "type 10 was taken"); + Require(sceNpServiceChecker2IntIsSetServiceType(nullptr, 0) == 0, "sceNpServiceChecker2IntIsSetServiceType", "a null mask was taken"); + Require(sceNpServiceChecker2IntFinalize() == 0, "sceNpServiceChecker2IntFinalize", "finalization failed"); + Require(sceNpServiceChecker2IntInitialize() == 0, "sceNpServiceChecker2IntInitialize", "reinitialization failed"); + int boundaryId = 0; + for (std::uint32_t index = 0; index < 0x1000; ++index) { + Require(sceNpServiceChecker2IntCreateRequest(&boundaryId) == 0, "sceNpServiceChecker2IntCreateRequest", "a request below the host limit was refused"); + } + const int lastId = boundaryId; + Require(sceNpServiceChecker2IntCreateRequest(&boundaryId) == TooManyObjects, "sceNpServiceChecker2IntCreateRequest", "the host request limit was not enforced"); + Require(boundaryId == lastId, "sceNpServiceChecker2IntCreateRequest", "the request output changed on refusal"); + Require(sceNpServiceChecker2IntGetServiceAvailability(lastId, selfNpId, &availability) == SignedOut, "sceNpServiceChecker2IntGetServiceAvailability", "an existing request was lost at the host limit"); + Require(sceNpServiceChecker2IntDestroyRequest(lastId) == 0, "sceNpServiceChecker2IntDestroyRequest", "destroying a request at the host limit failed"); + for (std::uint32_t index = 0; index < 0x2000; ++index) { + Require(sceNpServiceChecker2IntCreateRequest(&boundaryId) == 0, "sceNpServiceChecker2IntCreateRequest", "the host limit counted destroyed requests"); + Require(sceNpServiceChecker2IntDestroyRequest(boundaryId) == 0, "sceNpServiceChecker2IntDestroyRequest", "destroying a recycled request failed"); + } + Require(sceNpServiceChecker2IntFinalize() == 0, "sceNpServiceChecker2IntFinalize", "finalization after the host limit failed"); + Require(sceNpServiceChecker2IntCreateRequest(&request) == NotInitialized, "sceNpServiceChecker2IntCreateRequest", "a request was created after finalization"); +} + void CheckBandwidthTestInitStart() { Require(sceNpBandwidthTestInitStart() == SignedOut, "sceNpBandwidthTestInitStart", "starting a test while signed out did not return SIGNED_OUT"); Require(sceNpBandwidthTestInitStart() == sceNpBandwidthTestInitStartDownload(nullptr), "sceNpBandwidthTestInitStart", "the answer differs from sceNpBandwidthTestInitStartDownload"); @@ -259,6 +313,7 @@ int main() { std::strcpy(reinterpret_cast(selfNpId), "player_1"); CheckBandwidthTestInitStart(); CheckBandwidthTestShutdownWithDetailedInfo(); + CheckServiceChecker2(); CheckLookup(); CheckWordFilter(); CheckLookupAsync(); diff --git a/core/libs/tests/GuestPadTracker.cpp b/core/libs/tests/GuestPadTracker.cpp index 2fb34b53f5..0a358c8b6c 100644 --- a/core/libs/tests/GuestPadTracker.cpp +++ b/core/libs/tests/GuestPadTracker.cpp @@ -18,6 +18,8 @@ int main() { std::array second{kFill, kFill}; Require(scePadTrackerGetWorkingMemorySize(nullptr, nullptr) == kErrInvalidArg); + Require(scePadTrackerGetWorkingMemorySize(reinterpret_cast(0x10), second.data()) == kErrInvalidArg); + Require(scePadTrackerGetWorkingMemorySize(first.data(), reinterpret_cast(0x10)) == kErrInvalidArg); Require(scePadTrackerGetWorkingMemorySize(first.data(), nullptr) == kErrInvalidArg); Require(scePadTrackerGetWorkingMemorySize(nullptr, second.data()) == kErrInvalidArg); Require(first[0] == kFill && first[1] == kFill && second[0] == kFill && second[1] == kFill); diff --git a/core/libs/tests/GuestPadVibrationMode.cpp b/core/libs/tests/GuestPadVibrationMode.cpp index 40dd9f6648..b5b1029a9c 100644 --- a/core/libs/tests/GuestPadVibrationMode.cpp +++ b/core/libs/tests/GuestPadVibrationMode.cpp @@ -34,4 +34,8 @@ int main() { Require(scePadSetVibrationMode(special, 1) == PAD_OK); Require(scePadSetVibrationMode(special, 4) == PAD_ERROR_INVALID_ARG); Require(scePadClose_nid_postfix(special) == PAD_OK); + Require(scePadSetVibrationMode(special, 1) == PAD_ERROR_INVALID_HANDLE); + Require(scePadClose_nid_postfix(-1) == PAD_ERROR_INVALID_HANDLE); + Require(scePadClose_nid_postfix(0) == PAD_ERROR_INVALID_HANDLE); + Require(scePadClose_nid_postfix(12345) == PAD_ERROR_INVALID_HANDLE); } diff --git a/core/libs/tests/GuestPoll.cpp b/core/libs/tests/GuestPoll.cpp index 78e6059b12..4b145d21e9 100644 --- a/core/libs/tests/GuestPoll.cpp +++ b/core/libs/tests/GuestPoll.cpp @@ -388,6 +388,60 @@ static void PollsAPipeClosedDuringTheWait() { } } +static void PollsASocketClosedDuringTheWait() { + for (int attempt = 0; attempt < 20; ++attempt) { + const int socket = socket_nid_postfix(2, 2, 0); + Require(socket >= 0); + int result = 0; + short revents = 0; + std::atomic started{false}; + std::thread waiter([&] { + GuestPollDescriptor entry{socket, In, 0}; + started = true; + result = poll_nid_postfix(&entry, 1, 200); + revents = entry.revents; + }); + while (!started) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::microseconds(1000 + attempt * 100)); + Require(close_nid_postfix(socket) == 0); + waiter.join(); + Require(result == 1 && revents == Invalid); + } +} + +static void RejectsReadinessAfterSocketClose() { + for (int attempt = 0; attempt < 20; ++attempt) { + const int receiver = socket_nid_postfix(2, 2, 0); + Require(receiver >= 0); + std::array address{16, 2, 0, 0, 127, 0, 0, 1}; + Require(bind_nid_postfix(receiver, address.data(), address.size()) == 0); + std::uint32_t size = address.size(); + Require(getsockname_nid_postfix(receiver, address.data(), &size) == 0); + const int alias = dup_nid_postfix(receiver); + Require(alias >= 0); + const NativeSocket sender = ::socket(AF_INET, SOCK_DGRAM, 0); + Require(sender != NativeInvalid); + sockaddr_in destination{}; + destination.sin_family = AF_INET; + std::memcpy(&destination.sin_port, address.data() + 2, 2); + std::memcpy(&destination.sin_addr, address.data() + 4, 4); + GuestPollDescriptor entry{receiver, In, 0}; + std::atomic started{false}; + int result = -1; + std::thread waiter([&] { + started = true; + result = poll_nid_postfix(&entry, 1, 200); + }); + while (!started) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + Require(close_nid_postfix(receiver) == 0); + Require(::sendto(sender, "x", 1, 0, reinterpret_cast(&destination), sizeof(destination)) == 1); + waiter.join(); + CloseNative(sender); + Require(close_nid_postfix(alias) == 0); + Require(result == 1 && entry.revents == Invalid); + } +} int main() { for (const int descriptor : {0, 1, 2}) StandardDescriptorFile(descriptor); PollsUrgentData(); @@ -495,5 +549,7 @@ int main() { Require(std::chrono::steady_clock::now() - inputStart >= std::chrono::milliseconds(25)); PollsKqueues(listener); Require(close_nid_postfix(listener) == 0); + PollsASocketClosedDuringTheWait(); + RejectsReadinessAfterSocketClose(); PollsSocketsShutForReading(); } diff --git a/core/libs/tests/GuestRandom.cpp b/core/libs/tests/GuestRandom.cpp index 661667f77f..e677394f5c 100644 --- a/core/libs/tests/GuestRandom.cpp +++ b/core/libs/tests/GuestRandom.cpp @@ -10,6 +10,7 @@ int main() { constexpr int invalid = static_cast(0x817C0016); unsigned char buffer[80]; Require(sceRandomGetRandomNumber(nullptr, 16) == invalid); + Require(sceRandomGetRandomNumber(reinterpret_cast(0x10), 16) == invalid); Require(sceRandomGetRandomNumber(nullptr, 0) == invalid); Require(sceRandomGetRandomNumber(nullptr, 64) == invalid); Require(sceRandomGetRandomNumber(nullptr, 65) == invalid); diff --git a/core/libs/tests/GuestS3da.cpp b/core/libs/tests/GuestS3da.cpp new file mode 100644 index 0000000000..a36f635ac5 --- /dev/null +++ b/core/libs/tests/GuestS3da.cpp @@ -0,0 +1,59 @@ +#include "prx/libSceS3da/include/Api.hpp" +#include +#include +#include + +#ifndef _WIN32 +extern "C" [[noreturn]] void NotImplemented_nid_no_patch(const char* name) { + (void)name; + std::abort(); +} +#endif + +namespace { + +void Require(bool value) { + if (!value) { + std::abort(); + } +} + +constexpr int ERROR_INVALID_ARGUMENT = static_cast(0x81600002u); +constexpr int ERROR_ALREADY_OPEN = static_cast(0x81600006u); + +} + +int main() { + std::uint8_t dataBuffer[64]{}; + + for (const std::uint32_t port : {2u, 3u, 32u, 0xFFFFFFFFu}) { + Require(sceS3daOpen(port) == ERROR_INVALID_ARGUMENT); + Require(sceS3daClose(port) == ERROR_INVALID_ARGUMENT); + Require(sceS3daGetInfo(port, nullptr) == ERROR_INVALID_ARGUMENT); + Require(sceS3daSendData(port, dataBuffer, sizeof(dataBuffer)) == ERROR_INVALID_ARGUMENT); + Require(sceS3daSendData(port, nullptr, sizeof(dataBuffer)) == ERROR_INVALID_ARGUMENT); + Require(sceS3daSendData(port, dataBuffer, 0) == ERROR_INVALID_ARGUMENT); + Require(sceS3daSendData(port, dataBuffer, 0xA001u) == ERROR_INVALID_ARGUMENT); + } + + Require(sceS3daClose(0) == ERROR_INVALID_ARGUMENT); + Require(sceS3daClose(1) == ERROR_INVALID_ARGUMENT); + + Require(sceS3daOpen(0) == 0); + Require(sceS3daOpen(0) == ERROR_ALREADY_OPEN); + Require(sceS3daClose(0) == 0); + Require(sceS3daClose(0) == ERROR_INVALID_ARGUMENT); + + Require(sceS3daOpen(1) == 1); + Require(sceS3daOpen(1) == ERROR_ALREADY_OPEN); + Require(sceS3daClose(1) == 0); + Require(sceS3daClose(1) == ERROR_INVALID_ARGUMENT); + + for (const std::uint32_t port : {0u, 1u}) { + Require(sceS3daSendData(port, nullptr, sizeof(dataBuffer)) == ERROR_INVALID_ARGUMENT); + Require(sceS3daSendData(port, dataBuffer, 0) == ERROR_INVALID_ARGUMENT); + Require(sceS3daSendData(port, dataBuffer, 0xA001u) == ERROR_INVALID_ARGUMENT); + } + + return 0; +} diff --git a/core/libs/tests/GuestSandboxCertificateFailure.cpp b/core/libs/tests/GuestSandboxCertificateFailure.cpp new file mode 100644 index 0000000000..fe35a51064 --- /dev/null +++ b/core/libs/tests/GuestSandboxCertificateFailure.cpp @@ -0,0 +1,56 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/General.hpp" +#include "prx/libkernel/KernelErrors.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "Sandbox certificate failure check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +static void Clear(const std::filesystem::path& root) { + std::error_code error; + if (!std::filesystem::exists(root, error)) return; + for (std::filesystem::recursive_directory_iterator entry(root, error), end; !error && entry != end; entry.increment(error)) + std::filesystem::permissions(entry->path(), std::filesystem::perms::owner_write, std::filesystem::perm_options::add, error); + std::filesystem::remove_all(root, error); +} + +int main() { +#ifndef _WIN32 + const auto bundle = std::filesystem::absolute("host_bundle.pem"); + { + std::ofstream file(bundle, std::ios::binary | std::ios::trunc); + file << "-----BEGIN CERTIFICATE-----\nMIIBAA==\n-----END CERTIFICATE-----\n"; + } + Require(setenv("SSL_CERT_FILE", bundle.c_str(), 1) == 0); +#endif + const std::string path = "/common/cert/CA_LIST.cer"; + const auto cache = std::filesystem::current_path() / "cert_cache"; + Clear(cache); + + { std::ofstream file(cache, std::ios::binary | std::ios::trunc); file << "not a directory"; Require(static_cast(file)); } + Require(sceKernelOpen(path.c_str(), 0, 0) == SCE_KERNEL_ERROR_ENOENT); + Require(std::filesystem::remove(cache)); + + std::filesystem::create_directories(cache / "CA_LIST.cer"); + Require(sceKernelOpen(path.c_str(), 0, 0) == SCE_KERNEL_ERROR_ENOENT); + Require(std::filesystem::remove(cache / "CA_LIST.cer")); + for (const auto& entry : std::filesystem::directory_iterator(cache)) Require(entry.path().extension() != ".tmp"); + + const int descriptor = sceKernelOpen(path.c_str(), 0, 0); + Require(descriptor >= 0); + Require(sceKernelClose(descriptor) == 0); + Clear(cache); + return 0; +} diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index 47a78c252d..ea2edc5537 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -23,6 +23,8 @@ #include "tests/TemporaryFile.hpp" #include #include +#include +#include #include #include #include @@ -60,6 +62,19 @@ static bool FailResolverLookup(const char* hostname, int family, std::vector blockedResolverLookup{0}; + +static bool LoopbackResolverLookup(const char* hostname, int family, std::vector>& addresses) { + if (hostname != nullptr && std::strcmp(hostname, "guest-sce-net.resolver-blocked") == 0) { + blockedResolverLookup = 1; + while (blockedResolverLookup == 1) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } else if (hostname == nullptr || std::strcmp(hostname, "guest-sce-net.resolver-epoll") != 0 || family != AF_INET) { + return false; + } + addresses.push_back({127, 0, 0, 1}); + return true; +} + class ScopedResolverLookupOverride { public: explicit ScopedResolverLookupOverride(GuestSceNetTest::ResolverLookup lookup) { @@ -331,9 +346,14 @@ static void CheckPoolErrors() { static void CheckStatisticsInfo() { Require(Failed(sceNetGetStatisticsInfo(nullptr, 0), 14)); + Require(errno == 14); + Require(Failed(sceNetGetStatisticsInfo(reinterpret_cast(std::uintptr_t{0x10}), 0), 14)); + Require(errno == 14); NetStatisticsInfo info; std::memset(&info, 0xaa, sizeof(info)); + errno = 71; Require(sceNetGetStatisticsInfo(&info, 0) == 0); + Require(errno == 71); Require(sizeof(info) == 24); Require(info.kernel_mem_free_size > 0 && info.kernel_mem_free_min > 0); Require(info.libnet_mem_free_size > 0 && info.libnet_mem_free_min > 0); @@ -342,6 +362,7 @@ static void CheckStatisticsInfo() { Require(info.packet_count == 0 && info.packet_qos_count == 0); NetStatisticsInfo again; Require(sceNetGetStatisticsInfo(&again, 0x1234) == 0); + Require(errno == 71); Require(std::memcmp(&again, &info, sizeof(info)) == 0); } @@ -484,11 +505,16 @@ static void CheckDefaultRoute() { static void CheckRandom() { Require(Failed(sceNetGetRandom(nullptr), 22)); + Require(errno == 22); std::array values{}; + errno = 71; for (auto& value : values) Require(sceNetGetRandom(&value) == 0); + Require(errno == 71); std::array guarded{0x5a5a5a5au, 0x5a5a5a5au}; - Require(sceNetGetRandom(guarded.data()) == 0 && guarded[1] == 0x5a5a5a5au && *sceNetErrnoLoc() == 22); + Require(sceNetGetRandom(guarded.data()) == 0 && guarded[1] == 0x5a5a5a5au && *sceNetErrnoLoc() == 22 && errno == 71); Require(std::any_of(values.begin() + 1, values.end(), [&](std::uint32_t value) { return value != values[0]; })); + Require(Failed(sceNetGetRandom(reinterpret_cast(std::uintptr_t{0x10})), 14)); + Require(errno == 14); } static void CheckByteOrderConversion() { @@ -775,6 +801,77 @@ static void CheckEmptyEpoll() { } } +static void CheckResolverEpoll() { + constexpr std::uint32_t in = 0x1, hup = 0x10, descid = 0x10000; + constexpr int async = 0x1; + ScopedResolverLookupOverride lookupOverride(LoopbackResolverLookup); + const int resolver = sceNetResolverCreate("epoll-resolver", 0, 0); + const int epoll = sceNetEpollCreate("resolver-epoll", 0); + Require(resolver >= 1024 && resolver < 0x4000 && epoll >= 0x4000); + const int second = sceNetResolverCreate("second-resolver", 0, 0); + Require(second > resolver && second < 0x4000); + Require(sceNetResolverDestroy(second) == 0); + Require(sceNetResolverCreate("reused-resolver", 0, 0) == second); + Require(sceNetResolverDestroy(second) == 0); + NetEpollEvent registration{}; + registration.events = in; + registration.data.u64 = 0x1234; + Require(sceNetEpollControl(epoll, 1, resolver, ®istration) == 0); + Require(Failed(sceNetEpollControl(epoll, 1, resolver, ®istration), 48)); + NetEpollEvent ready{}; + Require(sceNetEpollWait(epoll, &ready, 1, 0) == 0); + + std::uint32_t address = 0; + Require(sceNetResolverStartNtoa(resolver, "guest-sce-net.resolver-epoll", &address, 0, 0, 0) == 0); + Require(sceNetEpollWait(epoll, &ready, 1, 0) == 0); + address = 0; + Require(sceNetResolverStartNtoa(resolver, "guest-sce-net.resolver-epoll", &address, 0, 0, async) == 0); + Require(address == 0x0100007fu); + Require(sceNetEpollWait(epoll, &ready, 1, 0) == 1); + Require(ready.events == (in | descid) && ready.ident == static_cast(resolver) && ready.data.u64 == 0x1234 && ready.reserved == 0); + Require(sceNetEpollWait(epoll, &ready, 1, 0) == 0); + + Require(sceNetResolverStartNtoa(resolver, "guest-sce-net.unknown", &address, 0, 0, async) == 0); + int status = 0; + Require(sceNetResolverGetError(resolver, &status) == 0 && status == static_cast(0x804101E1u)); + Require(sceNetEpollWait(epoll, &ready, 1, 0) == 1 && ready.events == (in | descid)); + + auto waiter = std::async(std::launch::async, [epoll] { + NetEpollEvent woken{}; + return std::make_pair(sceNetEpollWait(epoll, &woken, 1, -1), woken.events); + }); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + Require(waiter.wait_for(std::chrono::milliseconds(0)) == std::future_status::timeout); + Require(sceNetResolverStartNtoa(resolver, "guest-sce-net.resolver-epoll", &address, 0, 0, async) == 0); + Require(waiter.wait_for(std::chrono::seconds(1)) == std::future_status::ready); + Require(waiter.get() == std::make_pair(1, in | descid)); + + const int writer = sceNetEpollCreate("resolver-writer", 0); + registration.events = 0x2; + Require(sceNetEpollControl(writer, 1, resolver, ®istration) == 0); + Require(sceNetResolverStartNtoa(resolver, "guest-sce-net.resolver-epoll", &address, 0, 0, async) == 0); + Require(sceNetEpollWait(writer, &ready, 1, 0) == 0); + Require(sceNetEpollControl(writer, 3, resolver, nullptr) == 0); + Require(Failed(sceNetEpollControl(writer, 3, resolver, nullptr), 2)); + Require(sceNetEpollDestroy(writer) == 0); + Require(sceNetEpollWait(epoll, &ready, 1, 0) == 1); + + auto aborted = std::async(std::launch::async, [resolver, &address] { + return sceNetResolverStartNtoa(resolver, "guest-sce-net.resolver-blocked", &address, 0, 0, async); + }); + while (blockedResolverLookup != 1) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + Require(sceNetResolverAbort(resolver, 0) == 0); + blockedResolverLookup = 0; + Require(aborted.get() == 0); + Require(sceNetResolverGetError(resolver, &status) == 0 && status == static_cast(0x80410104u)); + Require(sceNetEpollWait(epoll, &ready, 1, 0) == 1 && ready.events == (in | descid | hup)); + + Require(sceNetResolverDestroy(resolver) == 0); + Require(sceNetEpollWait(epoll, &ready, 1, 0) == 0); + Require(Failed(sceNetEpollControl(epoll, 1, resolver, ®istration), 9)); + Require(sceNetEpollDestroy(epoll) == 0); +} + static void CheckRejectedArguments() { Require(Failed(sceNetEpollCreate(nullptr, 0), 22)); for (const int flags : {1, 0x10000, -1}) Require(Failed(sceNetEpollCreate("rejected-epoll", flags), 22)); @@ -1183,6 +1280,25 @@ static void CheckIoctl() { Require(Failed(sceNetIoctl(socket, 0x4004667f, reinterpret_cast(std::uintptr_t{0x10})), 14)); Require(Failed(sceNetIoctl(socket, 123, &readable), 25)); Require(Failed(sceNetIoctl(socket, 123, nullptr), 25)); + for (const int value : {0, 3}) { + int request = value; + Require(ioctl_nid_postfix(socket, 0x8004667e, &request) == 0); + int nonblocking = -1; + std::uint32_t length = sizeof(nonblocking); + Require(sceNetGetsockopt(socket, 0xffff, 0x1200, &nonblocking, &length) == 0 && nonblocking == (value != 0 ? 1 : 0)); + Require(fcntl_nid_postfix(socket, 3) == (value != 0 ? 2 | 4 : 2)); + } + Require(sceNetSendto(socket, payload, sizeof(payload), 0, address.data(), address.size()) == sizeof(payload)); + entry.revents = 0; + Require(poll_nid_postfix(&entry, 1, 1000) == 1 && entry.revents == 1); + readable = -1; + Require(ioctl_nid_postfix(socket, 0x4004667f, &readable) == 0 && readable == sizeof(payload)); + Require(sceNetRecv(socket, buffer, sizeof(buffer), 0) == sizeof(payload)); + Require(ioctl_nid_postfix(socket, 0x4004667f, &readable) == 0 && readable == 0); + *__error_nid_postfix() = 0; + Require(ioctl_nid_postfix(socket, 0x4004667f, nullptr) == -1 && *__error_nid_postfix() == 14); + *__error_nid_postfix() = 0; + Require(ioctl_nid_postfix(socket, 0x8004667e, nullptr) == -1 && *__error_nid_postfix() == 14); Require(sceNetSocketClose(socket) == 0); Require(Failed(sceNetIoctl(socket, 0x8004667e, &enabled), 9)); Require(Failed(sceNetIoctl(-1, 0x4004667f, &readable), 9)); @@ -2414,6 +2530,7 @@ int main() { CheckNetMemory(); CheckNetstat(); CheckEmptyEpoll(); + CheckResolverEpoll(); CheckIfFlags(); CheckRejectedArguments(); CheckDontWait(); diff --git a/core/libs/tests/GuestSceV2LocaleBasics.cpp b/core/libs/tests/GuestSceV2LocaleBasics.cpp new file mode 100644 index 0000000000..2412c4ac1c --- /dev/null +++ b/core/libs/tests/GuestSceV2LocaleBasics.cpp @@ -0,0 +1,206 @@ +#include "prx/libc/include/ApplicationHeap.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/GuestLocale.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +struct TypeRecord { const void* vtable; const char* name; const TypeRecord* base; }; + +using GuestWhat = const char* (APS5_VABI *)(const LibcException::ExceptionObject*); +using Thrower = void (APS5_VABI *)(const void*); + +extern "C" { +extern const TypeRecord _ZTISt9exception_nid_postfix; +extern GuestLocale::Implementation* _ZNSt8__sce_v26locale7_Locimp8_ClocptrE_nid_postfix; +extern GuestLocale::Implementation* _ZNSt8__sce_v221_sceLibcClassicLocaleE_nid_postfix; + +void APS5_VABI SceV2Catch(Thrower thrower, const void* argument); + +const void* SceV2CatchType = nullptr; +const void* SceV2OtherType = nullptr; +int SceV2Selector = 0; +} + +void* APS5_VABI TestHeapAllocate(std::size_t bytes) { return std::malloc(bytes); } +void APS5_VABI TestHeapFree(void* pointer) { std::free(pointer); } +void* APS5_VABI TestHeapCalloc(std::size_t count, std::size_t bytes) { return std::calloc(count, bytes); } +void* APS5_VABI TestHeapReallocate(void* pointer, std::size_t bytes) { return std::realloc(pointer, bytes); } +void* APS5_VABI TestHeapAlign(std::size_t, std::size_t bytes) { return std::malloc(bytes); } +void* APS5_VABI TestHeapRealign(void* pointer, std::size_t bytes, std::size_t) { return std::realloc(pointer, bytes); } +int APS5_VABI TestHeapPosixAlign(void** pointer, std::size_t, std::size_t bytes) { + *pointer = std::malloc(bytes); + return *pointer != nullptr ? 0 : -1; +} + +static unsigned failures = 0; +static std::string inspected; + +static void Require(bool value, const char* what) { + if (value) return; + std::fprintf(stderr, "FAIL: %s\n", what); + ++failures; +} + +extern "C" void APS5_VABI SceV2Inspect(void* object) { + const auto* exception = static_cast(object); + const auto* vtable = static_cast(exception->vtable); + inspected = static_cast(vtable[-1])->name; + inspected += '|'; + inspected += reinterpret_cast(const_cast(vtable[2]))(exception); +} + +enum class LocinfoCall { Check, Construct, Addcats }; + +struct LocinfoArguments { LocinfoCall call; GuestLocale::SceV2Locinfo* self; int category; const char* name; }; + +extern "C" void APS5_VABI SceV2CallLocinfo(const void* argument) { + const auto* arguments = static_cast(argument); + if (arguments->call == LocinfoCall::Check) _ZNSt8__sce_v215_sceLibcLocinfoEPKc_nid_postfix(arguments->name); + if (arguments->call == LocinfoCall::Construct) _ZNSt8__sce_v28_Locinfo13_Locinfo_ctorEPS0_iPKc_nid_postfix(arguments->self, arguments->category, arguments->name); + if (arguments->call == LocinfoCall::Addcats) _ZNSt8__sce_v28_Locinfo16_Locinfo_AddcatsEPS0_iPKc_nid_postfix(arguments->self, arguments->category, arguments->name); +} + +static bool ThrowsBadLocaleName(const LocinfoArguments& arguments) { + const TypeRecord runtimeError {_ZTISt9exception_nid_postfix.vtable, "NSt8__sce_v213runtime_errorE", nullptr}; + const TypeRecord logicError {_ZTISt9exception_nid_postfix.vtable, "NSt8__sce_v211logic_errorE", nullptr}; + SceV2CatchType = &runtimeError; + SceV2OtherType = &logicError; + SceV2Selector = 0; + inspected.clear(); + SceV2Catch(reinterpret_cast(SceV2CallLocinfo), &arguments); + return SceV2Selector == 2 && inspected == "NSt8__sce_v213runtime_errorE|bad locale name"; +} + +static bool IsEmptyLocimp(const GuestLocale::Implementation* locimp, bool transparent) { + return locimp != nullptr && locimp->base.vtable != nullptr && locimp->base.references == 1 && locimp->facets == nullptr + && locimp->facetCount == 0 && locimp->category == 0 && locimp->transparent == transparent && locimp->name != nullptr + && std::strcmp(locimp->name, "*") == 0; +} + +static void CheckClassic() { + auto* classic = _ZNSt8__sce_v221_sceLibcClassicLocaleE_nid_postfix; + Require(_ZNSt8__sce_v26locale7_Locimp8_ClocptrE_nid_postfix == classic, "_Clocptr points at the classic _Locimp"); + auto* facade = _ZNSt8__sce_v26locale7classicEv_nid_postfix(); + Require(facade != nullptr && facade->implementation == classic, "classic() holds the classic _Locimp"); + Require(_ZNSt8__sce_v26locale7classicEv_nid_postfix() == facade, "classic() returns the same object"); + Require(_ZNSt8__sce_v26locale16_GetgloballocaleEv_nid_postfix() == classic, "the global locale starts as the classic one"); + Require(_ZNSt8__sce_v26locale5_InitEb_nid_postfix(false) == classic, "_Init returns the global locale"); +} + +static void CheckEmpty() { + GuestLocaleFacade first {}; + GuestLocaleFacade second {}; + Require(_ZNSt8__sce_v26locale5emptyEv_nid_postfix(&first) == &first, "empty() returns its result"); + Require(IsEmptyLocimp(first.implementation, true), "empty() builds a transparent _Locimp named *"); + _ZNSt8__sce_v26locale5emptyEv_nid_postfix(&second); + Require(second.implementation != first.implementation, "empty() builds a new _Locimp each time"); + Require(IsEmptyLocimp(_ZNSt8__sce_v26locale7_Locimp11_New_LocimpEb_nid_postfix(false), false), "_New_Locimp(false) builds an opaque _Locimp"); + Require(IsEmptyLocimp(_ZNSt8__sce_v26locale7_Locimp11_New_LocimpEb_nid_postfix(true), true), "_New_Locimp(true) builds a transparent _Locimp"); +} + +static void CheckGlobal() { + auto* classic = _ZNSt8__sce_v221_sceLibcClassicLocaleE_nid_postfix; + const auto classicReferences = classic->base.references; + GuestLocaleFacade target {}; + _ZNSt8__sce_v26locale5emptyEv_nid_postfix(&target); + auto* locimp = target.implementation; + + GuestLocaleFacade previous {}; + Require(_ZNSt8__sce_v26locale6globalERKS0__nid_postfix(&previous, &target) == &previous, "global() returns its result"); + Require(previous.implementation == classic, "global() returns the previous global locale"); + Require(classic->base.references == classicReferences, "global() releases the reference the old global held"); + Require(_ZNSt8__sce_v26locale16_GetgloballocaleEv_nid_postfix() == locimp, "global() installs the new locale"); + Require(locimp->base.references == 2, "global() retains the new global locale"); + Require(_ZNSt8__sce_v26locale5_InitEb_nid_postfix(false) == locimp, "_Init returns the new global locale"); + Require(locimp->base.references == 2, "_Init(false) takes no reference"); + + GuestLocaleFacade same {}; + _ZNSt8__sce_v26locale6globalERKS0__nid_postfix(&same, &target); + Require(same.implementation == locimp, "global() with the current locale returns it"); + Require(locimp->base.references == 3, "global() with the current locale only takes the result's reference"); + + GuestLocaleFacade back {classic}; + GuestLocaleFacade old {}; + _ZNSt8__sce_v26locale6globalERKS0__nid_postfix(&old, &back); + Require(old.implementation == locimp, "global() returns the replaced locale"); + Require(locimp->base.references == 3, "the replaced locale keeps the result's reference only"); + Require(classic->base.references == classicReferences + 1, "the classic locale is retained again as the global"); + Require(_ZNSt8__sce_v26locale16_GetgloballocaleEv_nid_postfix() == classic, "global() restores the classic locale"); + + auto* other = _ZNSt8__sce_v26locale7_Locimp11_New_LocimpEb_nid_postfix(false); + _ZNSt8__sce_v26locale16_SetgloballocaleEPv_nid_postfix(other); + Require(_ZNSt8__sce_v26locale16_GetgloballocaleEv_nid_postfix() == other, "_Setgloballocale installs the locale"); + Require(other->base.references == 1, "_Setgloballocale takes no reference"); + _ZNSt8__sce_v26locale16_SetgloballocaleEPv_nid_postfix(classic); + bool threw = false; + try { + _ZNSt8__sce_v26locale16_SetgloballocaleEPv_nid_postfix(nullptr); + } catch (const std::invalid_argument&) { + threw = true; + } + Require(threw, "_Setgloballocale(null) throws"); + Require(_ZNSt8__sce_v26locale16_GetgloballocaleEv_nid_postfix() == classic, "a rejected _Setgloballocale keeps the global locale"); +} + +static void CheckLocinfo() { + const char untouched[] = "untouched"; + const char* const accepted[] {"", "C"}; + const char* const rejected[] {nullptr, "POSIX", "c", "C.UTF-8", "CX", "en_US.UTF-8"}; + const int categories[] {0, 1, 0x3f, -1}; + + for (const char* name : accepted) { + LocinfoArguments check {LocinfoCall::Check, nullptr, 0, name}; + SceV2CallLocinfo(&check); + for (int category : categories) { + GuestLocale::SceV2Locinfo locinfo {0x5a5a5a5a5a5a5a5aull, nullptr, nullptr}; + _ZNSt8__sce_v28_Locinfo13_Locinfo_ctorEPS0_iPKc_nid_postfix(&locinfo, category, name); + Require(locinfo.name != nullptr && std::strcmp(locinfo.name, "C") == 0, "_Locinfo(int) stores the current name"); + Require(locinfo.classicName != nullptr && std::strcmp(locinfo.classicName, "C") == 0, "_Locinfo(int) stores C at offset 0x10"); + Require(locinfo.reserved == 0x5a5a5a5a5a5a5a5aull, "_Locinfo(int) preserves the caller word"); + + GuestLocale::SceV2Locinfo added {0x5a5a5a5a5a5a5a5aull, untouched, untouched}; + Require(_ZNSt8__sce_v28_Locinfo16_Locinfo_AddcatsEPS0_iPKc_nid_postfix(&added, category, name) == &added, "_Addcats returns its object"); + Require(added.classicName != nullptr && std::strcmp(added.classicName, "C") == 0, "_Addcats stores C at offset 0x10"); + Require(added.name == untouched && added.reserved == 0x5a5a5a5a5a5a5a5aull, "_Addcats leaves the other fields"); + } + } + + for (const char* name : rejected) { + Require(ThrowsBadLocaleName({LocinfoCall::Check, nullptr, 0, name}), "_sceLibcLocinfo rejects the name"); + + GuestLocale::SceV2Locinfo locinfo {0x5a5a5a5a5a5a5a5aull, nullptr, untouched}; + Require(ThrowsBadLocaleName({LocinfoCall::Construct, &locinfo, 0x3f, name}), "_Locinfo(int) rejects the name"); + Require(locinfo.name != nullptr && std::strcmp(locinfo.name, "C") == 0, "_Locinfo(int) stores the current name before rejecting"); + Require(locinfo.classicName == untouched, "_Locinfo(int) leaves offset 0x10 when rejecting"); + + GuestLocale::SceV2Locinfo added {0x5a5a5a5a5a5a5a5aull, untouched, untouched}; + Require(ThrowsBadLocaleName({LocinfoCall::Addcats, &added, 0x3f, name}), "_Addcats rejects the name"); + Require(added.name == untouched && added.classicName == untouched, "_Addcats changes nothing when rejecting"); + } +} + +int main() { + std::array heapApi{}; + heapApi[0] = reinterpret_cast(&TestHeapAllocate); + heapApi[1] = reinterpret_cast(&TestHeapFree); + heapApi[2] = reinterpret_cast(&TestHeapCalloc); + heapApi[3] = reinterpret_cast(&TestHeapReallocate); + heapApi[4] = reinterpret_cast(&TestHeapAlign); + heapApi[5] = reinterpret_cast(&TestHeapRealign); + heapApi[6] = reinterpret_cast(&TestHeapPosixAlign); + ApplicationHeapRegister_nid_no_patch(heapApi.data()); + + CheckClassic(); + CheckEmpty(); + CheckGlobal(); + CheckLocinfo(); + if (failures != 0) return 1; + std::puts("std::__sce_v2 locale basics passed"); + return 0; +} diff --git a/core/libs/tests/GuestSelect.cpp b/core/libs/tests/GuestSelect.cpp index d73abe445f..daee0bd5b2 100644 --- a/core/libs/tests/GuestSelect.cpp +++ b/core/libs/tests/GuestSelect.cpp @@ -2,6 +2,7 @@ #include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "SceTypes.hpp" +#include #include #include #include @@ -10,6 +11,7 @@ #include #include #include +#include struct GuestTimeval { std::int64_t seconds; @@ -108,6 +110,34 @@ static void StandardDescriptorFile(int descriptor) { Require(unlink_nid_postfix(path.c_str()) == 0); } +static void SocketClosedDuringAWait() { + std::vector noticed; + for (int attempt = 0; attempt < 20; ++attempt) { + const int socket = socket_nid_postfix(2, 2, 0); + Require(socket >= 0); + int result = 0; + int error = 0; + std::atomic started{false}; + std::thread waiter([&] { + GuestDescriptorSet read{}; + read.Add(socket); + GuestTimeval limit{2, 0}; + started = true; + result = select_nid_postfix(socket + 1, read.words.data(), nullptr, nullptr, &limit); + error = *__error_nid_postfix(); + }); + while (!started) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::microseconds(1000 + attempt * 100)); + const auto closedAt = std::chrono::steady_clock::now(); + Require(close_nid_postfix(socket) == 0); + waiter.join(); + noticed.push_back(std::chrono::steady_clock::now() - closedAt); + Require(result == -1 && error == GuestEbadf); + } + std::sort(noticed.begin(), noticed.end()); + Require(noticed[noticed.size() / 2] < std::chrono::milliseconds(35)); +} + static void Kqueues() { const int kq = kqueue_nid_postfix(); Require(kq >= 0 && kq < GuestDescriptorLimit); @@ -358,4 +388,5 @@ int main() { Require(select_nid_postfix(reader + 1, read.words.data(), nullptr, nullptr, &limit) == -1); Require(*__error_nid_postfix() == GuestEbadf); } + SocketClosedDuringAWait(); } diff --git a/core/libs/tests/GuestSysUtilNotification.cpp b/core/libs/tests/GuestSysUtilNotification.cpp index a28fcc951b..287f0da736 100644 --- a/core/libs/tests/GuestSysUtilNotification.cpp +++ b/core/libs/tests/GuestSysUtilNotification.cpp @@ -75,12 +75,18 @@ int main() { int hello = -1; int empty = -1; + int emptyString = -1; + int type1 = -1; + int type2 = -1; int truncated = -1; std::string output; try { StandardErrorCapture capture; hello = sceSysUtilSendSystemNotificationWithText(7, "hello"); empty = sceSysUtilSendSystemNotificationWithText(-1, nullptr); + emptyString = sceSysUtilSendSystemNotificationWithText(0, ""); + type1 = sceSysUtilSendSystemNotificationWithText(1, "notification type 1"); + type2 = sceSysUtilSendSystemNotificationWithText(2, "notification type 2"); truncated = sceSysUtilSendSystemNotificationWithText(0, longText.c_str()); output = capture.Text(); } catch (const std::exception& error) { @@ -90,10 +96,19 @@ int main() { Require(hello == 0, "a text notification was not sent"); Require(empty == 0, "a null message was not sent as an empty text"); + Require(emptyString == 0, "an empty string notification was not sent"); + Require(type1 == 0, "a type 1 notification was not sent"); + Require(type2 == 0, "a type 2 notification was not sent"); Require(truncated == 0, "a long message was not sent"); Require(sceSysUtilSendSystemNotificationWithText(0, reinterpret_cast(0x10)) == static_cast(0x8002000Eu), "an unreadable message pointer did not return EFAULT"); Require(sceSysUtilSendSystemNotificationWithText(0, reinterpret_cast(~std::uintptr_t{0xfff})) == static_cast(0x8002000Eu), "an unreadable message pointer did not return EFAULT"); - const std::string expected = "[notification] hello\n[notification] \n[notification] " + longText.substr(0, 179) + "\n"; + const std::string expected = + "[notification] hello\n" + "[notification] \n" + "[notification] \n" + "[notification] notification type 1\n" + "[notification] notification type 2\n" + "[notification] " + longText.substr(0, 179) + "\n"; if (output != expected) { std::fprintf(stderr, "notification output was:\n%s", output.c_str()); Require(false, "the notification output differs from the sent texts"); diff --git a/core/libs/tests/GuestSysmodule.cpp b/core/libs/tests/GuestSysmodule.cpp index 0b5218ab5e..b87039e03a 100644 --- a/core/libs/tests/GuestSysmodule.cpp +++ b/core/libs/tests/GuestSysmodule.cpp @@ -3,10 +3,12 @@ #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libSceSysmodule/ModuleTable.hpp" +#include #include #include #include #include +#include #include #include #ifdef _WIN32 @@ -260,8 +262,49 @@ void CheckUnwindInfo() { } +void CheckAcceptedIdSet() { + constexpr std::uint16_t kAccepted[] = { + 0x0005, 0x0006, 0x0007, 0x000b, 0x0017, 0x0019, 0x001a, 0x001b, 0x001c, 0x001e, 0x0021, 0x002c, + 0x0038, 0x0081, 0x0083, 0x0084, 0x0085, 0x0088, 0x008a, 0x008b, 0x008c, 0x008d, 0x0095, 0x0096, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009f, 0x00a0, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00b4, 0x00b6, 0x00b7, 0x00b9, 0x00ba, 0x00bc, + 0x00bd, 0x00be, 0x00c0, 0x00c1, 0x00c2, 0x00c4, 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00cc, 0x00cd, + 0x00ce, 0x00cf, 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d8, 0x00da, 0x00de, + 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e7, 0x00e9, 0x00ee, 0x00ef, 0x00f0, 0x00f1, 0x00fa, + 0x00fb, 0x00fe, 0x00ff, 0x0103, 0x0104, 0x0105, 0x0106, 0x0107, 0x010a, 0x010b, 0x010c, 0x010d, + 0x010e, 0x010f, 0x0110, 0x0111, 0x0112, 0x0113, 0x0114, 0x0115, 0x0116, 0x0117, 0x011a, 0x011b, + 0x011c, 0x011d, 0x011e, 0x011f, 0x0120, 0x0121, 0x0122, 0x0123, 0x0124, 0x0125, 0x0126, 0x0127, + 0x0128, 0x0129, 0x012a, 0x012b, 0x012c, 0x012d, 0x012e, 0x012f, 0x0130, 0x0132, 0x0133, 0x0134, + 0x0135, 0x0136, 0x0137, 0x0138, 0x0139, 0x013a, 0x013b, 0x013c, 0x013d, + }; + static_assert(sizeof(kAccepted) / sizeof(kAccepted[0]) == 141); + constexpr std::uint16_t kAcceptedWithoutName[] = { + 0x0111, 0x0117, 0x011c, 0x011d, 0x011e, 0x011f, 0x0121, 0x0122, 0x0123, 0x0124, 0x0129, + 0x012c, 0x012d, 0x012e, 0x0133, 0x0134, 0x0136, 0x013a, 0x013b, 0x013c, 0x013d, + }; + const auto accepted = [&kAccepted](std::uint16_t id) { + return std::binary_search(std::begin(kAccepted), std::end(kAccepted), id); + }; + for (std::uint32_t id = 0; id <= 0xffff; ++id) { + const std::uint16_t value = static_cast(id); + Require((sceSysmoduleIsLoaded(value) != kInvalidValue) == accepted(value)); + if (!accepted(value)) { + Require(sceSysmoduleLoadModule(value) == kInvalidValue); + Require(sceSysmoduleUnloadModule(value) == kInvalidValue); + } + } + for (const std::uint16_t id : kAcceptedWithoutName) { + Require(accepted(id)); + Require(kModuleTable.find(id) == kModuleTable.end()); + Require(sceSysmoduleIsLoaded(id) == 0); + Require(sceSysmoduleLoadModule(id) == 0); + Require(sceSysmoduleUnloadModule(id) == 0); + } +} + int main() { CheckUnknownIds(); + CheckAcceptedIdSet(); Require(sceSysmoduleIsLoaded(0) == kInvalidValue); Require(sceSysmoduleLoadModule(0) == kInvalidValue); Require(sceSysmoduleUnloadModule(0) == kInvalidValue); diff --git a/core/libs/tests/GuestTcp.cpp b/core/libs/tests/GuestTcp.cpp index 2538d521a1..ba32a68be0 100644 --- a/core/libs/tests/GuestTcp.cpp +++ b/core/libs/tests/GuestTcp.cpp @@ -3,6 +3,7 @@ #include "prx/libc/include/general/VabiMacros.hpp" #include #include +#include #include #include #include @@ -275,6 +276,59 @@ static void SocketNameFaults() { for (const int descriptor : {unconnected, accepted, client, listener}) Require(close_nid_postfix(descriptor) == 0); } +static void BlockedReceiveEndsAtShutRead() { + enum Call { Receive, ReceiveFrom, ReceiveMessage, Read, ThroughDuplicate, WithTimeout, Calls }; + for (int call = Receive; call < Calls; ++call) { + const int listener = socket_nid_postfix(2, 1, 0); + Require(listener >= 0); + std::array address{16, 2, 0, 0, 127, 0, 0, 1}; + Require(bind_nid_postfix(listener, address.data(), address.size()) == 0 && listen_nid_postfix(listener, 4) == 0); + std::uint32_t size = address.size(); + Require(getsockname_nid_postfix(listener, address.data(), &size) == 0); + const int client = socket_nid_postfix(2, 1, 0); + Require(client >= 0 && connect_nid_postfix(client, address.data(), address.size()) == 0); + const int accepted = accept_nid_postfix(listener, nullptr, nullptr); + Require(accepted >= 0); + const int watched = call == ThroughDuplicate ? dup_nid_postfix(accepted) : accepted; + Require(watched >= 0); + if (call == WithTimeout) { + const KernelTimeval timeout{3, 0}; + Require(setsockopt_nid_postfix(accepted, 0xffff, 0x1006, &timeout, sizeof(timeout)) == 0); + } + char buffer[8]; + std::array sender{}; + std::uint32_t senderSize = sender.size(); + KernelIovec vector{buffer, sizeof(buffer)}; + GuestMsghdr message{sender.data(), static_cast(sender.size()), &vector, 1, nullptr, 0, -1}; + std::atomic entered{false}; + std::atomic returned{false}; + std::int64_t received = -2; + std::chrono::steady_clock::duration waited{}; + std::thread receiver([&] { + const auto start = std::chrono::steady_clock::now(); + entered = true; + if (call == ReceiveFrom) received = recvfrom_nid_postfix(watched, buffer, sizeof(buffer), 0, sender.data(), &senderSize); + else if (call == ReceiveMessage) received = recvmsg_nid_postfix(watched, &message, 0); + else if (call == Read) received = read_nid_postfix(watched, buffer, sizeof(buffer)); + else received = recv_nid_postfix(watched, buffer, sizeof(buffer), 0); + waited = std::chrono::steady_clock::now() - start; + returned = true; + }); + while (!entered) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + Require(!returned); + Require(shutdown_nid_postfix(accepted, 0) == 0); + for (int poll = 0; poll < 100 && !returned; ++poll) std::this_thread::sleep_for(std::chrono::milliseconds(20)); + if (!returned) Require(shutdown_nid_postfix(client, 2) == 0); + receiver.join(); + Require(received == 0 && waited >= std::chrono::milliseconds(40) && waited < std::chrono::seconds(2)); + if (call == ReceiveFrom) Require(senderSize == 0); + if (call == ReceiveMessage) Require(message.nameLength == 0 && message.flags == 0); + if (call == ThroughDuplicate) Require(close_nid_postfix(watched) == 0); + for (const int descriptor : {client, accepted, listener}) Require(close_nid_postfix(descriptor) == 0); + } +} + int main() { StreamReceiveReportsNoSender(); SocketNameFaults(); @@ -282,6 +336,7 @@ int main() { AcceptedSocketsFollowListenerBlocking(); WaitAllReceivesTheWholeRequest(); ReceiveAnsweredWithoutTheHost(); + BlockedReceiveEndsAtShutRead(); UnboundSocketNameIsZero(); const int listener = socket_nid_postfix(2, 1, 6); Require(listener >= 0); diff --git a/core/libs/tests/GuestUdp.cpp b/core/libs/tests/GuestUdp.cpp index e177213445..99b6c7a997 100644 --- a/core/libs/tests/GuestUdp.cpp +++ b/core/libs/tests/GuestUdp.cpp @@ -4,6 +4,7 @@ #include "prx/libc/include/general/VabiMacros.hpp" #include #include +#include #include #include #include @@ -221,6 +222,49 @@ static void CheckShutReadUnconnected() { Require(close_nid_postfix(receiver) == 0 && close_nid_postfix(sender) == 0); } +#ifndef __APPLE__ +static void CheckShutReadEndsBlockedReceive() { + for (const bool stamped : {false, true}) { + const int receiver = socket_nid_postfix(2, 2, 0); + const int sender = socket_nid_postfix(2, 2, 0); + Require(receiver >= 0 && sender >= 0); + std::array address{16, 2, 0, 0, 127, 0, 0, 1}; + Require(bind_nid_postfix(receiver, address.data(), address.size()) == 0); + std::uint32_t size = address.size(); + Require(getsockname_nid_postfix(receiver, address.data(), &size) == 0); + const int enabled = 1; + if (stamped) Require(setsockopt_nid_postfix(receiver, 0xffff, 0x400, &enabled, sizeof(enabled)) == 0); + std::atomic entered{false}; + std::atomic returned{false}; + std::int64_t received = -2; + std::array control{}; + std::uint32_t controlLength = 0; + std::chrono::steady_clock::duration waited{}; + std::thread waiter([&] { + char buffer[4]; + KernelIovec vector{buffer, sizeof(buffer)}; + GuestMsghdr message{nullptr, 0, &vector, 1, control.data(), static_cast(control.size()), -1}; + const auto start = std::chrono::steady_clock::now(); + entered = true; + received = stamped ? recvmsg_nid_postfix(receiver, &message, 0) : recv_nid_postfix(receiver, buffer, sizeof(buffer), 0); + waited = std::chrono::steady_clock::now() - start; + controlLength = message.controlLength; + returned = true; + }); + while (!entered) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + Require(!returned); + Require(shutdown_nid_postfix(receiver, 0) == -1 && *__error_nid_postfix() == 57); + for (int poll = 0; poll < 100 && !returned; ++poll) std::this_thread::sleep_for(std::chrono::milliseconds(20)); + if (!returned) Require(sendto_nid_postfix(sender, "x", 1, 0, address.data(), address.size()) == 1); + waiter.join(); + Require(received == 0 && waited >= std::chrono::milliseconds(40) && waited < std::chrono::seconds(2)); + if (stamped) Require(controlLength == 0); + Require(close_nid_postfix(receiver) == 0 && close_nid_postfix(sender) == 0); + } +} +#endif + static void CheckDontWait() { const int receiver = socket_nid_postfix(2, 2, 0); const int sender = socket_nid_postfix(2, 2, 0); @@ -501,6 +545,9 @@ int main() { CheckDontWait(); CheckSendToPortZero(); CheckShutReadUnconnected(); +#ifndef __APPLE__ + CheckShutReadEndsBlockedReceive(); +#endif const int receiver = socket_nid_postfix(2, 2, 0); const int sender = socket_nid_postfix(2, 2, 17); Require(receiver >= 0 && sender >= 0 && receiver != sender); diff --git a/core/libs/tests/GuestUsbStorageDialog.cpp b/core/libs/tests/GuestUsbStorageDialog.cpp index 62ecc41378..41f7ef2e08 100644 --- a/core/libs/tests/GuestUsbStorageDialog.cpp +++ b/core/libs/tests/GuestUsbStorageDialog.cpp @@ -80,6 +80,7 @@ int main() { Require(sceUsbStorageDialogGetStatus() == kStatusFinished); Require(sceUsbStorageDialogGetResult(nullptr) == kErrArgNull); + Require(sceUsbStorageDialogGetResult(reinterpret_cast(0x10)) == static_cast(0x80B8000Au)); result.size = 79; Require(sceUsbStorageDialogGetResult(&result) == static_cast(0x80B8000Au)); result.size = sizeof(result); diff --git a/core/libs/tests/GuestUserService.cpp b/core/libs/tests/GuestUserService.cpp index 2281482617..3f1b0196ae 100644 --- a/core/libs/tests/GuestUserService.cpp +++ b/core/libs/tests/GuestUserService.cpp @@ -224,6 +224,28 @@ int main() { Require(sceUserServiceGetUserColor(USER_SERVICE_INITIAL_USER_ID + 1, &color) == USER_SERVICE_OK); Require(color == 1); + char homePath[64]{}; + Require(sceUserServiceGetHomeDirectory(USER_SERVICE_INITIAL_USER_ID, nullptr, sizeof(homePath)) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetHomeDirectory(USER_SERVICE_INITIAL_USER_ID, homePath, 1) == USER_SERVICE_ERROR_BUFFER_TOO_SHORT); + Require(sceUserServiceGetHomeDirectory(USER_SERVICE_INITIAL_USER_ID, homePath, sizeof(homePath)) == USER_SERVICE_OK); + Require(std::strcmp(homePath, "/user/home/10000000") == 0); + Require(sceUserServiceGetSecureHomeDirectory(USER_SERVICE_INITIAL_USER_ID, nullptr, sizeof(homePath)) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetSecureHomeDirectory(USER_SERVICE_INITIAL_USER_ID, homePath, 1) == USER_SERVICE_ERROR_BUFFER_TOO_SHORT); + Require(sceUserServiceGetSecureHomeDirectory(USER_SERVICE_INITIAL_USER_ID, homePath, sizeof(homePath)) == USER_SERVICE_OK); + Require(std::strstr(homePath, "/priv/home/10000000") != nullptr); + Require(libSceUserServiceUnknown_ErTMkmSho4E_nid_no_patch(USER_SERVICE_INITIAL_USER_ID, nullptr, 37) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(libSceUserServiceUnknown_ErTMkmSho4E_nid_no_patch(USER_SERVICE_INITIAL_USER_ID, homePath, 36) == USER_SERVICE_ERROR_BUFFER_TOO_SHORT); + Require(libSceUserServiceUnknown_ErTMkmSho4E_nid_no_patch(USER_SERVICE_INITIAL_USER_ID, homePath, 37) == USER_SERVICE_ERROR_BUFFER_TOO_SHORT); + Require(libSceUserServiceUnknown_ErTMkmSho4E_nid_no_patch(USER_SERVICE_INITIAL_USER_ID, homePath, 38) == USER_SERVICE_OK); + Require(std::strlen(homePath) == 36); + Require(sceUserServiceGetCurrentUserGroupIndex(nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceSetUserGroupIndex(-2) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceSetUserGroupIndex(129) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetUserGroupName(0, nullptr, 129) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetUserGroupName(0, homePath, 128) == USER_SERVICE_ERROR_BUFFER_TOO_SHORT); + char oversizedGroupName[256]{}; + Require(sceUserServiceGetUserGroupName(0, oversizedGroupName, sizeof(oversizedGroupName)) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetAgeLevel(USER_SERVICE_INITIAL_USER_ID + 1, &age_level) == USER_SERVICE_ERROR_NOT_LOGGED_IN); Require(sceUserServiceGetAgeLevel(USER_SERVICE_INITIAL_USER_ID + 1, nullptr) == USER_SERVICE_ERROR_NOT_LOGGED_IN); privacy = 0x5a5a; diff --git a/core/libs/tests/GuestUserServiceGroupIo.cpp b/core/libs/tests/GuestUserServiceGroupIo.cpp new file mode 100644 index 0000000000..83aedee935 --- /dev/null +++ b/core/libs/tests/GuestUserServiceGroupIo.cpp @@ -0,0 +1,59 @@ +#include "prx/libSceUserService/UserService.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libkernel/KernelErrors.hpp" +#include +#include +#include +#include +#include + +namespace { +int openResult = 7; +std::int64_t readResult = 4; +std::int64_t writeResult = 4; +int opens{}; +int closes{}; +std::string written; +void Require(bool value) { if (!value) std::abort(); } +} + +extern "C" { +int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t) { ++opens; return openResult; } +int APS5_VABI sceKernelClose(int) { ++closes; return 0; } +int APS5_VABI sceKernelStat(const char*, FileStat*) { return 0; } +std::int64_t APS5_VABI sceKernelRead(int, void* buffer, std::size_t size) { + if (readResult > 0) std::memcpy(buffer, "Team", std::min(size, static_cast(readResult))); + return readResult; +} +std::int64_t APS5_VABI sceKernelWrite(int, const void* buffer, std::size_t size) { + written.assign(static_cast(buffer), size); + return writeResult; +} +} + +int main() { + char name[129]{}; + Require(sceUserServiceGetUserGroupName(INT_MAX, name, sizeof(name)) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetUserGroupName(INT_MIN, name, sizeof(name)) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(opens == 0); + Require(sceUserServiceGetUserGroupName(0, name, sizeof(name)) == USER_SERVICE_OK); + Require(std::strcmp(name, "Team") == 0 && closes == 1); + readResult = 0; + Require(sceUserServiceGetUserGroupName(128, name, sizeof(name)) == USER_SERVICE_OK); + Require(std::strcmp(name, "User Group 129") == 0); + readResult = SCE_KERNEL_ERROR_EIO; + Require(sceUserServiceGetUserGroupName(0, name, sizeof(name)) == USER_SERVICE_OK); + Require(std::strcmp(name, "User Group 1") == 0); + openResult = SCE_KERNEL_ERROR_EIO; + Require(sceUserServiceGetUserGroupName(0, name, sizeof(name)) == USER_SERVICE_OK); + Require(std::strcmp(name, "User Group 1") == 0); + openResult = 7; + Require(sceUserServiceSetUserGroupName(0, "Team") == USER_SERVICE_OK); + Require(written == "Team"); + writeResult = 2; + Require(sceUserServiceSetUserGroupName(0, "Team") == SCE_KERNEL_ERROR_EIO); + writeResult = SCE_KERNEL_ERROR_EIO; + Require(sceUserServiceSetUserGroupName(0, "Team") == SCE_KERNEL_ERROR_EIO); + writeResult = 0; + Require(sceUserServiceSetUserGroupName(0, "") == USER_SERVICE_OK); +} diff --git a/core/libs/tests/GuestVideoOutFirmwareAnswers.cpp b/core/libs/tests/GuestVideoOutFirmwareAnswers.cpp new file mode 100644 index 0000000000..c1f55364e6 --- /dev/null +++ b/core/libs/tests/GuestVideoOutFirmwareAnswers.cpp @@ -0,0 +1,86 @@ +#include "prx/libSceVideoOut/include/InventoryExports.hpp" +#include +#include +#include +#include +#include +#include + +static void Require(bool value, const char* message) { + if (!value) { + std::fprintf(stderr, "%s\n", message); + std::abort(); + } +} + +static int ModeIsVr(std::uint32_t flags, std::uint8_t fill) { + std::array mode; + mode.fill(fill); + std::memcpy(mode.data() + 0x20, &flags, sizeof(flags)); + return sceVideoOutSysModeIsVr(mode.data()); +} + +static std::array Mode(std::uint32_t flags, std::uint8_t fill) { + std::array mode; + mode.fill(fill); + std::memcpy(mode.data() + 0x20, &flags, sizeof(flags)); + return mode; +} + +static std::array ModeWithByte6(std::uint8_t value, std::uint8_t fill) { + std::array mode; + mode.fill(fill); + mode[6] = value; + return mode; +} + +static void CheckInitMode() { + std::array mode; + mode.fill(0x5A); + sceVideoOutSysInitMode(mode.data()); + std::uint32_t size; + std::memcpy(&size, mode.data(), sizeof(size)); + Require(size == 0x20, "sceVideoOutSysInitMode did not store 0x20 in the first dword"); + for (std::size_t i = 4; i < 0x20; ++i) Require(mode[i] == 0xFF, "sceVideoOutSysInitMode did not fill bytes 4 to 0x1F with 0xFF"); + for (std::size_t i = 0x20; i < mode.size(); ++i) Require(mode[i] == 0x5A, "sceVideoOutSysInitMode wrote past 0x20 bytes"); +} + +static void CheckModeFlags() { + for (std::uint8_t fill : {std::uint8_t{0}, std::uint8_t{0xFF}}) { + for (std::uint32_t flags = 0; flags < 8; ++flags) { + for (std::uint32_t high : {0u, 0xFFFFFFF8u}) { + const auto mode = Mode(flags | high, fill); + Require(sceVideoOutSysModeIsSystemDefault(mode.data()) == ((flags & 1u) ? 0 : 1), "sceVideoOutSysModeIsSystemDefault did not report bit 0 of the dword at 0x20 clear"); + Require(sceVideoOutSysModeIsVrNative(mode.data()) == ((flags & 6u) == 2u ? 1 : 0), "sceVideoOutSysModeIsVrNative did not report bits 1 and 2 of the dword at 0x20 as 1 and 0"); + Require(sceVideoOutSysModeIsVrLegacy(mode.data()) == ((flags & 6u) == 6u ? 1 : 0), "sceVideoOutSysModeIsVrLegacy did not report bits 1 and 2 of the dword at 0x20 both set"); + } + } + } +} + +static void CheckModeIsHdr() { + for (unsigned value = 0; value < 256; ++value) { + const int expected = value >= 12 && value <= 17 ? 1 : 0; + for (std::uint8_t fill : {std::uint8_t{0}, std::uint8_t{0xFF}}) { + const auto mode = ModeWithByte6(static_cast(value), fill); + Require(sceVideoOutSysModeIsHdr(mode.data()) == expected, "sceVideoOutSysModeIsHdr did not report byte 6 from 12 to 17"); + } + } +} + +int main() { + Require(sceVideoOutAddBufferHdrPrivilege() == 0, "sceVideoOutAddBufferHdrPrivilege did not return 0"); + Require(sceVideoOutRazorRecallCallbacks() == 0, "sceVideoOutRazorRecallCallbacks did not return 0"); + Require(sceVideoOutSysUpdateRenderingMode() == 0, "sceVideoOutSysUpdateRenderingMode did not return 0"); + Require(sceVideoOutCurveSamplePointsCalc_() == static_cast(0x8029001Cu), "sceVideoOutCurveSamplePointsCalc_ did not return 0x8029001C"); + Require(sceVideoOutDriverGetLatestEopHandle() == static_cast(0x802900FFu), "sceVideoOutDriverGetLatestEopHandle did not return 0x802900FF"); + Require(sceVideoOutSysGetMonitorInfo_() == -1, "sceVideoOutSysGetMonitorInfo_ did not return -1"); + Require(ModeIsVr(0x2u, 0) == 1, "bit 1 of the dword at 0x20 did not report VR"); + Require(ModeIsVr(0xFFFFFFFFu, 0xFF) == 1, "a mode with every bit set did not report VR"); + Require(ModeIsVr(0xFFFFFFFDu, 0xFF) == 0, "the other bits of the dword at 0x20 reported VR"); + Require(ModeIsVr(0u, 0xFF) == 0, "bytes outside the dword at 0x20 reported VR"); + CheckInitMode(); + CheckModeFlags(); + CheckModeIsHdr(); + return 0; +} diff --git a/core/libs/tests/GuestVideodec.cpp b/core/libs/tests/GuestVideodec.cpp index 68b3d8493e..c0d3ff00f2 100644 --- a/core/libs/tests/GuestVideodec.cpp +++ b/core/libs/tests/GuestVideodec.cpp @@ -2,11 +2,18 @@ #include "prx/libSceVideodec/include/Api.hpp" #include #include +#include #include #include #include #include "prx/libc/include/General.hpp" #include "H264Fixture.hpp" +#ifdef _WIN32 +#include +#else +#include +#include +#endif using namespace SceVideodec; @@ -38,6 +45,52 @@ void check(bool condition, const std::string& message) { if (!condition) throw std::runtime_error(message); } +class PageTail { + void* memory = nullptr; + std::size_t page = 0; + void* data = nullptr; + +public: + PageTail(const void* source, std::size_t bytes) { +#ifdef _WIN32 + SYSTEM_INFO info; + GetSystemInfo(&info); + page = info.dwPageSize; + memory = VirtualAlloc(nullptr, page * 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + check(memory != nullptr, "the guard pages could not be allocated"); + DWORD previous; + check(VirtualProtect(static_cast(memory) + page, page, PAGE_NOACCESS, &previous) != 0, "the guard page could not be protected"); +#else + page = static_cast(sysconf(_SC_PAGESIZE)); + memory = mmap(nullptr, page * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + check(memory != MAP_FAILED, "the guard pages could not be allocated"); + check(mprotect(static_cast(memory) + page, page, PROT_NONE) == 0, "the guard page could not be protected"); +#endif + check(bytes <= page, "the guarded value does not fit in a page"); + data = static_cast(memory) + page - bytes; + std::memcpy(data, source, bytes); + } + PageTail(const PageTail&) = delete; + PageTail& operator=(const PageTail&) = delete; + ~PageTail() { +#ifdef _WIN32 + VirtualFree(memory, 0, MEM_RELEASE); +#else + munmap(memory, page * 2); +#endif + } + void ReadOnly() { +#ifdef _WIN32 + DWORD previous; + check(VirtualProtect(memory, page, PAGE_READONLY, &previous) != 0, "the page could not be made read-only"); +#else + check(mprotect(memory, page, PROT_READ) == 0, "the page could not be made read-only"); +#endif + } + template + T* As() const { return static_cast(data); } +}; + struct Session { Ctrl ctrl{sizeof(Ctrl)}; std::vector buffer; @@ -127,6 +180,17 @@ void testErrors() { const SceVideodec2::InputData empty{sizeof(SceVideodec2::InputData), nullptr, 0, 0, 0, 0}; FrameBuffer frame{sizeof(FrameBuffer), nullptr, 0}; PictureInfo picture{sizeof(PictureInfo)}; + auto* const badCtrl = reinterpret_cast(std::uintptr_t{0x10}); + auto* const badInput = reinterpret_cast(std::uintptr_t{0x10}); + auto* const badFrame = reinterpret_cast(std::uintptr_t{0x10}); + auto* const badPicture = reinterpret_cast(std::uintptr_t{0x10}); + check(sceVideodecDecode(badCtrl, &empty, &frame, &picture) == ErrorArgumentPointer, "an unreadable control block was accepted by decode"); + check(sceVideodecDecode(&ctrl, badInput, &frame, &picture) == ErrorArgumentPointer, "an unreadable input block was accepted by decode"); + check(sceVideodecDecode(&ctrl, &empty, badFrame, &picture) == ErrorFrameBufferPointer, "an unreadable frame buffer was accepted by decode"); + check(sceVideodecDecode(&ctrl, &empty, &frame, badPicture) == ErrorArgumentPointer, "an unreadable picture info was accepted by decode"); + check(sceVideodecFlush(badCtrl, &frame, &picture) == ErrorArgumentPointer, "an unreadable control block was accepted by flush"); + check(sceVideodecFlush(&ctrl, badFrame, &picture) == ErrorFrameBufferPointer, "an unreadable frame buffer was accepted by flush"); + check(sceVideodecFlush(&ctrl, &frame, badPicture) == ErrorArgumentPointer, "an unreadable picture info was accepted by flush"); check(sceVideodecDecode(nullptr, &empty, &frame, &picture) == ErrorArgumentPointer, "a null control block was accepted"); check(sceVideodecDecode(&ctrl, &empty, &frame, &picture) == ErrorAuPointer, "a null access unit was accepted"); const SceVideodec2::InputData sized{sizeof(SceVideodec2::InputData), reinterpret_cast(&config), 0, 0, 0, 0}; @@ -247,8 +311,18 @@ void testQueryResourceInfo() { Ctrl ctrl{sizeof(Ctrl)}; check(sceVideodecCreateDecoder(&config, &resource, &ctrl) == 0, "the queried resources were refused"); check(sceVideodecDeleteDecoder(&ctrl) == 0, "the decoder was not deleted"); + auto* const badConfig = reinterpret_cast(std::uintptr_t{0x10}); + auto* const badResource = reinterpret_cast(std::uintptr_t{0x10}); + auto* const badCtrl = reinterpret_cast(std::uintptr_t{0x10}); check(sceVideodecQueryResourceInfo(nullptr, &resource) == ErrorArgumentPointer, "a null configuration was accepted"); check(sceVideodecQueryResourceInfo(&config, nullptr) == ErrorArgumentPointer, "null resource information was accepted"); + check(sceVideodecQueryResourceInfo(badConfig, &resource) == ErrorArgumentPointer, "an unreadable configuration was accepted"); + check(sceVideodecQueryResourceInfo(&config, badResource) == ErrorArgumentPointer, "an unwritable resource information pointer was accepted"); + check(sceVideodecCreateDecoder(badConfig, &resource, &ctrl) == ErrorArgumentPointer, "an unreadable configuration was accepted by create"); + check(sceVideodecCreateDecoder(&config, badResource, &ctrl) == ErrorArgumentPointer, "an unreadable resource was accepted by create"); + check(sceVideodecCreateDecoder(&config, &resource, badCtrl) == ErrorArgumentPointer, "an unwritable control block was accepted by create"); + check(sceVideodecDeleteDecoder(badCtrl) == ErrorArgumentPointer, "an unreadable control block was accepted by delete"); + check(sceVideodecReset(badCtrl) == ErrorArgumentPointer, "an unreadable control block was accepted by reset"); const ResourceInfo sentinel{sizeof(ResourceInfo), 7, nullptr, 7, nullptr, 7, 7}; auto refused = sentinel; auto shortConfig = config; @@ -263,6 +337,48 @@ void testQueryResourceInfo() { check(refused.cpuMemorySize == 7 && refused.cpuGpuMemorySize == 7 && refused.maxFrameBufferSize == 7 && refused.frameBufferAlignment == 7, "a refused query wrote the resource information"); check(shortResource.cpuMemorySize == 7 && shortResource.maxFrameBufferSize == 7, "a refused query wrote the resource information"); + + auto wideConfig = config; + wideConfig.thisSize = std::uint64_t{1} << 32; + auto wideResource = resource; + wideResource.thisSize = std::uint64_t{1} << 32; + check(sceVideodecQueryResourceInfo(&wideConfig, &wideResource) == 0, "a size field with only its upper bits set was refused by query"); + Ctrl wideCtrl{std::uint64_t{1} << 32}; + check(sceVideodecCreateDecoder(&wideConfig, &resource, &wideCtrl) == 0, "a size field with only its upper bits set was refused by create"); + wideCtrl.thisSize = std::uint64_t{1} << 32; + check(sceVideodecDeleteDecoder(&wideCtrl) == 0, "a control block size with only its upper bits set was refused by delete"); + + Ctrl kept{0x5a5a5a5a5a5a5a5au, reinterpret_cast(std::uintptr_t{0x5a}), 0x5au}; + auto unsizedConfig = config; + unsizedConfig.maxFrameWidth = 0; + check(sceVideodecCreateDecoder(&unsizedConfig, &resource, &kept) == ErrorConfigInfo, "create accepted a configuration without a frame size"); + check(kept.thisSize == 0x5a5a5a5a5a5a5a5au && kept.handle == reinterpret_cast(std::uintptr_t{0x5a}) && kept.version == 0x5au, "a failed create wrote the control block"); + + const std::uint64_t shortSize = 8; + PageTail shortCtrl(&shortSize, sizeof(shortSize)); + PageTail shortFrame(&shortSize, sizeof(shortSize)); + PageTail shortPicture(&shortSize, sizeof(shortSize)); + const SceVideodec2::InputData empty{sizeof(SceVideodec2::InputData), nullptr, 0, 0, 0, 0}; + FrameBuffer frame{sizeof(FrameBuffer), nullptr, 0}; + PictureInfo picture{sizeof(PictureInfo)}; + Ctrl sized{sizeof(Ctrl)}; + check(sceVideodecDeleteDecoder(shortCtrl.As()) == ErrorStructSize, "delete gave a pointer error for a short control block at the end of a page"); + check(sceVideodecReset(shortCtrl.As()) == ErrorStructSize, "reset gave a pointer error for a short control block at the end of a page"); + check(sceVideodecFlush(shortCtrl.As(), &frame, &picture) == ErrorStructSize, "flush gave a pointer error for a short control block at the end of a page"); + check(sceVideodecFlush(&sized, shortFrame.As(), &picture) == ErrorStructSize, "flush gave a pointer error for a short frame buffer at the end of a page"); + check(sceVideodecFlush(&sized, &frame, shortPicture.As()) == ErrorStructSize, "flush gave a pointer error for a short picture at the end of a page"); + check(sceVideodecDecode(shortCtrl.As(), &empty, &frame, &picture) == ErrorStructSize, "decode gave a pointer error for a short control block at the end of a page"); + check(sceVideodecDecode(&sized, &empty, shortFrame.As(), &picture) == ErrorStructSize, "decode gave a pointer error for a short frame buffer at the end of a page"); + + PageTail readOnlyResource(&resource, sizeof(resource)); + readOnlyResource.ReadOnly(); + check(sceVideodecQueryResourceInfo(&config, readOnlyResource.As()) == ErrorArgumentPointer, "query accepted a read-only resource information block"); + const Ctrl unwritten{sizeof(Ctrl)}; + PageTail readOnlyCtrl(&unwritten, sizeof(unwritten)); + readOnlyCtrl.ReadOnly(); + check(sceVideodecCreateDecoder(&config, &resource, readOnlyCtrl.As()) == ErrorArgumentPointer, "create accepted a read-only control block"); + check(sceVideodecCreateDecoder(&config, &resource, &ctrl) == 0, "create failed after a refused read-only control block"); + check(sceVideodecDeleteDecoder(&ctrl) == 0, "the decoder created after a refused read-only control block was not deleted"); } } diff --git a/core/libs/tests/GuestVrHand.cpp b/core/libs/tests/GuestVrHand.cpp index aeb0d2d77f..f3f9b168c7 100644 --- a/core/libs/tests/GuestVrHand.cpp +++ b/core/libs/tests/GuestVrHand.cpp @@ -3,6 +3,13 @@ #include #include #include +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else +#include +#include +#endif namespace { @@ -11,6 +18,39 @@ void Require(bool value) { if (!value) std::abort(); } constexpr int kErrInvalidArg = static_cast(0x8a870006); constexpr std::uint8_t kFill = 0xaa; +struct GuardedRegion { + std::size_t pageSize{0}; + unsigned char* memory{nullptr}; + + GuardedRegion() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + pageSize = static_cast(info.dwPageSize); + memory = static_cast(VirtualAlloc(nullptr, pageSize * 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + Require(memory != nullptr); + DWORD previous; + Require(VirtualProtect(memory + pageSize, pageSize, PAGE_NOACCESS, &previous) != 0); +#else + const long nativePageSize = sysconf(_SC_PAGESIZE); + Require(nativePageSize > 0); + pageSize = static_cast(nativePageSize); + void* allocation = mmap(nullptr, pageSize * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(allocation != MAP_FAILED); + memory = static_cast(allocation); + Require(mprotect(memory + pageSize, pageSize, PROT_NONE) == 0); +#endif + } + + ~GuardedRegion() { +#ifdef _WIN32 + VirtualFree(memory, 0, MEM_RELEASE); +#else + munmap(memory, pageSize * 2); +#endif + } +}; + std::array ValidParam() { std::array param{}; param[0] = 0x20; @@ -34,9 +74,30 @@ int main() { auto param = ValidParam(); out.fill(kFill); + auto* const badPtr = reinterpret_cast(std::uintptr_t{0x10}); + Require(sceVrHandQueryMemory(badPtr, out.data()) == kErrInvalidArg && Untouched(out)); + Require(sceVrHandQueryMemory(param.data(), badPtr) == kErrInvalidArg); Require(sceVrHandQueryMemory(nullptr, out.data()) == kErrInvalidArg && Untouched(out)); Require(sceVrHandQueryMemory(param.data(), nullptr) == kErrInvalidArg); + GuardedRegion region; + auto* const boundaryParam = region.memory + region.pageSize - 0x20; + std::memset(boundaryParam, 0, 0x20); + boundaryParam[0] = 0x20; + out.fill(kFill); + Require(sceVrHandQueryMemory(boundaryParam, out.data()) == 0); + + auto* const spanningParam = region.memory + region.pageSize - 0x10; + out.fill(kFill); + Require(sceVrHandQueryMemory(spanningParam, out.data()) == kErrInvalidArg && Untouched(out)); + + auto* const boundaryOut = region.memory + region.pageSize - 28; + std::memset(boundaryOut, kFill, 28); + Require(sceVrHandQueryMemory(param.data(), boundaryOut) == 0); + + auto* const spanningOut = region.memory + region.pageSize - 8; + Require(sceVrHandQueryMemory(param.data(), spanningOut) == kErrInvalidArg); + for (const std::uint32_t size : {0u, 0x1fu, 0x21u, 0x40u, 0x120u, 0x10020u, 0x1000020u}) { param = ValidParam(); std::memcpy(param.data(), &size, sizeof(size)); diff --git a/core/libs/tests/GuestVrTracker2.cpp b/core/libs/tests/GuestVrTracker2.cpp index 1a590ba742..0fa78e98c9 100644 --- a/core/libs/tests/GuestVrTracker2.cpp +++ b/core/libs/tests/GuestVrTracker2.cpp @@ -257,6 +257,13 @@ int main() { parameter[0] = 0x21; Require(sceVrTracker2QueryMemory(reinterpret_cast(parameter.data()), reinterpret_cast(requirement.data())) == InvalidArgument, "query size"); + auto rawMeshReq = Filled(); + Require(sceVrTracker2GetRawCameraMeshSize(nullptr, 0) == NotInitialized, "raw camera mesh size null"); + Require(sceVrTracker2GetRawCameraMeshSize(reinterpret_cast(rawMeshReq.data()), 0) == NotInitialized, "raw camera mesh size return"); + Require(AllEqual(rawMeshReq.data(), rawMeshReq.data() + rawMeshReq.size(), 0xAA), "raw camera mesh size untouched"); + Require(sceVrTracker2GetRawCameraMeshSize(reinterpret_cast(rawMeshReq.data()), 1) == NotInitialized, "raw camera mesh size format"); + Require(AllEqual(rawMeshReq.data(), rawMeshReq.data() + sizeof(VrTracker2MeshRequirement), 0xAA), "raw camera mesh size preserves struct"); + std::vector memory(TrackerMemory + WorkMemoryTail); CheckInitializeValidation(memory); const auto previous = std::filesystem::current_path(); diff --git a/core/libs/tests/GuestWideFormatting.cpp b/core/libs/tests/GuestWideFormatting.cpp index a52cefda8e..18e61f72c1 100644 --- a/core/libs/tests/GuestWideFormatting.cpp +++ b/core/libs/tests/GuestWideFormatting.cpp @@ -256,6 +256,9 @@ static void CheckSwprintf() { Require(swprintf_nid_postfix(wide, 16, u"%5ls|%-3c|%lc", u"wide", 'z', 0x20ac) == 11, "swprintf padded length"); Require(wide == std::u16string(u" wide|z |€"), "swprintf padded output"); Require(swprintf_nid_postfix(wide, 16, u"[%q][%5q]") == 11 && wide == std::u16string(u"[%q][ %q]"), "swprintf prints an unknown conversion"); + char16_t letters[32]; + Require(swprintf_nid_postfix(letters, 32, u"%C|%5C|%hC|%S|%-3S|%lS%d", 7) == 22 && letters == std::u16string(u"%C| %C|%C|%S|%S |%S7"), "swprintf prints %C and %S as unknown conversions"); + Require(swprintf_nid_postfix(letters, 32, u"%2$d %S %f", 1, 2, 3.5) == 13 && letters == std::u16string(u"2 %S 3.500000"), "positional %S takes no argument"); for (const char16_t* format : {u"%99999999999d", u"%.99999999999d", u"%4294967296s", u"%.4294967296s", u"%2147483648ls", u"%.2147483648ls"}) { wide[0] = u'x'; Require(swprintf_nid_postfix(wide, 16, format, 7, u"a") < 0 && wide[0] == 0, "swprintf width or precision beyond int"); diff --git a/core/libs/tests/GuestXmlValueTypes.cpp b/core/libs/tests/GuestXmlValueTypes.cpp index 3a9bb35b5f..bddfb6d2aa 100644 --- a/core/libs/tests/GuestXmlValueTypes.cpp +++ b/core/libs/tests/GuestXmlValueTypes.cpp @@ -179,6 +179,19 @@ void TestNodeId() { for (const XmlNodeId* other : {&otherFlag, &otherFirst, &otherSecond}) { Require(!_ZN3sce3Xml3Dom6NodeIdeqES2_(&base, *other) && _ZN3sce3Xml3Dom6NodeIdneES2_(&base, *other), "NodeId comparison uses every field"); } + for (const std::uint8_t padding : {static_cast(0x00), static_cast(0xff)}) { + const XmlNodeId null = NodeIdValue(0, 0, 0, padding); + Require(_ZN3sce3Xml3Dom6NodeIdntEv(&null), "operator! is true for a null NodeId whatever its padding"); + Require(_ZN3sce3Xml3Dom6NodeIdeqEi(&null, 0) && !_ZN3sce3Xml3Dom6NodeIdneEi(&null, 0), "a null NodeId compares equal to 0"); + for (const int value : {1, -1, 0x7fffffff}) { + Require(!_ZN3sce3Xml3Dom6NodeIdeqEi(&null, value) && _ZN3sce3Xml3Dom6NodeIdneEi(&null, value), "a NodeId never compares equal to a nonzero int"); + } + } + for (const XmlNodeId value : {NodeIdValue(1, 0, 0, 0), NodeIdValue(0, 1, 0, 0), NodeIdValue(0, 0, 1, 0), NodeIdValue(0, 0, 0x80, 0xff)}) { + Require(!_ZN3sce3Xml3Dom6NodeIdntEv(&value), "operator! is false once any field is set"); + Require(!_ZN3sce3Xml3Dom6NodeIdeqEi(&value, 0) && _ZN3sce3Xml3Dom6NodeIdneEi(&value, 0), "a NodeId with any field set compares unequal to 0"); + Require(!_ZN3sce3Xml3Dom6NodeIdeqEi(&value, 1) && _ZN3sce3Xml3Dom6NodeIdneEi(&value, 1), "a NodeId with any field set compares unequal to a nonzero int"); + } XmlNodeId any = NodeIdValue(0x1234, 0x5678, 1, 0); Require(_ZN3sce3Xml3Dom6NodeIdcvPvEv(&any) == nullptr, "NodeId::operator void* is null"); } diff --git a/core/libs/tests/JxrStream.cpp b/core/libs/tests/JxrStream.cpp index 066f187541..73bde91b1f 100644 --- a/core/libs/tests/JxrStream.cpp +++ b/core/libs/tests/JxrStream.cpp @@ -1,4 +1,5 @@ #include "prx/libSceJxr/libSceJxr.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libkernel/include/Api.hpp" #include @@ -289,9 +290,147 @@ void GuestMapping(Jxr::Stream& stream) { Require(sceKernelReleaseDirectMemory(physical, mappingSize) == 0, "release guest packet backing"); } +constexpr std::size_t Max = std::numeric_limits::max(); + +bool Filled(const std::uint8_t* begin, const std::uint8_t* end, std::uint8_t value) { + return std::all_of(begin, end, [value](std::uint8_t byte) { return byte == value; }); +} + +Jxr::Stream* MemoryStream(void* buffer, std::size_t size) { + Jxr::Stream* stream = nullptr; + Require(CreateWS_Memory_nid_postfix(&stream, buffer, size) == 0 && stream, "memory stream creation failed"); + return stream; +} + +void CloseMemory(Jxr::Stream* stream) { + const auto close = stream->close; + Require(close(&stream) == 0 && stream == nullptr, "memory stream close did not free and clear the pointer"); + Require(close(&stream) == 0 && stream == nullptr, "memory stream close of a null pointer failed"); +} + +void Allocation() { + void* memory = &calls; + Require(WMPAlloc_nid_postfix(&memory, 24) == 0 && memory && memory != &calls, "allocation of 24 bytes failed"); + auto* bytes = static_cast(memory); + Require(Filled(bytes, bytes + 24, 0), "allocation is not zeroed"); + Require(WMPFree_nid_postfix(&memory) == 0 && memory == nullptr, "free did not release and clear the pointer"); + Require(WMPFree_nid_postfix(&memory) == 0 && memory == nullptr, "free of a null pointer failed"); + memory = &calls; + Require(WMPAlloc_nid_postfix(&memory, Max) == -101 && memory == nullptr, "failed allocation did not report out of memory"); + RequireInvalidArgument([] { WMPAlloc_nid_postfix(nullptr, 24); }, "allocation accepted a null pointer"); + RequireInvalidArgument([] { WMPFree_nid_postfix(nullptr); }, "free accepted a null pointer"); +} + +void MemoryCreate() { + std::array bytes{}; + for (void* buffer : {static_cast(bytes.data()), static_cast(nullptr)}) { + auto* stream = reinterpret_cast(&calls); + Require(CreateWS_Memory_nid_postfix(&stream, buffer, bytes.size()) == 0 && stream && stream != reinterpret_cast(&calls), "memory stream creation failed"); + Require(stream->state == std::array{reinterpret_cast(buffer), bytes.size(), 0, 0} && stream->memory == 0, "memory stream state differs"); + Require(stream->end && stream->read && stream->write && stream->setPosition && stream->getPosition && stream->close, "memory stream callbacks are missing"); + if (buffer) { + const std::array source{9, 8, 7, 6}; + std::array target{}; + std::size_t position = 99; + Require(stream->write(stream, source.data(), source.size()) == 0 && std::equal(source.begin(), source.end(), bytes.begin()), "stored write callback differs"); + Require(stream->setPosition(stream, 1) == 0 && stream->read(stream, target.data(), 3) == 0 && std::equal(target.begin(), target.begin() + 3, source.begin() + 1), "stored read callback differs"); + Require(stream->getPosition(stream, &position) == 0 && position == 4 && stream->end(stream) == 0, "stored position callbacks differ"); + Require(stream->setPosition(stream, bytes.size()) == 0 && stream->end(stream) == 1, "stored end callback differs"); + } + CloseMemory(stream); + } + RequireInvalidArgument([&] { CreateWS_Memory_nid_postfix(nullptr, bytes.data(), bytes.size()); }, "memory stream creation accepted a null stream pointer"); +} + +void MemoryRead() { + const std::array source{1, 2, 3, 4, 5, 6, 7, 8}; + auto* stream = MemoryStream(const_cast(source.data()), source.size()); + std::array target; + target.fill(0xcc); + Require(ReadWS_Memory_nid_postfix(stream, target.data(), 3) == 0 && stream->state[2] == 3, "in-bounds read failed or did not advance"); + Require(std::equal(source.begin(), source.begin() + 3, target.begin()) && Filled(target.data() + 3, target.data() + 8, 0xcc), "in-bounds read data differs"); + target.fill(0xcc); + Require(ReadWS_Memory_nid_postfix(stream, target.data(), 8) == 0 && stream->state[2] == 8, "read across the end did not clamp"); + Require(std::equal(source.begin() + 3, source.end(), target.begin()) && Filled(target.data() + 5, target.data() + 8, 0xcc), "clamped read data differs"); + target.fill(0xcc); + Require(ReadWS_Memory_nid_postfix(stream, target.data(), 1) == 0 && stream->state[2] == 8 && Filled(target.data(), target.data() + 8, 0xcc), "read at the end copied or moved"); + Require(SetPosWS_Memory_nid_postfix(stream, 20) == 0 && ReadWS_Memory_nid_postfix(stream, nullptr, 4) == 0 && stream->state[2] == 20, "read past the end failed or moved"); + Require(ReadWS_Memory_nid_postfix(stream, nullptr, Max) == 0 && stream->state[2] == 20, "read past the end checked for overflow first"); + Require(SetPosWS_Memory_nid_postfix(stream, 1) == 0 && ReadWS_Memory_nid_postfix(stream, target.data(), Max) == -103, "wrapping read did not overflow"); + Require(stream->state[2] == 1 && Filled(target.data(), target.data() + 8, 0xcc), "wrapping read copied or moved"); + RequireInvalidArgument([&] { ReadWS_Memory_nid_postfix(stream, nullptr, 1); }, "read accepted a null destination"); + Require(stream->state[2] == 1, "rejected read moved"); + CloseMemory(stream); +} + +void MemoryWrite() { + const std::array source{1, 2, 3, 4, 5, 6, 7, 8}; + std::array target; + target.fill(0xcc); + auto* stream = MemoryStream(target.data(), target.size()); + Require(WriteWS_Memory_nid_postfix(stream, source.data(), 5) == 0 && stream->state[2] == 5, "in-bounds write failed or did not advance"); + Require(std::equal(source.begin(), source.begin() + 5, target.begin()) && Filled(target.data() + 5, target.data() + 8, 0xcc), "in-bounds write data differs"); + Require(WriteWS_Memory_nid_postfix(stream, source.data(), 4) == -103 && stream->state[2] == 5 && Filled(target.data() + 5, target.data() + 8, 0xcc), "write past the end did not overflow cleanly"); + Require(WriteWS_Memory_nid_postfix(stream, source.data(), 3) == 0 && stream->state[2] == 8 && std::equal(source.begin(), source.begin() + 3, target.begin() + 5), "write up to the end failed"); + Require(SetPosWS_Memory_nid_postfix(stream, 1) == 0 && WriteWS_Memory_nid_postfix(stream, source.data(), Max) == -103 && stream->state[2] == 1, "wrapping write did not overflow"); + Require(WriteWS_Memory_nid_postfix(stream, nullptr, 0) == 0 && stream->state[2] == 1, "empty in-bounds write failed"); + RequireInvalidArgument([&] { WriteWS_Memory_nid_postfix(stream, nullptr, 1); }, "write accepted a null source"); + Require(SetPosWS_Memory_nid_postfix(stream, 9) == 0 && WriteWS_Memory_nid_postfix(stream, source.data(), 0) == -103 && stream->state[2] == 9, "empty write past the end did not overflow"); + Require(std::equal(source.begin(), source.begin() + 5, target.begin()) && std::equal(source.begin(), source.begin() + 3, target.begin() + 5), "failed writes changed the buffer"); + CloseMemory(stream); +} + +void MemoryPosition() { + std::array bytes{}; + auto* stream = MemoryStream(bytes.data(), bytes.size()); + std::size_t position = 99; + for (std::size_t offset : {std::size_t{0}, std::size_t{7}, std::size_t{8}, std::size_t{9}, Max}) { + Require(SetPosWS_Memory_nid_postfix(stream, offset) == 0 && stream->state[2] == offset, "set position failed"); + Require(EOSWS_Memory_nid_postfix(stream) == (offset >= 8 ? 1 : 0), "end of stream differs"); + Require(GetPosWS_Memory_nid_postfix(stream, &position) == 0 && position == offset, "get position differs"); + } + Require(stream->state[0] == reinterpret_cast(bytes.data()) && stream->state[1] == 8, "position calls changed the buffer"); + RequireInvalidArgument([] { EOSWS_Memory_nid_postfix(nullptr); }, "end of stream accepted a null stream"); + RequireInvalidArgument([&] { ReadWS_Memory_nid_postfix(nullptr, bytes.data(), 1); }, "read accepted a null stream"); + RequireInvalidArgument([&] { WriteWS_Memory_nid_postfix(nullptr, bytes.data(), 1); }, "write accepted a null stream"); + RequireInvalidArgument([] { SetPosWS_Memory_nid_postfix(nullptr, 0); }, "set position accepted a null stream"); + RequireInvalidArgument([&] { GetPosWS_Memory_nid_postfix(nullptr, &position); }, "get position accepted a null stream"); + RequireInvalidArgument([&] { GetPosWS_Memory_nid_postfix(stream, nullptr); }, "get position accepted a null result"); + CloseMemory(stream); +} + +void MemoryBitIo() { + std::vector source(2 * PacketSize + 100); + for (std::size_t i = 0; i < source.size(); ++i) source[i] = static_cast(i * 31 + (i >> 8) * 13); + auto* stream = MemoryStream(source.data(), source.size()); + std::fill(ring.begin(), ring.end(), 0xcc); + Jxr::BitIo io{7, 19, 13, ~8192, ring.data(), ring.data() + PacketSize, stream, PacketSize}; + Require(readIS_nid_postfix(nullptr, &io) == 0 && io.referenceOffset == 2 * PacketSize && io.start == ring.data() + PacketSize, "memory packet read did not advance"); + Require(stream->state[2] == 2 * PacketSize && std::equal(ring.begin(), ring.begin() + PacketSize, source.begin() + PacketSize), "memory packet read data differs"); + Require(Filled(ring.data() + PacketSize, ring.data() + ring.size(), 0xcc) && std::memcmp(&io.shadow, source.data() + PacketSize, sizeof(io.shadow)) == 0, "memory packet read changed the other half or shadow"); + io.current = ring.data(); + Require(readIS_nid_postfix(nullptr, &io) == 0 && io.referenceOffset == 3 * PacketSize && io.start == ring.data(), "clamped memory packet read did not advance"); + Require(stream->state[2] == 2 * PacketSize + 100 && std::equal(ring.begin() + PacketSize, ring.begin() + PacketSize + 100, source.begin() + 2 * PacketSize), "clamped memory packet read data differs"); + Require(Filled(ring.data() + PacketSize + 100, ring.data() + ring.size(), 0xcc) && std::equal(ring.begin(), ring.begin() + PacketSize, source.begin() + PacketSize), "clamped memory packet read changed bytes past the clamp"); + CloseMemory(stream); + + for (std::size_t i = 0; i < ring.size(); ++i) ring[i] = static_cast(i * 23 + (i >> 8) * 5); + std::vector target(PacketSize + 100, 0xcc); + stream = MemoryStream(target.data(), target.size()); + io = {7, 19, 13, ~8192, ring.data(), ring.data() + PacketSize, stream, 0}; + Require(writeIS_nid_postfix(nullptr, &io) == 0 && io.start == ring.data() + PacketSize && stream->state[2] == PacketSize, "memory packet write did not advance"); + Require(std::equal(ring.begin(), ring.begin() + PacketSize, target.begin()) && Filled(target.data() + PacketSize, target.data() + target.size(), 0xcc), "memory packet write data differs"); + io.current = ring.data(); + Require(writeIS_nid_postfix(nullptr, &io) == -103 && io.start == ring.data() + PacketSize && stream->state[2] == PacketSize, "overflowing memory packet write advanced"); + Require(std::equal(ring.begin(), ring.begin() + PacketSize, target.begin()) && Filled(target.data() + PacketSize, target.data() + target.size(), 0xcc), "overflowing memory packet write changed the buffer"); + CloseMemory(stream); +} + } int main() { + void* api[10]{}; + ApplicationHeapRegister_nid_no_patch(api); Jxr::Stream stream{}; stream.read = Read; stream.write = Write; @@ -302,5 +441,11 @@ int main() { CallbackMutation(stream); NullState(stream); GuestMapping(stream); + Allocation(); + MemoryCreate(); + MemoryRead(); + MemoryWrite(); + MemoryPosition(); + MemoryBitIo(); return 0; } diff --git a/core/libs/tests/LibreSsl3MixedEvp.cmake b/core/libs/tests/LibreSsl3MixedEvp.cmake index a958531217..a3d9f3fd83 100644 --- a/core/libs/tests/LibreSsl3MixedEvp.cmake +++ b/core/libs/tests/LibreSsl3MixedEvp.cmake @@ -12,7 +12,7 @@ execute_process(COMMAND "${PATCHER}" --write-export-list "${LIBC}" "${WORK_DIR}/ if(NOT status STREQUAL "0") message(FATAL_ERROR "libc export list failed: ${output}${error}") endif() -foreach(module LIBC KERNEL INTERNAL LEGACY SSL3 CONSUMER) +foreach(module LIBC KERNEL INTERNAL NETCTL NET LEGACY SSL3 CONSUMER) get_filename_component(name "${${module}}" NAME) get_filename_component(library "${${module}}" NAME_WE) file(COPY_FILE "${${module}}" "${WORK_DIR}/${name}") diff --git a/core/libs/tests/MimallocRestart.cpp b/core/libs/tests/MimallocRestart.cpp index 2d033bf463..d1e9675d7d 100644 --- a/core/libs/tests/MimallocRestart.cpp +++ b/core/libs/tests/MimallocRestart.cpp @@ -15,12 +15,12 @@ void Require(bool condition, const char* message) { } int main(int argc, char** argv) { - Require(argc == 2, "usage: mimalloc_restart_tests off|on|once"); + Require(argc == 2, "usage: mimalloc_restart_tests off|on|once|log"); const std::string_view mode = argv[1]; Require(std::getenv("APS5_MIMALLOC_RESTARTED") == nullptr, "the restart marker stayed in the environment"); using InHeap = bool (*)(const void*); const auto inHeap = reinterpret_cast(dlsym(RTLD_DEFAULT, "mi_is_in_heap_region")); - if (mode != "on") { + if (mode != "on" && mode != "log") { Require(inHeap == nullptr, "mimalloc was loaded without APS5_MIMALLOC or after a restart"); return 0; } @@ -31,5 +31,6 @@ int main(int argc, char** argv) { Require(inHeap(block), "malloc did not allocate through mimalloc"); Require(inHeap(object.get()) && inHeap(array.get()), "operator new did not allocate through mimalloc"); std::free(block); + if (mode == "log") std::printf("mimalloc_restart_tests: the restarted process writes to its standard output\n"); return 0; } diff --git a/core/libs/tests/PreciseWait.cpp b/core/libs/tests/PreciseWait.cpp index d9120b173c..3ecd193dc9 100644 --- a/core/libs/tests/PreciseWait.cpp +++ b/core/libs/tests/PreciseWait.cpp @@ -1,6 +1,13 @@ #include "prx/libc/include/PreciseWait.hpp" #include #include +#ifdef __linux__ +#include +#include +#include +#include +#include +#endif #include #include #include @@ -8,6 +15,86 @@ namespace { +#ifdef __linux__ +bool CheckLinuxTimerSlackFailures() { + for (unsigned mode = 0; mode < 5; ++mode) { + bool passed = false; + std::thread worker([&] { + if (prctl(PR_SET_TIMERSLACK, mode == 3 ? 1UL : 50000UL, 0UL, 0UL, 0UL) != 0) return; + if (mode == 4 && !PreciseLinuxLowTimerSlack()) return; + const bool denyRead = mode == 1 || mode == 2 || mode == 4; + const bool denyWrite = mode != 1; + sock_filter filter[] = { + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SYS_prctl, 0, 4), + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, PR_GET_TIMERSLACK, 1, 0), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, PR_SET_TIMERSLACK, 0, 1), + BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | EPERM), + BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), + }; + filter[3].jt = denyRead ? 1 : 2; + filter[4].jt = denyWrite ? 0 : 1; + sock_fprog program{static_cast(sizeof(filter) / sizeof(filter[0])), filter}; + if (prctl(PR_SET_NO_NEW_PRIVS, 1UL, 0UL, 0UL, 0UL) != 0 || + prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &program) != 0) return; + const bool expectedLowSlack = mode == 1 || mode == 3 || mode == 4; + if (PreciseLinuxLowTimerSlack() != expectedLowSlack || + PreciseLinuxLowTimerSlack() != expectedLowSlack) return; + for (const auto nanos : {1ULL, 15000ULL, 15001ULL, 99999ULL, 100000ULL, 100001ULL, 1000000ULL}) { + const auto start = PreciseMonotonicNanos(); + PreciseSleepFromNanosWithSlack(start, nanos, true); + if (PreciseMonotonicNanos() < start + nanos) return; + } + if (!denyRead && prctl(PR_GET_TIMERSLACK, 0UL, 0UL, 0UL, 0UL) != + (expectedLowSlack ? 1L : 50000L)) return; + passed = true; + }); + worker.join(); + if (!passed) { + std::cerr << "Linux timer slack failure scenario " << mode << " failed\n"; + return false; + } + } + return true; +} + +bool CheckLinuxTimerSlackBoundaries() { + bool passed = false; + std::thread worker([&] { + if (prctl(PR_SET_TIMERSLACK, 50000UL, 0UL, 0UL, 0UL) != 0) { + std::cerr << "Could not initialize timer slack for boundary checks\n"; + return; + } + const auto checkWait = [](unsigned long long nanos, bool allowLowSlack, long expectedSlack) { + const auto start = PreciseMonotonicNanos(); + PreciseSleepFromNanosWithSlack(start, nanos, allowLowSlack); + if (PreciseMonotonicNanos() < start + nanos) { + std::cerr << "Linux boundary wait returned before its deadline\n"; + return false; + } + if (prctl(PR_GET_TIMERSLACK, 0UL, 0UL, 0UL, 0UL) != expectedSlack) { + std::cerr << "Linux boundary wait changed timer slack unexpectedly\n"; + return false; + } + return true; + }; + for (const auto nanos : {0ULL, 1ULL, 14999ULL, 15000ULL}) { + if (!checkWait(nanos, true, 50000L)) return; + } + for (const auto nanos : {15001ULL, 99999ULL, 100000ULL, 100001ULL}) { + if (!checkWait(nanos, false, 50000L)) return; + } + for (const auto nanos : {15001ULL, 99999ULL, 100000ULL, 100001ULL}) { + if (!checkWait(nanos, true, 1L)) return; + } + passed = true; + }); + worker.join(); + return passed; +} +#endif + bool IsRunnerPreempted() { #ifdef _WIN32 char skip[8]{}; @@ -92,6 +179,11 @@ int main() { } } +#ifdef __linux__ + if (!CheckLinuxTimerSlackBoundaries()) return 1; + if (!CheckLinuxTimerSlackFailures()) return 1; +#endif + PreciseSleepUs(0); for (const unsigned long long micros : {50ULL, 1000ULL}) { diff --git a/core/relinker/codegen/tests/MulxLoweringTests.cpp b/core/relinker/codegen/tests/MulxLoweringTests.cpp index d3d7a0528d..bac8a51b65 100644 --- a/core/relinker/codegen/tests/MulxLoweringTests.cpp +++ b/core/relinker/codegen/tests/MulxLoweringTests.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -255,7 +256,14 @@ void check(const State& state, const Codegen::MulxOperands& operands, std::uint6 const auto expected = reg == operands.HighDestination ? high : reg == operands.LowDestination ? low : state.Input[reg]; require(state.Output[reg] == expected, "MULX changed an unrelated register or produced an incorrect result"); } - require((state.Flags & 0x8D5u) == (state.InputFlags & 0x8D5u), "MULX changed arithmetic flags"); + if ((state.Flags & 0x8D5u) != (state.InputFlags & 0x8D5u)) { + char text[192]; + std::snprintf(text, sizeof text, "MULX changed arithmetic flags: input %#llx, output %#llx, changed bits %#llx (wide %d, high %u, low %u)", + static_cast(state.InputFlags & 0x8D5u), static_cast(state.Flags & 0x8D5u), + static_cast((state.InputFlags ^ state.Flags) & 0x8D5u), operands.Wide ? 1 : 0, + static_cast(operands.HighDestination), static_cast(operands.LowDestination)); + throw std::runtime_error(text); + } for (const auto value : state.RedZone) require(value == Execution::kSentinel, "MULX overwrote the caller's red zone"); } diff --git a/core/relinker/relinker/tests/UnwindRecordBoundsTests.cpp b/core/relinker/relinker/tests/UnwindRecordBoundsTests.cpp index dd3ab410de..e4806e0882 100644 --- a/core/relinker/relinker/tests/UnwindRecordBoundsTests.cpp +++ b/core/relinker/relinker/tests/UnwindRecordBoundsTests.cpp @@ -387,6 +387,22 @@ int main() { unterminatedRecordRejected = std::string(error.what()).find("CIE fields exceed record") != std::string::npos; } Require(unterminatedRecordRejected); - std::cout << "33 unwind record cases passed\n"; + auto fdeZeroBytes = Fixture(); + Io::WriteU32(fdeZeroBytes, 96, 0); + bool fdeZeroRejected = false; + try { Relinker::UnusedNidFilter::ReadExceptionFunctions(fdeZeroBytes, {augLoad, augEh}, {}, {}); } + catch (const Domain::RelinkerException& error) { + fdeZeroRejected = std::string(error.what()).find("unsupported FDE record size") != std::string::npos; + } + Require(fdeZeroRejected); + auto fdeOverflowBytes = Fixture(); + Io::WriteU32(fdeOverflowBytes, 96, 1000); + bool fdeOverflowRejected = false; + try { Relinker::UnusedNidFilter::ReadExceptionFunctions(fdeOverflowBytes, {augLoad, augEh}, {}, {}); } + catch (const Domain::RelinkerException& error) { + fdeOverflowRejected = std::string(error.what()).find("unwind range is not file-backed") != std::string::npos; + } + Require(fdeOverflowRejected); + std::cout << "35 unwind record cases passed\n"; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; } } diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/Resources.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/Resources.hpp index cba24ca958..cbc6fc7986 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/Resources.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/Resources.hpp @@ -171,6 +171,10 @@ inline bool MirrorsIfUnnormalized(const SamplerResource& sampler) { return sampler.table == NoTable && !sampler.depthCompare && sampler.uses != 0u && ((sampler.uses & ~static_cast(SamplerUseExplicitLod | SamplerUseOffset | SamplerUseNonZeroLod)) == 0u || SamplesBaseLevelIfUnnormalized(sampler)); } +inline bool AppliesLodBiasInShader(const SamplerResource& sampler) { + return sampler.table == NoTable && (sampler.uses & (SamplerUseExplicitLod | SamplerUseImplicitLod | SamplerUseGradient)) != 0u; +} + struct SampledResourcePair { std::uint32_t image = 0; std::uint32_t sampler = 0; diff --git a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp index ed493ad037..36e92b6c4a 100644 --- a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp +++ b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp @@ -400,6 +400,14 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l if (FoldsTexelOffsets(info.samplers[index])) plan.specialization.push_back({PipelineSpecialization::FoldOffsetBase + index, unnormalized.samplers[index] ? 1u : 0u}); if (SamplesBaseLevelIfUnnormalized(info.samplers[index])) plan.specialization.push_back({PipelineSpecialization::UnnormalizedBaseLevelBase + index, unnormalized.samplers[index] ? 1u : 0u}); if (MirrorsIfUnnormalized(info.samplers[index])) plan.specialization.push_back({PipelineSpecialization::MirrorUnnormalizedBase + index, unnormalized.mirroredAxes[index]}); + if (info.samplers[index].table != NoTable) continue; + const auto bias = unnormalized.samplers[index] ? 0.0f : static_cast(static_cast((snapshot.samplers.at(index).dwords[2] & 0x3fffu) ^ 0x2000u) - 0x2000) / 256.0f; + const bool beyondThreshold = bias < -PipelineSpecialization::SamplerLodBiasShaderThreshold || bias > PipelineSpecialization::SamplerLodBiasShaderThreshold; + if (beyondThreshold && (!AppliesLodBiasInShader(info.samplers[index]) || (info.samplers[index].uses & (SamplerUseQueryLod | SamplerUseAdjust)) != 0u)) fail("a guest sampler LOD bias beyond the sampler limit reaches a shader use that cannot apply it, which is not implemented"); + for (const auto& pair : info.sampledPairs) { + if (beyondThreshold && pair.sampler == index && info.images.at(pair.image).cube) fail("a guest sampler LOD bias beyond the sampler limit on a cube image is not implemented"); + } + if (AppliesLodBiasInShader(info.samplers[index])) plan.specialization.push_back({PipelineSpecialization::SamplerLodBiasBase + index, beyondThreshold ? std::bit_cast(bias) : 0u}); } std::vector compareStates(info.images.size()); for (std::uint32_t index = 0; index < info.images.size(); ++index) { diff --git a/core/shader/recompiler/PipelineSpecialization.hpp b/core/shader/recompiler/PipelineSpecialization.hpp index 57143c36d5..8a367c5c18 100644 --- a/core/shader/recompiler/PipelineSpecialization.hpp +++ b/core/shader/recompiler/PipelineSpecialization.hpp @@ -51,6 +51,8 @@ inline constexpr std::uint32_t FmaskFormatBase = 57472u; inline constexpr std::uint32_t MirrorUnnormalizedBase = 57536u; inline constexpr std::uint32_t MinLodBase = 57600u; inline constexpr std::uint32_t VolumeGatherBase = 57664u; +inline constexpr std::uint32_t SamplerLodBiasBase = 57728u; +inline constexpr float SamplerLodBiasShaderThreshold = 15.0f; inline constexpr std::uint32_t DescriptorIndex(std::uint32_t binding, std::uint32_t element) { return DescriptorIndexBase + binding * DescriptorIndexStride + element; diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp index c1e192ce0b..9a8b23cd5c 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp @@ -991,9 +991,6 @@ RdnaInstruction DecodeRdnaDs(std::uint32_t programCounter, std::span= 0xE000u) { - throw std::runtime_error("DS swizzle FFT mode is not supported"); - } if (gds && (info.opcode == RdnaOpcode::DsSwizzleB32 || info.opcode == RdnaOpcode::DsBpermuteB32 || info.opcode == RdnaOpcode::DsPermuteB32 || info.opcode == RdnaOpcode::DsWriteAddtidB32 || info.opcode == RdnaOpcode::DsReadAddtidB32)) { throw std::runtime_error("DS lane operation is available only for LDS"); } diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index 02281d8d79..c1ef571320 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -1155,6 +1155,8 @@ constexpr Vop1SdwaRule vop1SdwaRules[] = { {RdnaOpcode::VCvtI32F32, sdwaSelFull(), sdwaSelBytes() | sdwaSelWords(), sdwaSelFull(), false}, {RdnaOpcode::VCvtRpiI32F32, sdwaSelFull(), sdwaSelBytes() | sdwaSelWords(), sdwaSelFull(), false}, {RdnaOpcode::VCvtFlrI32F32, sdwaSelFull(), sdwaSelBytes() | sdwaSelWords(), sdwaSelFull(), false}, + {RdnaOpcode::VSinF32, sdwaSelFull(), sdwaSelBytes() | sdwaSelWords(), sdwaSelFull(), false}, + {RdnaOpcode::VCosF32, sdwaSelFull(), sdwaSelBytes() | sdwaSelWords(), sdwaSelFull(), false}, }; const Vop1SdwaRule* findVop1SdwaRule(RdnaOpcode opcode) { diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp index 4e7efa7f94..422dfbd357 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp @@ -1836,25 +1836,32 @@ void EmitSampleOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, con operandMask |= spv::ImageOperandsBiasMask; operands.push_back(AddressF32(ctx, access, setup.layout.bias)); } + const auto unclampedLod = (operandMask & spv::ImageOperandsLodMask) != 0u ? operands.back() : 0u; + std::uint32_t lodClamp = 0; + std::uint32_t lodViewClamp = 0; + const auto fmax = [&](std::uint32_t left, std::uint32_t right) { + const auto value = state.module.AllocateId(); + state.module.AddFunction(spv::OpExtInst, TypeF32(state), value, GlslStd450(state), GLSLstd450FMax, left, right); + return value; + }; + const auto clampLod = [&](std::uint32_t lod) { + if (lodClamp != 0u) lod = fmax(lod, lodClamp); + if (lodViewClamp != 0u) lod = Select(state, TypeF32(state), Binary(state, spv::OpFOrdGreaterThan, TypeBool(state), lodViewClamp, ZeroF32(state)), fmax(lod, lodViewClamp), lod); + return lod; + }; const bool descriptorClamp = state.shaderImageMinLod && state.program.Info().images.at(mem.resource).table == NoTable; if (descriptorClamp && state.registrationModules) ctx.Fail(access.inst, "applies its T#'s minimum LOD clamp in the shader, which registration modules do not implement"); if (setup.layout.clamp != NoImageComponent || descriptorClamp) { const auto& capabilities = state.supportedCapabilities; if (std::find(capabilities.begin(), capabilities.end(), static_cast(spv::CapabilityMinLod)) == capabilities.end()) ctx.Fail(access.inst, "clamps its LOD, which needs the device's shaderResourceMinLod"); - const auto fmax = [&](std::uint32_t left, std::uint32_t right) { - const auto value = state.module.AllocateId(); - state.module.AddFunction(spv::OpExtInst, TypeF32(state), value, GlslStd450(state), GLSLstd450FMax, left, right); - return value; - }; - const auto clamp = setup.layout.clamp != NoImageComponent ? AddressF32(ctx, access, setup.layout.clamp) : 0u; - const auto viewClamp = descriptorClamp ? Unary(state, spv::OpBitcast, TypeF32(state), state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::MinLodBase + mem.resource, 0u)) : 0u; + lodClamp = setup.layout.clamp != NoImageComponent ? AddressF32(ctx, access, setup.layout.clamp) : 0u; + lodViewClamp = descriptorClamp ? Unary(state, spv::OpBitcast, TypeF32(state), state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::MinLodBase + mem.resource, 0u)) : 0u; if ((operandMask & spv::ImageOperandsLodMask) != 0u) { - if (clamp != 0u) operands.back() = fmax(operands.back(), clamp); - if (viewClamp != 0u) operands.back() = Select(state, TypeF32(state), Binary(state, spv::OpFOrdGreaterThan, TypeBool(state), viewClamp, ZeroF32(state)), fmax(operands.back(), viewClamp), operands.back()); + operands.back() = clampLod(operands.back()); } else { state.module.EmitCapability(spv::CapabilityMinLod); operandMask |= spv::ImageOperandsMinLodMask; - operands.push_back(clamp == 0u ? viewClamp : viewClamp == 0u ? clamp : fmax(clamp, viewClamp)); + operands.push_back(lodClamp == 0u ? lodViewClamp : lodViewClamp == 0u ? lodClamp : fmax(lodClamp, lodViewClamp)); } } const auto constantOffset = [&]() -> const IrValue* { @@ -1920,6 +1927,42 @@ void EmitSampleOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, con return emitDirectSample(coord, mask, arguments, sampleOpcode); }); }; + const auto emitBiasedSample = [&](std::uint32_t coord, std::uint32_t mask, const std::vector& arguments, std::uint32_t sampleOpcode) { + if (!AppliesLodBiasInShader(state.program.Info().samplers.at(mem.sampler))) return emitSample(coord, mask, arguments, sampleOpcode); + const auto f32 = TypeF32(state); + const auto selector = state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::SamplerLodBiasBase + mem.sampler, 0u); + const auto beyondLimit = Binary(state, spv::OpINotEqual, TypeBool(state), selector, ConstantU32(state, 0u)); + return EmitValueIfElse(state, beyondLimit, resultType, [&] { + const auto bias = Unary(state, spv::OpBitcast, f32, selector); + auto biased = arguments; + if ((mask & spv::ImageOperandsLodMask) != 0u) { + biased.front() = clampLod(Binary(state, spv::OpFAdd, f32, unclampedLod, bias)); + return emitSample(coord, mask, biased, sampleOpcode); + } + auto gradMask = mask; + auto total = bias; + if ((mask & spv::ImageOperandsBiasMask) != 0u) { + total = Binary(state, spv::OpFAdd, f32, total, biased.front()); + biased.erase(biased.begin()); + gradMask &= ~static_cast(spv::ImageOperandsBiasMask); + } + const auto components = setup.dimensionInfo.spatialComponents; + const auto gradientType = components == 1u ? f32 : TypeF32Vector(state, components); + if ((mask & spv::ImageOperandsGradMask) == 0u) { + const auto spatial = CoordF32(ctx, access, setup.layout.coord, components, AddressDimension(access).spatialComponents); + biased.insert(biased.begin(), {Unary(state, spv::OpDPdx, gradientType, spatial), Unary(state, spv::OpDPdy, gradientType, spatial)}); + gradMask |= spv::ImageOperandsGradMask; + } + const auto scale = state.module.AllocateId(); + state.module.AddFunction(spv::OpExtInst, f32, scale, GlslStd450(state), GLSLstd450Pow, ConstantF32(state, 0x40000000u), total); + for (std::size_t index = 0; index < 2u; ++index) { + biased[index] = Binary(state, components == 1u ? spv::OpFMul : spv::OpVectorTimesScalar, gradientType, biased[index], scale); + } + return emitSample(coord, gradMask, biased, setup.dref ? spv::OpImageSampleDrefExplicitLod : spv::OpImageSampleExplicitLod); + }, [&] { + return emitSample(coord, mask, arguments, sampleOpcode); + }); + }; std::uint32_t sample = 0; const bool alreadyBaseLevel = !hasOffset && opcode == spv::OpImageSampleExplicitLod && operandMask == spv::ImageOperandsLodMask && !HasFlag(mem, RdnaImageSampleFlagLod) && setup.layout.clamp == NoImageComponent; const bool baseLevelIfUnnormalized = !setup.dref && !alreadyBaseLevel && SamplesBaseLevelIfUnnormalized(state.program.Info().samplers.at(mem.sampler)); @@ -1929,12 +1972,12 @@ void EmitSampleOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, con sample = EmitValueIfElse(state, unnormalized, resultType, [&] { return emitSample(hasOffset ? FoldedOffsetCoord(ctx, access, setup) : setup.coord, spv::ImageOperandsLodMask, {ZeroF32(state)}, spv::OpImageSampleExplicitLod); }, [&] { - return emitSample(setup.coord, operandMask, operands, opcode); + return emitBiasedSample(setup.coord, operandMask, operands, opcode); }); } else if (!foldOffset) { - sample = emitSample(setup.coord, operandMask, operands, opcode); + sample = emitBiasedSample(setup.coord, operandMask, operands, opcode); } else if (!offsetOperand) { - sample = emitSample(FoldedOffsetCoord(ctx, access, setup), foldedMask, foldedOperands, opcode); + sample = emitBiasedSample(FoldedOffsetCoord(ctx, access, setup), foldedMask, foldedOperands, opcode); } else { const auto selector = state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::FoldOffsetBase + mem.sampler, 0u); const auto folded = Binary(state, spv::OpINotEqual, TypeBool(state), selector, ConstantU32(state, 0u)); @@ -1944,11 +1987,11 @@ void EmitSampleOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, con state.module.AddFunction(spv::OpSelectionMerge, merge, spv::SelectionControlMaskNone); state.module.AddFunction(spv::OpBranchConditional, folded, foldLabel, operandLabel); EmitLabel(state, foldLabel); - const auto foldSample = emitSample(FoldedOffsetCoord(ctx, access, setup), foldedMask, foldedOperands, opcode); + const auto foldSample = emitBiasedSample(FoldedOffsetCoord(ctx, access, setup), foldedMask, foldedOperands, opcode); const auto foldEnd = state.currentLabel; state.module.AddFunction(spv::OpBranch, merge); EmitLabel(state, operandLabel); - const auto operandSample = emitSample(setup.coord, operandMask, operands, opcode); + const auto operandSample = emitBiasedSample(setup.coord, operandMask, operands, opcode); const auto operandEnd = state.currentLabel; state.module.AddFunction(spv::OpBranch, merge); EmitLabel(state, merge); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvFormatConvert.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvFormatConvert.cpp index c2497d5f54..9fb96b6406 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvFormatConvert.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvFormatConvert.cpp @@ -345,6 +345,14 @@ void EmitDeviceAtomicMemoryBarrier(SpirvEmitterState& state) { } std::uint32_t EmitDsSwizzleTargetLane(SpirvEmitterState& state, std::uint32_t subid, std::uint32_t control) { + if ((control & 0xe000u) == 0xe000u) { + const std::uint32_t mask = control & 0x1fu; + const auto shift = 27u + static_cast(std::popcount(mask)); + const auto kept = EmitAndConstant(state, subid, mask | 0xffffffe0u); + if (shift >= 32u) return kept; + const auto reversed = EmitBinaryU32(state, spv::OpShiftRightLogical, Unary(state, spv::OpBitReverse, TypeU32(state), subid), ConstantU32(state, shift)); + return EmitOrU32(state, kept, reversed); + } if ((control & 0xc000u) == 0xc000u) { const std::uint32_t mask = control & 0x1fu; const std::uint32_t rotate = (control >> 5u) & 0x1fu; diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp index b0680c202a..4cb3a6a5cd 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp @@ -145,9 +145,6 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) { case RdnaOpcode::BufferAtomicSub: return bufferAtomic(inst, IrOpcode::BufferAtomicISub32); case RdnaOpcode::BufferAtomicCsub: - if (!inst.glc) { - throw std::runtime_error("buffer_atomic_csub without glc is not supported, at pc " + std::to_string(inst.programCounter)); - } return bufferAtomic(inst, IrOpcode::BufferAtomicUSubSat32); case RdnaOpcode::BufferAtomicSmin: return bufferAtomic(inst, IrOpcode::BufferAtomicSMin32); diff --git a/docs/dev/ARCHITECTURE.md b/docs/dev/ARCHITECTURE.md index 5d1804cb9a..d59caa10c4 100644 --- a/docs/dev/ARCHITECTURE.md +++ b/docs/dev/ARCHITECTURE.md @@ -42,7 +42,7 @@ flowchart LR app0 -- "OS loader binds imports by NID" --> libs ``` -- [`core/relinker/main.cpp`](../../core/relinker/main.cpp) runs the steps in this order. `--to-intel` is optional, see [USAGE.md](../user/USAGE.md). +- `Conversion::Convert` in [`Conversion.cpp`](../../core/relinker/relinker/src/pipeline/Conversion.cpp), called from [`core/relinker/main.cpp`](../../core/relinker/main.cpp), runs the steps in this order. `--to-intel` is optional, see [USAGE.md](../user/USAGE.md). - Each library in [`core/libs/prx`](../../core/libs/prx) builds as a shared library. After the build, `nid_patcher` ([`core/libs/nid`](../../core/libs/nid)) renames every export to its NID, computed from the function name. `APS5_EXPORT("", func)` sets the NID directly when the name is unknown. Every other library keeps the names libc exports; it reads them from `libc.exports`, which `nid_patcher --write-export-list` rewrites only when that list changes. On Windows the link writes each import library with `SOURCE_DATE_EPOCH=0` and `ranlib -D` and replaces it only when its bytes change. A source edit that keeps the exports therefore relinks and re-patches only its own library. - On Windows and Linux, bundled modules come before AnyPS5's libraries when an import is looked up. On Windows the entry stub ([`WindowsEntryStubBuilder.cpp`](../../core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp)) tries the converted `app0` modules before `libs`, by symbol name first and then by NID, and takes the first module that exports it; it also adds the host `libc.prx` to the search of every other host import. Imports from `libSceLibcInternal.prx` are the exception: the stub tries the host `libSceLibcInternal.prx` and `libc.prx` first and the bundled `libc.prx` last, skipping its exports that only return zero. On Linux [`GuestModuleBuilder.cpp`](../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) lists the guest modules loaded at startup before every host library in the executable's `DT_NEEDED`, `libSceLibcInternal.prx` included. A title that ships its own `libc.prx`, `libSceJobManager.prx` or other module therefore runs that module's code for the imports that name its library and that it exports, and the host library serves the rest. @@ -60,7 +60,7 @@ On Linux the argument block is the process stack itself (`mov rdi, rsp`). On Win ## Game patches -[`GamePatch.cpp`](../../core/relinker/relinker/src/patching/GamePatch.cpp) parses the patch file, a strict subset of TOML. `Conversion::Convert` applies the entries of its own target to the executable bytes right after reading them, and to each bundled module before `GuestModuleBuilder` reads it, so every later stage sees patched bytes. Entry virtual addresses are translated through the `PT_LOAD` segments, and a range may not touch the ELF header or program header table, which the output writers rewrite. The patched file ranges are then checked against every in-place writer: the `--to-intel` reports and trampoline sites, `RelinkPatch` records and syscall trap ranges. The patched virtual address ranges are checked against the 8-byte targets of the `RelaData` and `RelaPltData` relocations, which the loader or the output writers overwrite. `main.cpp` builds the manifest text before it writes any output. `main.cpp` matches targets after the conversions, aggregates the applied records into `.patches.toml`, and runs `--verify-patches` through `GamePatch::VerifyPatches`, which reads only bytes. +[`GamePatch.cpp`](../../core/relinker/relinker/src/patching/GamePatch.cpp) parses the patch file, a strict subset of TOML. `Conversion::Convert` applies the entries of its own target to the executable bytes right after reading them, and to each bundled module before `GuestModuleBuilder` reads it, so every later stage sees patched bytes. Entry virtual addresses are translated through the `PT_LOAD` segments, and a range may not touch the ELF header or program header table, which the output writers rewrite. The patched file ranges are then checked against every in-place writer: the `--to-intel` reports and trampoline sites, the `--frame-red-zone` frame sites and moved displacements, `RelinkPatch` records and syscall trap ranges. The patched virtual address ranges are checked against the 8-byte targets of the `RelaData` and `RelaPltData` relocations, which the loader or the output writers overwrite. `main.cpp` builds the manifest text before it writes any output. `main.cpp` matches targets after the conversions, aggregates the applied records into `.patches.toml`, and runs `--verify-patches` through `GamePatch::VerifyPatches`, which reads only bytes. ## Windows deferred module initialization @@ -83,15 +83,20 @@ On Windows, a relinked game run with `APS5_MIMALLOC` set (to any value) allocate - After the redirection these calls would enter mimalloc again, before the calling thread's mimalloc state exists. - Linker wraps send them to [`RuntimeHeap.c`](../../core/libs/allocator/RuntimeHeap.c), which allocates from the process heap (`HeapAlloc` on `GetProcessHeap()`). The same code frees these blocks through the same wraps. -## macOS allocator +## macOS and Linux allocator -On macOS, a relinked game run with `APS5_MIMALLOC` set (to any value) allocates through [mimalloc](../../3rdparty/mimalloc) instead of the system allocator: its libraries, the C and C++ runtime and the system frameworks they call. Without the variable the game keeps the system allocator. +On macOS and Linux, a relinked game run with `APS5_MIMALLOC` set (to any value) allocates through [mimalloc](../../3rdparty/mimalloc) instead of the system allocator: its libraries, the C and C++ runtime and the system libraries and frameworks they call, the Vulkan driver among them. Without the variable the game keeps the system allocator. -- The build copies `libmimalloc.dylib` beside the libraries ([`core/libs/CMakeLists.txt`](../../core/libs/CMakeLists.txt)). It keeps mimalloc's macOS defaults: it interposes the C allocation functions and `operator new` and `operator delete`, and replaces the default malloc zone. As on Windows, `mi_free` checks the pointers it gets. -- dyld interposes only for libraries loaded at launch, so mimalloc has to be in `DYLD_INSERT_LIBRARIES` when the process starts. When `APS5_MIMALLOC` is set and mimalloc is not loaded, a constructor in libkernel ([`MimallocMacOs.cpp`](../../core/libs/prx/libkernel/System/src/MimallocMacOs.cpp)) puts the `libmimalloc.dylib` beside libkernel first in `DYLD_INSERT_LIBRARIES`, sets `APS5_MIMALLOC_RESTARTED` and runs the same executable again with `execv`, with the same arguments and the same process ID. The restarted process removes `APS5_MIMALLOC_RESTARTED` from its environment. - - Without `libmimalloc.dylib` beside libkernel the title does not restart and keeps the system allocator. - - When dyld ignores `DYLD_INSERT_LIBRARIES`, as for an executable with the hardened runtime, the restarted process prints one line and keeps the system allocator instead of restarting again. - - `DYLD_INSERT_LIBRARIES` stays set, so a process the title starts also allocates through mimalloc. +- The build copies `libmimalloc.dylib` (macOS) or `libmimalloc.so` (Linux) beside the libraries ([`core/libs/CMakeLists.txt`](../../core/libs/CMakeLists.txt)). It keeps mimalloc's defaults: it interposes the C allocation functions and `operator new` and `operator delete`, on macOS also the default malloc zone and on Linux also glibc's `__libc_malloc` family. As on Windows, `mi_free` checks the pointers it gets. + - On Linux, glibc's `malloc_trim`, which the shader registry calls, then has nothing to trim; mimalloc returns freed memory to the system itself. + - A Linux build with `ANYPS5_ENABLE_ASAN` or `ANYPS5_ENABLE_TSAN` has no mimalloc: the sanitizer's runtime replaces the allocation functions itself and has to be the first library loaded. +- The dynamic linker binds the allocation functions to mimalloc only when it loads mimalloc at launch, from `DYLD_INSERT_LIBRARIES` (macOS) or `LD_PRELOAD` (Linux). When `APS5_MIMALLOC` is set and mimalloc is not loaded, a constructor in libkernel ([`MimallocRestart.cpp`](../../core/libs/prx/libkernel/System/src/MimallocRestart.cpp)) puts the mimalloc library beside libkernel first in that variable, sets `APS5_MIMALLOC_RESTARTED` and runs the same executable again with `execv`, with the same arguments and the same process ID. On Linux the executable is the target of `/proc/self/exe` and the arguments are those of `/proc/self/cmdline`, so a title started through `ld.so` starts through it again. The restarted process removes `APS5_MIMALLOC_RESTARTED` from its environment. + - The constructor has priority 101, so it runs before libkernel's other initializers, which have no priority, and none of their work is done twice or carried into the restarted process. It calls only the host C and C++ runtimes and libc, separate libraries that libkernel depends on, so they have initialized before it. + - libc's `ANYPS5_LOG_FILE` copy has taken the standard streams by then. libkernel gives them back before `execv`, as LoadExec does, and the restarted process starts the copy again. + - Without the mimalloc library beside libkernel the title does not restart and keeps the system allocator. + - The variable cannot list a path that has its separator, a colon (and on Linux a space): the title prints one line and keeps the system allocator. + - When the dynamic linker ignores the variable, as for an executable with the hardened runtime on macOS or one that runs in secure-execution mode on Linux (set-user-ID or with file capabilities), the restarted process prints one line and keeps the system allocator instead of restarting again. + - The variable stays set, so a process the title starts also allocates through mimalloc. - mimalloc asks for its arenas at addresses from 2 TiB up, above the guest arena's range; the few blocks it maps without that hint come from the system like any other host mapping. Guest code allocates through libc's guest heap, as on Windows. ## Thread cancellation @@ -135,10 +140,10 @@ flowchart LR ``` - [`Recompiler.cpp`](../../core/shader/recompiler/Recompiler.cpp) runs the stages in this order. With `ANYPS5_ENABLE_SPIRV_TOOLS`, the SPIR-V is also validated and optimized with SPIRV-Tools. -- `ShaderRecompiler::Recompile` keeps compiled variants in memory, and `ShaderDiskCache` stores them on disk so later runs reuse them. Before the writable cache, a miss is also looked up in read-only prebuilt packs and loose entry folders under `shader_cache/prebuilt`; pack entries are ordinary encoded cache entries behind a sorted key-hash index, validated by magic, compiler source version and index checksum when mapped, and a miss or rejected entry falls through to the writable cache and the compiler. Cache format 35 also stores the finite stencil-reference set, its replay-safety proof and reserved push-data word in the artifact and binding layouts. Entries from formats 31 to 34 are rejected. +- `ShaderRecompiler::Recompile` keeps compiled variants in memory, and `ShaderDiskCache` stores them on disk so later runs reuse them. Before the writable cache, a miss is also looked up in read-only prebuilt packs and loose entry folders under `shader_cache/prebuilt`; pack entries are ordinary encoded cache entries behind a sorted key-hash index, validated by magic, compiler source version and index checksum when mapped, and a miss or rejected entry falls through to the writable cache and the compiler. The cache format (`ShaderDiskCache::FormatVersion`) stores, among other things, the finite stencil-reference set, its replay-safety proof and reserved push-data word in the artifact and binding layouts. An entry of any other format, or written by another compiler source version, is rejected. - The Vulkan pipelines are journaled as well ([`PipelineJournal.cpp`](../../core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp)): a graphics pipeline the store builds (`CachedPipeline`) or a compute pipeline a dispatch builds is appended to `pipelines.journal` next to the shader cache entries, as the materialized result of each stage (the specialized SPIR-V, bindings and push constants, through `ShaderDiskCache::EncodeResult`) plus the recipe the pipeline was built from (`GraphicsPipelineRecipe`: the stages' identities, vertex input layout, descriptor set layout keys and the state fields the `Pipeline` constructor reads, `CanonicalPipelineState`). When a `VulkanDevice` is created, the journal is loaded and its pipelines are built on a pool of `hardware_concurrency() / 2` threads (`APS5_PIPELINE_JOURNAL_THREADS`) while the title loads. The load parses the file a record at a time and keeps it open, and a replay thread reads an entry's modules from it when it builds that entry (a record that no longer reads back as loaded rejects the entry), so the replay holds the modules of the entries being built rather than the file, so the first draw or dispatch of a configuration seen on an earlier run finds its pipeline resident. The pipeline stores key on `RecompileResult::Identity`, a hash of the module words, specialization constants, subgroup width and bindings that is the same on every run (`SharedSpirv::ContentHash`, computed once per shared module), rather than on the process-lifetime variant ids, and a graphics pipeline is built outside the store's mutex, so the replay threads and the queue workers build in parallel. A draw that arrives before its replayed entry builds the pipeline itself; the first inserted wins. A replayed entry enters the store at its least recently used end and a full store takes none, so the title's own pipelines are never evicted for a replayed one, and the optimized relink of a replayed pipeline (`OptimizedPipeline::Schedule`) waits for the first draw that binds it, so the relink thread serves the title's pipelines first. A device created without a window starts its replay at its first draw or dispatch rather than at creation, since `Driver::AttachWindow` usually replaces it before the title draws. The journal records a pipeline once (modules and recipes are deduplicated by content), keeps at most `APS5_PIPELINE_JOURNAL_MAX_MIB` (1024), replays the last `APS5_PIPELINE_JOURNAL_ENTRIES` recorded (4096, the pipeline store's largest bound: a replayed pipeline that finds the store full grows its bound from 1024 instead of being dropped), and is disabled with `APS5_NO_PIPELINE_JOURNAL=1` or without a shader cache directory. The file header names the device (`PipelineJournalDeviceFingerprint`, the properties the driver's pipeline cache file is named by), so a journal written for another GPU or driver is started over rather than replayed; appends and reads hold `pipelines.journal.lock` against another process sharing the directory, and a device cancels its replay before it is replaced (`PrepareForReplacement`) or destroyed, so a replaced headless device never inserts entries the window's device then sweeps. `APS5_PROFILE_DRAW=1` prints a `[pipeline-journal]` summary when the replay finishes and at teardown. - A compute shader whose LDS plus its lock dword exceeds the host's `maxComputeSharedMemorySize` is not given a `Workgroup` array. `Recompile` sets `ShaderInfo::sharedMemoryBytes` for it and the SPIR-V backend addresses the LDS in a coherent storage buffer at descriptor set 1 (`WorkgroupMemoryDescriptorSet`), with a slice of `RecompileResult::workgroupMemoryDwords` per workgroup, indexed by `WorkgroupID` and `NumWorkGroups`. `VulkanDevice` keeps one such buffer, sized from the dispatch dimensions, and binds it after the resource set; programs that fit keep using shared memory. -- `Driver::RegisterShader` splits shader preparation in two. `PlanRegistered` runs on the registering guest thread: it validates the registers and entry point, decodes the code and builds the recompile request, so invalid registrations still fail there. The RDNA to IR translation and resource plan (`PrepareShader`) run on a background pool of `hardware_concurrency() / 2` detached threads (at least 2, 64 MiB stacks). +- `Driver::RegisterShader` splits shader preparation in two. `PlanRegistered` runs on the registering guest thread: it validates the registers and entry point, decodes the code and builds the recompile request, so invalid registrations still fail there. The RDNA to IR translation and resource plan (`PrepareShader`) run on a background pool of `hardware_concurrency() / 2` threads (at least 2, 64 MiB stacks), which `Driver::stop` joins when it closes the pool. - A thread that needs the GPU mutex, the committer's next item or the end of the committer's queue spins before it blocks (`HandoffSpin.hpp`): `GpuMutexType::lock` retries `try_lock` for the window, the committer polls its item count, and `DrawPipeline::Drain` polls it for twice the window, so a hand-off that completes within the window costs no kernel wake-up. The window is 150 us when the process may run on more than 8 logical processors (its affinity mask, via `CpuTopology`) and 0 otherwise, where the spinning threads would compete with the holder; `APS5_HANDOFF_SPIN_US=` sets it (0 turns it off, at most 10000). Ordering and the lock's semantics are unchanged. - Queue 0's packet worker decodes, captures and prepares a draw and hands its recording (`Graphics::Draw` under the GPU lock) to a committer thread in [`DrawPipeline.cpp`](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp), so the next packet is decoded while the previous draw is recorded. Each queued item carries the guest ranges it will write (`DrawWriteRanges`: written buffers and storage images, color targets with their DCC and CMASK, depth and stencil). A later packet that reads or writes one of those ranges, or that the pipeline can't order, drains it first; labels, submission ends, buffer fills and DMA packets are queued as items instead. With `APS5_PIPELINE_DISPATCH=1` (off by default), a `DISPATCH_DIRECT` whose dispatch-cache entry hits with a recipe is queued as an item too, carrying the dispatch's written buffers and storage images (`DispatchWriteRanges`); the capture and the cache lookup stay on the worker and the recipe is recorded on the committer. A dispatch still drains when its lookup misses, when what it reads overlaps a write still pending, when the hit is verified against data or when its writes cannot be ranged (bindless access, GDS, fault buffer). An indirect draw resolved on the CPU (its records read by the worker) drains only when its records or its count overlap a queued write; the records it draws inline drain first, and its last drawn record is queued like a direct draw (`APS5_NO_PIPELINED_INDIRECT=1` drains at every indirect draw). An exception thrown by a commit is rethrown on the worker at its next enqueue or drain, as an inline draw error would be. On by default; `APS5_PIPELINED_DRAWS=0` records every draw inline, and `APS5_EXACT_DRAW_WRITES=0` widens the color and depth ranges to estimates. - A dispatch's resource build runs stage A (binding plan, buffers, descriptor set) without the GPU lock and stage B (image lookups) under it. Between them `VulkanDevice::presync` waits, still without the lock, for recorded work that writes a surface stage B would read on the CPU and for recorded work that writes the DCC keys of an image the dispatch binds (`ShaderResources::PresyncSurfaces`), so the locked `WaitForKeyWriters` finds nothing pending to synchronize with. A key range is waited for only when recorded work writes it, the key-store memo does not give its value (`DccKeyWritersPending`, without the mutex) and the pending stores do not give it either (`DccKeyWriterNeedsSync`, under the mutex, the same test `WaitForKeyWriters` makes before it synchronizes). Stage A's own key scan runs before that wait, and stage B compares it with the keys it reads under the lock as before. `APS5_NO_DCC_PRESYNC=1` leaves the key ranges out; `APS5_NO_PRESYNC=1` disables the whole pre-sync. @@ -148,7 +153,7 @@ flowchart LR - `APS5_LAZY_SHADER_PREPARE=1` retains registration validation and decoding but discards the temporary plan without queueing preparation. The snapshot is deferred, not pending; draws and dispatches prepare and cache it on first use. A compute or pixel stage's explicit ABI resolution (`ResolveShaderAbi`) plans the stage with the ABI's state, records the ABI and leaves its preparation to the shader's first use, which prepares it with that state (`PreparedShaders::deferredAbis`), so a shader that never draws or dispatches stays unprepared; a graphics-stage ABI is still prepared when it is resolved. This opt-in mode takes precedence over synchronous registration preparation and trades background CPU/memory use for possible first-use stalls; disk caches and shader output are unchanged. - On devices with graphics pipeline libraries (`SpirvTarget::registrationModules`), a pixel artifact is emitted in registration form: the buffer V# words, image modes, swizzles and mip ranges, depth-compare state and export mappings it used to specialize on per draw are read at run time (image `ResourceMetadata` and a few state dwords in the shader data), descriptor heaps keep every element in place (an unused one gets a null or placeholder descriptor), and the pixel stage's bindings live in descriptor set 1 so each stage's library depends only on its own set layout (`INDEPENDENT_SETS`). A vertex artifact is emitted specialized: in registration form each vertex buffer load would decode the V# stride, swizzle, bounds mode and format at run time for every vertex, so its pre-rasterization library is built at its first draw instead. An artifact above `registrationWordLimit` SPIR-V words is re-emitted specialized, and `CompiledShaderArtifact::registrationModule` records which form it has. For artifacts in registration form, `RegistrationResult` materializes the module once, and `PrebuildStageLibrary` builds its fragment-shader library on a separate pool of `max(2, hardware_concurrency() / 2)` threads (`APS5_PREBUILD_THREADS` sets the count, at least 1) with a fixed default state, pairing it with a stub vertex stage. A later draw with that state finds the fragment-shader library in the store. Ahead-of-time dispatch resolution (`resolveGroupAhead` in [`DispatchAhead.cpp`](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp)) scans the upcoming dispatch run up to the next wait or synchronization packet, moves still-pending registered compute shaders to the front of the registration preparation queue (`PrioritizeRegistrationPreparation`) and queues preparation (`SubmitRegistrationPreparation`) for registered compute shaders that have no prepared entries yet (such as under `APS5_LAZY_SHADER_PREPARE=1`) with the previewed register state and entry offset; when a dispatch resolved ahead misses the dispatch cache in [`Dispatch.cpp`](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp), `PrebuildComputePipeline` is submitted to the same `APS5_PREBUILD_THREADS` pool so the compute `VkPipeline` is built before the worker reaches the packet. - 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 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 and `ProcessShutdown` still propagate from the ABI call. A stage whose registration preparation stored a failure is logged once with that reason and marked deferred (`DeferFailedRegistration`); the draw or dispatch that uses the stage rethrows the stored failure. - 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, barycentrics emulated through generated tessellation stages, tessellation on a driver without a usable tessellation pipeline cache (MoltenVK, `TessellationPipelineCacheUsable`), a pixel shader with a fragment shader interlock or with its LDS array in a storage buffer (`RecompileResult::functionLdsBuffer`), or a stage with an empty `RecompileResult::Identity`, and every pipeline under `APS5_PIPELINE_STATS=1`, are created whole (`libraryKeys` in [`Pipeline.cpp`](../../core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp)), still with dynamic rendering; devices without the extensions (or with `APS5_NO_GPL=1`) keep render passes and whole pipelines. - A storage or sampled view of a thin array whose whole surface has more layers than the device's `maxImageArrayLayers` (2048 on NVIDIA; a PS5 array may hold up to 8192) is served from its viewed slices alone: `ArrayLayerWindow` in [`TextureTiling.cpp`](../../core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp) describes them as an array of their own starting at the first viewed slice, which is exact because a non-XOR swizzle does not depend on the slice index. A 3D volume is not an array (its depth is bounded by `maxImageDimension3D`) and a 2D image is one layer whatever its last array field holds; both pass unchanged. A view whose slices alone exceed the limit, a view of an XOR swizzle that mixes in the slice index, and any other image past the limit throw; a sampled lookup (`cachedTexture`) throws before it reads or snapshots the surface. @@ -173,16 +178,16 @@ flowchart LR - **Queue.** One logger thread drains a queue that holds at most 16 MiB of waiting lines; the batch the logger is writing comes on top. A caller that finds the queue full blocks until the logger has taken the pending batch, so no line is dropped. The logger writes entries in queue order and joins consecutive entries of one stream; it writes them with one `fwrite` and flush when the text reaches 64 KiB, the stream changes or the batch ends. `AGC_LOG_DEFERRED` queues the format string with numeric, enum and `LogLiteral` arguments and formats them on the logger thread; `APS5_LOG_TIMESTAMPS` stamps a line when it is queued. - **Order.** Lines that pass through the sink, on stdout and stderr alike, appear in call order. A direct `fprintf(stderr, ...)` or `printf` does not pass through it and is written at once, so it can appear before older queued lines; the coalesced `deferred write-back failed` notes are of this kind. `APS5_LOG_SYNC=1` keeps one order for all of them. -- **Flush.** `Flush` waits without a limit until every line queued before the call has been written, including while another thread holds the stream. It runs when the driver fails and in `Driver::stop`. At exit `AsyncLog` runs a bounded flush (`FlushFor`, 2 s) instead: on Windows the `atexit` handlers of a DLL run after the process has ended its other threads, so the logger thread may be gone with lines still queued, and a `Flush` there would wait forever. It also registers that bounded flush with `LibcSetEndFlush_nid_no_patch`; libc runs it where the process ends without `atexit`: `abort()` and `std::terminate` (a `SIGABRT` handler that the first registration chains in front of the previous one, so `raise(SIGABRT)` still ends the process), guest `abort()`, the Windows crash report of a fatal exception and guest `_Exit`. A title that installs its own `SIGABRT` disposition replaces that handler. The bounded flush polls two counters and takes no lock, so a thread that dies holding the queue lock or a stream cannot stall it. After 2 s it gives up, later calls return at once, and the lines still queued are lost. +- **Flush.** `Flush` waits without a limit until every line queued before the call has been written, including while another thread holds the stream. It runs when the driver fails. `Driver::stop` runs the bounded flush described next. At exit `AsyncLog` runs a bounded flush (`FlushFor`, 2 s) instead: on Windows the `atexit` handlers of a DLL run after the process has ended its other threads, so the logger thread may be gone with lines still queued, and a `Flush` there would wait forever. It also registers that bounded flush with `LibcSetEndFlush_nid_no_patch`; libc runs it where the process ends without `atexit`: `abort()` and `std::terminate` (a `SIGABRT` handler that the first registration chains in front of the previous one, so `raise(SIGABRT)` still ends the process), guest `abort()`, the Windows crash report of a fatal exception and guest `_Exit`. A title that installs its own `SIGABRT` disposition replaces that handler. The bounded flush polls two counters and takes no lock, so a thread that dies holding the queue lock or a stream cannot stall it. After 2 s it gives up, later calls return at once, and the lines still queued are lost. - **Statistics.** A `[log]` line every 10 s in which lines were logged reports lines, bytes, the time spent by the callers and by the logger, and the queue peak; `APS5_LOG_STATS=0` hides it. Identical `deferred write-back failed` notes print three times per 10 s and are then counted in one `repeated N more times` line. -`agc_async_log` checks the order of deferred and written lines, long lines, the repeat limit, a `Flush` that waits for a held stream, a bounded flush that gives up, and a full queue that blocks its caller. `agc_async_log_fatal` starts the test executable as a child that queues lines behind a held stream and then calls `abort()`, calls `std::terminate()`, raises an access violation (Windows) or `SIGABRT` (elsewhere), or aborts without ever releasing the stream; the first four must leave every line in the file in order, the last must still end after the bound. +`agc_async_log` checks the order of deferred and written lines, long lines, the repeat limit, a `Flush` that waits for a held stream, a bounded flush that gives up, and a full queue that blocks its caller. `agc_async_log_fatal` starts the test executable as a child that queues lines behind a held stream and then calls `abort()`, calls `std::terminate()`, raises an access violation (Windows) or `SIGABRT` (elsewhere), or aborts without ever releasing the stream; the first three must leave every line in the file in order, the last must still end after the bound. `ANYPS5_LOG_FILE=` (libc, [`LogFile.cpp`](../../core/libs/prx/libc/src/LogFile.cpp)) adds a copy of the runtime log that does not go through `WriteLogLine` or the sink. At load, libc keeps the real standard output and error, diverts descriptors 1 and 2 (the standard handles on Windows) into two pipes and starts one thread per pipe. Each thread writes what it reads to the kept stream at once and each whole line to the file under one lock, so a line of one stream never lands inside a line of the other. A line without a newline stays in a bounded carry (1 MiB) and is written to the file when it ends, when the carry is full or when the process ends. The crash and abort reporters do not use the pipes: `log_file::CrashWrite` takes the lock for at most 250 ms, moves what is still in the pipes into the file and writes the report to the kept stderr and the file directly. `LibcRunEndFlush_nid_no_patch` and an `atexit` handler drain at the end; `LogFileRelease_nid_no_patch` puts the real streams back before `sceSystemServiceLoadExec` replaces the process. Unset or empty, nothing is created. ### Runtime image and sampler tables -A T# or S# that a shader loads from a buffer at a runtime offset (`key * stride + addend`, or `(key << n) & mask`) is not a direct descriptor. [`ResourceTracker.cpp`](../../core/shader/recompiler/Optimization/src/ResourceTracker.cpp) plans it as a table column (`TableColumn`): the heap V#'s descriptor source, the record stride and addend, the descriptor's offset in the record, and the key domains that may narrow it. Accesses with the same heap and record shape share one column. The shader keeps computing the offset and indexes a table binding: images use one variable-count `SAMPLED_IMAGE` array (`RuntimeAbi::ImageTableBinding`, in the fragment set when pipeline libraries split the sets), samplers a `SAMPLER` array (`Binding::SamplerTable`). Devices without `runtimeDescriptorArray`, `descriptorBindingPartiallyBound` and `descriptorBindingVariableDescriptorCount` refuse such shaders. +A T# or S# that a shader loads from a buffer at a runtime offset (`key * stride + addend`, or `(key << n) & mask`) is not a direct descriptor. [`ResourceTracker.cpp`](../../core/shader/recompiler/Optimization/src/ResourceTracker.cpp) plans it as a table column (`TableColumn`): the heap V#'s descriptor source, the record stride and addend, the descriptor's offset in the record, and the key domains that may narrow it. Accesses with the same heap and record shape share one column. The shader keeps computing the offset and indexes a table binding: sampled images use one variable-count `SAMPLED_IMAGE` array (`RuntimeAbi::ImageTableBinding`, in the fragment set when pipeline libraries split the sets), images stored through a table a partially bound `STORAGE_IMAGE` array (`RuntimeAbi::ImageStorageTableBinding`), samplers a `SAMPLER` array (`Binding::SamplerTable`). Devices without `runtimeDescriptorArray`, `descriptorBindingPartiallyBound` and `descriptorBindingVariableDescriptorCount` refuse such shaders. A T# whose eight dwords and record offset the SRT walker can evaluate is planned as a selected column (`TableColumn::selected`): the offset is kept as a descriptor source (`TableColumn::selection`) and the shader indexes entry 0 of a one-record table. The column stays a whole table when the shader may write the memory its offset is read from at run time: the buffer it is read from, or any memory when the read or a write goes through a pointer or a GPU-only descriptor. Samplers are not selected: an S# the walker can evaluate is a direct sampler. When the selected columns would take a shader past `ShaderInfo::MaxImages` or `ShaderInfo::MaxSampledPairs`, `PlanTableColumns` plans the columns of one heap unselected again, the heap with the most selected columns first, until both fit. @@ -196,9 +201,9 @@ The disk cache stores the table and table operation of each image and sampler, t ### Untracked storage image comparisons -A storage image over guest memory that the write watch does not cover (a host-pointer import on a driver that drops its write protection, or an unwatched range) has no block stamps to tell a CPU edit apart, so `StorageTexture::compareUntracked` in [`Texture.cpp`](../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) compares the guest bytes with the image's snapshot. A whole-surface comparison that found no change is remembered under a stamp of: +A storage image over guest memory that the write watch does not cover (a host-pointer import on a driver that drops its write protection, or an unwatched range) has no block stamps to tell a CPU edit apart, so `StorageTexture::compareUntracked` in [`Texture.cpp`](../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) compares the guest bytes with the image's snapshot. A comparison of the whole surface, or of the layer range a use of an array covers, that found no change is remembered under a stamp of: -- the calling queue worker's collect epoch (`GuestMemory::CollectEpoch`), which advances at every ordering point where a CPU store may have landed (submission start, a satisfied `WAIT_REG_MEM`, a drain); a thread that never bumps it gets epoch 0 and is never memoized, so the presenter adopts the newest epoch any worker started (`GuestMemory::AdoptLatestCollectEpoch`, never moving a thread back; `APS5_NO_PRESENT_EPOCH=1` disables it); +- the calling queue worker's collect epoch (`GuestMemory::CollectEpoch`), which advances at every ordering point where a CPU store may have landed (submission start, a `WAIT_REG_MEM` the CPU may have satisfied, a drain, and reaping completions outside a submission); a thread that never bumps it gets epoch 0 and is never memoized, so the presenter adopts the newest epoch any worker started (`GuestMemory::AdoptLatestCollectEpoch`, never moving a thread back; `APS5_NO_PRESENT_EPOCH=1` disables it); on Windows, when every block of the range has left the watch but keeps its write-watch marks as a hint (`GuestMemory::UntrackedHint`, walked at most once per collect epoch and thread), the newest hint stamp of the range takes the epoch's place, so the image is compared again after a CPU store marks its pages rather than at every epoch; - `GuestMemory::NewestStamp` of the surface: the newest block stamp of its watched part, and the generation of the newest driver store (`MarkWritten`, `StoreOwnBytes`) into its unwatched part; - `GuestMemory::ForgetSerial`, which changes when guest memory is released or remapped; - the recorder's newest pending GPU write note over the surface. @@ -209,18 +214,18 @@ The comparison runs again when any of them changes. A CPU store into unwatched m A cached shader keeps an `IrResourcePlan` (`ResourceMaterializer::ExtractPlan` in [`ResourceMaterializer.cpp`](../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp)) for as long as it is registered: a copy of the IR values its descriptor sources, SRT reads, uniform fill and control-flow conditions depend on, which every capture walks to read the shader's resources from guest memory. The copy is sized to what the walk can use: -- A control-flow condition is kept only when `Detail::Evaluator::MayEvaluate` finds a runtime it could be evaluated for. A condition that depends on a vector register, or on an opcode the evaluator does not evaluate, takes both successors whatever the runtime holds, so the plan stores it as no condition and does not copy its operands. A first-lane read is judged by its source under the read's active mask, and a constant read by the SRT read it names, as the evaluator reads them. A first-lane read that reaches itself, through a loop-carried phi or a constant slot that holds it, is not evaluable: both `MayEvaluate` and the evaluator track the first-lane reads they are already inside, since each one starts a new walk under its own mask. Such conditions are most of a shader's IR when the masked-lane code feeds its branches. +- A control-flow condition is kept only when `Detail::Evaluator::MayDecide` finds a runtime it could be decided for: one `MayEvaluate` can evaluate, a logical not, and or or with an operand that may decide it (the evaluator decides and/or from one operand alone), or an unsigned `<` or `>` compare with one evaluable operand (decided when that operand is 0 or 0xffffffff). A condition that cannot be decided that way (one that depends only on vector registers or on opcodes the evaluator does not evaluate) takes both successors whatever the runtime holds, so the plan stores it as no condition and does not copy its operands. A first-lane read is judged by its source under the read's active mask, and a constant read by the SRT read it names, as the evaluator reads them. A first-lane read that reaches itself, through a loop-carried phi or a constant slot that holds it, is not evaluable: both `MayEvaluate` and the evaluator track the first-lane reads they are already inside, since each one starts a new walk under its own mask. Such conditions are most of a shader's IR when the masked-lane code feeds its branches. - The copied values are allocated in one `IrValueArena` and hold no use lists: the walk only follows arguments. ### Shader MODE register access -`GuestContext::floatMode` carries the initial shader mode through `Recompile` and `TranslateOptions` into each block's `TranslationContext`. `s_getreg_b32` reads confined to MODE bits 0–3 return the selected initial rounding bits in the destination's low bits. Bits 0–1 select f32 rounding; bits 2–3 select f16/f64 rounding. Missing mode metadata retains the legacy round-to-nearest-even assumption. Reading other MODE fields or hardware registers throws. +`GuestContext::floatMode` carries the initial shader mode through `Recompile` and `TranslateOptions` into each block's `TranslationContext`. `s_getreg_b32` reads confined to MODE bits 0–3 return the rounding bits tracked at that instruction (the initial bits, updated by the rounding writes on the paths reaching it) in the destination's low bits; bits that differ between those paths or were written from an SGPR throw. Bits 0–1 select f32 rounding; bits 2–3 select f16/f64 rounding. Missing mode metadata retains the legacy round-to-nearest-even assumption. Reading other MODE fields or hardware registers throws. -An immediate MODE write confined to the rounding fields is accepted only when its selected replacement bits equal the initial state. Source bits above the selected width are ignored. `s_round_mode` similarly compares its low four immediate bits with the initial rounding fields. A state-changing write or an SGPR-sourced MODE write throws with its instruction and program counter. Runtime transitions require control-flow-aware mode tracking and matching arithmetic lowering. Writes to other hardware registers retain their existing behavior and remain technical debt. +An immediate MODE write confined to the rounding fields (`s_setreg_imm32_b32` on MODE bits 0–3) sets those bits, and `ComputeEntryRounding` in [`RoundingMode.cpp`](../../core/shader/recompiler/Translation/src/RoundingMode.cpp) follows the result through the control-flow graph into each block's entry state (`SetEntryRounding`); a shader without `FLOAT_MODE` metadata throws on any write that leaves a non-zero or unknown rounding state. Source bits above the selected width are ignored. `s_round_mode` sets the four rounding bits from its low four immediate bits. An SGPR-sourced `s_setreg_b32` to the rounding fields marks them unknown; an `s_getreg_b32` or float instruction that reads unknown bits, or bits that differ between the paths reaching it, throws with its program counter. A MODE write that touches a field other than the rounding fields throws. Only `v_cvt_f32_i32`, `v_cvt_f32_u32`, `v_ldexp_f32`, `v_cvt_pk_u8_f32`, the f16 conversions and `v_ldexp_f16` (with f16 denormals kept) and the instructions that already read `FLOAT_MODE` follow a rounding mode set at run time (`checkRoundingUse`); any other float instruction that runs while a directed rounding mode differs from the initial one throws at that instruction. Writes to other hardware registers retain their existing behavior and remain technical debt. The register layout and instruction contract follow [AMD RDNA2 ISA sections 5.8 and 6.4, Table 24](https://www.amd.com/content/dam/amd/en/documents/radeon-tech-docs/instruction-set-architectures/rdna2-shader-instruction-set-architecture.pdf), cross-checked against LLVM 20.1.8's [hardware-register encoding](https://github.com/llvm/llvm-project/blob/87f0227cb60147a26a1eeb4fb06e3b505e9c7261/llvm/lib/Target/AMDGPU/Utils/AMDGPUBaseInfo.h) and [MODE state analysis](https://github.com/llvm/llvm-project/blob/87f0227cb60147a26a1eeb4fb06e3b505e9c7261/llvm/lib/Target/AMDGPU/SIModeRegister.cpp). This contract does not establish arithmetic support for every initial mode or console-specific behavior. -`agc_shader_mode_register` decodes instruction words, builds the control-flow graph and checks production translation without a Vulkan device. It covers rounding subfield reads, masked constant writes, branches, unsupported runtime changes and diagnostics, plus mode propagation through full recompilation. The requirements remain active in Release; CTest bounds execution to 20 seconds. +`agc_shader_mode_register` decodes instruction words, builds the control-flow graph and checks production translation without a Vulkan device. It covers rounding subfield reads, immediate and SGPR-sourced writes, `s_round_mode`, branches, joins and loops, the instructions that follow a rounding mode set at run time and those that throw under one, unsupported reads and writes and their diagnostics, plus mode propagation through full recompilation. The requirements remain active in Release; CTest bounds execution to 20 seconds. ### Hardware f32 denormal flush diff --git a/docs/dev/BUILD.md b/docs/dev/BUILD.md index b2f12324e4..8d78a00205 100644 --- a/docs/dev/BUILD.md +++ b/docs/dev/BUILD.md @@ -254,14 +254,14 @@ On Windows use `agc_pm4_replay.exe` and prepend `build/core/libs/libs/unpatched` On Windows and Linux, set `APS5_FRAME_CAPTURE` to a directory to record a few frames of a title at the AGC driver boundary, then replay them with `agc_frame_replay` through any build of the driver with no title running. From the start of the process the driver keeps every shader registration and ABI resolution, video output registration and EOP flip reservation. At the frame boundary picked by `APS5_FRAME_CAPTURE_AT_FLIP` (default 1), or by the appearance of the file `APS5_FRAME_CAPTURE_TRIGGER` names (a number in it replaces the frame count), it waits for the driver to go idle and writes every committed, readable allocation of the guest allocation registry, the title's image included, as zstd chunks without their all-zero 64 KiB blocks. A frame ends at a CPU flip or at a submission that flips (a flip packet, or the EOP release of a reserved flip token); the reservation itself only records its token. For the next `APS5_FRAME_CAPTURE_FRAMES` (10) frames it records each submission (queue, command buffer address and size), flip and suspend point with the thread that made it and the accepted and completed serials, each preceded by the guest pages the write tracker saw the CPU write since the previous call that handed work to the GPU (`GuestMemory::CollectWrittenBlocks`, per 4 KiB where the tracker kept page stamps), read once the submissions already accepted have completed (a recorded submission, flip or suspend point reaches the driver before the recorder takes its next call, so that wait covers every call recorded before the delta); an allocation the registry gained after the snapshot is recorded there as a new range with its nonzero 64 KiB blocks. A submission ends a frame only when it flips itself, not when another thread's submission flipped while it was being submitted. Shader registrations and ABI resolutions only collect the blocks of the shader header and the arrays it points to, and the pages of the title's image only when their content changed. At the end it stores each display buffer the title presented, decoded, as a reference image. Without the guest write watch (`APS5_NO_WRITE_WATCH`, or no write watch on the host) the capture throws when it starts, since it could not record CPU stores. A capture is `calls.bin`, `memory.bin` and `capture.json` (commit, switches, sizes, warnings), and the recorder stops with a warning when its volume has less than 4 GiB free. A 20-frame capture in the Demon's Souls Nexus is 5.3 GiB of snapshot and 11.5 GiB of deltas before compression, 3.9 GiB on disk. -`agc_frame_replay [--no-pace] [--no-release] [--release-after-ms ] [--no-synthesize] [--strict] [--dump
::]... [--frames ]` maps the captured ranges at their addresses and registers them with their access (a range the replaying process already maps writable, such as the driver's fixed memory, is only refilled, and a non-GPU range it uses itself is skipped and counted), repeats the registrations, then replays the calls of each captured thread on a thread of its own, started in captured order. Before a call that hands work to the GPU it waits until no more submissions are in flight than when the title made it (`--no-pace` turns this off). Titles release GPU waits from threads that never call the driver, so when the driver completes nothing for 3 ms the replay reads the waits its queues are blocked on (`AgcDriverAwaitedWaits`) and writes the next recorded CPU value of the awaited dword that satisfies the compare, else the last earlier one, else a value derived from the reference (`--no-synthesize` turns this last step off). It prints one JSON line with the frame count and times, the submissions, the pacing wait, the released waits and how many were derived, the submissions that never completed and, for captures with reference images, the share of identical pixels and the largest channel difference of the replayed display buffers (`--frames` writes both images). Effects that depend on timing (temporal antialiasing, particles) make pixels differ, so expect a visual match rather than identical frames. Replay times follow the GPU clock state: lock the clocks (`nvidia-smi -lgc`, `-lmc`) when comparing two drivers. +`agc_frame_replay [--no-pace] [--no-release] [--release-after-ms ] [--no-synthesize] [--strict] [--repeat ] [--cold-pipelines] [--shaders] [--shader-code ] [--submits ] [--trace-serials] [--dump
::]... [--frames ]` maps the captured ranges at their addresses and registers them with their access (a range the replaying process already maps writable, such as the driver's fixed memory, is only refilled, and a non-GPU range it uses itself is skipped and counted), repeats the registrations, then replays the calls of each captured thread on a thread of its own, started in captured order. Before a call that hands work to the GPU it waits until no more submissions are in flight than when the title made it (`--no-pace` turns this off). Titles release GPU waits from threads that never call the driver, so when the driver completes nothing for 3 ms the replay reads the waits its queues are blocked on (`AgcDriverAwaitedWaits`) and writes the next recorded CPU value of the awaited dword that satisfies the compare, else the last earlier one, else a value derived from the reference (`--no-synthesize` turns this last step off). It prints one JSON line with the frame count and times, the submissions, the pacing wait, the released waits and how many were derived, the submissions that never completed and, for captures with reference images, the share of identical pixels and the largest channel difference of the replayed display buffers (`--frames` writes both images). Effects that depend on timing (temporal antialiasing, particles) make pixels differ, so expect a visual match rather than identical frames. The replay's device has no window, but it loads the title's Vulkan pipeline cache file (`vk-pipelines-*.bin` in the shader cache directory) as the windowed device does, so pipelines the title already compiled are not compiled again (`--cold-pipelines` starts with an empty one and clears an inherited `APS5_HEADLESS_PIPELINE_CACHE`; that variable is the driver switch). The title replayed its pipeline journal at boot, long before the captured frames, so after the registrations the replay waits for the driver's journal replay to finish before the first pass (`journal_s` in the JSON line). The first pass still compiles every shader and pipeline the frames use that neither cache holds, so `--repeat ` replays the frames n times in one process: before each later pass it waits for the driver to go idle, clears the captured ranges, restores the snapshot and makes the reservations that were pending at the snapshot again (a token still reserved from the previous pass is kept), and the frame times then come from passes 2 to n, with each pass listed under `passes`. Later passes replay the same frames, so driver caches keyed on values that change every frame in the title (ring addresses in user data, for example) can hit entries of an earlier pass; compare such changes on the first pass with warm disk caches instead. `--shaders` replays nothing and maps no guest memory: it reads the snapshot, applies the captured writes in call order and prints one JSON line per shader registration (code address, size, type, code hash and how many earlier registrations had the same code), then a summary; `--shader-code ` also writes each distinct code as `/type-.bin`. `--submits ` also reads captured memory without mapping guest addresses, applies writes in call order, and lists the first n submissions and flips of the captured frames with their queue and serials, the packets of each and the fields of its waits and releases. `--trace-serials` prints each submission, flip and suspend point with its call ordinal and command address as it is issued; with `APS5_TRACE_GPU` the driver prints the serial it accepts for each address and each completion, so a replay that stops completing can be followed to the call that made the submission. Replay times follow the GPU clock state: lock the clocks (`nvidia-smi -lgc`, `-lmc`) when comparing two drivers. ```sh cmake --build build --target agc_frame_replay build/core/libs/prx/libSceAgcDriver/agc_frame_replay capture ``` -On Windows use `agc_frame_replay.exe` and prepend `build/core/libs/libs/unpatched` to the process `PATH`. With `BUILD_TESTING=ON`, `agc_frame_capture` checks the file format (round trip, zero-block elision, truncated and corrupt files), and, on Windows only, `agc_driver_frame_capture_eop` reserves and submits six EOP flips with `APS5_FRAME_CAPTURE_FRAMES=3` and checks that the capture starts after the first flipping submission and holds three frames of one reservation and one submission each, then that a flipping submission on another thread leaves this thread's flip count unchanged, and, on Windows only, `agc_driver_frame_replay` captures two frames of CPU writes followed by DMA copies, the second from a block committed during the capture, replays them in `agc_frame_replay` and compares the copies. +On Windows use `agc_frame_replay.exe` and prepend `build/core/libs/libs/unpatched` to the process `PATH`. With `BUILD_TESTING=ON`, `agc_frame_capture` checks the file format (round trip, zero-block elision, truncated and corrupt files), and, on Windows only, `agc_driver_frame_capture_eop` reserves and submits six EOP flips with `APS5_FRAME_CAPTURE_FRAMES=3` and checks that the capture starts after the first flipping submission and holds three frames of one reservation and one submission each, then that a flipping submission on another thread leaves this thread's flip count unchanged, and, on Windows only, `agc_driver_frame_replay` captures two frames of CPU writes followed by DMA copies, the second from a block committed during the capture, replays them in `agc_frame_replay`, once and with `--repeat 3`, and compares the copies. ## Pipeline statistics diff --git a/docs/dev/ENVIRONMENT.md b/docs/dev/ENVIRONMENT.md index d45501430c..88a3b4a485 100644 --- a/docs/dev/ENVIRONMENT.md +++ b/docs/dev/ENVIRONMENT.md @@ -10,6 +10,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `ANYPS5_JOB_MANAGER_CASE` | string | `core/libs/tests/JobManagerReference.cpp` | | selects the CPU fixture run by `tools/run_job_manager_reference.py` | | `ANYPS5_JOB_MANAGER_HOST` | presence | `core/libs/tests/JobManagerReference.cpp` | | runs the reference adapter against the host fallback instead of the loaded guest module | | `ANYPS5_LOADEXEC_PARENT` | string | `core/libs/tests/GuestLoadExecHost.cpp` | | variable the Windows entry stub reads; its wide name is built in `core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp` | +| `ANYPS5_KERNEL_SYS_EXEC_MODE` | string | `core/libs/tests/GuestExecHost.cpp` | | selects `_execve` (`ve`) or `_execvpe` (`vpe`) in the exec replacement test fixture; unset tests libkernel `execve` | | `ANYPS5_LOADEXEC_TEST_ROOT` | string | `core/libs/tests/GuestLoadExecHost.cpp` | | directory the load-exec host test reads its guest executable from | | `ANYPS5_POSIX_SPAWN_ROLE` | string | `core/libs/tests/GuestPosixSpawn.cpp` | | role of the test's own copy spawned as a `posix_spawn` child, which exits or sleeps on it and otherwise fails | | `ANYPS5_TEST_QUOTED_ARGV0` | presence | `core/libs/tests/GuestLoadExecHost.cpp` | | makes the load-exec test's first argument one with quotes, a backslash and non-ASCII text | @@ -187,6 +188,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_FRAME_CAPTURE_FRAMES` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/FrameRecorder.cpp` | 10 | number of frame boundaries the frame capture records | | `APS5_FRAME_CAPTURE_MIN_FREE_BYTES` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/FrameRecorder.cpp` | 4294967296 | free bytes the capture volume must keep or the frame capture stops with a warning; the tests set 0 so a nearly full runner disk cannot fail them | | `APS5_FRAME_CAPTURE_TRIGGER` | string | `core/libs/prx/libSceAgcDriver/Execution/src/FrameRecorder.cpp` | | file whose appearance starts the frame capture at the next frame boundary; a number in it replaces the frame count | +| `APS5_FRAME_REPLAY_ATTEMPT` | integer | `core/libs/prx/libSceAgcDriver/tools/FrameReplay.cpp` | 0 | number of times `agc_frame_replay` has started itself again on Windows because a host region sat in a captured GPU-visible range; set by the replay for the process it starts, which stops relaunching after 8 | | `APS5_FULL_BARRIERS` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | records every barrier instead of merging redundant ones | | `APS5_GPU_CHECKPOINTS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | attaches device diagnostic checkpoints through VK_NV_device_diagnostic_checkpoints, or buffer markers through VK_AMD_buffer_marker on a device without it | | `APS5_GPU_COPY_MAX_KIB` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | 1024 | size cap in KiB on a descriptor sub-range copied through the GPU path, larger ones staying on the CPU | @@ -194,6 +196,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_GPU_WAIT_TIMEOUT_MS` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp` | 1000 | milliseconds a GPU wait may run before a warning is printed | | `APS5_GROUP_CAPTURE_ALL_QUEUES` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp` | | resolves group-ahead capture on queue 0 as well | | `APS5_HANDOFF_SPIN_US` | integer | `core/libs/prx/libSceAgcDriver/Execution/include/HandoffSpin.hpp` | | microseconds a thread spins for a handed-off draw's work or for the GPU mutex before blocking, clamped to 0..10000; unset spins 150 us when the process has more than 8 usable processors and not at all otherwise | +| `APS5_HEADLESS_PIPELINE_CACHE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | loads the pipeline cache file on a device without a window too; `agc_frame_replay` sets it by default and clears it with `--cold-pipelines`, including inherited values | | `APS5_HEAP_MIRROR_MIB` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | 24576 | budget in MiB for the heap mirrors held for registered allocations | | `APS5_HOOK_FLUSH_CPU_BLOCKS` | string | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | set to 0 flushes pending images whose every 64 KiB block the CPU wrote, instead of skipping them | | `APS5_HOOK_FULL_SYNC` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | makes the flush hook's recorded-store wait a full Sync under the GpuMutex | @@ -683,8 +686,8 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_GAME_MODE` | string | `core/libs/prx/libkernel/System/src/HostPerformanceProcess.cpp` | off | asks the host for its game or performance mode while the title runs, read when libkernel loads: `off`, `on` or `auto`; on Linux it requests Feral GameMode (`libgamemode.so.0`, loaded with dlopen) and on macOS it holds a user-initiated, latency-critical `NSProcessInfo` activity; `on` logs a missing library or backend once, `auto` stays silent about it, and a refused request is logged once in both; nothing here is fatal, and any other value stops the title with an error naming the variable | | `APS5_JOB_AFFINITY` | presence | `core/libs/prx/libkernel/Pthread/src/Thread.cpp` | | on Windows, pins the title's job worker threads to the efficiency cores of a hybrid CPU | | `APS5_JOB_AFFINITY_MASK` | integer | `core/libs/prx/libkernel/Pthread/src/Thread.cpp` | | on Windows, hex core mask that job worker threads are pinned to | -| `APS5_MIMALLOC` | presence | `core/libs/prx/libkernel/System/src/MimallocMacOs.cpp` | | makes the process allocate through mimalloc instead of the system allocator: on Windows the entry stub (`core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp`) loads `mimalloc.dll`, on macOS libkernel restarts the title with `libmimalloc.dylib` in `DYLD_INSERT_LIBRARIES` | -| `APS5_MIMALLOC_RESTARTED` | presence | `core/libs/prx/libkernel/System/src/MimallocMacOs.cpp` | | on macOS, set by libkernel for the restart that loads mimalloc and removed at start-up, so a restart that did not load it is not repeated | +| `APS5_MIMALLOC` | presence | `core/libs/prx/libkernel/System/src/MimallocRestart.cpp` | | makes the process allocate through mimalloc instead of the system allocator: on Windows the entry stub (`core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp`) loads `mimalloc.dll`, on macOS libkernel restarts the title with `libmimalloc.dylib` in `DYLD_INSERT_LIBRARIES` and on Linux with `libmimalloc.so` in `LD_PRELOAD`; on macOS and Linux both variables stay set after the restart, so processes the title starts inherit them | +| `APS5_MIMALLOC_RESTARTED` | presence | `core/libs/prx/libkernel/System/src/MimallocRestart.cpp` | | on macOS and Linux, set by libkernel for the restart that loads mimalloc and removed at start-up, so a restart that did not load it is not repeated | | `APS5_NO_DIRECT_MEMORY_SECTION_POOL` | presence | `core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp` | | on Windows, creates a section for every direct-memory allocation and closes it at release instead of reusing released sections that never held guest data | | `APS5_NO_JOB_AFFINITY` | presence | `core/libs/prx/libkernel/Pthread/src/Thread.cpp` | | on Windows, disables pinning of job worker threads | | `APS5_NO_SLEEP_WAKE` | presence | `core/libs/prx/libkernel/Time/SleepWake.cpp` | | keeps every sleep of 1 ms or less in full instead of ending it at a GPU batch, batch completion action or flip completion | diff --git a/docs/dev/HW_ORACLE.md b/docs/dev/HW_ORACLE.md index 4ec301392c..260be76b4a 100644 --- a/docs/dev/HW_ORACLE.md +++ b/docs/dev/HW_ORACLE.md @@ -40,12 +40,13 @@ elsewhere; `vulkan` is never the default, see [Recompiler comparison](#recompile - `v4`-`v7` hold the row's 4 input dwords. `v10`-`v25` start at 0 and are stored as the result (16 dwords). - Leave `v0` (lane id), `v1` (input offset), `v2` (output offset) and `s[4:7]` (input and output addresses) unchanged. -- 4 KiB of LDS, or the group segment size `--lds` (Python `lds`) gives in bytes, up to 64 KiB. Rows run in workgroups of up to - 1024 lanes (512 with `--wave64`), padded to whole waves with zero rows. -- Bytes passed as `extra` follow the rows of each dispatch, at `s[4:5] + 16 * rows`: buffer contents, texels, etc. - Build buffer and image descriptors in SGPRs from that address. A kernel that needs more than 4 input dwords per lane - takes the rest from there too, for example `v_lshlrev_b32 v40, 3, v0` / `v_add_nc_u32 v40, 16 * rows, v40` / - `global_load_dwordx2 v[8:9], v40, s[4:5]` for two more dwords per lane, with `rows` the padded count. +- 4 KiB of LDS, or the group segment size `--lds` (Python `lds`) gives in bytes, up to 64 KiB. Rows run in dispatches of up to + 1024 rows (512 with wave64), one workgroup each, padded to whole waves with zero rows. +- Bytes passed as `extra` start at `s[4:5] + 16 * extra_rows`, where `extra_rows` is the padded row count for one + dispatch and the dispatch capacity for multiple dispatches (1024 in wave32, 512 in wave64). Tail dispatches reserve + unused row slots so the address stays fixed. Build buffer and image descriptors in SGPRs from that address. A kernel + that needs more than 4 input dwords per lane takes the rest from there too, for example `v_lshlrev_b32 v40, 3, v0` / + `v_add_nc_u32 v40, 16 * extra_rows, v40` / `global_load_dwordx2 v[8:9], v40, s[4:5]`. Every run must explicitly set all floating-point controls, including runs of integer instructions. The CLI flags use hyphens; Python keywords use underscores. Record these settings alongside the GPU and measured results. diff --git a/docs/dev/TRACING.md b/docs/dev/TRACING.md index da065304b5..b7c4c75d1a 100644 --- a/docs/dev/TRACING.md +++ b/docs/dev/TRACING.md @@ -18,7 +18,7 @@ Set a switch before starting the emulator. Unless noted, any value enables it. M | `APS5_TRACE_MEMORY` | Direct-memory allocation, mapping and protection operations. | | `APS5_TRACE_PREPARATION_WAIT` | Shader preparation tasks moved to the front of the queue to satisfy a waiting caller. | | `APS5_TRACE_SSE4A` | Each emulated SSE4A instruction once per guest instruction address (Windows). | -| `APS5_TRACE_SYNC` | Event-flag operations and their wait and wake results. | +| `APS5_TRACE_SYNC` | Event-flag and semaphore operations and their wait and wake results. | | `APS5_TRACE_TIMERS` | Timing measurements for the host wait primitives used by the emulator. | | `APS5_TRACE_UDS` | Universal Data System events posted to a context, and the trophy title data it cannot find. | | `APS5_TRACE_USLEEP` | Every 2,000 guest `usleep` calls, with caller addresses and requested delays. | @@ -79,4 +79,4 @@ See [Build](BUILD.md#performance-trace) for the trace format, summary fields and ## Running titles repeatably -`python tools/title_run.py --games [ID ...]` relinks and runs extracted title packages found under `--games` (a directory holding `eboot.bin` and `sce_sys/param.json`) and writes `summary.json` and a fixed-format `summary.md` under `--out` (default `build/title-runs/-