Merge remote-tracking branch 'origin/main' into feat/demons-souls-boot

Brings in main up to 9958d26 (homebrew runtime, POSIX wrappers in
libkernel, sce_module guest modules and --skip-sce-module, the --to-intel
general pipeline, libSceRandom, libSceRtc, libScePosixForWebKit,
libRenoirCore, test wiring).

Resolutions where both sides implemented the same thing:
- libkernel pthread: main's structure (lazy static initialisation,
  destroyed markers, MutexOperations/CondOperations, mutex protocol and
  condattr clock) with this branch's TimedWait deadlines and condition
  (precise timed lock/wait on Windows), wait tracing, the adaptive mutex
  type 4, POSIX default ERRORCHECK mutexes, EDEADLK/EPERM results instead
  of throws, and condition clocks honoured for absolute timeouts.
- libc locale: the guest Dinkumware ABI (_Locimp, facet vtables, short
  ctype/case tables, locale ids, stream storage) is kept because guest code
  inlines those layouts; main's C character functions are kept on top.
- libc stdio/strings/math: main's stream functions (guest FILE status
  sync), strerror/perror, remove, formatting, string and math exports win
  over this branch's duplicates.
- libc paths: main's working directory with this branch's path aliases
  resolved on the normalised guest path.
- libkernel file: this branch's sceKernel* results as SCE errors, main's
  POSIX wrappers and both fstat helpers.
- relinker: this branch's stub lowering keeps its trampolines through the
  main executable path; guest modules refuse lowerings that need stubs.
- AGC driver: main's rendering wait is executed by this branch's workers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0177gthrWEBBfBu5xJDXgBfo
This commit is contained in:
devilismyfriend 2026-09-27 10:52:21 -07:00
commit a503d0b617
146 changed files with 6763 additions and 750 deletions

View File

@ -28,7 +28,10 @@ set(SDL_HAPTIC OFF CACHE BOOL "" FORCE)
set(SDL_HIDAPI OFF CACHE BOOL "" FORCE)
set(SDL_SENSOR OFF CACHE BOOL "" FORCE)
set(SDL_STATIC_PIC ON CACHE BOOL "" FORCE)
set(ANYPS5_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "")
add_subdirectory(3rdparty/SDL2)
set(CMAKE_EXE_LINKER_FLAGS "${ANYPS5_EXE_LINKER_FLAGS}")
add_subdirectory(core/shader/recompiler)
add_subdirectory(core/libs)

View File

@ -12,7 +12,7 @@ Releases will be published after the first full successful launch of at least on
Execution reaches `_start`, [stack unwinding](core/libs/prx/libc/src/exception/Unwind.cpp) and exception handling tables are built, reaches main. Unsupported or unexpected states strictly throw `std::runtime_error`. `what()` is printed to stderr and the process terminates.
The [shader recompiler](core/shader/recompiler/Recompiler.cpp) successfully produces validated via [Spirv-Tools](3rdparty/SPIRV-Tools) SPIR-V.
The [shader recompiler](core/shader/recompiler/Recompiler.cpp) successfully produces SPIR-V, validated via [Spirv-Tools](3rdparty/SPIRV-Tools) when built with `ANYPS5_ENABLE_SPIRV_TOOLS`.
The real game reaches the logo, main menu, and [gameplay](https://gist.github.com/user-attachments/assets/81d28e9b-c237-4545-b2ca-720071129816) with audio.

View File

@ -1,3 +1,8 @@
if(NOT WIN32)
string(REPLACE "-static " "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ")
string(STRIP "${CMAKE_EXE_LINKER_FLAGS}" CMAKE_EXE_LINKER_FLAGS)
endif()
add_executable(nid_patcher
${CMAKE_CURRENT_SOURCE_DIR}/nid/src/Sha1.cpp
${CMAKE_CURRENT_SOURCE_DIR}/nid/src/NidCompute.cpp
@ -103,6 +108,255 @@ endforeach()
add_custom_target(libs DEPENDS ${PATCHED_OUTPUTS})
if(BUILD_TESTING)
add_executable(guest_rendering_wait_tests tests/GuestRenderingWait.cpp)
target_include_directories(guest_rendering_wait_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_rendering_wait_tests PRIVATE libSceAgcDriver libc)
configure_windows_unwind(guest_rendering_wait_tests)
add_test(NAME guest_rendering_wait COMMAND guest_rendering_wait_tests)
if(WIN32)
set_tests_properties(guest_rendering_wait PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_executable(guest_buffer_reuse_tests tests/GuestBufferReuse.cpp)
target_include_directories(guest_buffer_reuse_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_buffer_reuse_tests PRIVATE libc)
configure_windows_unwind(guest_buffer_reuse_tests)
add_test(NAME guest_buffer_reuse COMMAND guest_buffer_reuse_tests)
if(WIN32)
set_tests_properties(guest_buffer_reuse PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_executable(guest_shader_link_tests tests/GuestShaderLink.cpp)
target_include_directories(guest_shader_link_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_shader_link_tests PRIVATE libSceAgc libc)
configure_windows_unwind(guest_shader_link_tests)
add_test(NAME guest_shader_link COMMAND guest_shader_link_tests)
if(WIN32)
set_tests_properties(guest_shader_link PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_executable(guest_system_exit_tests tests/GuestSystemExit.cpp)
target_include_directories(guest_system_exit_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_system_exit_tests PRIVATE libSceSystemService libc)
configure_windows_unwind(guest_system_exit_tests)
add_test(NAME guest_system_exit_parameters COMMAND guest_system_exit_tests)
add_test(NAME guest_system_exit COMMAND guest_system_exit_tests --exit)
add_test(NAME guest_immediate_exit COMMAND guest_system_exit_tests --immediate)
set_tests_properties(guest_system_exit PROPERTIES
PASS_REGULAR_EXPRESSION "Guest shutdown and atexit completed")
if(WIN32)
set_tests_properties(guest_system_exit guest_system_exit_parameters guest_immediate_exit PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_executable(guest_character_tests tests/GuestCharacter.cpp)
target_include_directories(guest_character_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_character_tests PRIVATE libc)
configure_windows_unwind(guest_character_tests)
add_test(NAME guest_character COMMAND guest_character_tests)
if(WIN32)
set_tests_properties(guest_character PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_executable(guest_math_tests tests/GuestMath.cpp)
target_include_directories(guest_math_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_math_tests PRIVATE libc)
configure_windows_unwind(guest_math_tests)
add_test(NAME guest_math COMMAND guest_math_tests)
if(WIN32)
set_tests_properties(guest_math PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_executable(guest_working_directory_tests tests/GuestWorkingDirectory.cpp)
target_include_directories(guest_working_directory_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_working_directory_tests PRIVATE libc)
configure_windows_unwind(guest_working_directory_tests)
add_test(NAME guest_working_directory COMMAND guest_working_directory_tests)
if(WIN32)
set_tests_properties(guest_working_directory PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_library(guest_module_fixture SHARED tests/GuestModuleFixture.cpp)
target_include_directories(guest_module_fixture PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
add_custom_command(TARGET guest_module_fixture POST_BUILD
COMMAND $<TARGET_FILE:nid_patcher> guest_module_fixture $<TARGET_FILE:guest_module_fixture>
VERBATIM)
add_dependencies(guest_module_fixture nid_patcher)
add_executable(guest_dynamic_loader_tests tests/GuestDynamicLoader.cpp)
target_include_directories(guest_dynamic_loader_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_dynamic_loader_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_dynamic_loader_tests)
add_dependencies(guest_dynamic_loader_tests guest_module_fixture)
add_test(NAME guest_dynamic_loader COMMAND guest_dynamic_loader_tests $<TARGET_FILE_NAME:guest_module_fixture>)
set_tests_properties(guest_dynamic_loader PROPERTIES
WORKING_DIRECTORY "$<TARGET_FILE_DIR:guest_module_fixture>")
if(WIN32)
set_tests_properties(guest_dynamic_loader PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_executable(guest_filesystem_tests tests/GuestFilesystem.cpp)
target_include_directories(guest_filesystem_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_filesystem_tests PRIVATE libc)
configure_windows_unwind(guest_filesystem_tests)
add_test(NAME guest_filesystem COMMAND guest_filesystem_tests)
if(WIN32)
set_tests_properties(guest_filesystem PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_executable(guest_directory_tests tests/GuestDirectory.cpp)
target_include_directories(guest_directory_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_directory_tests PRIVATE libc)
configure_windows_unwind(guest_directory_tests)
add_test(NAME guest_directory COMMAND guest_directory_tests)
if(WIN32)
set_tests_properties(guest_directory PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_executable(guest_environment_tests tests/GuestEnvironment.cpp)
target_include_directories(guest_environment_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_environment_tests PRIVATE libc)
configure_windows_unwind(guest_environment_tests)
add_test(NAME guest_environment COMMAND guest_environment_tests)
if(WIN32)
set_tests_properties(guest_environment PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_executable(guest_standard_stream_tests tests/GuestStandardStreams.cpp)
target_include_directories(guest_standard_stream_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_standard_stream_tests PRIVATE libc)
configure_windows_unwind(guest_standard_stream_tests)
add_test(NAME guest_standard_streams COMMAND guest_standard_stream_tests)
if(WIN32)
set_tests_properties(guest_standard_streams PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_executable(guest_once_tests tests/GuestOnce.cpp)
target_include_directories(guest_once_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_once_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_once_tests)
add_test(NAME guest_once COMMAND guest_once_tests)
set_tests_properties(guest_once PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_once PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_executable(guest_signal_tests tests/GuestSignals.cpp)
target_include_directories(guest_signal_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_signal_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_signal_tests)
add_test(NAME guest_signals COMMAND guest_signal_tests)
if(WIN32)
set_tests_properties(guest_signals PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_executable(guest_system_configuration_tests tests/GuestSystemConfiguration.cpp)
target_include_directories(guest_system_configuration_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_system_configuration_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_system_configuration_tests)
add_test(NAME guest_system_configuration COMMAND guest_system_configuration_tests)
if(WIN32)
set_tests_properties(guest_system_configuration PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_executable(guest_udp_tests tests/GuestUdp.cpp)
target_include_directories(guest_udp_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_udp_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_udp_tests)
add_test(NAME guest_udp COMMAND guest_udp_tests)
set_tests_properties(guest_udp PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_udp PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_executable(guest_address_tests tests/GuestAddressConversion.cpp)
target_include_directories(guest_address_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_address_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_address_tests)
add_test(NAME guest_addresses COMMAND guest_address_tests)
if(WIN32)
set_tests_properties(guest_addresses PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_executable(guest_error_tests tests/GuestErrors.cpp)
target_include_directories(guest_error_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_error_tests PRIVATE libc)
configure_windows_unwind(guest_error_tests)
add_test(NAME guest_errors COMMAND guest_error_tests)
if(WIN32)
set_tests_properties(guest_errors PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_executable(guest_random_tests tests/GuestRandom.cpp)
target_include_directories(guest_random_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_random_tests PRIVATE libSceRandom)
add_test(NAME guest_random COMMAND guest_random_tests)
if(WIN32)
set_tests_properties(guest_random PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceRandom>")
endif()
add_executable(guest_resolver_tests tests/GuestResolver.cpp)
target_include_directories(guest_resolver_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_resolver_tests PRIVATE libScePosixForWebKit)
add_test(NAME guest_resolver COMMAND guest_resolver_tests)
if(WIN32)
set_tests_properties(guest_resolver PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libScePosixForWebKit>")
endif()
add_executable(guest_rtc_tests tests/GuestRtc.cpp)
target_include_directories(guest_rtc_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_rtc_tests PRIVATE libSceRtc)
add_test(NAME guest_rtc COMMAND guest_rtc_tests)
if(WIN32)
set_tests_properties(guest_rtc PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceRtc>")
endif()
add_executable(mspace_tests tests/Mspace.cpp)
target_include_directories(mspace_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(mspace_tests PRIVATE libc)
configure_windows_unwind(mspace_tests)
add_test(NAME mspace COMMAND mspace_tests)
if(WIN32)
set_tests_properties(mspace PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_executable(guest_memory_tests tests/GuestMemory.cpp)
target_include_directories(guest_memory_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_memory_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_memory_tests)
add_test(NAME guest_memory COMMAND guest_memory_tests)
if(WIN32)
set_tests_properties(guest_memory PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_executable(guest_search_tests tests/GuestSearch.cpp)
target_include_directories(guest_search_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_search_tests PRIVATE libc)
configure_windows_unwind(guest_search_tests)
add_test(NAME guest_search COMMAND guest_search_tests)
if(WIN32)
set_tests_properties(guest_search PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_executable(guest_string_tests tests/GuestStrings.cpp)
target_include_directories(guest_string_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_string_tests PRIVATE libc libScePosixForWebKit)
configure_windows_unwind(guest_string_tests)
add_test(NAME guest_strings COMMAND guest_string_tests)
if(WIN32)
set_tests_properties(guest_strings PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_executable(exception_runtime_tests tests/ExceptionRuntime.cpp)
target_include_directories(exception_runtime_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(exception_runtime_tests PRIVATE libc)
configure_windows_unwind(exception_runtime_tests)
add_test(NAME exception_runtime COMMAND exception_runtime_tests)
if(WIN32)
set_tests_properties(exception_runtime PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
add_executable(application_heap_tests EXCLUDE_FROM_ALL tests/ApplicationHeap.cpp prx/libc/src/ApplicationHeap.cpp)
target_include_directories(application_heap_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
set_target_properties(application_heap_tests PROPERTIES CXX_EXTENSIONS OFF)
@ -115,11 +369,6 @@ set_target_properties(guest_locale_tests PROPERTIES CXX_EXTENSIONS OFF)
target_link_libraries(guest_locale_tests PRIVATE libc)
configure_windows_unwind(guest_locale_tests)
add_executable(guest_rtc_tests EXCLUDE_FROM_ALL tests/GuestRtc.cpp)
target_include_directories(guest_rtc_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
set_target_properties(guest_rtc_tests PROPERTIES CXX_EXTENSIONS OFF)
target_link_libraries(guest_rtc_tests PRIVATE libSceRtc)
if(MINGW)
foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static)
string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}")

View File

@ -173,9 +173,9 @@ using PthreadCondattr = PthreadCondattrPrivate*;
using PthreadKey = int;
using pthread_entry_func_t = void* (*)(void*);
using pthread_key_destructor_func_t = void (*)(void*);
using thread_dtors_func_t = void (*)();
using get_thread_atexit_count_func_t = int (*)(KernelModule);
using thread_atexit_report_func_t = void (*)(KernelModule);
using thread_dtors_func_t = void (APS5_VABI *)();
using get_thread_atexit_count_func_t = int (APS5_VABI *)(KernelModule);
using thread_atexit_report_func_t = void (APS5_VABI *)(KernelModule);
struct FileStat {
std::uint32_t st_dev;

View File

@ -44,7 +44,7 @@ inline std::string StripNidPostfix(const std::string& name) {
std::string result = name;
if (result.size() >= kNidPostfixLen &&
result.compare(result.size() - kNidPostfixLen, kNidPostfixLen, kNidPostfix) == 0) {
result = result.substr(0u, result.size() - kNidPostfixLen);
result.resize(result.size() - kNidPostfixLen);
}
const auto disambigPos = result.rfind(kNidDisambigMarker);
if (disambigPos != std::string::npos) {
@ -54,7 +54,7 @@ inline std::string StripNidPostfix(const std::string& name) {
[](unsigned char c) { return std::isdigit(c) != 0; }
);
if (allDigits && suffixStart < result.size())
result = result.substr(0u, disambigPos);
result.resize(disambigPos);
}
return result;
}

View File

@ -0,0 +1,145 @@
#include <cstdint>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
extern "C" {
APS5_EXPORT("0DsDTr534gc", renoirStub00);
uint64_t APS5_VABI renoirStub00(void) {
NotImplemented_nid_no_patch("0DsDTr534gc");
return 0;
}
APS5_EXPORT("2T56HgbPW08", renoirStub01);
uint64_t APS5_VABI renoirStub01(void) {
NotImplemented_nid_no_patch("2T56HgbPW08");
return 0;
}
APS5_EXPORT("3HnCozh8Az8", renoirStub02);
uint64_t APS5_VABI renoirStub02(void) {
NotImplemented_nid_no_patch("3HnCozh8Az8");
return 0;
}
APS5_EXPORT("5Ysce+s-CWU", renoirStub03);
uint64_t APS5_VABI renoirStub03(void) {
NotImplemented_nid_no_patch("5Ysce+s-CWU");
return 0;
}
APS5_EXPORT("AdlSqA4lGS8", renoirStub04);
uint64_t APS5_VABI renoirStub04(void) {
NotImplemented_nid_no_patch("AdlSqA4lGS8");
return 0;
}
APS5_EXPORT("bC37hk7rHqs", renoirStub05);
uint64_t APS5_VABI renoirStub05(void) {
NotImplemented_nid_no_patch("bC37hk7rHqs");
return 0;
}
APS5_EXPORT("dfPK9R6M0qM", renoirStub06);
uint64_t APS5_VABI renoirStub06(void) {
NotImplemented_nid_no_patch("dfPK9R6M0qM");
return 0;
}
APS5_EXPORT("fZiBbVa2ZDU", renoirStub07);
uint64_t APS5_VABI renoirStub07(void) {
NotImplemented_nid_no_patch("fZiBbVa2ZDU");
return 0;
}
APS5_EXPORT("hkWBH8-WfW4", renoirStub08);
uint64_t APS5_VABI renoirStub08(void) {
NotImplemented_nid_no_patch("hkWBH8-WfW4");
return 0;
}
APS5_EXPORT("hUxvGdi64Io", renoirStub09);
uint64_t APS5_VABI renoirStub09(void) {
NotImplemented_nid_no_patch("hUxvGdi64Io");
return 0;
}
APS5_EXPORT("NxqsZxGisrA", renoirStub10);
uint64_t APS5_VABI renoirStub10(void) {
NotImplemented_nid_no_patch("NxqsZxGisrA");
return 0;
}
APS5_EXPORT("OgZVIkaNmb0", renoirStub11);
uint64_t APS5_VABI renoirStub11(void) {
NotImplemented_nid_no_patch("OgZVIkaNmb0");
return 0;
}
APS5_EXPORT("ojLQdRUnVgA", renoirStub12);
uint64_t APS5_VABI renoirStub12(void) {
NotImplemented_nid_no_patch("ojLQdRUnVgA");
return 0;
}
APS5_EXPORT("s0qDfgsAgVg", renoirStub13);
uint64_t APS5_VABI renoirStub13(void) {
NotImplemented_nid_no_patch("s0qDfgsAgVg");
return 0;
}
APS5_EXPORT("sgrd-r1Nw60", renoirStub14);
uint64_t APS5_VABI renoirStub14(void) {
NotImplemented_nid_no_patch("sgrd-r1Nw60");
return 0;
}
APS5_EXPORT("sjTRb8YD7dQ", renoirStub15);
uint64_t APS5_VABI renoirStub15(void) {
NotImplemented_nid_no_patch("sjTRb8YD7dQ");
return 0;
}
APS5_EXPORT("tG86XGc2VtE", renoirStub16);
uint64_t APS5_VABI renoirStub16(void) {
NotImplemented_nid_no_patch("tG86XGc2VtE");
return 0;
}
APS5_EXPORT("umlGxOcNzgc", renoirStub17);
uint64_t APS5_VABI renoirStub17(void) {
NotImplemented_nid_no_patch("umlGxOcNzgc");
return 0;
}
APS5_EXPORT("vPxA9OFW0os", renoirStub18);
uint64_t APS5_VABI renoirStub18(void) {
NotImplemented_nid_no_patch("vPxA9OFW0os");
return 0;
}
APS5_EXPORT("X2qEBlOLkNo", renoirStub19);
uint64_t APS5_VABI renoirStub19(void) {
NotImplemented_nid_no_patch("X2qEBlOLkNo");
return 0;
}
APS5_EXPORT("ynh4E+BIaqY", renoirStub20);
uint64_t APS5_VABI renoirStub20(void) {
NotImplemented_nid_no_patch("ynh4E+BIaqY");
return 0;
}
APS5_EXPORT("YXe6ABbHZPs", renoirStub21);
uint64_t APS5_VABI renoirStub21(void) {
NotImplemented_nid_no_patch("YXe6ABbHZPs");
return 0;
}
APS5_EXPORT("z0jsXZVz75I", renoirStub22);
uint64_t APS5_VABI renoirStub22(void) {
NotImplemented_nid_no_patch("z0jsXZVz75I");
return 0;
}
}

View File

@ -44,6 +44,7 @@ add_library(libSceAgc SHARED EXCLUDE_FROM_ALL
${CMAKE_CURRENT_SOURCE_DIR}/Predication/src/Predication.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Shader/src/CreateShader.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Shader/src/InterpolantMapping.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Shader/src/LinkShaders.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Shader/src/PrimState.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Shader/src/ShaderUtils.cpp
)

View File

@ -39,6 +39,8 @@ std::uint32_t* APS5_VABI sceAgcCbReleaseMem(CommandBuffer* buf, std::uint8_t act
} else if (dataSelect == 5) {
Agc::Command::Require(data == 0, __func__, "GDS release cannot use immediate data");
value = gdsOffset | (static_cast<std::uint64_t>(gdsSize) << 16u);
} else {
Agc::Command::Require(gdsOffset == 0 && gdsSize <= 1, __func__, "GDS parameters supplied for a non-GDS release");
}
// A null label is accepted: games emit releases whose destination is never read.
if (dataSelect != 0 && interrupt != 4 && guestAddress != 0) {

View File

@ -9,6 +9,11 @@
extern "C" {
void* APS5_VABI sceAgcGetRegisterDefaults() {
// The legacy API has no version argument; use the baseline public table.
return Agc::Command::GetRegisterDefaults(0, false, __func__);
}
void* APS5_VABI sceAgcGetRegisterDefaults2(std::uint32_t version) {
return Agc::Command::GetRegisterDefaults(version, false, __func__);
}

View File

@ -0,0 +1,30 @@
#include "SceShaders.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libSceAgc/Shader/include/ShaderConstants.hpp"
#include <array>
#include <algorithm>
extern "C" int APS5_VABI sceAgcCreatePrimState(ShaderRegister*, ShaderRegister*, const Shader*, const Shader*, std::uint32_t);
extern "C" int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister*, const Shader*, const Shader*);
extern "C" int APS5_VABI sceAgcLinkShaders(ShaderRegister* context, ShaderRegister* primitive,
const void* reserved, const Shader* vertex, const Shader* pixel, std::uint32_t primitiveType) {
using namespace ShaderRegs;
if (!context || !primitive || reserved || !vertex ||
vertex->type != static_cast<std::uint8_t>(ShaderBinaryType::Gs) ||
(vertex->num_output_semantics && !vertex->output_semantics) ||
(pixel && (pixel->type != static_cast<std::uint8_t>(ShaderBinaryType::Ps) ||
pixel->num_input_semantics > 32 ||
(pixel->num_input_semantics && !pixel->input_semantics))))
return GRAPHICS5_ERROR_INVALID_SHADER_PROGRAM;
// The simple vertex/pixel pipeline combines two primitive context registers
// and 32 interpolant registers. Publish only after both helpers succeed.
std::array<ShaderRegister, 34> contextValues{};
std::array<ShaderRegister, 3> primitiveValues{};
sceAgcCreatePrimState(contextValues.data(), primitiveValues.data(), nullptr, vertex, primitiveType);
sceAgcCreateInterpolantMapping(contextValues.data() + 2, vertex, pixel);
std::copy(contextValues.begin(), contextValues.end(), context);
std::copy(primitiveValues.begin(), primitiveValues.end(), primitive);
return 0;
}

View File

@ -218,6 +218,8 @@ add_executable(agc_driver_graphics_tests EXCLUDE_FROM_ALL
tests/GuestTextureResource.cpp
tests/GuestSamplerResource.cpp
tests/TextureDetiler.cpp
Graphics/src/Draw.cpp
Graphics/src/Pipeline.cpp
Graphics/src/State.cpp
Graphics/src/ColorTargetLayout.cpp
Graphics/src/ColorTargetTransfer.cpp
@ -253,6 +255,7 @@ target_include_directories(agc_driver_graphics_tests PRIVATE
)
target_link_libraries(agc_driver_graphics_tests PRIVATE
shader_recompiler_agc_driver
libc
)
@ -278,6 +281,18 @@ target_link_libraries(agc_driver_bda_device_tests PRIVATE SDL2-static libc shade
set_target_properties(agc_driver_bda_device_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_driver_bda_device_tests)
if(BUILD_TESTING)
foreach(agcTest IN ITEMS agc_driver_tests agc_shader_memory_tests agc_driver_flip_tests agc_driver_pm4_tests)
string(REGEX REPLACE "_tests$" "" agcTestName "${agcTest}")
set_target_properties(${agcTest} PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME ${agcTestName} COMMAND ${agcTest})
if(WIN32)
set_tests_properties(${agcTestName} PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endforeach()
endif()
option(AGC_BUILD_VISUAL_TEST "Enable the standalone AGC SPIR-V visual test" OFF)
if(AGC_BUILD_VISUAL_TEST)

View File

@ -4,6 +4,7 @@
#include <cstdint>
#include <exception>
#include <memory>
#include <stdexcept>
namespace AgcDriver {
@ -11,6 +12,15 @@ class FrameTiming;
inline constexpr std::uint32_t FlipPacketHeader = 0xc004105cu;
inline constexpr std::uint32_t FlipPacketWords = 6;
// AnyPS5's internal encoding for the driver-generated rendering wait.
inline constexpr std::uint32_t RenderingWaitPacketHeader = 0xc0021018u;
inline constexpr std::uint32_t RenderingWaitPacketWords = 4;
class IRenderingWait {
public:
virtual ~IRenderingWait() = default;
virtual void Wait() = 0;
};
struct FlipInfo {
std::uint32_t handle;
@ -30,6 +40,9 @@ class IVideoOutput {
public:
virtual ~IVideoOutput() = default;
virtual std::shared_ptr<IFlipRequest> Reserve(const FlipInfo& info) = 0;
virtual std::shared_ptr<IRenderingWait> CaptureRenderingWait(std::uint32_t index) {
throw std::runtime_error("VideoOut: rendering waits are unsupported by this output");
}
virtual void Fail(std::exception_ptr error) noexcept = 0;
};

View File

@ -194,6 +194,7 @@ struct Submission {
// copies it first when a submission still holds it).
std::shared_ptr<const ShaderRegistry> shaders;
std::map<std::size_t, std::shared_ptr<IFlipRequest>> flips;
std::map<std::size_t, std::shared_ptr<IRenderingWait>> renderingWaits;
bool suspend = false;
// Record-order stamp (Driver::eventSerial) taken when the game submitted: a WAIT_REG_MEM of this
// submission trusts only labels the recorder noted with a newer stamp (see Recorder::NoteLabel).
@ -508,6 +509,13 @@ public:
require(accepted != std::numeric_limits<std::uint64_t>::max(), "submission serial overflow");
for (std::size_t cursor = 0; cursor < submission.commands.size();) {
const auto* words = submission.commands.data() + cursor;
if (words[0] == RenderingWaitPacketHeader) {
const auto output = outputs.find(words[1]);
require(output != outputs.end(), "rendering wait references an unregistered video output");
auto wait = output->second->CaptureRenderingWait(words[2]);
require(wait != nullptr, "video output returned a null rendering wait");
submission.renderingWaits.emplace(cursor, std::move(wait));
}
if (words[0] == FlipPacketHeader) {
const auto output = outputs.find(words[1]);
require(output != outputs.end(), "flip references an unregistered video output");
@ -5870,7 +5878,7 @@ private:
}
traceLabel(packet, submission.queue);
// Waits do not count as progress; every other packet does, including while it runs.
const bool waitPacket = opcode == 0x3c || opcode == 0x93;
const bool waitPacket = opcode == 0x3c || opcode == 0x93 || header == RenderingWaitPacketHeader;
struct Progress {
Driver& driver;
bool counted;
@ -5882,7 +5890,9 @@ private:
} progress{*this, !waitPacket};
if (!waitPacket) ++packetsInFlight;
recent.Record(cursor);
if (header == FlipPacketHeader) {
if (header == RenderingWaitPacketHeader) {
timed(&WorkerProfile::waitMs, [&] { submission.renderingWaits.at(cursor)->Wait(); });
} else if (header == FlipPacketHeader) {
CheckFailure();
std::uint64_t batchesAtFlip = 0, unsignaledAtFlip = 0;
if (!drains) {

View File

@ -131,8 +131,7 @@ std::string_view UnsupportedReason(std::uint32_t header) {
const auto opcode = (header >> 8u) & 0xffu;
if (opcode == 0x10) {
switch ((header >> 2u) & 0x3fu) {
case 0: case 0x09: case 0x0b: case 0x0c: case 0x17: case 0x1a: return {};
case 0x06: return "VideoOut buffer-completion wait interface is not implemented";
case 0: case 0x06: case 0x09: case 0x0b: case 0x0c: case 0x17: case 0x1a: return {};
case 0x14: case 0x18: return "guest cache actions and GPU release events are not implemented";
default: return "custom packet has no implemented contract in the reference dispatch table";
}
@ -187,6 +186,7 @@ void Validate(std::span<const std::uint32_t> packet, std::uint32_t queue) {
require((packet[1] & 0xffff0000u) != 0x68750000u, "typed user-data and legacy flip markers are not implemented");
break;
case 0x09: size(2); break;
case 0x06: graphics(); size(4); require(packet[3] == 0, "unsupported rendering wait mode"); break;
case 0x0b: {
const auto data = std::as_bytes(packet.subspan(1));
require(std::find(data.begin(), data.end(), std::byte{}) != data.end(), "unterminated marker text");

View File

@ -1,5 +1,9 @@
#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include <algorithm>
#include <iterator>
#include <cstdint>
#include <cstddef>
@ -8,6 +12,22 @@
extern "C" {
std::uint32_t APS5_VABI sceAgcDriverGetWaitRenderingPacketSizeInDwords() {
return AgcDriver::RenderingWaitPacketWords;
}
std::uint32_t APS5_VABI sceAgcDriverWaitUntilSafeForRendering(std::uint32_t** command,
std::uint32_t capacity, std::uint32_t mode, std::uint32_t handle, int index) {
if (!command || capacity < AgcDriver::RenderingWaitPacketWords || mode != 0 || index < 0)
throw std::invalid_argument("AGC driver: invalid rendering wait arguments");
AgcDriver::GuestMemory::CheckRange(command, sizeof(*command), alignof(std::uint32_t*), true);
AgcDriver::GuestMemory::CheckRange(*command, AgcDriver::RenderingWaitPacketWords * sizeof(std::uint32_t), alignof(std::uint32_t), true);
const std::uint32_t words[] = {AgcDriver::RenderingWaitPacketHeader, handle, static_cast<std::uint32_t>(index), mode};
std::copy(std::begin(words), std::end(words), *command);
*command += AgcDriver::RenderingWaitPacketWords;
return 0;
}
int APS5_VABI sceAgcDriverSubmitDcb(const Packet* packet) {
AgcDriver::Submit(packet, 0);
return 0;

View File

@ -1,14 +1,9 @@
#include <cstdint>
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/HeapDiagnostics.hpp"
extern "C" {
void APS5_VABI sceLibcHeapGetTraceInfo_nid_postfix(Info* info) {
(void)info;
NotImplemented_nid_no_patch(__func__);
LibcHeapTraceInfo_nid_no_patch(info);
}
}

View File

@ -0,0 +1,6 @@
add_library(libScePosixForWebKit SHARED EXCLUDE_FROM_ALL Export.cpp Resolver.cpp)
target_include_directories(libScePosixForWebKit PRIVATE ${LIBS_INCLUDE_DIR})
if(WIN32)
target_link_libraries(libScePosixForWebKit PRIVATE ws2_32)
endif()
configure_windows_unwind(libScePosixForWebKit)

View File

@ -0,0 +1,22 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cctype>
extern "C" {
char* APS5_VABI strcasestr_nid_postfix(const char* text, const char* needle) {
if (*needle == '\0') return const_cast<char*>(text);
for (; *text != '\0'; ++text) {
const char* candidate = text;
const char* match = needle;
while (*candidate != '\0' && *match != '\0' &&
std::tolower(static_cast<unsigned char>(*candidate)) ==
std::tolower(static_cast<unsigned char>(*match))) {
++candidate;
++match;
}
if (*match == '\0') return const_cast<char*>(text);
}
return nullptr;
}
}

View File

@ -0,0 +1,19 @@
#pragma once
#include <cstddef>
#include <cstdint>
namespace GuestResolver {
struct AddressInfo {
std::int32_t flags;
std::int32_t family;
std::int32_t socketType;
std::int32_t protocol;
std::uint32_t addressLength;
char* canonicalName;
void* address;
AddressInfo* next;
};
static_assert(sizeof(AddressInfo) == 48);
static_assert(offsetof(AddressInfo, canonicalName) == 24);
static_assert(offsetof(AddressInfo, address) == 32);
}

View File

@ -0,0 +1,184 @@
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#endif
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdint>
#include <cstring>
#include <cstdlib>
#include "GuestResolver.hpp"
namespace {
bool Ready() {
#ifdef _WIN32
static const int status = [] { WSADATA data{}; return WSAStartup(MAKEWORD(2, 2), &data); }();
return status == 0;
#else
return true;
#endif
}
int GuestError(int error) {
if (error == 0) return 0;
if (error == EAI_AGAIN) return 2;
if (error == EAI_BADFLAGS) return 3;
if (error == EAI_FAMILY) return 5;
if (error == EAI_MEMORY) return 6;
if (error == EAI_NONAME) return 8;
if (error == EAI_SERVICE) return 9;
if (error == EAI_SOCKTYPE) return 10;
#ifdef _WIN32
if (error == WSAEFAULT) return 14;
#else
if (error == EAI_OVERFLOW) return 14;
#endif
return 4;
}
}
extern "C" {
void APS5_VABI freeaddrinfo_nid_postfix(GuestResolver::AddressInfo* first) {
while (first) {
auto* next = first->next;
std::free(first->canonicalName);
std::free(first->address);
std::free(first);
first = next;
}
}
int APS5_VABI getaddrinfo_nid_postfix(const char* node, const char* service,
const GuestResolver::AddressInfo* hints, GuestResolver::AddressInfo** output) {
if (!output) return 4;
*output = nullptr;
addrinfo nativeHints{};
if (hints) {
if (hints->addressLength || hints->address || hints->canonicalName || hints->next) return 12;
if ((hints->flags & ~0xd0f) != 0) return 3;
if (hints->family != 0 && hints->family != 2 && hints->family != 28) return 5;
if (hints->socketType != 0 && hints->socketType != 1 && hints->socketType != 2) return 10;
nativeHints.ai_family = hints->family == 28 ? AF_INET6 : hints->family == 2 ? AF_INET : AF_UNSPEC;
nativeHints.ai_socktype = hints->socketType == 1 ? SOCK_STREAM : hints->socketType == 2 ? SOCK_DGRAM : 0;
nativeHints.ai_protocol = hints->protocol;
if (hints->flags & 1) nativeHints.ai_flags |= AI_PASSIVE;
if (hints->flags & 2) nativeHints.ai_flags |= AI_CANONNAME;
if (hints->flags & 4) nativeHints.ai_flags |= AI_NUMERICHOST;
if (hints->flags & 8) nativeHints.ai_flags |= AI_NUMERICSERV;
if (hints->flags & 0x100) nativeHints.ai_flags |= AI_ALL;
if (hints->flags & 0x400) nativeHints.ai_flags |= AI_ADDRCONFIG;
if (hints->flags & 0x800) nativeHints.ai_flags |= AI_V4MAPPED;
}
if (!Ready()) return 4;
addrinfo* native = nullptr;
const auto error = ::getaddrinfo(node, service, &nativeHints, &native);
if (error) return GuestError(error);
GuestResolver::AddressInfo* first = nullptr;
auto** tail = &first;
int failure = 0;
for (auto* item = native; item; item = item->ai_next) {
if (item->ai_family != AF_INET && item->ai_family != AF_INET6) { failure = 5; break; }
auto* converted = static_cast<GuestResolver::AddressInfo*>(std::calloc(1, sizeof(GuestResolver::AddressInfo)));
if (!converted) { failure = 6; break; }
*tail = converted;
tail = &converted->next;
converted->flags = hints ? hints->flags : 0;
converted->family = item->ai_family == AF_INET ? 2 : 28;
converted->socketType = item->ai_socktype == SOCK_STREAM ? 1 : item->ai_socktype == SOCK_DGRAM ? 2 : 0;
converted->protocol = item->ai_protocol;
converted->addressLength = item->ai_family == AF_INET ? 16 : 28;
auto* bytes = static_cast<unsigned char*>(std::calloc(1, converted->addressLength));
converted->address = bytes;
if (!bytes) { failure = 6; break; }
bytes[0] = static_cast<unsigned char>(converted->addressLength);
bytes[1] = static_cast<unsigned char>(converted->family);
if (item->ai_family == AF_INET) {
const auto* v4 = reinterpret_cast<const sockaddr_in*>(item->ai_addr);
std::memcpy(bytes + 2, &v4->sin_port, 2);
std::memcpy(bytes + 4, &v4->sin_addr, 4);
} else {
const auto* v6 = reinterpret_cast<const sockaddr_in6*>(item->ai_addr);
std::memcpy(bytes + 2, &v6->sin6_port, 2);
std::memcpy(bytes + 4, &v6->sin6_flowinfo, 4);
std::memcpy(bytes + 8, &v6->sin6_addr, 16);
std::memcpy(bytes + 24, &v6->sin6_scope_id, 4);
}
if (item->ai_canonname) {
const auto size = std::strlen(item->ai_canonname) + 1;
converted->canonicalName = static_cast<char*>(std::malloc(size));
if (!converted->canonicalName) { failure = 6; break; }
std::memcpy(converted->canonicalName, item->ai_canonname, size);
}
}
::freeaddrinfo(native);
if (failure) { freeaddrinfo_nid_postfix(first); return failure; }
*output = first;
return 0;
}
int APS5_VABI getnameinfo_nid_postfix(const void* address, std::uint32_t length,
char* host, std::uint32_t hostLength, char* service, std::uint32_t serviceLength, int flags) {
// PS5 sockaddr begins with byte-sized length and family. The host's does not.
if (!address || length < 2) return 5;
if ((!host && hostLength) || (!service && serviceLength)) return 4;
if ((flags & ~0x1f) != 0) return 3;
const auto* bytes = static_cast<const unsigned char*>(address);
sockaddr_storage native{};
socklen_t nativeLength;
if (bytes[1] == 2) {
if (length < 16 || bytes[0] != 16) return 5;
auto& v4 = reinterpret_cast<sockaddr_in&>(native);
v4.sin_family = AF_INET;
std::memcpy(&v4.sin_port, bytes + 2, 2);
std::memcpy(&v4.sin_addr, bytes + 4, 4);
nativeLength = sizeof(v4);
} else if (bytes[1] == 28) {
if (length < 28 || bytes[0] != 28) return 5;
auto& v6 = reinterpret_cast<sockaddr_in6&>(native);
v6.sin6_family = AF_INET6;
std::memcpy(&v6.sin6_port, bytes + 2, 2);
std::memcpy(&v6.sin6_flowinfo, bytes + 4, 4);
std::memcpy(&v6.sin6_addr, bytes + 8, 16);
std::memcpy(&v6.sin6_scope_id, bytes + 24, 4);
nativeLength = sizeof(v6);
} else return 5;
int nativeFlags = 0;
if (flags & 1) nativeFlags |= NI_NOFQDN;
if (flags & 2) nativeFlags |= NI_NUMERICHOST;
if (flags & 4) nativeFlags |= NI_NAMEREQD;
if (flags & 8) nativeFlags |= NI_NUMERICSERV;
if (flags & 16) nativeFlags |= NI_DGRAM;
if (!Ready()) return 4;
// Stage results so host-specific short-buffer errors do not leak into the
// guest ABI, and neither guest output is partially modified on overflow.
char resolvedHost[NI_MAXHOST]{};
char resolvedService[NI_MAXSERV]{};
const bool wantHost = host && hostLength;
const bool wantService = service && serviceLength;
const int error = ::getnameinfo(reinterpret_cast<const sockaddr*>(&native), nativeLength,
wantHost ? resolvedHost : nullptr, wantHost ? sizeof(resolvedHost) : 0,
wantService ? resolvedService : nullptr, wantService ? sizeof(resolvedService) : 0, nativeFlags);
if (error) return GuestError(error);
if ((wantHost && std::strlen(resolvedHost) >= hostLength) ||
(wantService && std::strlen(resolvedService) >= serviceLength)) return 14;
if (wantHost) std::memcpy(host, resolvedHost, std::strlen(resolvedHost) + 1);
if (wantService) std::memcpy(service, resolvedService, std::strlen(resolvedService) + 1);
return 0;
}
const char* APS5_VABI gai_strerror_nid_postfix(int error) {
static const char* messages[] = {
"Success", "Address family for hostname not supported", "Temporary failure in name resolution",
"Invalid flags", "Name resolution failed", "Address family not supported",
"Memory allocation failed", "No address for hostname", "Name or service not known",
"Service not supported for socket type", "Socket type not supported", "System error",
"Invalid hints", "Unknown protocol", "Buffer too small"
};
if (error < 0 || error >= static_cast<int>(sizeof(messages) / sizeof(messages[0])))
return "Unknown address resolution error";
return messages[error];
}
}

View File

@ -1,15 +1,29 @@
#include <cstdint>
#include <cstddef>
#include <cstring>
#include <algorithm>
#include <random>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
namespace {
constexpr int SCE_RANDOM_ERROR_INVALID = static_cast<int>(0x817C0016);
constexpr std::size_t SCE_RANDOM_MAX_SIZE = 64;
}
extern "C" {
int APS5_VABI sceRandomGetRandomNumber(void* buf, size_t size) {
(void)buf;
(void)size;
NotImplemented_nid_no_patch(__func__);
return 0;
if (!buf || size > SCE_RANDOM_MAX_SIZE) return SCE_RANDOM_ERROR_INVALID;
static thread_local std::random_device device;
auto* bytes = static_cast<unsigned char*>(buf);
for (std::size_t offset = 0; offset < size; offset += sizeof(unsigned int)) {
const unsigned int value = device();
std::memcpy(bytes + offset, &value, std::min(sizeof(value), size - offset));
}
return 0;
}
}

View File

@ -55,8 +55,8 @@ bool fillModuleInfoForUnwind(std::uint64_t addr, ModuleInfoForUnwind* info) {
std::uint64_t end = 0;
char perms[8] = {};
std::uint64_t offset = 0;
int devMajor = 0;
int devMinor = 0;
unsigned int devMajor = 0;
unsigned int devMinor = 0;
std::uint64_t inode = 0;
char path[4096] = {};
int parsed = std::sscanf(

View File

@ -1,12 +1,24 @@
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <cstdlib>
#include "prx/libc/include/Shutdown.hpp"
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libSceSystemService/SystemService.hpp"
extern "C" {
int APS5_VABI sceSystemServiceLoadExec(const char* path, const char* const* arguments) {
if (!path || !*path) return SYSTEM_SERVICE_ERROR_PARAMETER;
if (std::strcmp(path, "exit") != 0) {
NotImplemented_nid_no_patch("sceSystemServiceLoadExec: executable replacement");
}
(void)arguments;
LibcRunShutdown_nid_postfix();
std::exit(0);
}
int APS5_VABI sceSystemServiceDisableNoticeScreenSkipFlagAutoSet(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
@ -95,9 +107,4 @@ int APS5_VABI sceSystemServiceSetNoticeScreenSkipFlag(void) {
return 0;
}
int APS5_VABI sceSystemServiceLoadExec() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}

View File

@ -0,0 +1,29 @@
#pragma once
#include <cstdint>
#include <limits>
#include <set>
#include <stdexcept>
#include <exception>
// Caller serializes access with the owning VideoOutConfig mutex.
class BufferReuseTracker {
std::uint64_t _issued = 0;
std::set<std::uint64_t> _pending;
public:
std::uint64_t Reserve() {
if (_issued == std::numeric_limits<std::uint64_t>::max())
throw std::overflow_error("VideoOut: buffer reservation sequence exhausted");
const auto ticket = _issued + 1;
_pending.insert(ticket);
_issued = ticket;
return ticket;
}
std::uint64_t Capture() const { return _issued; }
bool IsComplete(std::uint64_t through) const {
if (through > _issued) throw std::invalid_argument("VideoOut: unissued buffer fence");
return _pending.empty() || *_pending.begin() > through;
}
void Complete(std::uint64_t ticket) noexcept {
if (_pending.erase(ticket) != 1) std::terminate();
}
};

View File

@ -29,7 +29,7 @@ private:
void removeSubclass() noexcept;
void applyAspectRatio(void* hwnd, std::uintptr_t edge, void* rect) const;
static std::intptr_t __stdcall windowProc(void* hwnd, unsigned int message, std::uintptr_t wParam, std::intptr_t lParam, std::uintptr_t subclassId, std::uintptr_t referenceData);
static std::intptr_t windowProc(void* hwnd, unsigned int message, std::uintptr_t wParam, std::intptr_t lParam, std::uintptr_t subclassId, std::uintptr_t referenceData);
SDL_Window* window = nullptr;
std::uint32_t aspectWidth = 0;

View File

@ -16,6 +16,7 @@
#include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp"
#include "prx/libSceAgcDriver/Execution/include/DisplayBuffer.hpp"
#include "prx/libSceVideoOut/include/DisplayWindow.hpp"
#include "prx/libSceVideoOut/include/BufferReuseTracker.hpp"
#include "SDL.h"
#include "SceTypes.hpp"
@ -111,6 +112,7 @@ struct VideoOutConfig {
std::array<VideoOutBuffer, VIDEO_OUT_BUFFER_NUM_MAX> buffers{};
std::array<uint32_t, VIDEO_OUT_BUFFER_NUM_MAX> bufferPending{};
std::array<BufferReuseTracker, VIDEO_OUT_BUFFER_NUM_MAX> bufferReuse;
std::array<BufferAttributeGroup, VIDEO_OUT_BUFFER_ATTRIBUTE_NUM_MAX> groups{};
void Check() const {
@ -122,6 +124,7 @@ struct VideoOutConfig {
struct FlipQueue;
struct FlipRequest final : AgcDriver::IFlipRequest, std::enable_shared_from_this<FlipRequest> {
std::uint64_t reuseTicket = 0;
std::shared_ptr<VideoOutConfig> cfg;
std::shared_ptr<FlipQueue> queue;
uint64_t generation = 0;

View File

@ -158,7 +158,7 @@ void DisplayWindow::applyAspectRatio(void* hwnd, std::uintptr_t edge, void* rect
#endif
}
std::intptr_t __stdcall DisplayWindow::windowProc(void* hwnd, unsigned int message, std::uintptr_t wParam, std::intptr_t lParam, std::uintptr_t subclassId, std::uintptr_t referenceData) {
std::intptr_t DisplayWindow::windowProc(void* hwnd, unsigned int message, std::uintptr_t wParam, std::intptr_t lParam, std::uintptr_t subclassId, std::uintptr_t referenceData) {
#ifdef _WIN32
static_cast<void>(subclassId);
if (message == WM_SIZING) {

View File

@ -29,10 +29,34 @@ void checkConfig(const VideoOutConfig& cfg) {
cfg.Check();
}
class RenderingWait final : public AgcDriver::IRenderingWait {
std::shared_ptr<VideoOutConfig> _config;
std::uint32_t _index;
std::uint64_t _ticket;
public:
RenderingWait(std::shared_ptr<VideoOutConfig> config, std::uint32_t index, std::uint64_t ticket)
: _config(std::move(config)), _index(index), _ticket(ticket) {}
void Wait() override {
std::unique_lock lock(_config->mutex);
_config->vblankCond.wait(lock, [&] {
return _config->failure || _config->closing || !_config->opened ||
_config->bufferReuse[_index].IsComplete(_ticket);
});
checkConfig(*_config);
}
};
class VideoOutput final : public AgcDriver::IVideoOutput {
public:
VideoOutput(std::shared_ptr<VideoOutConfig> config, std::shared_ptr<FlipQueue> requests) : cfg(std::move(config)), queue(std::move(requests)) {}
std::shared_ptr<AgcDriver::IRenderingWait> CaptureRenderingWait(std::uint32_t index) override {
std::lock_guard lock(cfg->mutex);
checkConfig(*cfg);
require(index < VIDEO_OUT_BUFFER_NUM_MAX && cfg->buffers[index].Occupied(), "wait buffer is not registered");
return std::make_shared<RenderingWait>(cfg, index, cfg->bufferReuse[index].Capture());
}
std::shared_ptr<AgcDriver::IFlipRequest> Reserve(const AgcDriver::FlipInfo& info) override {
// All flip modes are presented at the next vsync.
require(info.mode >= VIDEO_OUT_FLIP_MODE_VSYNC && info.mode <= 6, "unsupported flip mode");
@ -76,6 +100,7 @@ public:
}
request->generation = cfg->generation;
request->flipRate = cfg->flipRate;
if (info.index >= 0) request->reuseTicket = cfg->bufferReuse[info.index].Reserve();
++queue->reservations;
++cfg->flipStatus.flipPendingNum;
if (info.index >= 0) ++cfg->bufferPending[info.index];
@ -110,7 +135,10 @@ FlipRequest::~FlipRequest() {
if (!terminal) {
--cfg->flipStatus.flipPendingNum;
--queue->reservations;
if (index >= 0) --cfg->bufferPending[index];
if (index >= 0) {
--cfg->bufferPending[index];
cfg->bufferReuse[index].Complete(reuseTicket);
}
cfg->vblankCond.notify_all();
}
}
@ -154,7 +182,10 @@ void FlipRequest::Fail(std::exception_ptr error) noexcept {
if (reserved && !terminal) {
--cfg->flipStatus.flipPendingNum;
--queue->reservations;
if (index >= 0) --cfg->bufferPending[index];
if (index >= 0) {
--cfg->bufferPending[index];
cfg->bufferReuse[index].Complete(reuseTicket);
}
terminal = true;
}
cfg->vblankCond.notify_all();
@ -421,7 +452,10 @@ void VideoOutDriver::processFlip(FlipRequest& req) {
req.cfg->height = req.height;
--req.cfg->flipStatus.flipPendingNum;
--req.queue->reservations;
if (req.index >= 0) --req.cfg->bufferPending[req.index];
if (req.index >= 0) {
--req.cfg->bufferPending[req.index];
req.cfg->bufferReuse[req.index].Complete(req.reuseTicket);
}
req.terminal = true;
req.cfg->vblankCond.notify_all();
timing.Mark("notify_game");

View File

@ -5,13 +5,17 @@ target_sources(libc PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src/specifics/windows/CrashReport.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/specifics/linux/DlIteratePhdr.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Strings.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/StringExtensions.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Math.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Time.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/FileAndHeap.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/StdioExtras.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/StandardStreams.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/GuestAllocations.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/GuestArena.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/GuestHeap.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/HeapDiagnostics.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Mspace.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/ApplicationHeap.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/ApplicationProcessParameters.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/JumpBuffer.cpp
@ -19,6 +23,10 @@ target_sources(libc PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src/RuntimeSupport.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/LocaleSupport.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Process.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Environment.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Directory.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Filesystem.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Errors.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/CxxAbiSupport.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/exception/Exports.cpp
)

View File

@ -2,6 +2,7 @@
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/HeapDiagnostics.hpp"
uint32_t Need_sceLibc = 1;
@ -39,8 +40,7 @@ extern "C" {
}
void APS5_VABI LibcHeapGetTraceInfo_nid_postfix(LibcHeapInfo* info) {
(void)info;
NotImplemented_nid_no_patch(__func__);
LibcHeapTraceInfo_nid_no_patch(info);
}
int APS5_VABI LibcInternalExtCxaThreadAtexit_nid_postfix(void (*destructor)(void*), void* object, void* module_id) {

View File

@ -4,6 +4,28 @@
#include <cstdio>
#include <stdexcept>
#include <utility>
#include <cstdint>
#include <cstddef>
#include <type_traits>
#ifdef _WIN32
#include <io.h>
#endif
struct GuestFilePrefix {
unsigned char* position = nullptr;
std::int32_t readRemaining = 0;
std::int32_t writeRemaining = 0;
std::int16_t flags = 0;
std::int16_t descriptor = -1;
unsigned char* buffer = nullptr;
std::int32_t bufferSize = 0;
std::int32_t bufferPadding = 0;
std::int32_t lineBufferSize = 0;
};
static_assert(offsetof(GuestFilePrefix, flags) == 16);
static_assert(offsetof(GuestFilePrefix, descriptor) == 18);
static_assert(offsetof(GuestFilePrefix, buffer) == 24);
static_assert(offsetof(GuestFilePrefix, lineBufferSize) == 40);
static constexpr const char* FOPEN_EXT_VERT = ".vert";
static constexpr const char* FOPEN_MSG_NULL_ARG = "null argument";
@ -11,12 +33,23 @@ static constexpr const char* FOPEN_MSG_NOT_FOUND = "file not found";
static constexpr const char* FOPEN_MSG_OPEN_FAILED = "open failed";
class FileStream {
// Only the macro-accessed FreeBSD prefix is exposed. The remaining guest
// FILE area is reserved; host state lives beyond it and is never a guest FILE*.
GuestFilePrefix _guest{};
std::byte _reserved[256 - sizeof(GuestFilePrefix)]{};
std::FILE* _handle;
bool _dynamic;
public:
explicit FileStream(std::FILE* handle, bool dynamic = false) : _handle(handle), _dynamic(dynamic) {
if (!_handle) throw std::runtime_error("FileStream: null handle");
_guest.flags = handle == stdin ? 4 : handle == stdout || handle == stderr ? 8 : 0x10;
#ifdef _WIN32
const int descriptor = _fileno(handle);
#else
const int descriptor = ::fileno(handle);
#endif
_guest.descriptor = descriptor >= 0 && descriptor <= 32767 ? static_cast<std::int16_t>(descriptor) : -1;
}
FileStream(const FileStream&) = delete;
@ -31,11 +64,36 @@ public:
return _dynamic;
}
GuestFilePrefix& GuestState() { return _guest; }
bool Reopen(const char* filename, const char* mode) {
auto* previous = GetHandle();
_guest = {};
_handle = std::freopen(filename, mode, previous);
if (!_handle) return false;
_guest.flags = 0x10;
#ifdef _WIN32
const int descriptor = _fileno(_handle);
#else
const int descriptor = ::fileno(_handle);
#endif
_guest.descriptor = descriptor >= 0 && descriptor <= 32767 ? static_cast<std::int16_t>(descriptor) : -1;
return true;
}
void SyncStatus() {
_guest.readRemaining = 0;
_guest.writeRemaining = 0;
_guest.flags = static_cast<std::int16_t>((_guest.flags & ~0x60) |
(std::feof(GetHandle()) ? 0x20 : 0) | (std::ferror(GetHandle()) ? 0x40 : 0));
}
void Close() {
GetHandle();
if (std::fclose(std::exchange(_handle, nullptr)) != 0) throw std::runtime_error("FileStream: close failed");
_guest.flags = 0;
_guest.descriptor = -1;
}
};
static_assert(std::is_standard_layout_v<FileStream>);
inline std::FILE* GetNativeStream(FileStream* stream) {
if (!stream) throw std::runtime_error("FileStream: null stream");

View File

@ -24,9 +24,7 @@ using Lease = std::vector<std::shared_ptr<const Range>>;
extern "C" {
void* GuestAllocationsBegin_nid_postfix();
#ifdef _WIN32
void GuestAllocationsRegisterMainImage_nid_postfix(void* mutation);
#endif
void GuestAllocationsEnd_nid_postfix(void* mutation) noexcept;
void GuestAllocationsAdd_nid_postfix(void* mutation, void* pointer, std::size_t bytes, bool readable, bool writable);
void GuestAllocationsRequireUnpinned_nid_postfix(void* mutation, const void* pointer, std::size_t bytes);
@ -57,9 +55,7 @@ public:
~Mutation() { GuestAllocationsEnd_nid_postfix(handle); }
Mutation(const Mutation&) = delete;
Mutation& operator=(const Mutation&) = delete;
#ifdef _WIN32
void RegisterMainImage() { GuestAllocationsRegisterMainImage_nid_postfix(handle); }
#endif
void Add(void* pointer, std::size_t bytes, bool readable, bool writable) { GuestAllocationsAdd_nid_postfix(handle, pointer, bytes, readable, writable); }
void RequireUnpinned(const void* pointer, std::size_t bytes) const { GuestAllocationsRequireUnpinned_nid_postfix(handle, pointer, bytes); }
void RequireAvailable(const void* pointer, std::size_t bytes) const { GuestAllocationsRequireAvailable_nid_postfix(handle, pointer, bytes); }

View File

@ -0,0 +1,14 @@
#pragma once
#include <cstddef>
#include <cstdint>
// FreeBSD 11 ABI used by the PS5 SDK: names start at byte 8.
struct GuestDirectoryEntry {
std::uint32_t fileNumber;
std::uint16_t recordLength;
std::uint8_t type;
std::uint8_t nameLength;
char name[256];
};
static_assert(offsetof(GuestDirectoryEntry, name) == 8);
static_assert(sizeof(GuestDirectoryEntry) == 264);

View File

@ -0,0 +1,8 @@
#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_HEAPDIAGNOSTICS_HPP
#define CORE_LIBS_PRX_LIBC_INCLUDE_HEAPDIAGNOSTICS_HPP
#include "SceTypes.hpp"
extern "C" void LibcHeapTraceInfo_nid_no_patch(LibcHeapInfo* info);
#endif

View File

@ -3,5 +3,6 @@
extern "C" void LibcRegisterShutdown_nid_postfix(void (*callback)());
extern "C" void LibcRunShutdown_nid_postfix();
extern "C" [[noreturn]] void LibcExit_nid_no_patch(int code);
#endif

View File

@ -0,0 +1,102 @@
#include "prx/libc/include/GuestDirectory.hpp"
#include "prx/libc/include/General.hpp"
#include <dirent.h>
#include <cerrno>
#include <cstring>
#include <memory>
#include <mutex>
namespace {
int DirectoryError(int error) {
switch (error) {
case ENOENT: return 2;
case EACCES: return 13;
case ENOTDIR: return 20;
case EINVAL: return 22;
case EMFILE: return 24;
case ENFILE: return 23;
case ENOMEM: return 12;
case ENAMETOOLONG: return 63;
default: return 5;
}
}
struct Directory {
DIR* native = nullptr;
std::filesystem::path path;
GuestDirectoryEntry entry{};
std::mutex mutex;
~Directory() { if (native) ::closedir(native); }
};
std::uint8_t DirectoryType(const std::filesystem::path& path) {
std::error_code error;
switch (std::filesystem::symlink_status(path, error).type()) {
case std::filesystem::file_type::regular: return 8;
case std::filesystem::file_type::directory: return 4;
case std::filesystem::file_type::symlink: return 10;
case std::filesystem::file_type::block: return 6;
case std::filesystem::file_type::character: return 2;
case std::filesystem::file_type::fifo: return 1;
case std::filesystem::file_type::socket: return 12;
default: return 0;
}
}
}
extern "C" {
void* APS5_VABI opendir_nid_postfix(const char* path) {
if (!path) { errno = 14; return nullptr; }
if (!*path) { errno = 2; return nullptr; }
try {
auto directory = std::make_unique<Directory>();
directory->path = ResolvePath_nid_no_patch(path);
directory->native = ::opendir(directory->path.string().c_str());
if (!directory->native) { errno = DirectoryError(errno); return nullptr; }
return directory.release();
} catch (const std::bad_alloc&) { errno = 12; return nullptr; }
catch (const std::filesystem::filesystem_error&) { errno = 5; return nullptr; }
}
GuestDirectoryEntry* APS5_VABI readdir_nid_postfix(void* handle) {
if (!handle) { errno = 9; return nullptr; }
auto& directory = *static_cast<Directory*>(handle);
std::lock_guard lock(directory.mutex);
const int savedError = errno;
errno = 0;
auto* entry = ::readdir(directory.native);
if (!entry) {
errno = errno ? DirectoryError(errno) : savedError;
return nullptr;
}
const auto length = std::strlen(entry->d_name);
if (length > 255) { errno = 63; return nullptr; }
try {
directory.entry = {};
// MinGW reports no inode identity (zero); do not fabricate one.
const auto inode = static_cast<std::uint64_t>(entry->d_ino);
directory.entry.fileNumber = inode <= UINT32_MAX ? static_cast<std::uint32_t>(inode) : 0;
directory.entry.recordLength = static_cast<std::uint16_t>(8 + ((length + 1 + 3) & ~3));
directory.entry.nameLength = static_cast<std::uint8_t>(length);
directory.entry.type = DirectoryType(directory.path / entry->d_name);
std::memcpy(directory.entry.name, entry->d_name, length + 1);
errno = savedError;
return &directory.entry;
} catch (const std::bad_alloc&) { errno = 12; return nullptr; }
catch (const std::filesystem::filesystem_error&) { errno = 5; return nullptr; }
}
int APS5_VABI closedir_nid_postfix(void* handle) {
if (!handle) { errno = 9; return -1; }
std::unique_ptr<Directory> directory(static_cast<Directory*>(handle));
const int result = ::closedir(directory->native);
directory->native = nullptr;
if (result) errno = DirectoryError(errno);
return result;
}
void APS5_VABI rewinddir_nid_postfix(void* handle) {
if (!handle) { errno = 9; return; }
auto& directory = *static_cast<Directory*>(handle);
std::lock_guard lock(directory.mutex);
::rewinddir(directory.native);
}
}

View File

@ -0,0 +1,102 @@
#include <cstdlib>
#include <cerrno>
#include <cstring>
#include <algorithm>
#include <mutex>
#include <string>
#include <string_view>
#include <vector>
#include "prx/libc/include/general/VabiMacros.hpp"
#ifndef _WIN32
extern char** environ;
#endif
namespace {
struct EnvironmentEntry {
std::string owned;
char* borrowed = nullptr;
char* Data() { return borrowed ? borrowed : owned.data(); }
bool Matches(std::string_view name) {
const char* data = Data();
const char* separator = std::strchr(data, '=');
return separator && std::string_view(data, separator - data) == name;
}
};
std::mutex environmentMutex;
std::vector<EnvironmentEntry>& Environment() {
// Seed once from the host. Guest mutations remain local to the guest runtime.
static std::vector<EnvironmentEntry> entries = [] {
std::vector<EnvironmentEntry> result;
#ifdef _WIN32
char** source = _environ;
#else
char** source = environ;
#endif
if (source) for (; *source; ++source) {
if (**source && **source != '=' && std::strchr(*source, '='))
result.push_back({*source, nullptr});
}
return result;
}();
return entries;
}
bool ValidEnvironmentName(const char* name) {
return name && *name && !std::strchr(name, '=');
}
int EnvironmentError(int error) { errno = error; return -1; }
}
extern "C" {
char* APS5_VABI getenv_nid_postfix(const char* name) {
if (!ValidEnvironmentName(name)) return nullptr;
try {
std::lock_guard lock(environmentMutex);
for (auto& entry : Environment())
if (entry.Matches(name)) return entry.Data() + std::strlen(name) + 1;
return nullptr;
} catch (const std::bad_alloc&) { errno = 12; return nullptr; }
}
int APS5_VABI setenv_nid_postfix(const char* name, const char* value, int overwrite) {
if (!ValidEnvironmentName(name) || !value) return EnvironmentError(22);
try {
std::lock_guard lock(environmentMutex);
auto& entries = Environment();
auto found = std::find_if(entries.begin(), entries.end(),
[name](auto& entry) { return entry.Matches(name); });
if (found != entries.end() && !overwrite) return 0;
EnvironmentEntry replacement{std::string(name) + '=' + value, nullptr};
if (found != entries.end()) *found = std::move(replacement);
else entries.push_back(std::move(replacement));
return 0;
} catch (const std::bad_alloc&) { return EnvironmentError(12); }
}
int APS5_VABI unsetenv_nid_postfix(const char* name) {
if (!ValidEnvironmentName(name)) return EnvironmentError(22);
try {
std::lock_guard lock(environmentMutex);
auto& entries = Environment();
const std::string key(name);
std::erase_if(entries, [&key](auto& entry) { return entry.Matches(key); });
return 0;
} catch (const std::bad_alloc&) { return EnvironmentError(12); }
}
int APS5_VABI putenv_nid_postfix(char* text) {
const char* separator = text ? std::strchr(text, '=') : nullptr;
if (!separator || separator == text) return EnvironmentError(22);
try {
std::lock_guard lock(environmentMutex);
auto& entries = Environment();
const std::string_view name(text, separator - text);
auto found = std::find_if(entries.begin(), entries.end(),
[name](auto& entry) { return entry.Matches(name); });
EnvironmentEntry replacement{{}, text};
if (found != entries.end()) *found = std::move(replacement);
else entries.push_back(std::move(replacement));
return 0;
} catch (const std::bad_alloc&) { return EnvironmentError(12); }
}
}

View File

@ -0,0 +1,75 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdio>
#include <cstring>
extern "C" int* APS5_VABI __error_nid_postfix();
namespace {
// Guest FreeBSD errno numbering, not the host CRT's numbering.
constexpr const char* messages[] = {
"No error", "Operation not permitted", "No such file or directory", "No such process",
"Interrupted system call", "Input/output error", "Device not configured", "Argument list too long",
"Exec format error", "Bad file descriptor", "No child processes", "Resource deadlock avoided",
"Cannot allocate memory", "Permission denied", "Bad address", "Block device required",
"Device busy", "File exists", "Cross-device link", "Operation not supported by device",
"Not a directory", "Is a directory", "Invalid argument", "Too many open files in system",
"Too many open files", "Inappropriate ioctl for device", "Text file busy", "File too large",
"No space left on device", "Illegal seek", "Read-only filesystem", "Too many links", "Broken pipe",
"Numerical argument out of domain", "Result too large", "Resource temporarily unavailable",
"Operation now in progress", "Operation already in progress", "Socket operation on non-socket",
"Destination address required", "Message too long", "Protocol wrong type for socket",
"Protocol not available", "Protocol not supported", "Socket type not supported",
"Operation not supported", "Protocol family not supported", "Address family not supported",
"Address already in use", "Cannot assign requested address", "Network is down", "Network is unreachable",
"Network connection reset", "Connection aborted", "Connection reset by peer", "No buffer space available",
"Socket is already connected", "Socket is not connected", "Socket is shut down", "Too many references",
"Operation timed out", "Connection refused", "Too many symbolic links", "File name too long",
"Host is down", "No route to host", "Directory not empty", "Too many processes", "Too many users",
"Disk quota exceeded", "Stale file handle", "Remote path has too many levels", "Invalid RPC structure",
"RPC version mismatch", "RPC program unavailable", "Program version mismatch", "Invalid RPC procedure",
"No locks available", "Function not implemented", "Inappropriate file type", "Authentication error",
"Authentication required", "Identifier removed", "No message of requested type", "Value too large",
"Operation canceled", "Invalid byte sequence", "Attribute not found", "Programming error",
"Bad message", "Multihop attempted", "Link severed", "Protocol error", "Insufficient capabilities",
"Operation not permitted in capability mode", "State not recoverable", "Previous owner died"
};
static_assert(sizeof(messages) / sizeof(messages[0]) == 97);
bool Known(int error) { return error >= 0 && error < 97; }
}
extern "C" {
char* APS5_VABI strerror_nid_postfix(int error) {
thread_local char buffer[128];
const int saved = *__error_nid_postfix();
if (Known(error)) std::snprintf(buffer, sizeof(buffer), "%s", messages[error]);
else std::snprintf(buffer, sizeof(buffer), "Unknown error: %d", error);
*__error_nid_postfix() = Known(error) ? saved : 22;
return buffer;
}
int APS5_VABI strerror_r_nid_postfix(int error, char* buffer, std::size_t length) {
const int saved = *__error_nid_postfix();
char temporary[128];
if (Known(error)) std::snprintf(temporary, sizeof(temporary), "%s", messages[error]);
else std::snprintf(temporary, sizeof(temporary), "Unknown error: %d", error);
const auto required = std::strlen(temporary) + 1;
int result = Known(error) ? 0 : 22;
if (!buffer || length < required) result = 34;
if (buffer && length) {
const auto count = required <= length ? required - 1 : length - 1;
std::memcpy(buffer, temporary, count);
buffer[count] = '\0';
}
*__error_nid_postfix() = saved;
return result;
}
void APS5_VABI perror_nid_postfix(const char* prefix) {
const int saved = *__error_nid_postfix();
char message[128];
strerror_r_nid_postfix(saved, message, sizeof(message));
if (prefix && *prefix) std::fprintf(stderr, "%s: %s\n", prefix, message);
else std::fprintf(stderr, "%s\n", message);
*__error_nid_postfix() = saved;
}
}

View File

@ -6,6 +6,7 @@
#include <filesystem>
#include <limits>
#include <utility>
#include <cerrno>
#include "prx/libc/include/FileStream.hpp"
#include "prx/libc/include/ApplicationHeap.hpp"
@ -15,6 +16,25 @@ extern "C" {
[[noreturn]] void APS5_VABI _ZSt11_Xbad_allocv_nid_postfix();
FileStream* APS5_VABI freopen_nid_postfix(const char* filename, const char* mode, FileStream* stream) {
if (!stream || !mode) { errno = 22; return nullptr; }
if (!filename) { errno = 45; return nullptr; } // Mode-only reopening is not supported.
const char* supported[] = {"r", "w", "a", "rb", "wb", "ab", "r+", "w+", "a+",
"rb+", "wb+", "ab+", "r+b", "w+b", "a+b"};
bool valid = false;
for (const auto* candidate : supported) if (std::strcmp(mode, candidate) == 0) valid = true;
if (!valid) { errno = 22; return nullptr; }
try {
const auto path = *filename ? ResolvePath_nid_no_patch(filename).string() : std::string{};
if (stream->Reopen(path.c_str(), mode)) return stream;
const int error = errno;
if (stream->IsDynamic()) delete stream;
errno = error;
return nullptr;
} catch (const std::bad_alloc&) { errno = 12; return nullptr; }
catch (const std::filesystem::filesystem_error&) { errno = 5; return nullptr; }
}
FileStream* APS5_VABI fopen_nid_postfix(const char* filename, const char* mode) {
if (!filename || !mode) throw std::runtime_error(std::string(__func__) + ": " + FOPEN_MSG_NULL_ARG);
const std::filesystem::path fpath = ResolvePath_nid_no_patch(filename);
@ -55,6 +75,7 @@ size_t APS5_VABI fread_nid_postfix(void* buffer, size_t size, size_t count, File
if (size == 0 || count == 0) return 0;
if (!buffer) throw std::runtime_error("fread: null buffer");
const auto result = std::fread(buffer, size, count, handle);
stream->SyncStatus();
if (std::ferror(handle)) throw std::runtime_error("fread: read failed");
return result;
}
@ -64,22 +85,42 @@ size_t APS5_VABI fwrite_nid_postfix(const void* buffer, size_t size, size_t coun
if (size == 0 || count == 0) return 0;
if (!buffer) throw std::runtime_error("fwrite: null buffer");
const auto result = std::fwrite(buffer, size, count, handle);
stream->SyncStatus();
if (result != count || std::ferror(handle)) throw std::runtime_error("fwrite: write failed");
return result;
}
int APS5_VABI fseek_nid_postfix(FileStream* stream, long offset, int origin) {
if (origin != SEEK_SET && origin != SEEK_CUR && origin != SEEK_END) throw std::runtime_error("fseek: invalid origin");
if (std::fseek(GetNativeStream(stream), offset, origin) != 0) throw std::runtime_error("fseek: seek failed");
return 0;
}
long APS5_VABI ftell_nid_postfix(FileStream* stream) {
const auto result = std::ftell(GetNativeStream(stream));
if (result == -1L) throw std::runtime_error("ftell: position query failed");
int APS5_VABI fseeko_nid_postfix(FileStream* stream, std::int64_t offset, int origin) {
if (origin != SEEK_SET && origin != SEEK_CUR && origin != SEEK_END) { errno = 22; return -1; }
#ifdef _WIN32
const int result = _fseeki64(GetNativeStream(stream), offset, origin);
#else
static_assert(sizeof(off_t) == 8);
const int result = ::fseeko(GetNativeStream(stream), offset, origin);
#endif
const int nativeError = errno;
stream->SyncStatus();
if (result) errno = nativeError == EOVERFLOW ? 84 : nativeError;
return result;
}
std::int64_t APS5_VABI ftello_nid_postfix(FileStream* stream) {
#ifdef _WIN32
const auto result = _ftelli64(GetNativeStream(stream));
#else
static_assert(sizeof(off_t) == 8);
const auto result = ::ftello(GetNativeStream(stream));
#endif
if (result == -1 && errno == EOVERFLOW) errno = 84;
return result;
}
int APS5_VABI fseek_nid_postfix(FileStream* stream, std::int64_t offset, int origin) {
return fseeko_nid_postfix(stream, offset, origin);
}
std::int64_t APS5_VABI ftell_nid_postfix(FileStream* stream) { return ftello_nid_postfix(stream); }
int APS5_VABI fputs_nid_postfix(const char* str, FileStream* stream) {
if (!str) throw std::runtime_error("fputs: null string");
const int result = std::fputs(str, GetNativeStream(stream));
@ -116,6 +157,27 @@ int APS5_VABI posix_memalign_nid_postfix(void** pointer, size_t alignment, size_
return ApplicationHeapPosixAlign_nid_no_patch(pointer, alignment, size);
}
void* APS5_VABI bsearch_nid_postfix(const void* key, const void* base, size_t count,
size_t size, int (APS5_VABI *compare)(const void*, const void*)) {
if (count == 0) return nullptr;
if (!key || !base || !compare || size == 0)
throw std::invalid_argument("bsearch: invalid arguments");
if (count > std::numeric_limits<size_t>::max() / size)
throw std::overflow_error("bsearch: array size overflow");
const auto* bytes = static_cast<const unsigned char*>(base);
size_t first = 0;
while (count != 0) {
const size_t half = count / 2;
const size_t middle = first + half;
const auto* element = bytes + middle * size;
const int result = compare(key, element);
if (result == 0) return const_cast<unsigned char*>(element);
if (result < 0) count = half;
else { first = middle + 1; count -= half + 1; }
}
return nullptr;
}
void APS5_VABI qsort_nid_postfix(void* base, size_t count, size_t size, int (APS5_VABI *compare)(const void*, const void*)) {
if (!compare) throw std::invalid_argument("qsort: null comparator");
if (size == 0) throw std::invalid_argument("qsort: zero element size");

View File

@ -0,0 +1,77 @@
#include "prx/libc/include/General.hpp"
#include <cerrno>
#ifdef _WIN32
#define NOMINMAX
#include <windows.h>
#endif
namespace {
int FilesystemError(const std::error_code& error) {
if (error == std::errc::no_such_file_or_directory) return 2;
if (error == std::errc::permission_denied) return 13;
if (error == std::errc::operation_not_permitted) return 1;
if (error == std::errc::not_a_directory) return 20;
if (error == std::errc::is_a_directory) return 21;
if (error == std::errc::directory_not_empty) return 66;
if (error == std::errc::device_or_resource_busy) return 16;
if (error == std::errc::read_only_file_system) return 30;
if (error == std::errc::filename_too_long) return 63;
if (error == std::errc::too_many_symbolic_link_levels) return 62;
if (error == std::errc::not_enough_memory) return 12;
if (error == std::errc::invalid_argument) return 22;
if (error == std::errc::cross_device_link) return 18;
if (error == std::errc::file_exists) return 17;
if (error == std::errc::no_space_on_device) return 28;
return 5;
}
}
extern "C" int APS5_VABI rename_nid_postfix(const char* from, const char* to) {
if (!from || !to) { errno = 14; return -1; }
if (!*from || !*to) { errno = 2; return -1; }
try {
const auto source = ResolvePath_nid_no_patch(from);
const auto destination = ResolvePath_nid_no_patch(to);
std::error_code error;
std::filesystem::rename(source, destination, error);
if (error) { errno = FilesystemError(error); return -1; }
return 0;
} catch (const std::bad_alloc&) { errno = 12; return -1; }
catch (const std::filesystem::filesystem_error& error) {
errno = FilesystemError(error.code());
return -1;
}
}
extern "C" int APS5_VABI remove_nid_postfix(const char* path) {
if (!path) { errno = 14; return -1; }
if (!*path) { errno = 2; return -1; }
try {
const auto resolved = ResolvePath_nid_no_patch(path);
std::error_code error;
#ifdef _WIN32
const DWORD attributes = GetFileAttributesW(resolved.c_str());
bool removed = false;
if (attributes != INVALID_FILE_ATTRIBUTES) {
removed = (attributes & FILE_ATTRIBUTE_DIRECTORY) ?
RemoveDirectoryW(resolved.c_str()) != 0 : DeleteFileW(resolved.c_str()) != 0;
}
if (!removed) {
const DWORD nativeError = GetLastError();
if (nativeError == ERROR_DIR_NOT_EMPTY) { errno = 66; return -1; }
error = std::error_code(static_cast<int>(nativeError), std::system_category());
}
#else
const bool removed = std::filesystem::remove(resolved, error);
#endif
if (error || !removed) {
errno = error ? FilesystemError(error) : 2;
return -1;
}
return 0;
} catch (const std::bad_alloc&) { errno = 12; return -1; }
catch (const std::filesystem::filesystem_error& error) {
errno = FilesystemError(error.code());
return -1;
}
}

View File

@ -4,6 +4,7 @@
#include <cstdarg>
#include "SceTypes.hpp"
#include "prx/libc/include/VarArgsAbi.hpp"
#include "prx/libc/include/FileStream.hpp"
#ifdef _WIN32
#include "prx/libc/include/WindowsFormatting.hpp"
@ -11,6 +12,37 @@
extern "C" {
int APS5_VABI vfprintf_nid_postfix(FileStream* stream, const char* format, VaList* args) {
auto* native = GetNativeStream(stream);
#ifdef _WIN32
std::string buffer;
const int count = LibcDetail::FormatWindows(nullptr, 0, format, args, &buffer);
const int result = std::fwrite(buffer.data(), 1, static_cast<size_t>(count), native) ==
static_cast<size_t>(count) ? count : -1;
#else
const int result = std::vfprintf(native, format, *reinterpret_cast<std::va_list*>(args));
#endif
stream->SyncStatus();
return result;
}
int APS5_VABI fprintf_nid_postfix(FileStream* stream, const char* format, ...) {
#ifdef _WIN32
__builtin_sysv_va_list args;
__builtin_sysv_va_start(args, format);
#else
std::va_list args;
va_start(args, format);
#endif
const int result = vfprintf_nid_postfix(stream, format, reinterpret_cast<VaList*>(args));
#ifdef _WIN32
__builtin_sysv_va_end(args);
#else
va_end(args);
#endif
return result;
}
#ifdef _WIN32
int APS5_VABI printf_nid_postfix(const char* format, ...) {
@ -120,6 +152,14 @@ int APS5_VABI vprintf_nid_postfix(const char* str, VaList* c) {
#endif
}
int APS5_VABI vsprintf_nid_postfix(char* str, const char* format, VaList* args) {
#ifdef _WIN32
return LibcDetail::FormatWindows(str, SIZE_MAX, format, args);
#else
return std::vsprintf(str, format, *reinterpret_cast<std::va_list*>(args));
#endif
}
int APS5_VABI vsnprintf_nid_postfix(char* str, size_t size, const char* format, VaList* c) {
#ifdef _WIN32
return LibcDetail::FormatWindows(str, size, format, c);

View File

@ -4,11 +4,13 @@
#include <utility>
#include <vector>
#include <filesystem>
#include <optional>
#include <cerrno>
#include <cstring>
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/GuestHeap.hpp"
namespace {
// Guest prefixes (without leading slashes) mapped to host directories, e.g. save-data mount points:
// the PS5 hands the title a short mount point ("/_sm/0") whose files live under _sd/<dir name>.
struct PathAliases {
@ -34,6 +36,47 @@ std::string TrimSlashes(const char* path) {
return s.substr(start, end - start);
}
std::optional<std::filesystem::path> ResolveAlias(const std::string& guestPath) {
const auto relative = TrimSlashes(guestPath.c_str());
auto& aliases = Aliases();
std::lock_guard lock(aliases.mutex);
for (const auto& [prefix, host] : aliases.entries) {
if (relative.size() < prefix.size() || relative.compare(0, prefix.size(), prefix) != 0) continue;
if (relative.size() == prefix.size()) return std::filesystem::path(host).make_preferred();
if (relative[prefix.size()] != '/') continue;
std::filesystem::path result = std::filesystem::path(host) / std::filesystem::path(relative.substr(prefix.size() + 1));
return result.make_preferred();
}
return std::nullopt;
}
struct WorkingDirectory {
std::mutex mutex;
const std::filesystem::path root = std::filesystem::canonical(std::filesystem::current_path());
std::filesystem::path current = root;
};
WorkingDirectory& Directories() { static WorkingDirectory state; return state; }
std::filesystem::path Resolve(WorkingDirectory& state, const char* path) {
std::string text(path);
for (auto& character : text) if (character == '\\') character = '/';
std::filesystem::path input(text);
#ifdef _WIN32
// Preserve the existing ability to pass explicit native drive paths.
if (input.has_root_name()) return input;
#endif
auto guest = (std::filesystem::path("/") / state.current.lexically_relative(state.root));
guest = (input.is_absolute() ? input : guest / input).lexically_normal();
if (auto aliased = ResolveAlias(guest.relative_path().generic_string())) return *aliased;
return (state.root / guest.relative_path()).make_preferred();
}
int DirectoryFailure(const std::error_code& error) {
if (error == std::errc::permission_denied) return 13;
if (error == std::errc::not_a_directory) return 20;
if (error == std::errc::no_such_file_or_directory) return 2;
if (error == std::errc::filename_too_long) return 63;
if (error == std::errc::too_many_symbolic_link_levels) return 62;
return 5;
}
}
extern "C" void AddPathAlias_nid_no_patch(const char* guestPrefix, const char* hostPath) {
@ -63,23 +106,50 @@ extern "C" void RemovePathAlias_nid_no_patch(const char* guestPrefix) {
}
extern "C" std::filesystem::path ResolvePath_nid_no_patch(const char* path) {
if (path == nullptr) {
APS5_INVALID_ARG_EX;
}
const auto relative = TrimSlashes(path);
{
auto& aliases = Aliases();
std::lock_guard lock(aliases.mutex);
for (const auto& [prefix, host] : aliases.entries) {
if (relative.size() < prefix.size() || relative.compare(0, prefix.size(), prefix) != 0) continue;
if (relative.size() == prefix.size()) return std::filesystem::path(host).make_preferred();
if (relative[prefix.size()] != '/' && relative[prefix.size()] != '\\') continue;
std::filesystem::path result = std::filesystem::path(host) / std::filesystem::path(relative.substr(prefix.size() + 1));
return result.make_preferred();
if (!path) { APS5_INVALID_ARG_EX; }
auto& state = Directories();
std::lock_guard lock(state.mutex);
return Resolve(state, path);
}
extern "C" int APS5_VABI chdir_nid_postfix(const char* path) {
if (!path) { errno = 14; return -1; }
if (!*path) { errno = 2; return -1; }
try {
auto& state = Directories();
std::lock_guard lock(state.mutex);
std::error_code error;
const auto resolved = std::filesystem::canonical(Resolve(state, path), error);
if (error) { errno = DirectoryFailure(error); return -1; }
if (!std::filesystem::is_directory(resolved, error)) {
errno = error ? DirectoryFailure(error) : 20; return -1;
}
}
std::filesystem::path result = std::filesystem::current_path() / std::filesystem::path(relative);
return result.make_preferred();
const auto relative = resolved.lexically_relative(state.root);
if (relative.empty() || *relative.begin() == "..") { errno = 45; return -1; }
state.current = resolved;
return 0;
} catch (const std::bad_alloc&) { errno = 12; return -1; }
catch (const std::filesystem::filesystem_error& error) { errno = DirectoryFailure(error.code()); return -1; }
}
extern "C" char* APS5_VABI getcwd_nid_postfix(char* buffer, std::size_t size) {
if (buffer && size == 0) { errno = 22; return nullptr; }
try {
auto& state = Directories();
std::lock_guard lock(state.mutex);
std::error_code error;
if (!std::filesystem::is_directory(state.current, error)) {
errno = error ? DirectoryFailure(error) : 2; return nullptr;
}
const auto relative = state.current.lexically_relative(state.root);
const auto path = relative == "." ? std::string("/") : "/" + relative.generic_string();
const auto required = path.size() + 1;
if ((buffer || size) && size < required) { errno = 34; return nullptr; }
if (!buffer) buffer = static_cast<char*>(GuestHeap::GuestHeapAllocate_nid_postfix(size ? size : required));
std::memcpy(buffer, path.c_str(), required);
return buffer;
} catch (const std::bad_alloc&) { errno = 12; return nullptr; }
catch (const std::filesystem::filesystem_error& error) { errno = DirectoryFailure(error.code()); return nullptr; }
}
extern "C" void NotImplemented_nid_no_patch(const char* funcName) {

View File

@ -12,6 +12,9 @@
#define NOMINMAX
#endif
#include <windows.h>
#else
#include <link.h>
#include <unistd.h>
#endif
namespace GuestAllocations {
@ -116,6 +119,51 @@ void GuestAllocationsRegisterMainImage_nid_postfix(void*) {
state.ranges.swap(replacement);
state.mainImageRegistered = true;
}
#else
void GuestAllocationsRegisterMainImage_nid_postfix(void*) {
auto& state = registry();
if (state.mainImageRegistered) return;
struct Page {
bool readable = false;
bool writable = false;
};
std::map<std::uint64_t, Page> pages;
const auto pageSize = static_cast<std::uint64_t>(::sysconf(_SC_PAGESIZE));
std::pair<std::map<std::uint64_t, Page>*, std::uint64_t> collection{&pages, pageSize};
dl_iterate_phdr([](dl_phdr_info* image, std::size_t, void* data) {
auto& collected = *static_cast<std::pair<std::map<std::uint64_t, Page>*, std::uint64_t>*>(data);
for (int index = 0; index < image->dlpi_phnum; ++index) {
const auto& header = image->dlpi_phdr[index];
if (header.p_type != PT_LOAD || header.p_memsz == 0) continue;
const auto start = (image->dlpi_addr + header.p_vaddr) & ~(collected.second - 1);
const auto end = (image->dlpi_addr + header.p_vaddr + header.p_memsz + collected.second - 1) & ~(collected.second - 1);
for (auto page = start; page < end; page += collected.second) {
auto& entry = (*collected.first)[page];
entry.readable = entry.readable || (header.p_flags & (PF_R | PF_W)) != 0;
entry.writable = entry.writable || (header.p_flags & PF_W) != 0;
}
}
return 1;
}, &collection);
require(!pages.empty(), "main guest image has no loadable segments");
auto replacement = state.ranges;
for (auto page = pages.begin(); page != pages.end();) {
auto last = page;
while (std::next(last) != pages.end() && std::next(last)->first == last->first + pageSize && std::next(last)->second.readable == page->second.readable && std::next(last)->second.writable == page->second.writable) ++last;
const auto address = page->first;
const auto bytes = static_cast<std::size_t>(last->first + pageSize - address);
const auto next = replacement.lower_bound(address);
require(next == replacement.end() || address + bytes <= next->first, "guest image overlaps a registered allocation");
if (next != replacement.begin()) {
const auto& previous = *std::prev(next)->second;
require(previous.address + previous.bytes <= address, "guest image overlaps a registered allocation");
}
replacement.emplace(address, std::make_shared<const Range>(Range{address, bytes, page->second.readable, page->second.writable, address, bytes, false}));
page = std::next(last);
}
state.ranges.swap(replacement);
state.mainImageRegistered = true;
}
#endif
void GuestAllocationsAdd_nid_postfix(void* mutation, void* pointer, std::size_t bytes, bool readable, bool writable) {

View File

@ -0,0 +1,45 @@
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/HeapDiagnostics.hpp"
#include <array>
#include <cstddef>
#include <cstdint>
#include <stdexcept>
namespace {
std::uint64_t mspaceAtomicIdMask = 0;
std::array<std::uint64_t, 64> mstateTable{};
static_assert(sizeof(LibcHeapInfo) == 32);
static_assert(offsetof(LibcHeapInfo, mspace_atomic_id_mask) == 16);
static_assert(offsetof(LibcHeapInfo, mstate_table) == 24);
}
extern "C" {
void LibcHeapTraceInfo_nid_no_patch(LibcHeapInfo* info) {
if (info == nullptr)
throw std::invalid_argument("heap trace info: null output");
if (info->size != sizeof(LibcHeapInfo))
throw std::invalid_argument("heap trace info: unsupported structure size");
info->unknown2 = 0;
info->mspace_atomic_id_mask = &mspaceAtomicIdMask;
info->mstate_table = mstateTable.data();
}
// unknown signature
void APS5_VABI sceLibcInternalBacktraceForGame_nid_postfix(const char* heapName) {
(void)heapName;
NotImplemented_nid_no_patch(__func__);
}
// unknown signature
void APS5_VABI sceLibcInternalHeapErrorReportForGame_nid_postfix(void* heap, void* block, std::uint32_t error) {
(void)heap;
(void)block;
(void)error;
NotImplemented_nid_no_patch(__func__);
}
}

View File

@ -1,8 +1,11 @@
#include <climits>
#include <cstddef>
#include <cstdio>
#include <cstdint>
#include <cwchar>
#include <cwctype>
#include <ios>
#include <locale>
#include <mutex>
#include <atomic>
#include <cstring>
@ -100,10 +103,43 @@ constexpr auto g_classificationTable = MakeClassificationTable();
constexpr auto g_lowerTable = MakeCaseTable(false);
constexpr auto g_upperTable = MakeCaseTable(true);
void ValidateCharacter(int value) {
if (value != EOF && (value < 0 || value > UCHAR_MAX)) throw std::invalid_argument("Invalid character value");
}
int ClassifyCharacter(int value, std::ctype_base::mask mask) {
ValidateCharacter(value);
if (value == EOF) return 0;
return std::use_facet<std::ctype<char>>(std::locale::classic()).is(mask, static_cast<char>(value));
}
int ConvertCharacter(int value, bool upper) {
ValidateCharacter(value);
if (value == EOF) return EOF;
const auto& facet = std::use_facet<std::ctype<char>>(std::locale::classic());
const auto character = static_cast<char>(value);
return static_cast<unsigned char>(upper ? facet.toupper(character) : facet.tolower(character));
}
}
extern "C" {
int APS5_VABI isupper_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::upper); }
int APS5_VABI islower_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::lower); }
int APS5_VABI isalpha_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::alpha); }
int APS5_VABI isdigit_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::digit); }
int APS5_VABI isalnum_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::alnum); }
int APS5_VABI isspace_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::space); }
int APS5_VABI isblank_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::blank); }
int APS5_VABI iscntrl_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::cntrl); }
int APS5_VABI isprint_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::print); }
int APS5_VABI isgraph_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::graph); }
int APS5_VABI ispunct_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::punct); }
int APS5_VABI isxdigit_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::xdigit); }
int APS5_VABI toupper_nid_postfix(int c) { return ConvertCharacter(c, true); }
int APS5_VABI tolower_nid_postfix(int c) { return ConvertCharacter(c, false); }
std::uint64_t _ZNSt5ctypeIcE2idE_nid_postfix = 0;
std::uint64_t _ZNSt5ctypeIwE2idE_nid_postfix = 0;
std::uint64_t _ZNSt7collateIwE2idE_nid_postfix = 0;

View File

@ -1,11 +1,33 @@
#include <mutex>
#include <cstdint>
#include <cmath>
#include <cstdint>
#include "prx/libc/include/General.hpp"
extern "C" {
float APS5_VABI fmodf_nid_postfix(float x, float y) { return std::fmod(x, y); }
float APS5_VABI asinf_nid_postfix(float x) { return std::asin(x); }
float APS5_VABI acosf_nid_postfix(float x) { return std::acos(x); }
float APS5_VABI atan2f_nid_postfix(float y, float x) { return std::atan2(y, x); }
float APS5_VABI tanf_nid_postfix(float x) { return std::tan(x); }
float APS5_VABI log10f_nid_postfix(float x) { return std::log10(x); }
double APS5_VABI exp2_nid_postfix(double x) { return std::exp2(x); }
double APS5_VABI ldexp_nid_postfix(double x, int exponent) { return std::ldexp(x, exponent); }
double APS5_VABI scalbn_nid_postfix(double x, int exponent) { return std::scalbn(x, exponent); }
float APS5_VABI scalbnf_nid_postfix(float x, int exponent) { return std::scalbn(x, exponent); }
double APS5_VABI frexp_nid_postfix(double x, int* exponent) { return std::frexp(x, exponent); }
float APS5_VABI frexpf_nid_postfix(float x, int* exponent) { return std::frexp(x, exponent); }
// Guest long is 64-bit, including on Windows where native long is 32-bit.
std::int64_t APS5_VABI lround_nid_postfix(double x) { return std::llround(x); }
std::int64_t APS5_VABI lroundf_nid_postfix(float x) { return std::llround(x); }
std::int64_t APS5_VABI llround_nid_postfix(double x) { return std::llround(x); }
int APS5_VABI __isfinitef_nid_postfix(float x) { return std::isfinite(x) ? 1 : 0; }
int APS5_VABI __isnormal_nid_postfix(double x) { return std::isnormal(x) ? 1 : 0; }
int APS5_VABI __isnormalf_nid_postfix(float x) { return std::isnormal(x) ? 1 : 0; }
int APS5_VABI __isinff_nid_postfix(float x) { return std::isinf(x) ? 1 : 0; }
double APS5_VABI cbrt_nid_postfix(double x) { return std::cbrt(x); }
double APS5_VABI asin_nid_postfix(double x) { return std::asin(x); }
double APS5_VABI acos_nid_postfix(double x) { return std::acos(x); }
@ -51,21 +73,11 @@ int APS5_VABI __signbit_nid_postfix(double x) { return std::signbit(x) ? 1 : 0;
double APS5_VABI modf_nid_postfix(double x, double* integral) { return std::modf(x, integral); }
float APS5_VABI modff_nid_postfix(float x, float* integral) { return std::modf(x, integral); }
double APS5_VABI frexp_nid_postfix(double x, int* exponent) { return std::frexp(x, exponent); }
float APS5_VABI frexpf_nid_postfix(float x, int* exponent) { return std::frexp(x, exponent); }
double APS5_VABI ldexp_nid_postfix(double x, int exponent) { return std::ldexp(x, exponent); }
float APS5_VABI fmodf_nid_postfix(float x, float y) { return std::fmod(x, y); }
float APS5_VABI atan2f_nid_postfix(float y, float x) { return std::atan2(y, x); }
float APS5_VABI asinf_nid_postfix(float x) { return std::asin(x); }
float APS5_VABI acosf_nid_postfix(float x) { return std::acos(x); }
float APS5_VABI tanf_nid_postfix(float x) { return std::tan(x); }
double APS5_VABI tanh_nid_postfix(double x) { return std::tanh(x); }
float APS5_VABI tanhf_nid_postfix(float x) { return std::tanh(x); }
float APS5_VABI log10f_nid_postfix(float x) { return std::log10(x); }
float APS5_VABI _FSinh_nid_postfix(float x, float y) { return y * std::sinh(x); }
float APS5_VABI _FCosh_nid_postfix(float x, float y) { return y * std::cosh(x); }
int APS5_VABI __isnanf_nid_postfix(float x) { return std::isnan(x) ? 1 : 0; }
int APS5_VABI __isinff_nid_postfix(float x) { return std::isinf(x) ? 1 : 0; }
int APS5_VABI __signbitf_nid_postfix(float x) { return std::signbit(x) ? 1 : 0; }
static std::mutex g_randLock;

View File

@ -0,0 +1,177 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <limits>
#include <mutex>
extern "C" int* APS5_VABI __error_nid_postfix();
namespace {
struct alignas(16) Block {
std::size_t capacity;
Block* next;
void* pointer;
std::size_t used;
};
struct alignas(16) Arena {
Arena* next;
Block* first;
std::uintptr_t end;
};
std::mutex arenaMutex;
Arena* arenas = nullptr;
void Error(int value) { *__error_nid_postfix() = value; }
Arena* Find(void* handle) {
for (auto* arena = arenas; arena; arena = arena->next)
if (arena == handle) return arena;
Error(22);
return nullptr;
}
Block* FindBlock(Arena* arena, const void* pointer) {
if (arena && pointer)
for (auto* block = arena->first; block; block = block->next)
if (block->pointer == pointer) return block;
Error(22);
return nullptr;
}
void* Allocate(Arena* arena, std::size_t size, std::size_t alignment) {
if (!arena) return nullptr;
size = std::max<std::size_t>(size, 1);
for (auto* block = arena->first; block; block = block->next) {
if (block->pointer) continue;
const auto start = reinterpret_cast<std::uintptr_t>(block + 1);
if (start > std::numeric_limits<std::uintptr_t>::max() - (alignment - 1)) continue;
const auto aligned = (start + alignment - 1) & ~(alignment - 1);
const auto padding = aligned - start;
if (padding > block->capacity || size > block->capacity - padding) continue;
auto consumed = padding + size;
// Splitting preserves header alignment and avoids small unusable fragments.
if (consumed <= std::numeric_limits<std::size_t>::max() - 15) {
const auto rounded = (consumed + 15) & ~std::size_t{15};
if (rounded <= block->capacity && block->capacity - rounded >= sizeof(Block) + 16) {
auto* tail = reinterpret_cast<Block*>(start + rounded);
*tail = {block->capacity - rounded - sizeof(Block), block->next, nullptr, 0};
block->capacity = rounded;
block->next = tail;
}
}
block->pointer = reinterpret_cast<void*>(aligned);
block->used = size;
return block->pointer;
}
Error(12);
return nullptr;
}
void Release(Arena* arena, Block* released) {
released->pointer = nullptr;
released->used = 0;
for (auto* block = arena->first; block && block->next;) {
if (!block->pointer && !block->next->pointer) {
block->capacity += sizeof(Block) + block->next->capacity;
block->next = block->next->next;
} else block = block->next;
}
}
}
extern "C" {
void* APS5_VABI sceLibcMspaceCreate_nid_postfix(const char* name, void* base,
std::size_t size, unsigned flags) {
(void)name;
const auto start = reinterpret_cast<std::uintptr_t>(base);
if (!base || (start & 15) || size < sizeof(Arena) + sizeof(Block) + 16 ||
size > std::numeric_limits<std::uintptr_t>::max() - start || flags != 0) {
Error(22);
return nullptr;
}
std::lock_guard lock(arenaMutex);
for (auto* arena = arenas; arena; arena = arena->next) {
if (start < arena->end && reinterpret_cast<std::uintptr_t>(arena) < start + size) {
Error(22);
return nullptr;
}
}
auto* arena = static_cast<Arena*>(base);
auto* block = reinterpret_cast<Block*>(arena + 1);
*block = {size - sizeof(Arena) - sizeof(Block), nullptr, nullptr, 0};
*arena = {arenas, block, start + size};
arenas = arena;
return arena;
}
int APS5_VABI sceLibcMspaceDestroy_nid_postfix(void* handle) {
std::lock_guard lock(arenaMutex);
for (auto** entry = &arenas; *entry; entry = &(*entry)->next) {
if (*entry == handle) {
*entry = (*entry)->next;
return 0;
}
}
Error(22);
return -1;
}
void* APS5_VABI sceLibcMspaceMalloc_nid_postfix(void* handle, std::size_t size) {
std::lock_guard lock(arenaMutex);
return Allocate(Find(handle), size, 16);
}
void APS5_VABI sceLibcMspaceFree_nid_postfix(void* handle, void* pointer) {
if (!pointer) return;
std::lock_guard lock(arenaMutex);
auto* arena = Find(handle);
if (auto* block = FindBlock(arena, pointer)) Release(arena, block);
}
void* APS5_VABI sceLibcMspaceCalloc_nid_postfix(void* handle, std::size_t count, std::size_t size) {
if (size && count > std::numeric_limits<std::size_t>::max() / size) {
Error(12);
return nullptr;
}
std::lock_guard lock(arenaMutex);
void* result = Allocate(Find(handle), count * size, 16);
if (result) std::memset(result, 0, count * size);
return result;
}
void* APS5_VABI sceLibcMspaceRealloc_nid_postfix(void* handle, void* pointer, std::size_t size) {
std::lock_guard lock(arenaMutex);
auto* arena = Find(handle);
if (!pointer) return Allocate(arena, size, 16);
auto* block = FindBlock(arena, pointer);
if (!block) return nullptr;
if (!size) { Release(arena, block); return nullptr; }
const auto padding = reinterpret_cast<std::uintptr_t>(pointer) - reinterpret_cast<std::uintptr_t>(block + 1);
if (size <= block->capacity - padding) { block->used = size; return pointer; }
void* result = Allocate(arena, size, 16);
if (result) {
std::memcpy(result, pointer, block->used);
Release(arena, block);
}
return result;
}
int APS5_VABI sceLibcMspacePosixMemalign_nid_postfix(void* handle, void** result,
std::size_t alignment, std::size_t size) {
if (!result || alignment < sizeof(void*) || (alignment & (alignment - 1))) return 22;
std::lock_guard lock(arenaMutex);
auto* arena = Find(handle);
if (!arena) return 22;
void* pointer = Allocate(arena, size, std::max<std::size_t>(alignment, 16));
if (!pointer) return 12;
*result = pointer;
return 0;
}
std::size_t APS5_VABI sceLibcMspaceMallocUsableSize_nid_postfix(const void* pointer) {
if (!pointer) return 0;
std::lock_guard lock(arenaMutex);
for (auto* arena = arenas; arena; arena = arena->next)
for (auto* block = arena->first; block; block = block->next)
if (block->pointer == pointer) return block->used;
Error(22);
return 0;
}
}

View File

@ -58,14 +58,27 @@ extern "C" void LibcRunShutdown_nid_postfix() {
extern "C" {
[[noreturn]] void APS5_VABI _Exit_nid_postfix(int code) {
static const bool trace = std::getenv("APS5_TRACE_EXIT") != nullptr;
if (trace) {
std::fprintf(stderr, "[libc] _Exit(%d) called from %p\n", code, __builtin_return_address(0));
std::fflush(stderr);
}
std::_Exit(code);
}
[[noreturn]] void LibcExit_nid_no_patch(int code) {
LibcRunShutdown_nid_postfix();
std::exit(code);
}
void APS5_VABI exit_nid_postfix(int code) {
static const bool trace = std::getenv("APS5_TRACE_EXIT") != nullptr;
if (trace) {
std::fprintf(stderr, "[libc] exit(%d) called from %p\n", code, __builtin_return_address(0));
std::fflush(stderr);
}
LibcRunShutdown_nid_postfix();
std::exit(code);
LibcExit_nid_no_patch(code);
}
[[noreturn]] void abort_nid_postfix(
@ -98,5 +111,3 @@ int APS5_VABI atexit_nid_postfix(atexit_func_t func) {
}
}
#include "prx/libc/src/specifics/linux/PosixProcess.cpp"

View File

@ -0,0 +1,68 @@
#include "prx/libc/include/FileStream.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#ifdef _WIN32
#include <io.h>
#else
#include <unistd.h>
#endif
namespace { FileStream input{stdin}; }
extern "C" {
FileStream* __stdinp_nid_postfix = &input;
FileStream* __stdoutp_nid_postfix = &_Stdout_nid_postfix;
FileStream* __stderrp_nid_postfix = &_Stderr_nid_postfix;
int __isthreaded_nid_postfix = 1;
int APS5_VABI fgetc_nid_postfix(FileStream* stream) {
const int result = std::fgetc(GetNativeStream(stream));
stream->SyncStatus();
return result;
}
int APS5_VABI getc_nid_postfix(FileStream* stream) { return fgetc_nid_postfix(stream); }
int APS5_VABI __srget_nid_postfix(FileStream* stream) { return fgetc_nid_postfix(stream); }
int APS5_VABI getchar_nid_postfix() { return fgetc_nid_postfix(__stdinp_nid_postfix); }
int APS5_VABI fputc_nid_postfix(int value, FileStream* stream) {
const int result = std::fputc(value, GetNativeStream(stream));
stream->SyncStatus();
return result;
}
int APS5_VABI putc_nid_postfix(int value, FileStream* stream) { return fputc_nid_postfix(value, stream); }
int APS5_VABI __swbuf_nid_postfix(int value, FileStream* stream) { return fputc_nid_postfix(value, stream); }
int APS5_VABI putchar_nid_postfix(int value) { return fputc_nid_postfix(value, __stdoutp_nid_postfix); }
int APS5_VABI ungetc_nid_postfix(int value, FileStream* stream) {
const int result = std::ungetc(value, GetNativeStream(stream));
stream->SyncStatus();
return result;
}
char* APS5_VABI fgets_nid_postfix(char* buffer, int size, FileStream* stream) {
auto* result = std::fgets(buffer, size, GetNativeStream(stream));
stream->SyncStatus();
return result;
}
int APS5_VABI feof_nid_postfix(FileStream* stream) {
stream->SyncStatus();
return (stream->GuestState().flags & 0x20) != 0;
}
int APS5_VABI ferror_nid_postfix(FileStream* stream) {
stream->SyncStatus();
return (stream->GuestState().flags & 0x40) != 0;
}
void APS5_VABI clearerr_nid_postfix(FileStream* stream) {
std::clearerr(GetNativeStream(stream));
stream->SyncStatus();
}
int APS5_VABI fileno_nid_postfix(FileStream* stream) {
#ifdef _WIN32
const int descriptor = _fileno(GetNativeStream(stream));
#else
const int descriptor = ::fileno(GetNativeStream(stream));
#endif
stream->GuestState().descriptor = static_cast<std::int16_t>(descriptor);
return descriptor;
}
int APS5_VABI setvbuf_nid_postfix(FileStream* stream, char* buffer, int mode, std::size_t size) {
if (mode < 0 || mode > 2) return -1;
const int native = mode == 0 ? _IOFBF : mode == 1 ? _IOLBF : _IONBF;
return std::setvbuf(GetNativeStream(stream), buffer, native, size);
}
}

View File

@ -1,11 +1,8 @@
#include <cerrno>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <string>
#include "prx/libc/include/FileStream.hpp"
#include "prx/libc/include/GuestHeap.hpp"
@ -18,100 +15,16 @@
namespace {
using GuestCompare = int (APS5_VABI*)(const void*, const void*);
using GuestNewHandler = void (APS5_VABI*)();
GuestNewHandler g_newHandler = nullptr;
#ifdef _WIN32
// Formats with a guest (SysV) va_list into a host string.
std::string FormatGuest(const char* format, const void* args) {
std::string text;
LibcDetail::FormatWindows(nullptr, 0, format, args, &text);
return text;
}
#endif
}
extern "C" {
void* APS5_VABI bsearch_nid_postfix(const void* key, const void* base, size_t count, size_t size, GuestCompare compare) {
const auto* bytes = static_cast<const unsigned char*>(base);
size_t low = 0;
size_t high = count;
while (low < high) {
const size_t middle = low + (high - low) / 2;
const void* element = bytes + middle * size;
const int order = compare(key, element);
if (order == 0) return const_cast<void*>(element);
if (order < 0) high = middle;
else low = middle + 1;
}
return nullptr;
}
void APS5_VABI clearerr_nid_postfix(FileStream* stream) {
std::clearerr(GetNativeStream(stream));
}
int APS5_VABI feof_nid_postfix(FileStream* stream) {
return std::feof(GetNativeStream(stream));
}
int APS5_VABI ferror_nid_postfix(FileStream* stream) {
return std::ferror(GetNativeStream(stream));
}
int APS5_VABI fputc_nid_postfix(int character, FileStream* stream) {
return std::fputc(character, GetNativeStream(stream));
}
int APS5_VABI getc_nid_postfix(FileStream* stream) {
return std::getc(GetNativeStream(stream));
}
int APS5_VABI ungetc_nid_postfix(int character, FileStream* stream) {
return std::ungetc(character, GetNativeStream(stream));
}
char* APS5_VABI fgets_nid_postfix(char* buffer, int count, FileStream* stream) {
return std::fgets(buffer, count, GetNativeStream(stream));
}
int APS5_VABI putchar_nid_postfix(int character) {
return std::putchar(character);
}
void APS5_VABI perror_nid_postfix(const char* prefix) {
if (prefix && *prefix) std::fprintf(stderr, "%s: %s\n", prefix, std::strerror(errno));
else std::fprintf(stderr, "%s\n", std::strerror(errno));
}
int APS5_VABI remove_nid_postfix(const char* path) {
std::error_code error;
return std::filesystem::remove(ResolvePath_nid_no_patch(path), error) ? 0 : -1;
}
// FreeBSD and the host CRT agree on the classic errno values the messages are looked up by.
char* APS5_VABI strerror_nid_postfix(int error) {
return std::strerror(error);
}
#ifdef _WIN32
int APS5_VABI fprintf_nid_postfix(FileStream* stream, const char* format, ...) {
__builtin_sysv_va_list args;
__builtin_sysv_va_start(args, format);
const auto text = FormatGuest(format, args);
__builtin_sysv_va_end(args);
return static_cast<int>(std::fwrite(text.data(), 1, text.size(), GetNativeStream(stream)));
}
int APS5_VABI vsprintf_nid_postfix(char* buffer, const char* format, VaList* args) {
return LibcDetail::FormatWindows(buffer, SIZE_MAX, format, args);
}
int APS5_VABI vsprintf_s_nid_postfix(char* buffer, size_t size, const char* format, VaList* args) {
return LibcDetail::FormatWindows(buffer, size, format, args);
}
@ -132,13 +45,6 @@ int APS5_VABI sprintf_s_nid_postfix(char* buffer, size_t size, const char* forma
std::abort();
}
[[noreturn]] void APS5_VABI _Exit_nid_postfix(int status) {
static const bool trace = std::getenv("APS5_TRACE_EXIT") != nullptr;
if (trace) std::fprintf(stderr, "[libc] _Exit(%d) called from %p\n", status, __builtin_return_address(0));
std::fflush(nullptr);
std::_Exit(status);
}
// Only the "C" locale exists.
const char* APS5_VABI setlocale_nid_postfix(int category, const char* locale) {
(void)category;

View File

@ -0,0 +1,72 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstddef>
#include <cstring>
#include <cerrno>
#include <string_view>
extern "C" {
char* APS5_VABI basename_nid_postfix(const char* path) {
thread_local char buffer[1024];
std::string_view name = path && *path ? path : ".";
while (name.size() > 1 && name.back() == '/') name.remove_suffix(1);
if (name != "/") {
const auto separator = name.find_last_of('/');
if (separator != std::string_view::npos) name.remove_prefix(separator + 1);
}
if (name.size() >= sizeof(buffer)) { errno = 63; return nullptr; }
std::memcpy(buffer, name.data(), name.size());
buffer[name.size()] = '\0';
return buffer;
}
std::size_t APS5_VABI strnlen_nid_postfix(const char* text, std::size_t limit) {
std::size_t length = 0;
while (length < limit && text[length] != '\0') ++length;
return length;
}
char* APS5_VABI strncat_nid_postfix(char* destination, const char* source, std::size_t limit) {
return std::strncat(destination, source, limit);
}
char* APS5_VABI strpbrk_nid_postfix(const char* text, const char* accept) {
return const_cast<char*>(std::strpbrk(text, accept));
}
std::size_t APS5_VABI strcspn_nid_postfix(const char* text, const char* reject) {
return std::strcspn(text, reject);
}
std::size_t APS5_VABI strlcat_nid_postfix(char* destination, const char* source, std::size_t capacity) {
const auto destinationLength = strnlen_nid_postfix(destination, capacity);
const auto sourceLength = std::strlen(source);
if (destinationLength < capacity) {
const auto remaining = capacity - destinationLength - 1;
const auto count = sourceLength < remaining ? sourceLength : remaining;
std::memcpy(destination + destinationLength, source, count);
destination[destinationLength + count] = '\0';
}
return destinationLength + sourceLength;
}
char* APS5_VABI strtok_r_nid_postfix(char* text, const char* delimiters, char** state) {
if (text == nullptr) text = *state;
if (text == nullptr) return nullptr;
text += std::strspn(text, delimiters);
if (*text == '\0') {
*state = text;
return nullptr;
}
char* end = text + std::strcspn(text, delimiters);
if (*end != '\0') *end++ = '\0';
*state = end;
return text;
}
char* APS5_VABI strtok_nid_postfix(char* text, const char* delimiters) {
thread_local char* state = nullptr;
return strtok_r_nid_postfix(text, delimiters, &state);
}
}

View File

@ -97,6 +97,13 @@ double APS5_VABI strtod_nid_postfix(const char* str, char** endptr) {
return std::strtod(str, endptr);
}
double APS5_VABI atof_nid_postfix(const char* str) { return std::atof(str); }
float APS5_VABI strtof_nid_postfix(const char* str, char** endptr) { return std::strtof(str, endptr); }
long double APS5_VABI strtold_nid_postfix(const char* str, char** endptr) {
static_assert(sizeof(long double) == 16, "Guest long double requires x87 extended precision storage");
return std::strtold(str, endptr);
}
int APS5_VABI atoi_nid_postfix(const char* str) {
return std::atoi(str);
}
@ -158,18 +165,6 @@ size_t APS5_VABI strspn_nid_postfix(const char* s, const char* accept) {
return std::strspn(s, accept);
}
size_t APS5_VABI strcspn_nid_postfix(const char* s, const char* reject) {
return std::strcspn(s, reject);
}
const char* APS5_VABI strpbrk_nid_postfix(const char* s, const char* accept) {
return std::strpbrk(s, accept);
}
char* APS5_VABI strncat_nid_postfix(char* dest, const char* src, size_t n) {
return std::strncat(dest, src, n);
}
int APS5_VABI strcoll_nid_postfix(const char* s1, const char* s2) {
return std::strcmp(s1, s2);
}
@ -193,14 +188,6 @@ int APS5_VABI strncpy_s_nid_postfix(char* dest, size_t destsz, const char* src,
return 0;
}
float APS5_VABI strtof_nid_postfix(const char* str, char** endptr) {
return std::strtof(str, endptr);
}
double APS5_VABI atof_nid_postfix(const char* str) {
return std::atof(str);
}
unsigned long long APS5_VABI _Stoull_nid_postfix(const char* str, char** endptr, int base) {
return std::strtoull(str, endptr, base);
}

View File

@ -20,9 +20,25 @@ GCC_GLOBAL_ALIAS(__cxa_end_catch_nid_no_patch_cut, __cxa_end_catch_nid_postfix);
GCC_GLOBAL_ALIAS(__cxa_rethrow_nid_no_patch_cut, __cxa_rethrow_nid_postfix);
GCC_GLOBAL_ALIAS(__gxx_personality_v0_nid_no_patch_cut, __gxx_personality_v0_nid_postfix);
GCC_GLOBAL_ALIAS(_Unwind_Resume_nid_no_patch_cut, _Unwind_Resume_nid_postfix);
GCC_LOCAL_ALIAS(__cxa_free_exception, __cxa_free_exception_nid_postfix);
GCC_LOCAL_ALIAS(_ZSt9terminatev, _ZSt9terminatev_nid_postfix);
GCC_LOCAL_ALIAS(__cxa_call_terminate, __cxa_call_terminate_nid_postfix);
GCC_GLOBAL_ALIAS(__cxa_free_exception, __cxa_free_exception_nid_postfix);
GCC_GLOBAL_ALIAS(_ZSt9terminatev, _ZSt9terminatev_nid_postfix);
GCC_GLOBAL_ALIAS(__cxa_call_terminate, __cxa_call_terminate_nid_postfix);
GCC_GLOBAL_ALIAS(__cxa_allocate_exception, __cxa_allocate_exception_nid_postfix);
GCC_GLOBAL_ALIAS(__cxa_throw, __cxa_throw_nid_postfix);
GCC_GLOBAL_ALIAS(__cxa_init_primary_exception, __cxa_init_primary_exception_nid_postfix);
GCC_GLOBAL_ALIAS(__cxa_begin_catch, __cxa_begin_catch_nid_postfix);
GCC_GLOBAL_ALIAS(__cxa_end_catch, __cxa_end_catch_nid_postfix);
GCC_GLOBAL_ALIAS(__cxa_rethrow, __cxa_rethrow_nid_postfix);
GCC_GLOBAL_ALIAS(__cxa_get_exception_ptr, __cxa_get_exception_ptr_nid_postfix);
GCC_GLOBAL_ALIAS(__cxa_current_exception_type, __cxa_current_exception_type_nid_postfix);
GCC_GLOBAL_ALIAS(_ZSt13set_terminatePFvvE, _ZSt13set_terminatePFvvE_nid_postfix);
GCC_GLOBAL_ALIAS(_ZSt13get_terminatev, _ZSt13get_terminatev_nid_postfix);
GCC_GLOBAL_ALIAS(_ZSt18uncaught_exceptionv, _ZSt18uncaught_exceptionv_nid_postfix);
GCC_GLOBAL_ALIAS(_ZSt19uncaught_exceptionsv, _ZSt19uncaught_exceptionsv_nid_postfix);
GCC_GLOBAL_ALIAS(_Unwind_Resume, _Unwind_Resume_nid_postfix);
GCC_GLOBAL_ALIAS(_Unwind_RaiseException, _Unwind_RaiseException_nid_postfix);
GCC_GLOBAL_ALIAS(_Unwind_Resume_or_Rethrow, _Unwind_Resume_or_Rethrow_nid_postfix);
GCC_GLOBAL_ALIAS(_Unwind_DeleteException, _Unwind_DeleteException_nid_postfix);
#ifdef _WIN32
extern "C" {

View File

@ -246,6 +246,18 @@ void APS5_VABI __cxa_free_exception_nid_postfix(void* object) {
InvokeTerminate(header->terminate);
}
LibcException::Header* __cxa_init_primary_exception_nid_postfix(void* object, std::type_info* type, void (*destructor)(void*)) {
using namespace LibcException;
auto* header = FromObject(object);
header->type = type;
header->destructor = destructor;
header->terminate = terminateHandler.load(std::memory_order_acquire);
header->adjusted = object;
header->unwind.exception_class = PrimaryClass;
header->unwind.exception_cleanup = Cleanup;
return header;
}
void* APS5_VABI __cxa_begin_catch_nid_postfix(void* exception) {
using namespace LibcException;
auto* unwind = static_cast<_Unwind_Exception*>(exception);

View File

@ -8,7 +8,7 @@
#if defined(__linux__) || defined(_WIN32)
#ifdef _WIN32
#if defined(_WIN32) || defined(__linux__)
extern "C" _Unwind_Reason_Code __gxx_personality_v0(int, _Unwind_Action, std::uint64_t, _Unwind_Exception*, _Unwind_Context*);
#endif
@ -23,7 +23,7 @@ bool OwnPersonality(Word personality) {
}
#endif
if (personality == reinterpret_cast<Word>(__gxx_personality_v0_nid_postfix)) return true;
#ifdef _WIN32
#if defined(_WIN32) || defined(__linux__)
if (personality == reinterpret_cast<Word>(__gxx_personality_v0)) return true;
#endif
return false;

View File

@ -1,25 +0,0 @@
#include <cstdlib>
#include <cerrno>
#include <cstring>
extern "C" {
int APS5_VABI setenv_nid_postfix(const char* name, const char* value, int overwrite) {
if (overwrite == 0 && std::getenv(name) != nullptr) {
return 0;
}
const size_t nameLength = std::strlen(name);
const size_t valueLength = std::strlen(value);
char* entry = static_cast<char*>(std::malloc(nameLength + valueLength + 2));
if (entry == nullptr) {
errno = ENOMEM;
return -1;
}
std::memcpy(entry, name, nameLength);
entry[nameLength] = '=';
std::memcpy(entry + nameLength + 1, value, valueLength);
entry[nameLength + 1 + valueLength] = '\0';
return ::putenv(entry);
}
}

View File

@ -9,6 +9,7 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL
${CMAKE_CURRENT_SOURCE_DIR}/Semaphore/src/Semaphore.cpp
${CMAKE_CURRENT_SOURCE_DIR}/DirectMemory/Export.cpp
${CMAKE_CURRENT_SOURCE_DIR}/DirectMemory/DirectMemory.cpp
${CMAKE_CURRENT_SOURCE_DIR}/DirectMemory/PosixMemory.cpp
${CMAKE_CURRENT_SOURCE_DIR}/DirectMemory/MemoryPool.cpp
${CMAKE_CURRENT_SOURCE_DIR}/DirectMemory/MemoryPoolExport.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Pthread/src/Mutex.cpp
@ -23,17 +24,25 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL
${CMAKE_CURRENT_SOURCE_DIR}/Pthread/Posix/Mutex.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Pthread/Posix/Rwlock.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Pthread/Posix/Thread.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Pthread/Posix/Once.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Pthread/Posix/Tsd.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Pthread/Posix/Sem.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Socket/src/Socket.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Socket/src/Udp.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Socket/src/AddressConversion.cpp
${CMAKE_CURRENT_SOURCE_DIR}/EventFlag/src/EventFlag.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Module/src/Module.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Module/src/DynamicLoader.cpp
${LIBS_INCLUDE_DIR}/nid/src/NidCompute.cpp
${LIBS_INCLUDE_DIR}/nid/src/Sha1.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Aio/src/Aio.cpp
${CMAKE_CURRENT_SOURCE_DIR}/System/src/Coredump.cpp
${CMAKE_CURRENT_SOURCE_DIR}/System/src/Exception.cpp
${CMAKE_CURRENT_SOURCE_DIR}/System/src/Sanitizer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/System/src/Rtld.cpp
${CMAKE_CURRENT_SOURCE_DIR}/System/src/Process.cpp
${CMAKE_CURRENT_SOURCE_DIR}/System/src/SystemConfiguration.cpp
${CMAKE_CURRENT_SOURCE_DIR}/System/src/Signals.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Time/Time.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Time/TimedWait.cpp
${CMAKE_CURRENT_SOURCE_DIR}/AppMetadata/src/AppMetadata.cpp
@ -41,8 +50,11 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL
)
target_include_directories(libkernel PRIVATE ${LIBS_INCLUDE_DIR})
target_include_directories(libkernel PRIVATE ${LIBS_INCLUDE_DIR}/nid/include)
target_link_libraries(libkernel PRIVATE ${CMAKE_DL_LIBS})
target_link_libraries(libkernel PRIVATE libc)
if(WIN32)
target_link_libraries(libkernel PRIVATE ws2_32)
target_compile_definitions(libkernel PRIVATE _WIN32_WINNT=0x0602)
endif()
configure_windows_unwind(libkernel)

View File

@ -159,9 +159,9 @@ int LinuxProtFromSce(int prot) {
throw std::invalid_argument("Unsupported memory protection bits: " + std::to_string(prot));
}
int result = PROT_NONE;
if (prot & 1) result |= PROT_READ;
if (prot & 2) result |= PROT_WRITE;
if (prot & 4) result |= PROT_EXEC;
if (prot & 0x13) result |= PROT_READ;
if (prot & 0x22) result |= PROT_READ | PROT_WRITE;
if (prot & 4) result |= PROT_READ | PROT_EXEC;
return result;
}
@ -320,6 +320,8 @@ int DoMprotect(const void* addr, size_t len, int prot) {
if (memory.AllocationBase != GetModuleHandleW(nullptr)) throw std::invalid_argument("Memory protection of a foreign image is not supported");
mutation.RegisterMainImage();
}
#else
mutation.RegisterMainImage();
#endif
mutation.Protect(pointer, bytes, (prot & 3) != 0, (prot & 2) != 0, [&] {
if (mprotect(const_cast<void*>(pointer), bytes, nativeProtection) != 0) throw std::system_error(errno, std::generic_category(), "mprotect failed");

View File

@ -315,3 +315,14 @@ int APS5_VABI sceKernelBatchMap2(KernelBatchMapEntry* entries, int num_entries,
}
}
extern "C" {
int APS5_VABI sceKernelMlock_nid_postfix(void* address, std::uint64_t length) {
(void)address;
(void)length;
NotImplemented_nid_no_patch(__func__);
return 0;
}
}

View File

@ -0,0 +1,81 @@
#include "DirectMemory.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <limits>
#include <new>
#include <stdexcept>
extern "C" int* APS5_VABI __error_nid_postfix();
namespace {
// FreeBSD/PS5 ABI values, independent of the host's errno and mmap constants.
constexpr int GuestInvalid = 22;
constexpr int GuestNoMemory = 12;
constexpr int GuestNotSupported = 45;
constexpr int GuestPrivate = 0x2;
constexpr int GuestAnonymous = 0x1000;
bool RoundLength(std::size_t length, std::size_t& rounded) {
constexpr auto mask = PS5_PAGE_SIZE - 1;
if (length == 0 || length > std::numeric_limits<std::size_t>::max() - mask)
return false;
rounded = (length + mask) & ~mask;
return true;
}
void SetError(int error) {
*__error_nid_postfix() = error;
}
}
extern "C" {
void* APS5_VABI mmap_nid_postfix(void* address, std::size_t length, int protection,
int flags, int descriptor, std::int64_t offset) noexcept {
const auto failed = [](int error) -> void* {
SetError(error);
return reinterpret_cast<void*>(static_cast<std::uintptr_t>(-1));
};
std::size_t rounded;
if (!RoundLength(length, rounded) || (protection & ~7) != 0)
return failed(GuestInvalid);
if (flags != (GuestPrivate | GuestAnonymous))
return failed(GuestNotSupported);
if (descriptor != -1 || offset != 0)
return failed(GuestInvalid);
// A non-fixed address is a hint; the kernel may choose another address.
(void)address;
void* mapped = nullptr;
try {
const auto result = DoMapAnon(&mapped, rounded, protection, 0);
if (result != 0) return failed(GuestNoMemory);
return mapped;
} catch (const std::bad_alloc&) {
return failed(GuestNoMemory);
} catch (const std::exception&) {
return failed(GuestNoMemory);
}
}
int APS5_VABI munmap_nid_postfix(void* address, std::size_t length) noexcept {
const auto failed = [](int error) {
SetError(error);
return -1;
};
std::size_t rounded;
const auto start = reinterpret_cast<std::uintptr_t>(address);
if (!RoundLength(length, rounded) || start == 0 ||
(start & (PS5_PAGE_SIZE - 1)) != 0 ||
rounded > std::numeric_limits<std::uintptr_t>::max() - start)
return failed(GuestInvalid);
try {
if (DoMunmap(address, rounded) != 0) return failed(GuestInvalid);
return 0;
} catch (const std::bad_alloc&) {
return failed(GuestNoMemory);
} catch (const std::exception&) {
// The allocation tracker rejects foreign, pinned, and noncontiguous ranges.
return failed(GuestInvalid);
}
}
}

View File

@ -7,6 +7,7 @@
namespace File {
void FillFileStat(const std::filesystem::path& nativePath, FileStat* sb);
void FillFileStat(int nativeDescriptor, FileStat* sb);
bool FillFileStatFromDescriptor(int fd, FileStat* sb);
}

View File

@ -24,26 +24,7 @@ static int DoFstat(int fd, NativeStat* st) {
}
#endif
namespace File {
static void FillFromNative(const NativeStat& st, FileStat* sb);
void FillFileStat(const std::filesystem::path& nativePath, FileStat* sb) {
NativeStat st{};
if (DoStat(nativePath, &st) != 0) {
throw std::runtime_error(std::string("FillFileStat: stat failed for ") + nativePath.string());
}
FillFromNative(st, sb);
}
bool FillFileStatFromDescriptor(int fd, FileStat* sb) {
NativeStat st{};
if (DoFstat(fd, &st) != 0) return false;
FillFromNative(st, sb);
return true;
}
static void FillFromNative(const NativeStat& st, FileStat* sb) {
static void CopyNativeStat(const NativeStat& st, FileStat* sb) {
*sb = FileStat{};
sb->st_mode = static_cast<std::uint16_t>(st.st_mode);
sb->st_size = static_cast<std::int64_t>(st.st_size);
@ -91,4 +72,29 @@ static void FillFromNative(const NativeStat& st, FileStat* sb) {
#endif
}
namespace File {
void FillFileStat(const std::filesystem::path& nativePath, FileStat* sb) {
NativeStat st{};
if (DoStat(nativePath, &st) != 0) {
throw std::runtime_error(std::string("FillFileStat: stat failed for ") + nativePath.string());
}
CopyNativeStat(st, sb);
}
void FillFileStat(int nativeDescriptor, FileStat* sb) {
NativeStat st{};
if (DoFstat(nativeDescriptor, &st) != 0) {
throw std::runtime_error(std::string("FillFileStat: fstat failed for fd ") + std::to_string(nativeDescriptor));
}
CopyNativeStat(st, sb);
}
bool FillFileStatFromDescriptor(int fd, FileStat* sb) {
NativeStat st{};
if (DoFstat(fd, &st) != 0) return false;
CopyNativeStat(st, sb);
return true;
}
}

View File

@ -1,11 +1,105 @@
#include <cstdint>
#include <cstddef>
#include <limits>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libkernel/File/include/File.hpp"
#include "prx/libkernel/File/include/FileFlags.hpp"
#include "prx/libkernel/File/include/NativeStat.hpp"
#include "prx/libkernel/Socket/include/SocketRuntime.hpp"
#include <cerrno>
#include <cstdarg>
#include <filesystem>
#include <stdexcept>
#include <string>
#ifdef _WIN32
#include <windows.h>
#include <io.h>
#include <direct.h>
#include <sys/stat.h>
static int NativeRmdir(const std::filesystem::path& path) {
return ::_wrmdir(path.wstring().c_str());
}
static int NativeMkdir(const std::filesystem::path& path, std::uint16_t mode) {
(void)mode;
return ::_wmkdir(path.wstring().c_str());
}
static int NativeChmod(const std::filesystem::path& path, int mode) {
return ::_wchmod(path.wstring().c_str(), mode);
}
static int NativeFtruncate(int descriptor, std::int64_t length) {
return static_cast<int>(::_chsize_s(descriptor, length));
}
static int NativeFlock(int descriptor, int operation) {
HANDLE handle = reinterpret_cast<HANDLE>(::_get_osfhandle(descriptor));
if (handle == INVALID_HANDLE_VALUE) {
return -1;
}
OVERLAPPED overlapped{};
if (operation & 8) {
return ::UnlockFileEx(handle, 0, MAXDWORD, MAXDWORD, &overlapped) ? 0 : -1;
}
DWORD flags = 0;
if (operation & 2) flags |= LOCKFILE_EXCLUSIVE_LOCK;
if (operation & 4) flags |= LOCKFILE_FAIL_IMMEDIATELY;
return ::LockFileEx(handle, flags, 0, MAXDWORD, MAXDWORD, &overlapped) ? 0 : -1;
}
static std::int64_t NativePread(int descriptor, void* buf, std::size_t nbytes, std::int64_t offset) {
if (nbytes > static_cast<std::size_t>(std::numeric_limits<unsigned int>::max())) {
throw std::runtime_error("NativePread: nbytes exceeds platform limit");
}
std::int64_t saved = ::_lseeki64(descriptor, 0, SEEK_CUR);
if (saved < 0) {
return -1;
}
if (::_lseeki64(descriptor, offset, SEEK_SET) < 0) {
return -1;
}
int n = ::_read(descriptor, buf, static_cast<unsigned int>(nbytes));
::_lseeki64(descriptor, saved, SEEK_SET);
return n;
}
static std::int64_t NativePwrite(int descriptor, const void* buf, std::size_t nbytes, std::int64_t offset) {
if (nbytes > static_cast<std::size_t>(std::numeric_limits<unsigned int>::max())) {
throw std::runtime_error("NativePwrite: nbytes exceeds platform limit");
}
std::int64_t saved = ::_lseeki64(descriptor, 0, SEEK_CUR);
if (saved < 0) {
return -1;
}
if (::_lseeki64(descriptor, offset, SEEK_SET) < 0) {
return -1;
}
int n = ::_write(descriptor, buf, static_cast<unsigned int>(nbytes));
::_lseeki64(descriptor, saved, SEEK_SET);
return n;
}
#else
#include <unistd.h>
#include <sys/stat.h>
#include <sys/file.h>
static int NativeRmdir(const std::filesystem::path& path) {
return ::rmdir(path.c_str());
}
static int NativeMkdir(const std::filesystem::path& path, std::uint16_t mode) {
return ::mkdir(path.c_str(), static_cast<mode_t>(mode));
}
static int NativeChmod(const std::filesystem::path& path, int mode) {
return ::chmod(path.c_str(), static_cast<mode_t>(mode));
}
static int NativeFtruncate(int descriptor, std::int64_t length) {
return ::ftruncate(descriptor, static_cast<off_t>(length));
}
static int NativeFlock(int descriptor, int operation) {
return ::flock(descriptor, operation);
}
static std::int64_t NativePread(int descriptor, void* buf, std::size_t nbytes, std::int64_t offset) {
return static_cast<std::int64_t>(::pread(descriptor, buf, nbytes, static_cast<off_t>(offset)));
}
static std::int64_t NativePwrite(int descriptor, const void* buf, std::size_t nbytes, std::int64_t offset) {
return static_cast<std::int64_t>(::pwrite(descriptor, buf, nbytes, static_cast<off_t>(offset)));
}
#endif
static constexpr int GUEST_ENOENT = 2;
static constexpr int GUEST_EIO = 5;
@ -20,101 +114,155 @@ static int SceErrorFromErrno(int error) {
extern "C" {
int APS5_VABI chmod_nid_postfix(const char* path, int mode) {
(void)path;
(void)mode;
NotImplemented_nid_no_patch(__func__);
return 0;
if (path == nullptr) {
APS5_INVALID_ARG_EX;
}
auto native = ResolvePath_nid_no_patch(path);
if (NativeChmod(native, mode) != 0) {
throw std::runtime_error(std::string(__func__) + ": chmod failed for " + native.string() + ", errno=" + std::to_string(errno));
}
return 0;
}
int APS5_VABI close_nid_postfix(int d) {
(void)d;
NotImplemented_nid_no_patch(__func__);
return 0;
if (d >= GuestSockets::FirstDescriptor) return GuestSockets::Close(d);
#ifdef _WIN32
return _close(d);
#else
return ::close(d);
#endif
}
int APS5_VABI _close_nid_postfix(int descriptor) {
return close_nid_postfix(descriptor);
}
int APS5_VABI flock_nid_postfix(int d, int operation) {
(void)d;
(void)operation;
NotImplemented_nid_no_patch(__func__);
return 0;
if (NativeFlock(d, operation) != 0) {
#ifdef _WIN32
throw std::runtime_error(std::string(__func__) + ": flock failed, fd=" + std::to_string(d) + ", error=" + std::to_string(::GetLastError()));
#else
throw std::runtime_error(std::string(__func__) + ": flock failed, fd=" + std::to_string(d) + ", errno=" + std::to_string(errno));
#endif
}
return 0;
}
int64_t APS5_VABI fstat_nid_disambig1_nid_postfix(int d, FileStat* sb) {
(void)d;
(void)sb;
NotImplemented_nid_no_patch(__func__);
return 0;
if (sb == nullptr) {
APS5_INVALID_ARG_EX;
}
File::FillFileStat(d, sb);
return 0;
}
int APS5_VABI ftruncate_nid_postfix(int d, int64_t length) {
(void)d;
(void)length;
NotImplemented_nid_no_patch(__func__);
return 0;
if (length < 0) {
APS5_INVALID_ARG_EX;
}
#ifdef _WIN32
int error = NativeFtruncate(d, length);
if (error != 0) {
throw std::runtime_error(std::string(__func__) + ": ftruncate failed, fd=" + std::to_string(d) + ", error=" + std::to_string(error));
}
#else
if (NativeFtruncate(d, length) != 0) {
throw std::runtime_error(std::string(__func__) + ": ftruncate failed, fd=" + std::to_string(d) + ", errno=" + std::to_string(errno));
}
#endif
return 0;
}
int64_t APS5_VABI lseek_nid_postfix(int d, int64_t offset, int whence) {
(void)d;
(void)offset;
(void)whence;
NotImplemented_nid_no_patch(__func__);
return 0;
return static_cast<int64_t>(sceKernelLseek(d, offset, whence));
}
int APS5_VABI mkdir_nid_postfix(const char* path, uint16_t mode) {
(void)path;
(void)mode;
NotImplemented_nid_no_patch(__func__);
return 0;
if (path == nullptr) {
APS5_INVALID_ARG_EX;
}
auto native = ResolvePath_nid_no_patch(path);
if (NativeMkdir(native, mode) != 0) {
throw std::runtime_error(std::string(__func__) + ": mkdir failed for " + native.string() + ", errno=" + std::to_string(errno));
}
return 0;
}
int APS5_VABI open_nid_postfix(const char* path, int flags, int mode) {
(void)path;
(void)flags;
(void)mode;
NotImplemented_nid_no_patch(__func__);
return 0;
return sceKernelOpen(path, flags, static_cast<std::uint16_t>(mode));
}
int APS5_VABI _open_nid_postfix(const char* path, int flags, ...) {
std::uint16_t mode = 0;
if (flags & SCE_KERNEL_O_CREAT) {
#ifdef _WIN32
__builtin_sysv_va_list arguments;
__builtin_sysv_va_start(arguments, flags);
mode = static_cast<std::uint16_t>(__builtin_va_arg(arguments, int));
__builtin_sysv_va_end(arguments);
#else
std::va_list arguments;
va_start(arguments, flags);
mode = static_cast<std::uint16_t>(va_arg(arguments, int));
va_end(arguments);
#endif
}
return sceKernelOpen(path, flags, mode);
}
int64_t APS5_VABI pread_nid_postfix(int d, void* buf, size_t nbytes, int64_t offset) {
(void)d;
(void)buf;
(void)nbytes;
(void)offset;
NotImplemented_nid_no_patch(__func__);
return 0;
if (buf == nullptr) {
APS5_INVALID_ARG_EX;
}
if (offset < 0) {
APS5_INVALID_ARG_EX;
}
auto n = NativePread(d, buf, nbytes, offset);
if (n < 0) {
throw std::runtime_error(std::string(__func__) + ": pread failed, fd=" + std::to_string(d) + ", errno=" + std::to_string(errno));
}
return n;
}
int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int d, const void* buf, size_t nbytes, int64_t offset) {
(void)d;
(void)buf;
(void)nbytes;
(void)offset;
NotImplemented_nid_no_patch(__func__);
return 0;
if (buf == nullptr) {
APS5_INVALID_ARG_EX;
}
if (offset < 0) {
APS5_INVALID_ARG_EX;
}
auto n = NativePwrite(d, buf, nbytes, offset);
if (n < 0) {
throw std::runtime_error(std::string(__func__) + ": pwrite failed, fd=" + std::to_string(d) + ", errno=" + std::to_string(errno));
}
return n;
}
int64_t APS5_VABI read_nid_postfix(int d, void* buf, uint64_t nbytes) {
(void)d;
(void)buf;
(void)nbytes;
NotImplemented_nid_no_patch(__func__);
return 0;
return sceKernelRead(d, buf, static_cast<size_t>(nbytes));
}
std::int64_t APS5_VABI _read_nid_postfix(int descriptor, void* buffer, std::size_t count) {
return sceKernelRead(descriptor, buffer, count);
}
int64_t APS5_VABI write_nid_postfix(int d, const char* str, int64_t size) {
(void)d;
(void)str;
(void)size;
NotImplemented_nid_no_patch(__func__);
return 0;
if (size < 0) {
APS5_INVALID_ARG_EX;
}
return sceKernelWrite(d, str, static_cast<size_t>(size));
}
std::int64_t APS5_VABI _write_nid_postfix(int descriptor, const void* buffer, std::size_t count) {
return sceKernelWrite(descriptor, buffer, count);
}
int APS5_VABI stat_nid_postfix(const char* path, FileStat* sb) {
(void)path;
(void)sb;
NotImplemented_nid_no_patch(__func__);
return 0;
return sceKernelStat(path, sb);
}
int APS5_VABI unlink_nid_postfix(const char* path) {
return sceKernelUnlink(path);
}
int APS5_VABI sceKernelCheckReachability(const char* path) {
@ -201,4 +349,33 @@ int APS5_VABI sceKernelRmdir(const char* path) {
return 0;
}
int APS5_VABI rmdir_nid_postfix(const char* path) {
return sceKernelRmdir(path);
}
}
extern "C" {
int APS5_VABI sceKernelChmod_nid_postfix(const char* path, std::uint16_t mode) {
(void)path;
(void)mode;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceKernelTruncate_nid_postfix(const char* path, std::int64_t length) {
(void)path;
(void)length;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceKernelUtimes_nid_postfix(const char* path, const KernelTimeval* times) {
(void)path;
(void)times;
NotImplemented_nid_no_patch(__func__);
return 0;
}
}

View File

@ -0,0 +1,126 @@
#include "prx/libc/include/General.hpp"
#include <nid/NidCompute.hpp>
#include <array>
#include <cstdio>
#include <map>
#include <memory>
#include <mutex>
#include <vector>
#ifdef _WIN32
#define NOMINMAX
#include <windows.h>
#else
#include <dlfcn.h>
#endif
namespace {
thread_local std::array<char, 512> loaderError{};
thread_local bool pendingError = false;
void Error(const char* message) {
std::snprintf(loaderError.data(), loaderError.size(), "%s", message);
pendingError = true;
}
struct Module {
void* native = nullptr;
bool owned = true;
bool global = false;
~Module() {
if (owned && native) {
#ifdef _WIN32
FreeLibrary(static_cast<HMODULE>(native));
#else
::dlclose(native);
#endif
}
}
};
std::mutex modulesMutex;
std::map<std::uintptr_t, std::shared_ptr<Module>> modules;
std::uintptr_t nextHandle = 0x20000000;
void* Symbol(Module& module, const char* name) {
#ifdef _WIN32
return reinterpret_cast<void*>(GetProcAddress(static_cast<HMODULE>(module.native), name));
#else
return ::dlsym(module.native, name);
#endif
}
void* FindSymbol(Module& module, const char* name) {
if (auto* symbol = Symbol(module, name)) return symbol;
const auto nid = Nid::ComputeNid(name, "");
return Symbol(module, nid.c_str());
}
}
extern "C" {
char* APS5_VABI dlerror_nid_postfix() {
if (!pendingError) return nullptr;
pendingError = false;
return loaderError.data();
}
void* APS5_VABI dlopen_nid_postfix(const char* path, int flags) {
if ((flags & ~0x103) || (flags & 3) == 0 || (flags & 3) == 3) {
Error("dlopen: unsupported flags"); return nullptr;
}
try {
auto module = std::make_shared<Module>();
module->global = (flags & 0x100) != 0 || !path;
#ifdef _WIN32
if (!path) {
module->native = GetModuleHandleW(nullptr);
module->owned = false;
} else {
if (!*path) { Error("dlopen: empty module path"); return nullptr; }
const auto resolved = ResolvePath_nid_no_patch(path);
module->native = LoadLibraryExW(resolved.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH);
}
if (!module->native) {
char message[128];
std::snprintf(message, sizeof(message), "dlopen: Windows loader error %lu (module must be host-compatible)", GetLastError());
Error(message); return nullptr;
}
#else
const auto resolved = path ? ResolvePath_nid_no_patch(path).string() : std::string{};
const int nativeFlags = ((flags & 3) == 1 ? RTLD_LAZY : RTLD_NOW) |
((flags & 0x100) ? RTLD_GLOBAL : RTLD_LOCAL);
module->native = ::dlopen(path ? resolved.c_str() : nullptr, nativeFlags);
if (!module->native) { Error(::dlerror()); return nullptr; }
#endif
std::lock_guard lock(modulesMutex);
const auto handle = nextHandle++;
modules.emplace(handle, std::move(module));
return reinterpret_cast<void*>(handle);
} catch (const std::exception& error) { Error(error.what()); return nullptr; }
}
void* APS5_VABI dlsym_nid_postfix(void* handle, const char* name) {
if (!name || !*name) { Error("dlsym: empty symbol name"); return nullptr; }
try {
std::vector<std::shared_ptr<Module>> search;
{
std::lock_guard lock(modulesMutex);
if (handle == reinterpret_cast<void*>(static_cast<std::intptr_t>(-2))) {
for (const auto& [key, module] : modules) if (module->global) search.push_back(module);
} else {
auto found = modules.find(reinterpret_cast<std::uintptr_t>(handle));
if (found == modules.end()) { Error("dlsym: invalid or unsupported module handle"); return nullptr; }
search.push_back(found->second);
}
}
for (const auto& module : search) if (auto* result = FindSymbol(*module, name)) return result;
Error("dlsym: symbol not found in supported module scope");
return nullptr;
} catch (const std::exception& error) { Error(error.what()); return nullptr; }
}
int APS5_VABI dlclose_nid_postfix(void* handle) {
std::shared_ptr<Module> module;
{
std::lock_guard lock(modulesMutex);
auto found = modules.find(reinterpret_cast<std::uintptr_t>(handle));
if (found == modules.end()) { Error("dlclose: invalid module handle"); return -1; }
module = std::move(found->second);
modules.erase(found);
}
// Unload outside the registry lock: module destructors may call loader APIs.
module.reset();
return 0;
}
}

View File

@ -52,3 +52,21 @@ int APS5_VABI sceKernelStopUnloadModule(KernelModule handle, size_t args, const
}
}
extern "C" {
int APS5_VABI __elf_phdr_match_addr_nid_postfix(ModuleInfo* module, std::uint64_t address) {
(void)module;
(void)address;
NotImplemented_nid_no_patch(__func__);
return 0;
}
// unknown signature
std::int32_t APS5_VABI sceKernelInternalMemoryGetModuleSegmentInfo_nid_postfix(void* result) {
(void)result;
NotImplemented_nid_no_patch(__func__);
return 0;
}
}

View File

@ -1,75 +1,58 @@
#include <cstdint>
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "../include/Pthread.hpp"
#include "prx/libkernel/Pthread/include/Pthread.hpp"
#include "prx/libkernel/Pthread/include/Cond.hpp"
#include "Common.hpp"
#include <atomic>
#include <cstdint>
#include <stdexcept>
#include <string>
extern "C" {
int APS5_VABI scePthreadCondattrInit(PthreadCondattr* attr);
int APS5_VABI scePthreadCondattrDestroy(PthreadCondattr* attr);
int APS5_VABI scePthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr, const char* name);
int APS5_VABI scePthreadCondSignal(PthreadCond* cond);
int APS5_VABI scePthreadCondBroadcast(PthreadCond* cond);
int APS5_VABI scePthreadCondWait(PthreadCond* cond, PthreadMutex* mutex);
int APS5_VABI scePthreadCondTimedwait(PthreadCond* cond, PthreadMutex* mutex, unsigned int usec);
namespace {
int toPosix(int result) {
if (result == 0)
return 0;
const auto error = static_cast<std::uint32_t>(result);
if ((error & 0xffff0000u) != 0x80020000u)
throw std::runtime_error("Unexpected SCE condition variable error");
return static_cast<int>(error & 0xffffu);
}
static void InitializeStatic(PthreadCond* cond, const char* funcName) {
if (!cond) throw std::runtime_error(std::string(funcName) + ": null cond");
std::atomic_ref<PthreadCond> slot(*cond);
PthreadCond current = slot.load(std::memory_order_acquire);
if (current != nullptr) return;
auto* created = new PthreadCondPrivate();
if (!slot.compare_exchange_strong(current, created, std::memory_order_acq_rel, std::memory_order_acquire))
delete created;
}
extern "C" {
int APS5_VABI pthread_cond_broadcast_nid_postfix(PthreadCond* cond) {
InitializeStatic(cond, __func__);
return PosixThread::ToErrno(scePthreadCondBroadcast(cond));
return toPosix(scePthreadCondBroadcast(cond));
}
int APS5_VABI pthread_cond_init_nid_postfix(PthreadCond* cond, const PthreadCondattr* attr) {
return PosixThread::ToErrno(scePthreadCondInit(cond, attr, nullptr));
return toPosix(scePthreadCondInit(cond, attr, nullptr));
}
int APS5_VABI pthread_cond_signal_nid_postfix(PthreadCond* cond) {
InitializeStatic(cond, __func__);
return PosixThread::ToErrno(scePthreadCondSignal(cond));
return toPosix(scePthreadCondSignal(cond));
}
int APS5_VABI pthread_cond_timedwait_nid_postfix(PthreadCond* cond, PthreadMutex* mutex, const KernelTimespec* abstime) {
InitializeStatic(cond, __func__);
KernelUseconds usec = 0;
if (!PosixThread::RelativeMicroseconds((*cond)->_clockid, abstime, &usec)) return PosixThread::GUEST_EINVAL;
return PosixThread::ToErrno(scePthreadCondTimedwait(cond, mutex, usec));
if (!abstime || abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000) return PosixThread::GUEST_EINVAL;
return toPosix(CondOperations::AbsoluteTimedwait(cond, mutex, abstime));
}
int APS5_VABI pthread_cond_wait_nid_postfix(PthreadCond* cond, PthreadMutex* mutex) {
InitializeStatic(cond, __func__);
return PosixThread::ToErrno(scePthreadCondWait(cond, mutex));
return toPosix(scePthreadCondWait(cond, mutex));
}
int APS5_VABI pthread_condattr_destroy_nid_postfix(PthreadCondattr* attr) {
if (!attr || !*attr) return PosixThread::GUEST_EINVAL;
return PosixThread::ToErrno(scePthreadCondattrDestroy(attr));
return toPosix(scePthreadCondattrDestroy(attr));
}
int APS5_VABI pthread_condattr_init_nid_postfix(PthreadCondattr* attr) {
if (!attr) return PosixThread::GUEST_EINVAL;
return PosixThread::ToErrno(scePthreadCondattrInit(attr));
return toPosix(scePthreadCondattrInit(attr));
}
int APS5_VABI pthread_condattr_setclock_nid_postfix(PthreadCondattr* attr, KernelClockid clock_id) {
if (!attr || !*attr) return PosixThread::GUEST_EINVAL;
(*attr)->_clockid = clock_id;
return 0;
return toPosix(scePthreadCondattrSetclock(attr, clock_id));
}
}

View File

@ -3,22 +3,13 @@
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "../include/Pthread.hpp"
#include "../include/Mutex.hpp"
#include "Common.hpp"
#include <atomic>
#include <chrono>
#include <limits>
#include <stdexcept>
extern "C" {
int APS5_VABI scePthreadMutexattrInit(PthreadMutexattr* attr);
int APS5_VABI scePthreadMutexattrDestroy(PthreadMutexattr* attr);
int APS5_VABI scePthreadMutexattrSettype(PthreadMutexattr* attr, int type);
int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char* name);
int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex);
int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex);
int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex);
int APS5_VABI scePthreadMutexTimedlock(PthreadMutex* mutex, KernelUseconds usec);
int APS5_VABI scePthreadMutexTrylock(PthreadMutex* mutex);
}
#include <string>
namespace {
@ -27,10 +18,6 @@ constexpr int POSIX_MUTEX_ADAPTIVE_NP = 4;
constexpr int POSIX_PRIO_PROTECT = 2;
constexpr std::uintptr_t POSIX_ADAPTIVE_MUTEX_INITIALIZER = 1;
int _toErrno(int sceResult) {
return sceResult == 0 ? 0 : static_cast<int>(static_cast<std::uint32_t>(sceResult) & 0xFFFFu);
}
bool _isStaticInitializer(PthreadMutex mutex) {
const auto value = reinterpret_cast<std::uintptr_t>(mutex);
return value == 0 || value == POSIX_ADAPTIVE_MUTEX_INITIALIZER;
@ -57,18 +44,18 @@ int APS5_VABI pthread_mutex_destroy_nid_postfix(PthreadMutex* mutex) {
*mutex = nullptr;
return 0;
}
return _toErrno(scePthreadMutexDestroy(mutex));
return PosixThread::ToErrno(scePthreadMutexDestroy(mutex));
}
int APS5_VABI pthread_mutex_init_nid_postfix(PthreadMutex* mutex, const PthreadMutexattr* attr) {
const int result = scePthreadMutexInit(mutex, attr, nullptr);
if (result == 0 && (!attr || !*attr)) (*mutex)->_type = MutexType::ErrorCheck;
return _toErrno(result);
return PosixThread::ToErrno(result);
}
int APS5_VABI pthread_mutex_lock_nid_postfix(PthreadMutex* mutex) {
_initializeStatic(mutex, __func__);
return _toErrno(scePthreadMutexLock(mutex));
return PosixThread::ToErrno(scePthreadMutexLock(mutex));
}
int APS5_VABI pthread_mutex_timedlock_nid_postfix(PthreadMutex* mutex, const KernelTimespec* abstime) {
@ -79,27 +66,27 @@ int APS5_VABI pthread_mutex_timedlock_nid_postfix(PthreadMutex* mutex, const Ker
const auto now = std::chrono::system_clock::now().time_since_epoch();
const auto remaining = std::chrono::duration_cast<std::chrono::microseconds>(deadline - now).count();
const auto usec = remaining <= 0 ? 0 : remaining >= std::numeric_limits<KernelUseconds>::max() ? std::numeric_limits<KernelUseconds>::max() : static_cast<KernelUseconds>(remaining);
return _toErrno(scePthreadMutexTimedlock(mutex, usec));
return PosixThread::ToErrno(scePthreadMutexTimedlock(mutex, usec));
}
int APS5_VABI pthread_mutex_trylock_nid_postfix(PthreadMutex* mutex) {
_initializeStatic(mutex, __func__);
return _toErrno(scePthreadMutexTrylock(mutex));
return PosixThread::ToErrno(scePthreadMutexTrylock(mutex));
}
int APS5_VABI pthread_mutex_unlock_nid_postfix(PthreadMutex* mutex) {
_initializeStatic(mutex, __func__);
return _toErrno(scePthreadMutexUnlock(mutex));
return PosixThread::ToErrno(scePthreadMutexUnlock(mutex));
}
int APS5_VABI pthread_mutexattr_destroy_nid_postfix(PthreadMutexattr* attr) {
return _toErrno(scePthreadMutexattrDestroy(attr));
return PosixThread::ToErrno(scePthreadMutexattrDestroy(attr));
}
int APS5_VABI pthread_mutexattr_init_nid_postfix(PthreadMutexattr* attr) {
const int result = scePthreadMutexattrInit(attr);
if (result == 0) (*attr)->type = MutexType::ErrorCheck;
return _toErrno(result);
return PosixThread::ToErrno(result);
}
int APS5_VABI pthread_mutexattr_setprotocol_nid_postfix(PthreadMutexattr* attr, int protocol) {
@ -114,7 +101,7 @@ int APS5_VABI pthread_mutexattr_settype_nid_postfix(PthreadMutexattr* attr, int
(*attr)->type = MutexType::Normal;
return 0;
}
return _toErrno(scePthreadMutexattrSettype(attr, type));
return PosixThread::ToErrno(scePthreadMutexattrSettype(attr, type));
}
}

View File

@ -0,0 +1,57 @@
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <condition_variable>
#include <cstddef>
#include <cstdint>
#include <mutex>
namespace {
// FreeBSD guest ABI: PTHREAD_ONCE_INIT is {0, nullptr}; completed state is 1.
struct GuestOnce {
std::int32_t state;
void* mutex;
};
static_assert(offsetof(GuestOnce, mutex) == 8 && sizeof(GuestOnce) == 16);
std::mutex onceMutex;
std::condition_variable onceChanged;
}
extern "C" int APS5_VABI pthread_once_nid_postfix(
GuestOnce* control, void (APS5_VABI *initialize)()) {
if (!control || !initialize) return 22;
std::unique_lock lock(onceMutex);
onceChanged.wait(lock, [&] { return control->state != 2; });
if (control->state == 1) return 0;
if (control->state != 0) return 22;
control->state = 2;
lock.unlock();
// Initializers may initialize other once controls. Never run guest code
// under the shared lock. The lock publishes all initializer writes.
try {
initialize();
} catch (...) {
lock.lock();
control->state = 0;
lock.unlock();
onceChanged.notify_all();
throw;
}
lock.lock();
control->state = 1;
lock.unlock();
onceChanged.notify_all();
return 0;
}
using OnceRoutine = void (APS5_VABI *)();
extern "C" {
int APS5_VABI scePthreadOnce_nid_postfix(void* onceControl, OnceRoutine initRoutine) {
(void)onceControl;
(void)initRoutine;
NotImplemented_nid_no_patch(__func__);
return 0;
}
}

View File

@ -3,22 +3,23 @@
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "../include/Pthread.hpp"
#include "../include/Rwlock.hpp"
#include <atomic>
#include <stdexcept>
extern "C" {
int APS5_VABI scePthreadRwlockInit(PthreadRwlock* rwlock, const PthreadRwlockattr* attr, const char* name);
int APS5_VABI scePthreadRwlockDestroy(PthreadRwlock* rwlock);
int APS5_VABI scePthreadRwlockWrlock(PthreadRwlock* rwlock);
}
#include <string>
namespace {
int _toErrno(int sceResult) {
return sceResult == 0 ? 0 : static_cast<int>(static_cast<std::uint32_t>(sceResult) & 0xFFFFu);
int toPosix(int result) {
if (result == 0)
return 0;
const auto error = static_cast<std::uint32_t>(result);
if ((error & 0xffff0000u) != 0x80020000u)
throw std::runtime_error("Unexpected SCE rwlock error");
return static_cast<int>(error & 0xffffu);
}
void _initializeStatic(PthreadRwlock* rwlock, const char* funcName) {
void initializeStatic(PthreadRwlock* rwlock, const char* funcName) {
if (!rwlock) throw std::runtime_error(std::string(funcName) + ": null rwlock");
std::atomic_ref<PthreadRwlock> slot(*rwlock);
PthreadRwlock current = slot.load(std::memory_order_acquire);
@ -35,16 +36,26 @@ extern "C" {
int APS5_VABI pthread_rwlock_destroy_nid_postfix(PthreadRwlock* rwlock) {
if (!rwlock) throw std::runtime_error("pthread_rwlock_destroy: null rwlock");
if (*rwlock == nullptr) return 0;
return _toErrno(scePthreadRwlockDestroy(rwlock));
return toPosix(scePthreadRwlockDestroy(rwlock));
}
int APS5_VABI pthread_rwlock_init_nid_postfix(PthreadRwlock* rwlock, const PthreadRwlockattr* attr) {
return _toErrno(scePthreadRwlockInit(rwlock, attr, nullptr));
return toPosix(scePthreadRwlockInit(rwlock, attr, nullptr));
}
int APS5_VABI pthread_rwlock_rdlock_nid_postfix(PthreadRwlock* rwlock) {
initializeStatic(rwlock, __func__);
return toPosix(scePthreadRwlockRdlock(rwlock));
}
int APS5_VABI pthread_rwlock_unlock_nid_postfix(PthreadRwlock* rwlock) {
initializeStatic(rwlock, __func__);
return toPosix(scePthreadRwlockUnlock(rwlock));
}
int APS5_VABI pthread_rwlock_wrlock_nid_postfix(PthreadRwlock* rwlock) {
_initializeStatic(rwlock, __func__);
return _toErrno(scePthreadRwlockWrlock(rwlock));
initializeStatic(rwlock, __func__);
return toPosix(scePthreadRwlockWrlock(rwlock));
}
}

View File

@ -1,6 +1,7 @@
#include <cstdint>
#include <cstddef>
#include "SceTypes.hpp"
#include "../include/ThreadLifecycle.hpp"
#include "prx/libc/include/General.hpp"
#include "../include/Pthread.hpp"
#include "Common.hpp"

View File

@ -0,0 +1,26 @@
#ifndef CORE_LIBS_PRX_LIBKERNEL_PTHREAD_COND_HPP
#define CORE_LIBS_PRX_LIBKERNEL_PTHREAD_COND_HPP
#include "SceTypes.hpp"
class CondOperations {
public:
static int AbsoluteTimedwait(PthreadCond* cond, PthreadMutex* mutex, const KernelTimespec* abstime);
};
extern "C" {
int APS5_VABI scePthreadCondattrInit(PthreadCondattr* attr);
int APS5_VABI scePthreadCondattrDestroy(PthreadCondattr* attr);
int APS5_VABI scePthreadCondattrSetclock(PthreadCondattr* attr, KernelClockid clockId);
int APS5_VABI scePthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr, const char* name);
int APS5_VABI scePthreadCondDestroy(PthreadCond* cond);
int APS5_VABI scePthreadCondSignal(PthreadCond* cond);
int APS5_VABI scePthreadCondBroadcast(PthreadCond* cond);
int APS5_VABI scePthreadCondSignalto(PthreadCond* cond, Pthread thread);
int APS5_VABI scePthreadCondWait(PthreadCond* cond, PthreadMutex* mutex);
int APS5_VABI scePthreadCondTimedwait(PthreadCond* cond, PthreadMutex* mutex, KernelUseconds usec);
}
#endif

View File

@ -0,0 +1,26 @@
#ifndef CORE_LIBS_PRX_LIBKERNEL_PTHREAD_MUTEX_HPP
#define CORE_LIBS_PRX_LIBKERNEL_PTHREAD_MUTEX_HPP
#include "SceTypes.hpp"
class MutexOperations {
public:
static int Timedlock(PthreadMutex* mutex, const KernelTimespec* abstime);
};
extern "C" {
int APS5_VABI scePthreadMutexattrInit(PthreadMutexattr* attr);
int APS5_VABI scePthreadMutexattrDestroy(PthreadMutexattr* attr);
int APS5_VABI scePthreadMutexattrSettype(PthreadMutexattr* attr, int type);
int APS5_VABI scePthreadMutexattrSetprotocol(PthreadMutexattr* attr, int protocol);
int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char* name);
int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex);
int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex);
int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex);
int APS5_VABI scePthreadMutexTimedlock(PthreadMutex* mutex, KernelUseconds usec);
int APS5_VABI scePthreadMutexTrylock(PthreadMutex* mutex);
}
#endif

View File

@ -30,7 +30,7 @@ struct PthreadMutexPrivate {
std::atomic<std::thread::id> _owner;
int _count;
PthreadMutexPrivate() : _type(MutexType::Normal), _count(0) {}
PthreadMutexPrivate() : _type(MutexType::Normal), _owner(std::thread::id{}), _count(0) {}
};
struct PthreadRwlockattrPrivate {

View File

@ -0,0 +1,21 @@
#ifndef CORE_LIBS_PRX_LIBKERNEL_PTHREAD_RWLOCK_HPP
#define CORE_LIBS_PRX_LIBKERNEL_PTHREAD_RWLOCK_HPP
#include "SceTypes.hpp"
extern "C" {
int APS5_VABI scePthreadRwlockattrInit(PthreadRwlockattr* attr);
int APS5_VABI scePthreadRwlockattrDestroy(PthreadRwlockattr* attr);
int APS5_VABI scePthreadRwlockattrSettype(PthreadRwlockattr* attr, int type);
int APS5_VABI scePthreadRwlockInit(PthreadRwlock* rwlock, const PthreadRwlockattr* attr, const char* name);
int APS5_VABI scePthreadRwlockDestroy(PthreadRwlock* rwlock);
int APS5_VABI scePthreadRwlockRdlock(PthreadRwlock* rwlock);
int APS5_VABI scePthreadRwlockTryrdlock(PthreadRwlock* rwlock);
int APS5_VABI scePthreadRwlockWrlock(PthreadRwlock* rwlock);
int APS5_VABI scePthreadRwlockTrywrlock(PthreadRwlock* rwlock);
int APS5_VABI scePthreadRwlockUnlock(PthreadRwlock* rwlock);
}
#endif

View File

@ -0,0 +1,15 @@
#ifndef CORE_LIBS_PRX_LIBKERNEL_PTHREAD_THREADLIFECYCLE_HPP
#define CORE_LIBS_PRX_LIBKERNEL_PTHREAD_THREADLIFECYCLE_HPP
#include "SceTypes.hpp"
class ThreadLifecycle {
public:
static void SetThreadDtors(thread_dtors_func_t callback);
static void SetThreadAtexitCount(get_thread_atexit_count_func_t callback);
static void SetThreadAtexitReport(thread_atexit_report_func_t callback);
};
extern "C" void APS5_VABI scePthreadExit(void* retval);
#endif

View File

@ -1,9 +1,10 @@
#include "../include/Pthread.hpp"
#include "prx/libkernel/Pthread/include/Pthread.hpp"
#include "prx/libc/include/General.hpp"
#include <stdexcept>
#include <string>
static constexpr int SCE_OK = 0;
static constexpr int SCE_KERNEL_ERROR_EINVAL = 0x80020016;
static constexpr int SCE_KERNEL_ERROR_ENOMEM = 0x8002000C;
static constexpr std::size_t DEFAULT_STACK_SIZE = 1u << 20;
@ -19,7 +20,7 @@ static constexpr int SCHED_FIFO_PS5 = 1;
extern "C" {
int APS5_VABI scePthreadAttrInit(PthreadAttr* attr) {
if (!attr) throw std::runtime_error("scePthreadAttrInit: null attr");
if (!attr) throw std::runtime_error(std::string(__func__) + ": null attr");
auto* p = new (std::nothrow) PthreadAttrPrivate{};
if (!p) return SCE_KERNEL_ERROR_ENOMEM;
p->_stacksize = DEFAULT_STACK_SIZE;
@ -32,35 +33,35 @@ int APS5_VABI scePthreadAttrInit(PthreadAttr* attr) {
}
int APS5_VABI scePthreadAttrDestroy(PthreadAttr* attr) {
if (!attr || !*attr) throw std::runtime_error("scePthreadAttrDestroy: null attr");
if (!attr || !*attr) throw std::runtime_error(std::string(__func__) + ": null attr");
delete *attr;
*attr = nullptr;
return SCE_OK;
}
int APS5_VABI scePthreadAttrSetdetachstate(PthreadAttr* attr, int detachstate) {
if (!attr || !*attr) throw std::runtime_error("scePthreadAttrSetdetachstate: null attr");
if (!attr || !*attr) throw std::runtime_error(std::string(__func__) + ": null attr");
if (detachstate != DETACH_JOINABLE && detachstate != DETACH_DETACHED)
throw std::runtime_error("scePthreadAttrSetdetachstate: invalid state");
throw std::runtime_error(std::string(__func__) + ": invalid state");
(*attr)->_detachstate = detachstate;
return SCE_OK;
}
int APS5_VABI scePthreadAttrSetschedparam(PthreadAttr* attr, const KernelSchedParam* param) {
if (!attr || !*attr || !param)
throw std::runtime_error("scePthreadAttrSetschedparam: null arg");
throw std::runtime_error(std::string(__func__) + ": null arg");
(*attr)->_schedpriority = param->sched_priority;
return SCE_OK;
}
int APS5_VABI scePthreadAttrSetstacksize(PthreadAttr* attr, std::size_t stacksize) {
if (!attr || !*attr) throw std::runtime_error("scePthreadAttrSetstacksize: null attr");
if (stacksize < 16384) throw std::runtime_error("scePthreadAttrSetstacksize: too small");
if (!attr || !*attr) throw std::runtime_error(std::string(__func__) + ": null attr");
if (stacksize < 16384) throw std::runtime_error(std::string(__func__) + ": too small");
#ifdef _WIN32
SYSTEM_INFO system{};
GetSystemInfo(&system);
if (stacksize % system.dwPageSize != 0 || stacksize > std::numeric_limits<unsigned>::max())
throw std::runtime_error("scePthreadAttrSetstacksize: invalid Windows stack size");
throw std::runtime_error(std::string(__func__) + ": invalid Windows stack size");
#endif
(*attr)->_stacksize = stacksize;
return SCE_OK;
@ -68,7 +69,7 @@ int APS5_VABI scePthreadAttrSetstacksize(PthreadAttr* attr, std::size_t stacksiz
int APS5_VABI scePthreadAttrGetstack(const PthreadAttr* attr, void** stackaddr, std::size_t* stacksize) {
if (!attr || !*attr || !stackaddr || !stacksize)
throw std::runtime_error("scePthreadAttrGetstack: null arg");
throw std::runtime_error(std::string(__func__) + ": null arg");
*stackaddr = (*attr)->stackAddress;
*stacksize = (*attr)->_stacksize;
return SCE_OK;
@ -76,7 +77,7 @@ int APS5_VABI scePthreadAttrGetstack(const PthreadAttr* attr, void** stackaddr,
int APS5_VABI scePthreadAttrGet(Pthread thread, PthreadAttr* attr) {
if (!thread || !attr || !*attr)
throw std::runtime_error("scePthreadAttrGet: null arg");
return SCE_KERNEL_ERROR_EINVAL;
(*attr)->_stacksize = thread->stackSize;
(*attr)->stackAddress = thread->stackAddress;
(*attr)->_detachstate = thread->_detached ? DETACH_DETACHED : DETACH_JOINABLE;
@ -165,18 +166,17 @@ int APS5_VABI scePthreadAttrSetsolosched(PthreadAttr* attr, int solosched) {
}
int APS5_VABI scePthreadAttrSetstack(PthreadAttr* attr, void* addr, size_t size) {
(void)attr;
(void)addr;
(void)size;
NotImplemented_nid_no_patch(__func__);
return 0;
if (!attr || !*attr || !addr) throw std::runtime_error(std::string(__func__) + ": null arg");
if (size < 16384) throw std::runtime_error(std::string(__func__) + ": too small");
(*attr)->stackAddress = addr;
(*attr)->_stacksize = size;
return SCE_OK;
}
int APS5_VABI scePthreadAttrSetstackaddr(PthreadAttr* attr, void* addr) {
(void)attr;
(void)addr;
NotImplemented_nid_no_patch(__func__);
return 0;
if (!attr || !*attr || !addr) throw std::runtime_error(std::string(__func__) + ": null arg");
(*attr)->stackAddress = addr;
return SCE_OK;
}
}

View File

@ -1,103 +1,154 @@
#include "../include/Pthread.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libkernel/Pthread/include/Pthread.hpp"
#include "prx/libkernel/Pthread/include/Mutex.hpp"
#include "prx/libkernel/Pthread/include/Cond.hpp"
#include "prx/libkernel/Pthread/Posix/Common.hpp"
#include "prx/libkernel/Time/include/Time.hpp"
#include "prx/libkernel/Time/include/TimedWait.hpp"
#include <chrono>
#include <cstdint>
#include <memory>
#include <optional>
#include <thread>
#include <stdexcept>
#include <thread>
static constexpr int SCE_OK = 0;
static constexpr int SCE_KERNEL_ERROR_ENOMEM = 0x8002000C;
static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = 0x8002003C;
namespace {
static int WaitOn(PthreadCondPrivate* cond, PthreadMutexPrivate* mutex, std::optional<std::uint64_t> deadlineNanos, const void* caller) {
const auto self = std::this_thread::get_id();
constexpr int sceTimedOut = static_cast<int>(0x8002003cu);
std::mutex condInitializationMutex;
PthreadCond destroyedCond() {
return reinterpret_cast<PthreadCond>(std::uintptr_t{2});
}
PthreadCond resolveCond(PthreadCond* cond) {
if (!cond)
throw std::invalid_argument("Condition variable pointer is null");
std::lock_guard lock(condInitializationMutex);
if (*cond == destroyedCond())
throw std::runtime_error("Condition variable has been destroyed");
if (!*cond)
*cond = new PthreadCondPrivate();
return *cond;
}
PthreadMutex lockedMutex(PthreadMutex* mutex) {
if (!mutex || !*mutex)
throw std::invalid_argument("Mutex pointer is null");
auto* current = *mutex;
if (current->_owner.load(std::memory_order_acquire) != std::this_thread::get_id())
throw std::runtime_error("Condition wait mutex is not owned by the current thread");
return current;
}
int waitUntil(PthreadCond* cond, PthreadMutex* mutex, std::optional<std::uint64_t> deadlineNanos, const void* caller) {
auto* c = resolveCond(cond);
auto* m = lockedMutex(mutex);
bool timedOut = false;
const auto waitStart = std::chrono::steady_clock::now();
struct Trace {
const void* caller; const bool& timedOut; std::chrono::steady_clock::time_point start;
~Trace() { KernelTraceWait_nid_postfix("cond", caller, static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - start).count()), timedOut); }
} trace{caller, timedOut, waitStart};
mutex->_owner.store(std::thread::id{}, std::memory_order_relaxed);
if (mutex->_type == MutexType::Recursive) {
std::unique_lock<std::recursive_timed_mutex> lock(mutex->_rmtx, std::adopt_lock);
if (deadlineNanos) timedOut = !cond->_cv.WaitUntil(lock, *deadlineNanos);
else cond->_cv.Wait(lock);
lock.release();
} else {
std::unique_lock<std::timed_mutex> lock(mutex->_mtx, std::adopt_lock);
if (deadlineNanos) timedOut = !cond->_cv.WaitUntil(lock, *deadlineNanos);
else cond->_cv.Wait(lock);
if (m->_type == MutexType::Recursive) {
std::unique_lock<std::recursive_timed_mutex> lock(m->_rmtx, std::adopt_lock);
const auto previousCount = m->_count;
m->_count = 0;
m->_owner.store(std::thread::id{}, std::memory_order_release);
if (deadlineNanos) timedOut = !c->_cv.WaitUntil(lock, *deadlineNanos);
else c->_cv.Wait(lock);
m->_owner.store(std::this_thread::get_id(), std::memory_order_release);
m->_count = previousCount;
lock.release();
return timedOut ? sceTimedOut : 0;
}
mutex->_owner.store(self, std::memory_order_relaxed);
return timedOut ? SCE_KERNEL_ERROR_ETIMEDOUT : SCE_OK;
std::unique_lock<std::timed_mutex> lock(m->_mtx, std::adopt_lock);
m->_owner.store(std::thread::id{}, std::memory_order_release);
if (deadlineNanos) timedOut = !c->_cv.WaitUntil(lock, *deadlineNanos);
else c->_cv.Wait(lock);
m->_owner.store(std::this_thread::get_id(), std::memory_order_release);
lock.release();
return timedOut ? sceTimedOut : 0;
}
}
int CondOperations::AbsoluteTimedwait(PthreadCond* cond, PthreadMutex* mutex, const KernelTimespec* abstime) {
KernelUseconds usec = 0;
if (!PosixThread::RelativeMicroseconds(resolveCond(cond)->_clockid, abstime, &usec))
throw std::invalid_argument("Invalid absolute condition variable timeout");
return waitUntil(cond, mutex, TimedWait::DeadlineNanos(usec), __builtin_return_address(0));
}
extern "C" {
int APS5_VABI scePthreadCondattrInit(PthreadCondattr* attr) {
if (!attr) throw std::runtime_error("scePthreadCondattrInit: null attr");
auto* p = new (std::nothrow) PthreadCondattrPrivate{0};
if (!p) return SCE_KERNEL_ERROR_ENOMEM;
*attr = p;
return SCE_OK;
if (!attr)
throw std::invalid_argument("Condition attribute pointer is null");
*attr = new PthreadCondattrPrivate{0};
return 0;
}
int APS5_VABI scePthreadCondattrDestroy(PthreadCondattr* attr) {
if (!attr || !*attr) throw std::runtime_error("scePthreadCondattrDestroy: null attr");
if (!attr || !*attr)
throw std::invalid_argument("Condition attributes are not initialized");
delete *attr;
*attr = nullptr;
return SCE_OK;
return 0;
}
int APS5_VABI scePthreadCondattrSetclock(PthreadCondattr* attr, KernelClockid clockId) {
if (!attr || !*attr)
throw std::invalid_argument("Condition attributes are not initialized");
(*attr)->_clockid = static_cast<int>(clockId);
return 0;
}
int APS5_VABI scePthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr, const char*) {
if (!cond) throw std::runtime_error("scePthreadCondInit: null cond");
auto* p = new (std::nothrow) PthreadCondPrivate{};
if (!p) return SCE_KERNEL_ERROR_ENOMEM;
if (attr && *attr) p->_clockid = (*attr)->_clockid;
*cond = p;
return SCE_OK;
if (!cond)
throw std::invalid_argument("Condition variable pointer is null");
if (attr && !*attr)
throw std::invalid_argument("Condition attributes are not initialized");
auto replacement = std::make_unique<PthreadCondPrivate>();
if (attr)
replacement->_clockid = (*attr)->_clockid;
std::lock_guard lock(condInitializationMutex);
*cond = replacement.release();
return 0;
}
int APS5_VABI scePthreadCondDestroy(PthreadCond* cond) {
if (!cond || !*cond) throw std::runtime_error("scePthreadCondDestroy: null cond");
if (!cond)
throw std::invalid_argument("Condition variable pointer is null");
std::lock_guard lock(condInitializationMutex);
if (*cond == destroyedCond())
throw std::runtime_error("Condition variable has already been destroyed");
delete *cond;
*cond = nullptr;
return SCE_OK;
*cond = destroyedCond();
return 0;
}
int APS5_VABI scePthreadCondSignal(PthreadCond* cond) {
if (!cond || !*cond) throw std::runtime_error("scePthreadCondSignal: null cond");
(*cond)->_cv.NotifyOne();
return SCE_OK;
resolveCond(cond)->_cv.NotifyOne();
return 0;
}
int APS5_VABI scePthreadCondBroadcast(PthreadCond* cond) {
if (!cond || !*cond) throw std::runtime_error("scePthreadCondBroadcast: null cond");
(*cond)->_cv.NotifyAll();
return SCE_OK;
}
int APS5_VABI scePthreadCondTimedwait(PthreadCond* cond, PthreadMutex* mutex, unsigned int usec) {
if (!cond || !*cond || !mutex || !*mutex)
throw std::runtime_error("scePthreadCondTimedwait: null arg");
return WaitOn(*cond, *mutex, TimedWait::DeadlineNanos(usec), __builtin_return_address(0));
resolveCond(cond)->_cv.NotifyAll();
return 0;
}
int APS5_VABI scePthreadCondSignalto(PthreadCond* cond, Pthread thread) {
(void)thread;
if (!cond || !*cond) throw std::runtime_error("scePthreadCondSignalto: null cond");
(*cond)->_cv.NotifyAll();
return SCE_OK;
resolveCond(cond)->_cv.NotifyAll();
return 0;
}
int APS5_VABI scePthreadCondWait(PthreadCond* cond, PthreadMutex* mutex) {
if (!cond || !*cond || !mutex || !*mutex)
throw std::runtime_error("scePthreadCondWait: null arg");
return WaitOn(*cond, *mutex, std::nullopt, __builtin_return_address(0));
return waitUntil(cond, mutex, std::nullopt, __builtin_return_address(0));
}
int APS5_VABI scePthreadCondTimedwait(PthreadCond* cond, PthreadMutex* mutex, KernelUseconds usec) {
return waitUntil(cond, mutex, TimedWait::DeadlineNanos(usec), __builtin_return_address(0));
}
}

View File

@ -1,142 +1,174 @@
#include "../include/Pthread.hpp"
#include "prx/libc/include/General.hpp"
#include "../include/Mutex.hpp"
#include "prx/libkernel/Time/include/TimedWait.hpp"
#include <cerrno>
#include <chrono>
#include <cstdint>
#include <limits>
#include <memory>
#include <stdexcept>
#include <string>
static constexpr int SCE_OK = 0;
static constexpr int SCE_KERNEL_ERROR_ENOMEM = 0x8002000C;
static constexpr int SCE_KERNEL_ERROR_EDEADLK = 0x8002000B;
static constexpr int SCE_KERNEL_ERROR_EPERM = 0x80020001;
static constexpr int SCE_KERNEL_ERROR_EBUSY = 0x80020010;
static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = 0x8002003C;
namespace {
constexpr int sceNotPermitted = static_cast<int>(0x80020001u);
constexpr int sceDeadlock = static_cast<int>(0x8002000bu);
constexpr int sceBusy = static_cast<int>(0x80020010u);
constexpr int sceTimedOut = static_cast<int>(0x8002003cu);
std::mutex initializationMutex;
PthreadMutex destroyedMutex() {
return reinterpret_cast<PthreadMutex>(std::uintptr_t{2});
}
PthreadMutex resolveMutex(PthreadMutex* mutex, bool initialize) {
if (!mutex)
throw std::invalid_argument("Mutex pointer is null");
std::lock_guard lock(initializationMutex);
if (*mutex == destroyedMutex())
throw std::runtime_error("Mutex has been destroyed");
if (reinterpret_cast<std::uintptr_t>(*mutex) == 1)
throw std::runtime_error("Adaptive mutex initializer is unsupported");
if (!*mutex) {
if (!initialize)
throw std::runtime_error("Mutex is not initialized");
*mutex = new PthreadMutexPrivate();
}
return *mutex;
}
template<typename TAcquire>
int acquireMutex(PthreadMutex mutex, TAcquire acquire, int unavailable, bool tryOnly) {
const auto thread = std::this_thread::get_id();
const bool owned = mutex->_owner.load(std::memory_order_acquire) == thread;
if (owned && mutex->_type != MutexType::Recursive) {
if (tryOnly)
return sceBusy;
if (mutex->_type == MutexType::ErrorCheck)
return sceDeadlock;
throw std::runtime_error("Mutex is already owned by the current thread");
}
if (owned && mutex->_count == std::numeric_limits<int>::max())
throw std::overflow_error("Recursive mutex lock count overflow");
if (mutex->_type == MutexType::Recursive) {
if (!acquire(mutex->_rmtx))
return unavailable;
++mutex->_count;
} else {
if (!acquire(mutex->_mtx))
return unavailable;
}
mutex->_owner.store(thread, std::memory_order_release);
return 0;
}
}
int MutexOperations::Timedlock(PthreadMutex* mutex, const KernelTimespec* abstime) {
if (!abstime || abstime->tv_sec < 0 || abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000)
throw std::invalid_argument("Invalid absolute mutex timeout");
const auto maximum = std::chrono::nanoseconds::max().count();
if (abstime->tv_sec > (maximum - abstime->tv_nsec) / 1000000000)
throw std::overflow_error("Absolute mutex timeout exceeds the host clock range");
const auto duration = std::chrono::nanoseconds(abstime->tv_sec * 1000000000 + abstime->tv_nsec);
if (std::chrono::duration<long double>(duration) >= std::chrono::duration<long double>(std::chrono::system_clock::duration::max()))
throw std::overflow_error("Absolute mutex timeout exceeds the host clock range");
const auto deadline = std::chrono::system_clock::time_point(std::chrono::duration_cast<std::chrono::system_clock::duration>(duration));
return acquireMutex(resolveMutex(mutex, true), [&](auto& native) { return native.try_lock_until(deadline); }, sceTimedOut, false);
}
extern "C" {
int APS5_VABI scePthreadMutexattrInit(PthreadMutexattr* attr) {
if (!attr) throw std::runtime_error("scePthreadMutexattrInit: null attr");
auto* p = new (std::nothrow) PthreadMutexattrPrivate{MutexType::Normal};
if (!p) return SCE_KERNEL_ERROR_ENOMEM;
*attr = p;
return SCE_OK;
if (!attr)
throw std::invalid_argument("Mutex attribute pointer is null");
*attr = new PthreadMutexattrPrivate{MutexType::Normal};
return 0;
}
int APS5_VABI scePthreadMutexattrDestroy(PthreadMutexattr* attr) {
if (!attr || !*attr) throw std::runtime_error("scePthreadMutexattrDestroy: null attr");
if (!attr || !*attr)
throw std::invalid_argument("Mutex attributes are not initialized");
delete *attr;
*attr = nullptr;
return SCE_OK;
return 0;
}
int APS5_VABI scePthreadMutexattrSettype(PthreadMutexattr* attr, int type) {
if (!attr || !*attr) throw std::runtime_error("scePthreadMutexattrSettype: null attr");
if (!attr || !*attr)
throw std::invalid_argument("Mutex attributes are not initialized");
switch (type) {
case 1: (*attr)->type = MutexType::ErrorCheck; break;
case 2: (*attr)->type = MutexType::Recursive; break;
case 3: (*attr)->type = MutexType::Normal; break;
case 4: (*attr)->type = MutexType::Normal; break;
default: throw std::runtime_error("scePthreadMutexattrSettype: invalid type " + std::to_string(type));
default: throw std::invalid_argument("Invalid mutex type");
}
return SCE_OK;
}
int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char*) {
if (!mutex) throw std::runtime_error("scePthreadMutexInit: null mutex");
MutexType t = MutexType::Normal;
if (attr && *attr) t = (*attr)->type;
auto* p = new (std::nothrow) PthreadMutexPrivate();
if (!p) return SCE_KERNEL_ERROR_ENOMEM;
p->_type = t;
*mutex = p;
return SCE_OK;
}
int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex) {
if (!mutex || !*mutex) throw std::runtime_error("scePthreadMutexDestroy: null mutex");
delete *mutex;
*mutex = nullptr;
return SCE_OK;
}
int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex) {
if (!mutex || !*mutex) throw std::runtime_error("scePthreadMutexLock: null mutex");
auto* m = *mutex;
const auto tid = std::this_thread::get_id();
if (m->_type == MutexType::Recursive) {
m->_rmtx.lock();
m->_owner.store(tid, std::memory_order_relaxed);
++m->_count;
return SCE_OK;
}
if (m->_type == MutexType::ErrorCheck) {
if (m->_owner.load(std::memory_order_acquire) == tid) return SCE_KERNEL_ERROR_EDEADLK;
}
m->_mtx.lock();
m->_owner.store(tid, std::memory_order_relaxed);
return SCE_OK;
}
int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex) {
if (!mutex || !*mutex) throw std::runtime_error("scePthreadMutexUnlock: null mutex");
auto* m = *mutex;
if (m->_type == MutexType::ErrorCheck || m->_type == MutexType::Normal) {
if (m->_owner.load(std::memory_order_acquire) != std::this_thread::get_id())
return SCE_KERNEL_ERROR_EPERM;
}
if (m->_type == MutexType::Recursive) {
if (--m->_count == 0) m->_owner.store(std::thread::id{}, std::memory_order_relaxed);
m->_rmtx.unlock();
return SCE_OK;
}
m->_owner.store(std::thread::id{}, std::memory_order_relaxed);
m->_mtx.unlock();
return SCE_OK;
}
int APS5_VABI scePthreadMutexTimedlock(PthreadMutex* mutex, KernelUseconds usec) {
if (!mutex || !*mutex) throw std::runtime_error("scePthreadMutexTimedlock: null mutex");
auto* m = *mutex;
const auto tid = std::this_thread::get_id();
const std::uint64_t deadline = TimedWait::DeadlineNanos(usec);
if (m->_type == MutexType::Recursive) {
const bool locked = TimedWait::AcquireUntil(deadline, [&] { return m->_rmtx.try_lock(); }, [&](std::uint64_t micros) { return m->_rmtx.try_lock_for(std::chrono::microseconds(micros)); });
if (!locked) return SCE_KERNEL_ERROR_ETIMEDOUT;
m->_owner.store(tid, std::memory_order_relaxed);
++m->_count;
return SCE_OK;
}
if (m->_type == MutexType::ErrorCheck) {
if (m->_owner.load(std::memory_order_acquire) == tid) return SCE_KERNEL_ERROR_EDEADLK;
}
const bool locked = TimedWait::AcquireUntil(deadline, [&] { return m->_mtx.try_lock(); }, [&](std::uint64_t micros) { return m->_mtx.try_lock_for(std::chrono::microseconds(micros)); });
if (!locked) return SCE_KERNEL_ERROR_ETIMEDOUT;
m->_owner.store(tid, std::memory_order_relaxed);
return SCE_OK;
}
int APS5_VABI scePthreadMutexTrylock(PthreadMutex* mutex) {
if (!mutex || !*mutex) throw std::runtime_error("scePthreadMutexTrylock: null mutex");
auto* m = *mutex;
const auto tid = std::this_thread::get_id();
if (m->_type == MutexType::Recursive) {
if (!m->_rmtx.try_lock()) return SCE_KERNEL_ERROR_EBUSY;
m->_owner.store(tid, std::memory_order_relaxed);
++m->_count;
return SCE_OK;
}
if (!m->_mtx.try_lock()) return SCE_KERNEL_ERROR_EBUSY;
m->_owner.store(tid, std::memory_order_relaxed);
return SCE_OK;
return 0;
}
int APS5_VABI scePthreadMutexattrSetprotocol(PthreadMutexattr* attr, int protocol) {
(void)attr;
(void)protocol;
NotImplemented_nid_no_patch(__func__);
return 0;
if (!attr || !*attr)
throw std::invalid_argument("Mutex attributes are not initialized");
if (protocol != 0)
throw std::invalid_argument("Mutex priority inheritance and protection are unsupported");
return 0;
}
int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char*) {
if (!mutex)
throw std::invalid_argument("Mutex pointer is null");
if (attr && !*attr)
throw std::invalid_argument("Mutex attributes are not initialized");
auto replacement = std::make_unique<PthreadMutexPrivate>();
if (attr)
replacement->_type = (*attr)->type;
std::lock_guard lock(initializationMutex);
*mutex = replacement.release();
return 0;
}
int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex) {
if (!mutex)
throw std::invalid_argument("Mutex pointer is null");
std::lock_guard lock(initializationMutex);
if (*mutex == destroyedMutex())
throw std::runtime_error("Mutex has already been destroyed");
if (reinterpret_cast<std::uintptr_t>(*mutex) == 1)
throw std::runtime_error("Adaptive mutex initializer is unsupported");
if (*mutex && (*mutex)->_owner.load(std::memory_order_acquire) != std::thread::id{})
throw std::runtime_error("Cannot destroy a locked mutex");
delete *mutex;
*mutex = destroyedMutex();
return 0;
}
int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex) {
return acquireMutex(resolveMutex(mutex, true), [](auto& native) { native.lock(); return true; }, 0, false);
}
int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex) {
auto* current = resolveMutex(mutex, false);
if (current->_owner.load(std::memory_order_acquire) != std::this_thread::get_id())
return sceNotPermitted;
if (current->_type == MutexType::Recursive) {
if (--current->_count == 0)
current->_owner.store(std::thread::id{}, std::memory_order_release);
current->_rmtx.unlock();
} else {
current->_owner.store(std::thread::id{}, std::memory_order_release);
current->_mtx.unlock();
}
return 0;
}
int APS5_VABI scePthreadMutexTimedlock(PthreadMutex* mutex, KernelUseconds usec) {
const auto deadline = TimedWait::DeadlineNanos(usec);
return acquireMutex(resolveMutex(mutex, true), [=](auto& native) {
return TimedWait::AcquireUntil(deadline, [&] { return native.try_lock(); }, [&](std::uint64_t micros) { return native.try_lock_for(std::chrono::microseconds(micros)); });
}, sceTimedOut, false);
}
int APS5_VABI scePthreadMutexTrylock(PthreadMutex* mutex) {
return acquireMutex(resolveMutex(mutex, true), [](auto& native) { return native.try_lock(); }, sceBusy, true);
}
}

View File

@ -1,4 +1,5 @@
#include "../include/Pthread.hpp"
#include "../include/Rwlock.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libkernel/Time/include/TimedWait.hpp"
#include <chrono>

View File

@ -1,4 +1,5 @@
#include "../include/Pthread.hpp"
#include "../include/ThreadLifecycle.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/CpuTopology.hpp"
#include <algorithm>
@ -35,7 +36,44 @@ struct ThreadArgs {
PthreadPrivate* self;
};
static void FinishThread(PthreadPrivate* self, void* retval) {
static std::atomic<thread_dtors_func_t> threadDtors{nullptr};
static std::atomic<get_thread_atexit_count_func_t> threadAtexitCount{nullptr};
static std::atomic<thread_atexit_report_func_t> threadAtexitReport{nullptr};
static thread_local PthreadPrivate* currentThread = nullptr;
static thread_local bool threadFinishing = false;
void ThreadLifecycle::SetThreadDtors(thread_dtors_func_t callback) {
if (!callback)
throw std::runtime_error("Thread destructor callback is null");
thread_dtors_func_t expected = nullptr;
if (!threadDtors.compare_exchange_strong(expected, callback))
throw std::runtime_error("Thread destructor callback is already registered");
}
void ThreadLifecycle::SetThreadAtexitCount(get_thread_atexit_count_func_t callback) {
if (!callback)
throw std::runtime_error("Thread atexit count callback is null");
get_thread_atexit_count_func_t expected = nullptr;
if (!threadAtexitCount.compare_exchange_strong(expected, callback))
throw std::runtime_error("Thread atexit count callback is already registered");
}
void ThreadLifecycle::SetThreadAtexitReport(thread_atexit_report_func_t callback) {
if (!callback)
throw std::runtime_error("Thread atexit report callback is null");
thread_atexit_report_func_t expected = nullptr;
if (!threadAtexitReport.compare_exchange_strong(expected, callback))
throw std::runtime_error("Thread atexit report callback is already registered");
}
static void finishThread(PthreadPrivate* self, void* retval) {
if (!self || self != currentThread)
throw std::runtime_error("Finishing an unregistered guest thread");
if (threadFinishing)
throw std::runtime_error("Guest thread is already finishing");
threadFinishing = true;
if (const auto callback = threadDtors.load())
callback();
{
std::unique_lock<std::mutex> lk(self->_join_mtx);
self->_retval = retval;
@ -49,13 +87,13 @@ static void RunThread(std::unique_ptr<ThreadArgs> args) {
const auto entry = args->entry;
void* arg = args->arg;
PthreadPrivate* self = args->self;
currentThread = self;
args.reset();
FinishThread(self, entry(arg));
finishThread(self, entry(arg));
currentThread = nullptr;
}
#ifdef _WIN32
static thread_local PthreadPrivate* currentThread = nullptr;
static void ReleaseThread(PthreadPrivate* thread) {
if (thread->references.fetch_sub(1, std::memory_order_acq_rel) != 1)
return;
@ -229,18 +267,18 @@ int APS5_VABI scePthreadDetach(Pthread thread) {
}
void APS5_VABI scePthreadExit(void* retval) {
#ifdef _WIN32
if (!currentThread)
throw std::runtime_error("scePthreadExit: current thread is not registered");
auto* self = currentThread;
FinishThread(self, retval);
finishThread(self, retval);
currentThread = nullptr;
#ifdef _WIN32
ReleaseThread(self);
_endthreadex(0);
#else
pthread_exit(retval);
#endif
__builtin_unreachable();
throw std::runtime_error("Native thread exit returned");
}
Pthread APS5_VABI scePthreadSelf() {
@ -256,10 +294,8 @@ Pthread APS5_VABI scePthreadSelf() {
adopted->references.store(1, std::memory_order_relaxed);
currentThread = adopted.release();
}
return currentThread;
#else
return nullptr;
#endif
return currentThread;
}
void APS5_VABI scePthreadYield() {
@ -361,3 +397,12 @@ int APS5_VABI scePthreadOnce(int32_t* once, void (APS5_VABI* init)(void)) {
}
}
extern "C" {
void APS5_VABI __pthread_cxa_finalize_nid_postfix(void* argument) {
(void)argument;
NotImplemented_nid_no_patch(__func__);
}
}

View File

@ -0,0 +1,5 @@
#pragma once
namespace GuestSockets {
constexpr int FirstDescriptor = 0x10000000;
int Close(int descriptor);
}

View File

@ -0,0 +1,47 @@
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#endif
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdint>
#include <cstring>
extern "C" int* APS5_VABI __error_nid_postfix();
extern "C" {
int APS5_VABI __inet_pton_nid_postfix(int family, const char* text, void* destination) {
if (family != 2 && family != 28) { *__error_nid_postfix() = 47; return -1; }
if (!text || !destination) { *__error_nid_postfix() = 14; return -1; }
unsigned char address[16]{};
const int nativeFamily = family == 2 ? AF_INET : AF_INET6;
#ifdef _WIN32
const int result = InetPtonA(nativeFamily, text, address);
#else
const int result = ::inet_pton(nativeFamily, text, address);
#endif
if (result == 1) std::memcpy(destination, address, family == 2 ? 4 : 16);
else if (result == -1) *__error_nid_postfix() = 22;
return result;
}
const char* APS5_VABI __inet_ntop_nid_postfix(int family, const void* source,
char* destination, std::uint32_t capacity) {
if (family != 2 && family != 28) { *__error_nid_postfix() = 47; return nullptr; }
if (!source || !destination) { *__error_nid_postfix() = 14; return nullptr; }
char text[INET6_ADDRSTRLEN]{};
const int nativeFamily = family == 2 ? AF_INET : AF_INET6;
#ifdef _WIN32
const auto* result = InetNtopA(nativeFamily, const_cast<void*>(source), text, sizeof(text));
#else
const auto* result = ::inet_ntop(nativeFamily, source, text, sizeof(text));
#endif
if (!result) { *__error_nid_postfix() = 22; return nullptr; }
const auto size = std::strlen(text) + 1;
if (size > capacity) { *__error_nid_postfix() = 28; return nullptr; }
std::memcpy(destination, text, size);
return destination;
}
}

View File

@ -13,13 +13,6 @@ int APS5_VABI accept_nid_postfix(int s, void* addr, uint32_t* addrlen) {
return 0;
}
int APS5_VABI bind_nid_postfix(int s, const void* addr, uint32_t addrlen) {
(void)s;
(void)addr;
(void)addrlen;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI connect_nid_postfix(int s, const void* addr, uint32_t addrlen) {
(void)s;
@ -36,41 +29,9 @@ int APS5_VABI listen_nid_postfix(int s, int backlog) {
return 0;
}
int APS5_VABI socket_nid_postfix(int family, int type, int protocol) {
(void)family;
(void)type;
(void)protocol;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI getsockname_nid_postfix(int s, void* addr, uint32_t* addrlen) {
(void)s;
(void)addr;
(void)addrlen;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI getsockopt_nid_postfix(int s, int level, int optname, void* optval, uint32_t* optlen) {
(void)s;
(void)level;
(void)optname;
(void)optval;
(void)optlen;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI setsockopt_nid_postfix(int s, int level, int optname, const void* optval, uint32_t optlen) {
(void)s;
(void)level;
(void)optname;
(void)optval;
(void)optlen;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int64_t APS5_VABI send_nid_postfix(int s, const void* buf, uint64_t len, int flags) {
(void)s;
@ -81,16 +42,6 @@ int64_t APS5_VABI send_nid_postfix(int s, const void* buf, uint64_t len, int fla
return 0;
}
int64_t APS5_VABI sendto_nid_postfix(int s, const void* buf, uint64_t len, int flags, const void* addr, uint32_t addrlen) {
(void)s;
(void)buf;
(void)len;
(void)flags;
(void)addr;
(void)addrlen;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int64_t APS5_VABI recv_nid_postfix(int s, void* buf, uint64_t len, int flags) {
(void)s;
@ -101,16 +52,6 @@ int64_t APS5_VABI recv_nid_postfix(int s, void* buf, uint64_t len, int flags) {
return 0;
}
int64_t APS5_VABI recvfrom_nid_postfix(int s, void* buf, uint64_t len, int flags, void* addr, uint32_t* addrlen) {
(void)s;
(void)buf;
(void)len;
(void)flags;
(void)addr;
(void)addrlen;
NotImplemented_nid_no_patch(__func__);
return 0;
}
const char* APS5_VABI inet_ntop_nid_postfix(int af, const void* src, char* dst, uint32_t size) {
(void)af;

View File

@ -0,0 +1,287 @@
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cerrno>
#endif
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libkernel/Socket/include/SocketRuntime.hpp"
#include <algorithm>
#include <climits>
#include <cstdint>
#include <cstring>
#include <map>
#include <memory>
#include <mutex>
#include <cstdarg>
extern "C" int* APS5_VABI __error_nid_postfix();
namespace {
#ifdef _WIN32
using NativeSocket = SOCKET;
constexpr auto Invalid = INVALID_SOCKET;
#else
using NativeSocket = int;
constexpr auto Invalid = -1;
#endif
int Fail(int error) { *__error_nid_postfix() = error; return -1; }
int NativeError() {
#ifdef _WIN32
switch (WSAGetLastError()) {
case WSAEWOULDBLOCK: return 35;
case WSAEADDRINUSE: return 48;
case WSAEADDRNOTAVAIL: return 49;
case WSAEACCES: return 13;
case WSAEMSGSIZE: return 40;
case WSAENETUNREACH: return 51;
case WSAECONNRESET: return 54;
case WSAENOBUFS: return 55;
case WSAETIMEDOUT: return 60;
case WSAECONNREFUSED: return 61;
case WSAEINTR: return 4;
case WSAEINVAL: return 22;
default: return 5;
}
#else
switch (errno) {
case EAGAIN: return 35;
case EADDRINUSE: return 48;
case EADDRNOTAVAIL: return 49;
case EMSGSIZE: return 40;
case ENETUNREACH: return 51;
case ECONNREFUSED: return 61;
default: return 5;
}
#endif
}
struct Socket {
NativeSocket value;
std::mutex modeMutex;
bool nonblocking = false;
explicit Socket(NativeSocket value) : value(value) {}
~Socket() {
if (value == Invalid) return;
#ifdef _WIN32
closesocket(value);
#else
::close(value);
#endif
}
};
std::mutex socketsMutex;
std::map<int, std::shared_ptr<Socket>> sockets;
int nextDescriptor = GuestSockets::FirstDescriptor;
std::shared_ptr<Socket> Lookup(int descriptor) {
std::lock_guard lock(socketsMutex);
const auto found = sockets.find(descriptor);
if (found != sockets.end()) return found->second;
Fail(9);
return {};
}
int Option(int guest) {
switch (guest) {
case 0x4: return SO_REUSEADDR;
case 0x20: return SO_BROADCAST;
case 0x1001: return SO_SNDBUF;
case 0x1002: return SO_RCVBUF;
default: return -1;
}
}
bool Address(const void* input, std::uint32_t length, sockaddr_storage& native, socklen_t& size) {
if (!input || length < 2) { Fail(14); return false; }
const auto* bytes = static_cast<const unsigned char*>(input);
if (bytes[1] == 2 && length >= 16 && bytes[0] == 16) {
auto& v4 = reinterpret_cast<sockaddr_in&>(native);
v4.sin_family = AF_INET;
std::memcpy(&v4.sin_port, bytes + 2, 2);
std::memcpy(&v4.sin_addr, bytes + 4, 4);
size = sizeof(v4);
return true;
}
if (bytes[1] == 28 && length >= 28 && bytes[0] == 28) {
auto& v6 = reinterpret_cast<sockaddr_in6&>(native);
v6.sin6_family = AF_INET6;
std::memcpy(&v6.sin6_port, bytes + 2, 2);
std::memcpy(&v6.sin6_flowinfo, bytes + 4, 4);
std::memcpy(&v6.sin6_addr, bytes + 8, 16);
std::memcpy(&v6.sin6_scope_id, bytes + 24, 4);
size = sizeof(v6);
return true;
}
Fail(47);
return false;
}
void GuestAddress(const sockaddr_storage& native, void* output, std::uint32_t* length) {
unsigned char bytes[28]{};
bytes[0] = native.ss_family == AF_INET ? 16 : 28;
bytes[1] = native.ss_family == AF_INET ? 2 : 28;
if (native.ss_family == AF_INET) {
const auto& v4 = reinterpret_cast<const sockaddr_in&>(native);
std::memcpy(bytes + 2, &v4.sin_port, 2);
std::memcpy(bytes + 4, &v4.sin_addr, 4);
} else {
const auto& v6 = reinterpret_cast<const sockaddr_in6&>(native);
std::memcpy(bytes + 2, &v6.sin6_port, 2);
std::memcpy(bytes + 4, &v6.sin6_flowinfo, 4);
std::memcpy(bytes + 8, &v6.sin6_addr, 16);
std::memcpy(bytes + 24, &v6.sin6_scope_id, 4);
}
std::memcpy(output, bytes, std::min<std::uint32_t>(*length, bytes[0]));
*length = bytes[0];
}
}
int GuestSockets::Close(int descriptor) {
std::lock_guard lock(socketsMutex);
return sockets.erase(descriptor) ? 0 : Fail(9);
}
extern "C" {
int APS5_VABI fcntl_nid_postfix(int descriptor, int command, ...) {
const auto socket = Lookup(descriptor);
if (!socket) return -1;
std::lock_guard lock(socket->modeMutex);
if (command == 3) return 2 | (socket->nonblocking ? 4 : 0); // F_GETFL, O_RDWR
if (command != 4) return Fail(22);
#ifdef _WIN32
__builtin_sysv_va_list arguments;
__builtin_sysv_va_start(arguments, command);
const int flags = __builtin_va_arg(arguments, int);
__builtin_sysv_va_end(arguments);
#else
std::va_list arguments;
va_start(arguments, command);
const int flags = va_arg(arguments, int);
va_end(arguments);
#endif
if ((flags & ~7) != 0) return Fail(45);
#ifdef _WIN32
unsigned long enabled = (flags & 4) != 0;
if (ioctlsocket(socket->value, FIONBIO, &enabled)) return Fail(NativeError());
#else
int enabled = (flags & 4) != 0;
if (::ioctl(socket->value, FIONBIO, &enabled)) return Fail(NativeError());
#endif
socket->nonblocking = enabled != 0;
return 0;
}
int APS5_VABI setsockopt_nid_postfix(int descriptor, int level, int option,
const void* value, std::uint32_t length) {
const auto socket = Lookup(descriptor);
if (!socket) return -1;
const int nativeOption = Option(option);
if (level != 0xffff || nativeOption == -1) return Fail(42);
if (!value || length != sizeof(int)) return Fail(22);
return ::setsockopt(socket->value, SOL_SOCKET, nativeOption,
static_cast<const char*>(value), sizeof(int)) ? Fail(NativeError()) : 0;
}
int APS5_VABI getsockopt_nid_postfix(int descriptor, int level, int option,
void* value, std::uint32_t* length) {
const auto socket = Lookup(descriptor);
if (!socket) return -1;
const int nativeOption = Option(option);
if (level != 0xffff || nativeOption == -1) return Fail(42);
if (!value || !length || *length < sizeof(int)) return Fail(22);
int result = 0;
socklen_t size = sizeof(result);
if (::getsockopt(socket->value, SOL_SOCKET, nativeOption, reinterpret_cast<char*>(&result), &size))
return Fail(NativeError());
std::memcpy(value, &result, sizeof(result));
*length = sizeof(result);
return 0;
}
int APS5_VABI socket_nid_postfix(int family, int type, int protocol) {
if (family != 2 && family != 28) return Fail(47);
if (type != 2 || (protocol != 0 && protocol != 17)) return Fail(43);
#ifdef _WIN32
static const int startup = [] { WSADATA data{}; return WSAStartup(MAKEWORD(2, 2), &data); }();
if (startup) return Fail(5);
#endif
const auto native = ::socket(family == 2 ? AF_INET : AF_INET6, SOCK_DGRAM, protocol);
if (native == Invalid) return Fail(NativeError());
Socket guard(native);
try {
auto socket = std::make_shared<Socket>(native);
guard.value = Invalid;
std::lock_guard lock(socketsMutex);
if (nextDescriptor == INT_MAX) return Fail(24);
const int descriptor = nextDescriptor++;
sockets.emplace(descriptor, std::move(socket));
return descriptor;
} catch (const std::bad_alloc&) {
return Fail(12);
}
}
int APS5_VABI bind_nid_postfix(int descriptor, const void* address, std::uint32_t length) {
const auto socket = Lookup(descriptor);
if (!socket) return -1;
sockaddr_storage native{};
socklen_t size;
if (!Address(address, length, native, size)) return -1;
return ::bind(socket->value, reinterpret_cast<sockaddr*>(&native), size) ? Fail(NativeError()) : 0;
}
int APS5_VABI getsockname_nid_postfix(int descriptor, void* address, std::uint32_t* length) {
const auto socket = Lookup(descriptor);
if (!socket) return -1;
if (!address || !length) return Fail(14);
sockaddr_storage native{};
socklen_t size = sizeof(native);
if (::getsockname(socket->value, reinterpret_cast<sockaddr*>(&native), &size)) return Fail(NativeError());
GuestAddress(native, address, length);
return 0;
}
int APS5_VABI ioctl_nid_postfix(int descriptor, std::uint64_t request, void* argument) {
const auto socket = Lookup(descriptor);
if (!socket) return -1;
if (!argument) return Fail(14);
if (request != 0x8004667e && request != 0x4004667f) return Fail(25);
std::lock_guard lock(socket->modeMutex);
unsigned long value = 0;
if (request == 0x8004667e) value = *static_cast<int*>(argument) != 0;
#ifdef _WIN32
const auto result = ioctlsocket(socket->value, request == 0x8004667e ? FIONBIO : FIONREAD, &value);
#else
int nativeValue = static_cast<int>(value);
const auto result = ::ioctl(socket->value, request == 0x8004667e ? FIONBIO : FIONREAD, &nativeValue);
value = static_cast<unsigned long>(nativeValue);
#endif
if (result) return Fail(NativeError());
if (request == 0x8004667e) socket->nonblocking = value != 0;
if (request == 0x4004667f) *static_cast<int*>(argument) = static_cast<int>(value);
return 0;
}
std::int64_t APS5_VABI sendto_nid_postfix(int descriptor, const void* buffer, std::uint64_t length,
int flags, const void* address, std::uint32_t addressLength) {
const auto socket = Lookup(descriptor);
if (!socket) return -1;
if (flags != 0) return Fail(45);
if (length > INT_MAX) return Fail(40);
if (!buffer && length) return Fail(14);
sockaddr_storage native{};
socklen_t size;
if (!Address(address, addressLength, native, size)) return -1;
const auto result = ::sendto(socket->value, static_cast<const char*>(buffer), static_cast<int>(length), 0,
reinterpret_cast<sockaddr*>(&native), size);
return result < 0 ? Fail(NativeError()) : result;
}
std::int64_t APS5_VABI recvfrom_nid_postfix(int descriptor, void* buffer, std::uint64_t length,
int flags, void* address, std::uint32_t* addressLength) {
const auto socket = Lookup(descriptor);
if (!socket) return -1;
if ((flags & ~2) != 0) return Fail(45);
if (length > INT_MAX) return Fail(40);
if ((!buffer && length) || (address && !addressLength)) return Fail(14);
sockaddr_storage native{};
socklen_t size = sizeof(native);
const auto result = ::recvfrom(socket->value, static_cast<char*>(buffer), static_cast<int>(length),
flags & 2 ? MSG_PEEK : 0, reinterpret_cast<sockaddr*>(&native), &size);
if (result < 0) return Fail(NativeError());
if (address) GuestAddress(native, address, addressLength);
return result;
}
}

View File

@ -1,91 +1,320 @@
#include <cstdint>
#include <cstddef>
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#else
#include <sched.h>
#include <unistd.h>
#endif
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/ApplicationHeap.hpp"
#include "prx/libc/include/Shutdown.hpp"
#include "prx/libkernel/DirectMemory/DirectMemory.hpp"
#include <array>
#include <atomic>
#include <cerrno>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <limits>
#include <memory>
#include <mutex>
#include <random>
#include <stdexcept>
#include <string>
#include <system_error>
#include <vector>
#ifndef _WIN32
#include <sys/resource.h>
#endif
static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast<int>(0x80020016);
namespace {
constexpr int sceInvalidArgument = static_cast<int>(0x80020016u);
std::atomic<std::uint32_t> gpoBits{0};
constexpr std::array<std::uint8_t, 16> openPsId{'A', 'n', 'y', 'P', 'S', '5', 'O', 'p', 'e', 'n', 'P', 's', 'I', 'd', 0, 1};
class ProcessArguments {
public:
ProcessArguments() {
#ifdef _WIN32
std::array<char, 32768> path{};
const auto size = GetModuleFileNameA(nullptr, path.data(), static_cast<DWORD>(path.size()));
if (size == 0)
throw std::system_error(GetLastError(), std::system_category(), "Reading executable path");
if (size >= path.size())
throw std::runtime_error("Executable path exceeds the guest argument buffer");
arguments.emplace_back(path.data(), size);
#else
std::ifstream stream("/proc/self/cmdline", std::ios::binary);
if (!stream)
throw std::runtime_error("Cannot read process arguments");
std::string argument;
while (std::getline(stream, argument, '\0')) {
if (stream.eof())
throw std::runtime_error("Unterminated process argument");
arguments.push_back(argument);
}
if (stream.bad() || !stream.eof())
throw std::runtime_error("Reading process arguments failed");
#endif
if (arguments.empty() || arguments.size() > static_cast<std::size_t>(std::numeric_limits<int>::max()))
throw std::runtime_error("Invalid process argument count");
for (const auto& argument : arguments)
pointers.push_back(argument.c_str());
pointers.push_back(nullptr);
}
int GetCount() const { return static_cast<int>(arguments.size()); }
const char** GetValues() { return pointers.data(); }
private:
std::vector<std::string> arguments;
std::vector<const char*> pointers;
};
ProcessArguments& getProcessArguments() {
static ProcessArguments arguments;
return arguments;
}
void validateSchedulingPolicy(int policy) {
if (policy != 1 && policy != 3)
throw std::invalid_argument("Unsupported guest scheduling policy");
}
#ifdef _WIN32
void syncVolumes() {
std::array<wchar_t, 32768> name{};
const auto first = FindFirstVolumeW(name.data(), static_cast<DWORD>(name.size()));
if (first == INVALID_HANDLE_VALUE)
throw std::system_error(GetLastError(), std::system_category(), "Enumerating volumes for sync");
const std::unique_ptr<void, decltype(&FindVolumeClose)> search(first, &FindVolumeClose);
for (;;) {
DWORD flags = 0;
if (!GetVolumeInformationW(name.data(), nullptr, 0, nullptr, nullptr, &flags, nullptr, 0))
throw std::system_error(GetLastError(), std::system_category(), "Reading volume properties for sync");
if ((flags & FILE_READ_ONLY_VOLUME) == 0) {
std::wstring path(name.data());
if (path.empty() || path.back() != L'\\')
throw std::runtime_error("Invalid volume path for sync");
path.pop_back();
const auto native = CreateFileW(path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
if (native == INVALID_HANDLE_VALUE)
throw std::system_error(GetLastError(), std::system_category(), "Opening volume for sync");
const std::unique_ptr<void, decltype(&CloseHandle)> volume(native, &CloseHandle);
if (!FlushFileBuffers(volume.get()))
throw std::system_error(GetLastError(), std::system_category(), "Flushing volume");
}
if (FindNextVolumeW(search.get(), name.data(), static_cast<DWORD>(name.size())))
continue;
const auto error = GetLastError();
if (error != ERROR_NO_MORE_FILES)
throw std::system_error(error, std::system_category(), "Enumerating volumes for sync");
break;
}
}
#endif
}
struct GuestResourceUsage {
KernelTimeval ru_utime;
KernelTimeval ru_stime;
std::int64_t ru_maxrss;
std::int64_t ru_ixrss;
std::int64_t ru_idrss;
std::int64_t ru_isrss;
std::int64_t ru_minflt;
std::int64_t ru_majflt;
std::int64_t ru_nswap;
std::int64_t ru_inblock;
std::int64_t ru_oublock;
std::int64_t ru_msgsnd;
std::int64_t ru_msgrcv;
std::int64_t ru_nsignals;
std::int64_t ru_nvcsw;
std::int64_t ru_nivcsw;
};
extern "C" {
// unknown data
const char* __progname_nid_postfix = "eboot.bin";
int APS5_VABI getargc_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
return getProcessArguments().GetCount();
}
const char** APS5_VABI getargv_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return nullptr;
return getProcessArguments().GetValues();
}
int APS5_VABI getpagesize_nid_postfix(void) {
return 0x4000;
return PS5_PAGE_SIZE;
}
int APS5_VABI getpid_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
#ifdef _WIN32
const auto pid = GetCurrentProcessId();
#else
const auto pid = ::getpid();
#endif
if (pid == 0 || static_cast<std::uint64_t>(pid) > static_cast<std::uint64_t>(std::numeric_limits<int>::max()))
throw std::runtime_error("Process identifier is outside the guest range");
return static_cast<int>(pid);
}
void APS5_VABI exit_nid_postfix(int code) {
(void)code;
NotImplemented_nid_no_patch(__func__);
LibcExit_nid_no_patch(code);
}
[[noreturn]] void APS5_VABI _exit_nid_postfix(int status) {
std::_Exit(status);
}
int APS5_VABI sceKernelGetCurrentCpu(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
#ifdef _WIN32
PROCESSOR_NUMBER processor{};
GetCurrentProcessorNumberEx(&processor);
unsigned index = processor.Number;
for (WORD group = 0; group < processor.Group; ++group) {
const auto count = GetActiveProcessorCount(group);
if (count == 0)
throw std::system_error(GetLastError(), std::system_category(), "Reading processor group size");
index += count;
}
return static_cast<int>(index);
#else
const int cpu = ::sched_getcpu();
if (cpu < 0)
throw std::system_error(errno, std::generic_category(), "Reading current processor");
return cpu;
#endif
}
uint64_t APS5_VABI sceKernelGetGPI(void) {
std::uint64_t APS5_VABI sceKernelGetGPI(void) {
return gpoBits.load(std::memory_order_relaxed);
}
void APS5_VABI sceKernelSetGPO(std::uint32_t bits) {
gpoBits.store(bits, std::memory_order_relaxed);
}
int APS5_VABI sceKernelGetOpenPsId(void* openPsIdOutput) {
if (!openPsIdOutput)
throw std::invalid_argument("sceKernelGetOpenPsId: null output");
std::memcpy(openPsIdOutput, openPsId.data(), openPsId.size());
return 0;
}
void APS5_VABI sceKernelSetGPO(uint32_t bits) {
(void)bits;
}
int APS5_VABI sceKernelGetOpenPsId(void* open_ps_id) {
(void)open_ps_id;
NotImplemented_nid_no_patch(__func__);
return 0;
}
void* APS5_VABI sceKernelGetProcParam(void) {
NotImplemented_nid_no_patch(__func__);
return nullptr;
return const_cast<void*>(ApplicationProcessParameters_nid_no_patch());
}
int APS5_VABI sceKernelUuidCreate(uint8_t* uuid) {
if (!uuid) return SCE_KERNEL_ERROR_EINVAL;
static std::mutex lock;
static std::mt19937_64 generator{std::random_device{}()};
std::lock_guard guard(lock);
for (int index = 0; index < 16; index += 8) {
const uint64_t bits = generator();
std::memcpy(uuid + index, &bits, 8);
}
uuid[6] = static_cast<uint8_t>((uuid[6] & 0x0F) | 0x40);
uuid[8] = static_cast<uint8_t>((uuid[8] & 0x3F) | 0x80);
int APS5_VABI sceKernelUuidCreate(std::uint32_t* uuid) {
if (!uuid) return sceInvalidArgument;
static thread_local std::random_device device;
std::uniform_int_distribution<std::uint32_t> distribution;
std::array<std::uint32_t, 4> value;
for (auto& word : value)
word = distribution(device);
value[1] = (value[1] & 0x0fffffffu) | 0x40000000u;
value[2] = (value[2] & 0xffffff3fu) | 0x80u;
std::memcpy(uuid, value.data(), sizeof(value));
return 0;
}
void APS5_VABI sceKernelSync(void) {
NotImplemented_nid_no_patch(__func__);
#ifdef _WIN32
syncVolumes();
#else
::sync();
#endif
}
int APS5_VABI sched_get_priority_max_nid_postfix(int policy) {
(void)policy;
NotImplemented_nid_no_patch(__func__);
return 0;
validateSchedulingPolicy(policy);
return 256;
}
int APS5_VABI sched_get_priority_min_nid_postfix(int policy) {
(void)policy;
NotImplemented_nid_no_patch(__func__);
return 0;
validateSchedulingPolicy(policy);
return 767;
}
int APS5_VABI getrusage_nid_postfix(int who, GuestResourceUsage* usage) {
if (usage == nullptr)
throw std::invalid_argument("getrusage: usage is null");
if (who != 0 && who != 1)
throw std::invalid_argument("getrusage: unsupported who");
#ifdef _WIN32
FILETIME creation{};
FILETIME exitTime{};
FILETIME kernel{};
FILETIME user{};
if (who == 0) {
if (!GetProcessTimes(GetCurrentProcess(), &creation, &exitTime, &kernel, &user))
throw std::system_error(GetLastError(), std::system_category(), "getrusage: GetProcessTimes failed");
} else {
if (!GetThreadTimes(GetCurrentThread(), &creation, &exitTime, &kernel, &user))
throw std::system_error(GetLastError(), std::system_category(), "getrusage: GetThreadTimes failed");
}
const auto toMicros = [](const FILETIME& time) {
return ((static_cast<std::uint64_t>(time.dwHighDateTime) << 32) | time.dwLowDateTime) / 10ULL;
};
const auto userMicros = toMicros(user);
const auto kernelMicros = toMicros(kernel);
usage->ru_utime.tv_sec = static_cast<std::int64_t>(userMicros / 1000000ULL);
usage->ru_utime.tv_usec = static_cast<std::int64_t>(userMicros % 1000000ULL);
usage->ru_stime.tv_sec = static_cast<std::int64_t>(kernelMicros / 1000000ULL);
usage->ru_stime.tv_usec = static_cast<std::int64_t>(kernelMicros % 1000000ULL);
usage->ru_maxrss = 0;
usage->ru_ixrss = 0;
usage->ru_idrss = 0;
usage->ru_isrss = 0;
usage->ru_minflt = 0;
usage->ru_majflt = 0;
usage->ru_nswap = 0;
usage->ru_inblock = 0;
usage->ru_oublock = 0;
usage->ru_msgsnd = 0;
usage->ru_msgrcv = 0;
usage->ru_nsignals = 0;
usage->ru_nvcsw = 0;
usage->ru_nivcsw = 0;
#else
if (who == 1)
throw std::invalid_argument("getrusage: RUSAGE_THREAD is not supported on this platform");
struct rusage native{};
if (::getrusage(RUSAGE_SELF, &native) != 0)
throw std::system_error(errno, std::generic_category(), "getrusage: getrusage failed");
usage->ru_utime.tv_sec = static_cast<std::int64_t>(native.ru_utime.tv_sec);
usage->ru_utime.tv_usec = static_cast<std::int64_t>(native.ru_utime.tv_usec);
usage->ru_stime.tv_sec = static_cast<std::int64_t>(native.ru_stime.tv_sec);
usage->ru_stime.tv_usec = static_cast<std::int64_t>(native.ru_stime.tv_usec);
usage->ru_maxrss = static_cast<std::int64_t>(native.ru_maxrss);
usage->ru_ixrss = static_cast<std::int64_t>(native.ru_ixrss);
usage->ru_idrss = static_cast<std::int64_t>(native.ru_idrss);
usage->ru_isrss = static_cast<std::int64_t>(native.ru_isrss);
usage->ru_minflt = static_cast<std::int64_t>(native.ru_minflt);
usage->ru_majflt = static_cast<std::int64_t>(native.ru_majflt);
usage->ru_nswap = static_cast<std::int64_t>(native.ru_nswap);
usage->ru_inblock = static_cast<std::int64_t>(native.ru_inblock);
usage->ru_oublock = static_cast<std::int64_t>(native.ru_oublock);
usage->ru_msgsnd = static_cast<std::int64_t>(native.ru_msgsnd);
usage->ru_msgrcv = static_cast<std::int64_t>(native.ru_msgrcv);
usage->ru_nsignals = static_cast<std::int64_t>(native.ru_nsignals);
usage->ru_nvcsw = static_cast<std::int64_t>(native.ru_nvcsw);
usage->ru_nivcsw = static_cast<std::int64_t>(native.ru_nivcsw);
#endif
return 0;
}
int APS5_VABI sceKernelIsTrinityMode(void) {

View File

@ -1,13 +1,37 @@
#include <cstdint>
#include <cstddef>
#include <array>
#include <cstring>
#include <mutex>
#include <stdexcept>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/ApplicationHeap.hpp"
#include "prx/libkernel/Pthread/include/ThreadLifecycle.hpp"
namespace {
std::mutex rtldHeapMutex;
std::array<void*, 10> rtldHeapApi{};
void registerApplicationHeapApi(void* const* api) {
if (api == nullptr)
throw std::invalid_argument("RTLD application heap: null allocator API");
std::array<void*, 10> replacement;
std::memcpy(replacement.data(), api, sizeof(replacement));
if (replacement[0] == nullptr || replacement[1] == nullptr)
throw std::invalid_argument("RTLD application heap: malloc and free are required");
std::lock_guard lock(rtldHeapMutex);
if (rtldHeapApi[0] != nullptr && rtldHeapApi != replacement)
throw std::runtime_error("RTLD application heap: cannot replace an active allocator");
rtldHeapApi = replacement;
}
}
extern "C" {
void APS5_VABI sceKernelRtldSetApplicationHeapAPI(void* api[]) {
ApplicationHeapRegister_nid_no_patch(api);
registerApplicationHeapApi(api);
}
int APS5_VABI sceKernelRtldThreadAtexitDecrement(uint64_t* c) {
@ -23,18 +47,47 @@ int APS5_VABI sceKernelRtldThreadAtexitIncrement(uint64_t* c) {
}
void APS5_VABI sceKernelSetThreadAtexitCount(get_thread_atexit_count_func_t func) {
(void)func;
NotImplemented_nid_no_patch(__func__);
ThreadLifecycle::SetThreadAtexitCount(func);
}
void APS5_VABI sceKernelSetThreadAtexitReport(thread_atexit_report_func_t func) {
(void)func;
NotImplemented_nid_no_patch(__func__);
ThreadLifecycle::SetThreadAtexitReport(func);
}
void APS5_VABI sceKernelSetThreadDtors(thread_dtors_func_t dtors) {
(void)dtors;
NotImplemented_nid_no_patch(__func__);
ThreadLifecycle::SetThreadDtors(dtors);
}
}
extern "C" {
void APS5_VABI _sceKernelRtldSetApplicationHeapAPI_nid_postfix(void* api[]) {
registerApplicationHeapApi(api);
}
int APS5_VABI _sceKernelRtldThreadAtexitDecrement_nid_postfix(std::uint64_t* counter) {
(void)counter;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI _sceKernelRtldThreadAtexitIncrement_nid_postfix(std::uint64_t* counter) {
(void)counter;
NotImplemented_nid_no_patch(__func__);
return 0;
}
void APS5_VABI _sceKernelSetThreadAtexitCount_nid_postfix(get_thread_atexit_count_func_t callback) {
ThreadLifecycle::SetThreadAtexitCount(callback);
}
void APS5_VABI _sceKernelSetThreadAtexitReport_nid_postfix(thread_atexit_report_func_t callback) {
ThreadLifecycle::SetThreadAtexitReport(callback);
}
void APS5_VABI _sceKernelSetThreadDtors_nid_postfix(thread_dtors_func_t callback) {
ThreadLifecycle::SetThreadDtors(callback);
}
}

View File

@ -1,23 +1,19 @@
#include <cstdint>
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
extern "C" {
int APS5_VABI sceKernelIsAddressSanitizerEnabled(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
return 0;
}
MallocReplace* APS5_VABI sceKernelGetSanitizerMallocReplaceExternal(void) {
NotImplemented_nid_no_patch(__func__);
return nullptr;
static MallocReplace replacement{};
return &replacement;
}
NewReplace* APS5_VABI sceKernelGetSanitizerNewReplaceExternal(void) {
NotImplemented_nid_no_patch(__func__);
return nullptr;
static NewReplace replacement{};
return &replacement;
}
}

View File

@ -0,0 +1,97 @@
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <atomic>
#include <csignal>
#include <cstdint>
#include <mutex>
extern "C" int* APS5_VABI __error_nid_postfix();
namespace {
using GuestHandler = void (APS5_VABI *)(int);
std::atomic<GuestHandler> handlers[32]{};
static_assert(std::atomic<GuestHandler>::is_always_lock_free);
std::atomic<std::uint32_t> blockedMask{0};
static_assert(std::atomic<std::uint32_t>::is_always_lock_free);
std::mutex registration;
int NativeSignal(int guest) {
switch (guest) {
case 2: return SIGINT;
case 4: return SIGILL;
case 6: return SIGABRT;
case 8: return SIGFPE;
case 11: return SIGSEGV;
case 15: return SIGTERM;
default: return 0;
}
}
void Dispatch(int native) {
int guest = 0;
for (int candidate : {2, 4, 6, 8, 11, 15})
if (NativeSignal(candidate) == native) { guest = candidate; break; }
if (!guest) return;
#ifdef _WIN32
// Preserve the guest's persistent registration across CRT delivery.
std::signal(native, Dispatch);
#endif
if ((blockedMask.load() & (1u << guest)) != 0) return;
const auto callback = handlers[guest].load();
if (reinterpret_cast<std::uintptr_t>(callback) > 1) callback(guest);
}
}
struct GuestSignalSet {
std::uint32_t bits[4];
};
extern "C" {
GuestHandler APS5_VABI signal_nid_postfix(int guest, GuestHandler handler) {
const auto invalid = reinterpret_cast<GuestHandler>(static_cast<std::uintptr_t>(-1));
const int native = NativeSignal(guest);
if (!native || handler == invalid) { *__error_nid_postfix() = 22; return invalid; }
std::lock_guard lock(registration);
const auto previous = handlers[guest].exchange(handler);
const auto address = reinterpret_cast<std::uintptr_t>(handler);
auto hostHandler = address == 0 ? SIG_DFL : address == 1 ? SIG_IGN : Dispatch;
if (std::signal(native, hostHandler) == SIG_ERR) {
handlers[guest].store(previous);
*__error_nid_postfix() = 22;
return invalid;
}
return previous;
}
int APS5_VABI raise_nid_postfix(int guest) {
const int native = NativeSignal(guest);
if (!native) { *__error_nid_postfix() = 22; return -1; }
const int result = std::raise(native);
if (result) *__error_nid_postfix() = 22;
return result ? -1 : 0;
}
int APS5_VABI _sigprocmask_nid_postfix(int how, const GuestSignalSet* set, GuestSignalSet* previousSet) {
std::lock_guard lock(registration);
if (previousSet != nullptr) {
previousSet->bits[0] = blockedMask.load();
previousSet->bits[1] = 0;
previousSet->bits[2] = 0;
previousSet->bits[3] = 0;
}
if (set != nullptr) {
switch (how) {
case 1: blockedMask.fetch_or(set->bits[0]); break;
case 2: blockedMask.fetch_and(~set->bits[0]); break;
case 3: blockedMask.store(set->bits[0]); break;
default: throw std::invalid_argument("_sigprocmask: invalid how");
}
}
return 0;
}
}
extern "C" {
int APS5_VABI _is_signal_return_nid_postfix(std::uint64_t programCounter) {
(void)programCounter;
NotImplemented_nid_no_patch(__func__);
return 0;
}
}

View File

@ -0,0 +1,51 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libkernel/DirectMemory/DirectMemory.hpp"
#include <cstdint>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <unistd.h>
#endif
extern "C" int* APS5_VABI __error_nid_postfix();
extern "C" {
// Guest long is 64 bits, including when the Windows host long is 32 bits.
std::int64_t APS5_VABI sysconf_nid_postfix(int name) {
const int saved = *__error_nid_postfix();
std::int64_t result = -1;
switch (name) {
case 47: // _SC_PAGESIZE
result = PS5_PAGE_SIZE;
break;
case 57: // _SC_NPROCESSORS_CONF
case 58: // _SC_NPROCESSORS_ONLN
#ifdef _WIN32
result = GetActiveProcessorCount(ALL_PROCESSOR_GROUPS);
#else
result = ::sysconf(name == 57 ? _SC_NPROCESSORS_CONF : _SC_NPROCESSORS_ONLN);
#endif
break;
case 121: { // _SC_PHYS_PAGES: report host physical capacity in guest pages
#ifdef _WIN32
MEMORYSTATUSEX memory{};
memory.dwLength = sizeof(memory);
if (GlobalMemoryStatusEx(&memory)) result = memory.ullTotalPhys / PS5_PAGE_SIZE;
#else
const auto pages = ::sysconf(_SC_PHYS_PAGES);
const auto pageSize = ::sysconf(_SC_PAGESIZE);
if (pages > 0 && pageSize > 0)
result = static_cast<std::uint64_t>(pages) * pageSize / PS5_PAGE_SIZE;
#endif
break;
}
default:
*__error_nid_postfix() = 22;
return -1;
}
if (result <= 0) { *__error_nid_postfix() = 5; return -1; }
*__error_nid_postfix() = saved;
return result;
}
}

View File

@ -241,12 +241,16 @@ int APS5_VABI nanosleep_nid_postfix(const KernelTimespec* rqtp, KernelTimespec*
return sceKernelNanosleep(rqtp, rmtp);
}
int APS5_VABI _nanosleep_nid_postfix(const KernelTimespec* rqtp, KernelTimespec* rmtp) {
return sceKernelNanosleep(rqtp, rmtp);
}
int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) {
if (tp == nullptr) {
APS5_INVALID_ARG_EX;
}
#ifdef _WIN32
if (clockId == 0 || clockId == 9 || clockId == 10) {
if (clockId == 0 || clockId == 9) {
FILETIME ft{};
GetSystemTimePreciseAsFileTime(&ft);
std::uint64_t t = (static_cast<std::uint64_t>(ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
@ -256,6 +260,16 @@ int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) {
tp->tv_nsec = static_cast<std::int64_t>(t % 1000000000ULL);
return 0;
}
if (clockId == 10 || clockId == 13) {
FILETIME ft{};
GetSystemTimeAsFileTime(&ft);
std::uint64_t t = (static_cast<std::uint64_t>(ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
t -= 116444736000000000ULL;
t *= 100ULL;
tp->tv_sec = static_cast<std::int64_t>(t / 1000000000ULL);
tp->tv_nsec = static_cast<std::int64_t>(t % 1000000000ULL);
return 0;
}
if (clockId == 4 || clockId == 1 || clockId == 5 || clockId == 7 || clockId == 8 || clockId == 11 || clockId == 12) {
std::uint64_t nanos = GetMonotonicNanos();
tp->tv_sec = static_cast<std::int64_t>(nanos / 1000000000ULL);
@ -268,9 +282,16 @@ int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) {
switch (clockId) {
case 0:
case 9:
case 10:
nativeId = CLOCK_REALTIME;
break;
case 10:
case 13:
#ifdef CLOCK_REALTIME_COARSE
nativeId = CLOCK_REALTIME_COARSE;
#else
nativeId = CLOCK_REALTIME;
#endif
break;
case 4:
case 7:
case 11:
@ -336,7 +357,7 @@ int APS5_VABI clock_getres_nid_postfix(int clockId, KernelTimespec* res) {
res->tv_nsec = 100LL;
return 0;
}
if (clockId == 4 || clockId == 1 || clockId == 5 || clockId == 7 || clockId == 8 || clockId == 10 || clockId == 11 || clockId == 12) {
if (clockId == 4 || clockId == 1 || clockId == 5 || clockId == 7 || clockId == 8 || clockId == 10 || clockId == 11 || clockId == 12 || clockId == 13) {
static const std::uint64_t freq = [] {
LARGE_INTEGER f{};
QueryPerformanceFrequency(&f);
@ -353,9 +374,16 @@ int APS5_VABI clock_getres_nid_postfix(int clockId, KernelTimespec* res) {
switch (clockId) {
case 0:
case 9:
case 10:
nativeId = CLOCK_REALTIME;
break;
case 10:
case 13:
#ifdef CLOCK_REALTIME_COARSE
nativeId = CLOCK_REALTIME_COARSE;
#else
nativeId = CLOCK_REALTIME;
#endif
break;
case 4:
case 7:
case 11:

View File

@ -0,0 +1,34 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <array>
#include <cstdint>
#include <cstdlib>
#include <cstring>
extern "C" {
int APS5_VABI __inet_pton_nid_postfix(int, const char*, void*);
const char* APS5_VABI __inet_ntop_nid_postfix(int, const void*, char*, std::uint32_t);
int* APS5_VABI __error_nid_postfix();
}
static void Require(bool value) { if (!value) std::abort(); }
int main() {
for (int family : {2, 28}) {
const char* input = family == 2 ? "192.0.2.17" : "2001:db8::17";
std::array<unsigned char, 16> address{};
Require(__inet_pton_nid_postfix(family, input, address.data()) == 1);
char output[64]{};
Require(__inet_ntop_nid_postfix(family, address.data(), output, sizeof(output)) == output);
Require(std::strcmp(input, output) == 0);
output[0] = 'x';
Require(__inet_ntop_nid_postfix(family, address.data(), output, 1) == nullptr);
Require(*__error_nid_postfix() == 28 && output[0] == 'x');
const auto original = address;
Require(__inet_pton_nid_postfix(family, "not-an-address", address.data()) == 0);
Require(address == original);
}
std::array<unsigned char, 16> address{};
Require(__inet_pton_nid_postfix(10, "::1", address.data()) == -1);
Require(*__error_nid_postfix() == 47); // Linux AF_INET6 is not PS5 AF_INET6
Require(__inet_pton_nid_postfix(2, "256.1.2.3", address.data()) == 0);
Require(__inet_pton_nid_postfix(28, "::ffff:192.0.2.17", address.data()) == 1);
Require(address[10] == 255 && address[11] == 255 && address[12] == 192 && address[15] == 17);
}

View File

@ -0,0 +1,26 @@
#include "prx/libSceVideoOut/include/BufferReuseTracker.hpp"
#include <cstdlib>
static void Require(bool value) { if (!value) std::abort(); }
int main() {
BufferReuseTracker buffer;
Require(buffer.IsComplete(buffer.Capture()));
const auto first = buffer.Reserve();
const auto second = buffer.Reserve();
const auto fence = buffer.Capture();
const auto future = buffer.Reserve();
Require(!buffer.IsComplete(fence));
buffer.Complete(second); // Out-of-order retirement must not release first.
Require(!buffer.IsComplete(fence));
buffer.Complete(first);
Require(buffer.IsComplete(fence)); // Later reservations do not block this wait.
Require(!buffer.IsComplete(buffer.Capture()));
buffer.Complete(future); // Cancellation uses the same retirement operation.
Require(buffer.IsComplete(buffer.Capture()));
const auto next = buffer.Reserve();
Require(next > future && buffer.IsComplete(fence));
buffer.Complete(next);
bool rejected = false;
try { buffer.IsComplete(next + 1); }
catch (const std::invalid_argument&) { rejected = true; }
Require(rejected);
}

View File

@ -0,0 +1,45 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cctype>
#include <clocale>
#include <cstdio>
#include <cstdlib>
extern "C" {
int APS5_VABI isupper_nid_postfix(int);
int APS5_VABI islower_nid_postfix(int);
int APS5_VABI isalpha_nid_postfix(int);
int APS5_VABI isdigit_nid_postfix(int);
int APS5_VABI isalnum_nid_postfix(int);
int APS5_VABI isspace_nid_postfix(int);
int APS5_VABI isblank_nid_postfix(int);
int APS5_VABI iscntrl_nid_postfix(int);
int APS5_VABI isprint_nid_postfix(int);
int APS5_VABI isgraph_nid_postfix(int);
int APS5_VABI ispunct_nid_postfix(int);
int APS5_VABI isxdigit_nid_postfix(int);
int APS5_VABI toupper_nid_postfix(int);
int APS5_VABI tolower_nid_postfix(int);
const short* APS5_VABI _Getptolower_nid_postfix();
const short* APS5_VABI _Getptoupper_nid_postfix();
}
static void Require(bool value) { if (!value) std::abort(); }
int main() {
Require(std::setlocale(LC_CTYPE, "C") != nullptr);
for (int c = EOF; c <= 255; ++c) {
Require(bool(isupper_nid_postfix(c)) == bool(std::isupper(c)));
Require(bool(islower_nid_postfix(c)) == bool(std::islower(c)));
Require(bool(isalpha_nid_postfix(c)) == bool(std::isalpha(c)));
Require(bool(isdigit_nid_postfix(c)) == bool(std::isdigit(c)));
Require(bool(isalnum_nid_postfix(c)) == bool(std::isalnum(c)));
Require(bool(isspace_nid_postfix(c)) == bool(std::isspace(c)));
Require(bool(isblank_nid_postfix(c)) == bool(std::isblank(c)));
Require(bool(iscntrl_nid_postfix(c)) == bool(std::iscntrl(c)));
Require(bool(isprint_nid_postfix(c)) == bool(std::isprint(c)));
Require(bool(isgraph_nid_postfix(c)) == bool(std::isgraph(c)));
Require(bool(ispunct_nid_postfix(c)) == bool(std::ispunct(c)));
Require(bool(isxdigit_nid_postfix(c)) == bool(std::isxdigit(c)));
Require(toupper_nid_postfix(c) == std::toupper(c));
Require(tolower_nid_postfix(c) == std::tolower(c));
Require(toupper_nid_postfix(c) == _Getptoupper_nid_postfix()[c]);
Require(tolower_nid_postfix(c) == _Getptolower_nid_postfix()[c]);
}
}

View File

@ -0,0 +1,52 @@
#include "prx/libc/include/GuestDirectory.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <chrono>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <map>
#include <string>
extern "C" {
void* APS5_VABI opendir_nid_postfix(const char*);
GuestDirectoryEntry* APS5_VABI readdir_nid_postfix(void*);
int APS5_VABI closedir_nid_postfix(void*);
void APS5_VABI rewinddir_nid_postfix(void*);
int* APS5_VABI __error_nid_postfix();
}
static void Require(bool value) { if (!value) std::abort(); }
int main() {
const auto root = std::filesystem::path("anyps5-directory-test-" +
std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
Require(std::filesystem::create_directory(root));
Require(std::filesystem::create_directory(root / "subdirectory"));
{ std::ofstream file(root / "sample.txt"); file << "test"; }
void* directory = opendir_nid_postfix(root.string().c_str());
Require(directory != nullptr);
std::map<std::string, int> entries;
*__error_nid_postfix() = 13;
while (auto* entry = readdir_nid_postfix(directory)) {
Require(entry->nameLength == std::strlen(entry->name));
Require(entry->recordLength == 8 + ((entry->nameLength + 4) & ~3));
Require(entry->recordLength <= sizeof(*entry));
entries[entry->name] = entry->type;
}
Require(*__error_nid_postfix() == 13); // EOF preserves errno.
Require(entries.at("sample.txt") == 8);
Require(entries.at("subdirectory") == 4);
Require(entries.at(".") == 4 && entries.at("..") == 4);
rewinddir_nid_postfix(directory);
std::size_t count = 0;
while (readdir_nid_postfix(directory)) ++count;
Require(count == entries.size());
Require(closedir_nid_postfix(directory) == 0);
Require(opendir_nid_postfix((root / "missing").string().c_str()) == nullptr);
Require(*__error_nid_postfix() == 2);
Require(opendir_nid_postfix((root / "sample.txt").string().c_str()) == nullptr);
Require(*__error_nid_postfix() == 20);
Require(opendir_nid_postfix("") == nullptr && *__error_nid_postfix() == 2);
Require(closedir_nid_postfix(nullptr) == -1 && *__error_nid_postfix() == 9);
std::filesystem::remove(root / "sample.txt");
std::filesystem::remove(root / "subdirectory");
std::filesystem::remove(root);
}

View File

@ -0,0 +1,40 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdlib>
#include <thread>
extern "C" {
void* APS5_VABI dlopen_nid_postfix(const char*, int);
void* APS5_VABI dlsym_nid_postfix(void*, const char*);
int APS5_VABI dlclose_nid_postfix(void*);
char* APS5_VABI dlerror_nid_postfix();
}
static void Require(bool value) { if (!value) std::abort(); }
int main(int argc, char** argv) {
Require(argc == 2);
Require(dlerror_nid_postfix() == nullptr);
Require(dlopen_nid_postfix(argv[1], 0x2000) == nullptr);
Require(dlerror_nid_postfix() != nullptr);
Require(dlerror_nid_postfix() == nullptr);
Require(dlopen_nid_postfix("anyps5-missing-module-for-test.prx", 2) == nullptr);
Require(dlerror_nid_postfix() != nullptr);
void* executable = dlopen_nid_postfix(nullptr, 2);
Require(executable != nullptr && dlclose_nid_postfix(executable) == 0);
void* module = dlopen_nid_postfix(argv[1], 2 | 0x100);
Require(module != nullptr);
using Add = int (APS5_VABI *)(int, int);
auto add = reinterpret_cast<Add>(dlsym_nid_postfix(module, "GuestModuleAdd"));
Require(add && add(17, 25) == 42);
Require(dlsym_nid_postfix(reinterpret_cast<void*>(-2), "GuestModuleAdd") == reinterpret_cast<void*>(add));
Require(dlsym_nid_postfix(module, "missing_symbol") == nullptr);
std::thread other([] { Require(dlerror_nid_postfix() == nullptr); });
other.join();
Require(dlerror_nid_postfix() != nullptr);
Require(dlerror_nid_postfix() == nullptr);
void* second = dlopen_nid_postfix(argv[1], 1);
Require(second && second != module);
Require(dlclose_nid_postfix(module) == 0);
Require(dlsym_nid_postfix(module, "GuestModuleAdd") == nullptr);
add = reinterpret_cast<Add>(dlsym_nid_postfix(second, "GuestModuleAdd"));
Require(add && add(2, 3) == 5);
Require(dlclose_nid_postfix(second) == 0);
Require(dlclose_nid_postfix(second) == -1);
}

View File

@ -0,0 +1,46 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdlib>
#include <cstring>
extern "C" {
char* APS5_VABI getenv_nid_postfix(const char*);
int APS5_VABI setenv_nid_postfix(const char*, const char*, int);
int APS5_VABI unsetenv_nid_postfix(const char*);
int APS5_VABI putenv_nid_postfix(char*);
int* APS5_VABI __error_nid_postfix();
}
static void Require(bool value) { if (!value) std::abort(); }
int main() {
const char* key = "ANYPS5_GUEST_ENV_TEST_4C27";
#ifdef _WIN32
Require(_putenv_s(key, "inherited") == 0);
#else
Require(::setenv(key, "inherited", 1) == 0);
#endif
Require(std::strcmp(getenv_nid_postfix(key), "inherited") == 0);
Require(setenv_nid_postfix(key, "kept out", 0) == 0);
Require(std::strcmp(getenv_nid_postfix(key), "inherited") == 0);
char value[] = "copied";
Require(setenv_nid_postfix(key, value, 1) == 0);
value[0] = 'X';
Require(std::strcmp(getenv_nid_postfix(key), "copied") == 0);
Require(std::strcmp(std::getenv(key), "inherited") == 0);
Require(setenv_nid_postfix(key, "", 1) == 0);
Require(getenv_nid_postfix(key) && *getenv_nid_postfix(key) == 0);
char borrowed[] = "ANYPS5_GUEST_ENV_TEST_4C27=one";
Require(putenv_nid_postfix(borrowed) == 0);
char* position = std::strchr(borrowed, '=') + 1;
Require(getenv_nid_postfix(key) == position);
std::memcpy(position, "two", 3);
Require(std::strcmp(getenv_nid_postfix(key), "two") == 0);
Require(setenv_nid_postfix("anyps5_guest_env_test_4c27", "lower", 1) == 0);
Require(std::strcmp(getenv_nid_postfix(key), "two") == 0);
Require(setenv_nid_postfix("bad=name", "x", 1) == -1);
Require(*__error_nid_postfix() == 22);
Require(unsetenv_nid_postfix("") == -1);
char invalid[] = "no-separator";
Require(putenv_nid_postfix(invalid) == -1);
Require(unsetenv_nid_postfix(key) == 0);
Require(getenv_nid_postfix(key) == nullptr);
Require(unsetenv_nid_postfix(key) == 0);
Require(unsetenv_nid_postfix("anyps5_guest_env_test_4c27") == 0);
}

View File

@ -0,0 +1,37 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdlib>
#include <cstring>
#include <thread>
extern "C" {
char* APS5_VABI strerror_nid_postfix(int);
int APS5_VABI strerror_r_nid_postfix(int, char*, std::size_t);
int* APS5_VABI __error_nid_postfix();
}
static void Require(bool value) { if (!value) std::abort(); }
int main() {
*__error_nid_postfix() = 13;
Require(std::strcmp(strerror_nid_postfix(35), "Resource temporarily unavailable") == 0);
Require(*__error_nid_postfix() == 13);
Require(std::strcmp(strerror_nid_postfix(78), "Function not implemented") == 0);
char* parent = strerror_nid_postfix(22);
std::thread worker([] {
Require(std::strcmp(strerror_nid_postfix(45), "Operation not supported") == 0);
});
worker.join();
Require(std::strcmp(parent, "Invalid argument") == 0);
char buffer[128];
for (int error = 0; error <= 96; ++error) {
Require(strerror_r_nid_postfix(error, buffer, sizeof(buffer)) == 0);
Require(buffer[0] && std::strstr(buffer, "Unknown") == nullptr);
}
Require(strerror_r_nid_postfix(-1, buffer, sizeof(buffer)) == 22);
Require(std::strstr(buffer, "-1") != nullptr);
Require(*__error_nid_postfix() == 13);
char sentinel[] = "xyz";
Require(strerror_r_nid_postfix(22, sentinel, 1) == 34);
Require(sentinel[0] == 0 && sentinel[1] == 'y');
sentinel[0] = 'x';
Require(strerror_r_nid_postfix(22, sentinel, 0) == 34 && sentinel[0] == 'x');
Require(strerror_r_nid_postfix(22, nullptr, 0) == 34);
}

View File

@ -0,0 +1,46 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <chrono>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <cstdio>
extern "C" {
int APS5_VABI remove_nid_postfix(const char*);
int APS5_VABI rename_nid_postfix(const char*, const char*);
int* APS5_VABI __error_nid_postfix();
}
static void Check(bool value, int line) {
if (!value) {
std::fprintf(stderr, "Filesystem check failed at line %d (guest errno %d)\n", line, *__error_nid_postfix());
std::abort();
}
}
#define Require(value) Check((value), __LINE__)
int main() {
const auto root = std::filesystem::path("anyps5-filesystem-test-" +
std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
Require(std::filesystem::create_directory(root));
const auto file = root / "file.txt";
{ std::ofstream stream(file); stream << "retained until removal"; }
Require(remove_nid_postfix(root.string().c_str()) == -1);
Require(*__error_nid_postfix() == 66);
Require(std::filesystem::is_regular_file(file));
Require(remove_nid_postfix((file / "invalid").string().c_str()) == -1);
Require(remove_nid_postfix("") == -1 && *__error_nid_postfix() == 2);
Require(remove_nid_postfix(nullptr) == -1 && *__error_nid_postfix() == 14);
const auto renamed = root / "renamed.txt";
{ std::ofstream stream(renamed); stream << "old contents"; }
Require(rename_nid_postfix(file.string().c_str(), renamed.string().c_str()) == 0);
Require(!std::filesystem::exists(file));
{ std::ifstream stream(renamed); std::string contents; std::getline(stream, contents);
Require(contents == "retained until removal"); }
Require(rename_nid_postfix(renamed.string().c_str(), renamed.string().c_str()) == 0);
Require(rename_nid_postfix(file.string().c_str(), renamed.string().c_str()) == -1);
Require(*__error_nid_postfix() == 2);
Require(rename_nid_postfix(renamed.string().c_str(), file.string().c_str()) == 0);
Require(remove_nid_postfix(file.string().c_str()) == 0);
Require(!std::filesystem::exists(file));
Require(remove_nid_postfix(file.string().c_str()) == -1 && *__error_nid_postfix() == 2);
Require(remove_nid_postfix(root.string().c_str()) == 0);
Require(!std::filesystem::exists(root));
}

View File

@ -0,0 +1,74 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <limits>
extern "C" {
double APS5_VABI atof_nid_postfix(const char*);
float APS5_VABI strtof_nid_postfix(const char*, char**);
long double APS5_VABI strtold_nid_postfix(const char*, char**);
int* APS5_VABI __error_nid_postfix();
float APS5_VABI fmodf_nid_postfix(float, float);
float APS5_VABI asinf_nid_postfix(float);
float APS5_VABI acosf_nid_postfix(float);
float APS5_VABI atan2f_nid_postfix(float, float);
float APS5_VABI tanf_nid_postfix(float);
float APS5_VABI log10f_nid_postfix(float);
double APS5_VABI exp2_nid_postfix(double);
double APS5_VABI ldexp_nid_postfix(double, int);
double APS5_VABI scalbn_nid_postfix(double, int);
float APS5_VABI scalbnf_nid_postfix(float, int);
double APS5_VABI frexp_nid_postfix(double, int*);
float APS5_VABI frexpf_nid_postfix(float, int*);
std::int64_t APS5_VABI lround_nid_postfix(double);
std::int64_t APS5_VABI lroundf_nid_postfix(float);
std::int64_t APS5_VABI llround_nid_postfix(double);
int APS5_VABI __isfinitef_nid_postfix(float);
int APS5_VABI __isnormal_nid_postfix(double);
int APS5_VABI __isnormalf_nid_postfix(float);
int APS5_VABI __isinff_nid_postfix(float);
}
static void Require(bool value) { if (!value) std::abort(); }
int main() {
Require(atof_nid_postfix(" -12.5tail") == -12.5);
char* end = nullptr;
const char input[] = "0x1.8p+2 remainder";
Require(strtof_nid_postfix(input, &end) == 6.f && end == input + 8);
const char invalid[] = "invalid";
Require(strtof_nid_postfix(invalid, &end) == 0.f && end == invalid);
*__error_nid_postfix() = 0;
Require(std::isinf(strtof_nid_postfix("1e1000", nullptr)));
Require(*__error_nid_postfix() == 34);
Require(strtold_nid_postfix("1.0000000000000000001!", &end) > 1.L && *end == '!');
Require(fmodf_nid_postfix(5.5f, 2.f) == 1.5f);
Require(fmodf_nid_postfix(-5.5f, 2.f) == -1.5f);
Require(std::signbit(fmodf_nid_postfix(-4.f, 2.f)));
Require(std::isnan(fmodf_nid_postfix(1.f, 0.f)));
Require(std::abs(asinf_nid_postfix(0.5f) - 0.5235988f) < 0.000001f);
Require(std::abs(acosf_nid_postfix(0.5f) - 1.0471976f) < 0.000001f);
Require(std::abs(atan2f_nid_postfix(1.f, -1.f) - 2.3561945f) < 0.000001f);
Require(tanf_nid_postfix(0.f) == 0.f);
Require(log10f_nid_postfix(100.f) == 2.f);
Require(exp2_nid_postfix(-3.) == 0.125);
Require(ldexp_nid_postfix(0.75, 4) == 12.);
Require(scalbn_nid_postfix(0.75, -2) == 0.1875);
Require(scalbnf_nid_postfix(0.75f, 4) == 12.f);
int exponent = 0;
Require(frexp_nid_postfix(12., &exponent) == 0.75 && exponent == 4);
Require(frexpf_nid_postfix(-12.f, &exponent) == -0.75f && exponent == 4);
Require(lround_nid_postfix(4294967296.5) == INT64_C(4294967297));
Require(lround_nid_postfix(-2.5) == -3);
Require(lroundf_nid_postfix(4294967296.f) == INT64_C(4294967296));
Require(lroundf_nid_postfix(2.5f) == 3);
Require(llround_nid_postfix(-4294967296.5) == -INT64_C(4294967297));
const auto infinity = std::numeric_limits<float>::infinity();
const auto nan = std::numeric_limits<float>::quiet_NaN();
Require(__isinff_nid_postfix(infinity) == 1 && __isinff_nid_postfix(-infinity) == 1);
Require(__isinff_nid_postfix(nan) == 0 && __isinff_nid_postfix(1.f) == 0);
Require(__isfinitef_nid_postfix(0.f) == 1 && __isfinitef_nid_postfix(infinity) == 0);
Require(__isfinitef_nid_postfix(nan) == 0);
Require(__isnormalf_nid_postfix(1.f) == 1 && __isnormalf_nid_postfix(0.f) == 0);
Require(__isnormalf_nid_postfix(std::numeric_limits<float>::denorm_min()) == 0);
Require(__isnormal_nid_postfix(1.) == 1 && __isnormal_nid_postfix(0.) == 0);
Require(__isnormal_nid_postfix(std::numeric_limits<double>::denorm_min()) == 0);
}

View File

@ -0,0 +1,70 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <limits>
extern "C" {
void* APS5_VABI mmap_nid_postfix(void*, std::size_t, int, int, int, std::int64_t) noexcept;
int APS5_VABI munmap_nid_postfix(void*, std::size_t) noexcept;
int* APS5_VABI __error_nid_postfix();
}
static void Require(bool condition) {
if (!condition) {
std::fputs("Guest memory check failed\n", stderr);
std::abort();
}
}
int main() {
constexpr std::size_t page = 0x4000;
const auto failed = reinterpret_cast<void*>(static_cast<std::uintptr_t>(-1));
const auto reject = [&](std::size_t length, int protection, int flags, int fd,
std::int64_t offset, int error) {
*__error_nid_postfix() = 0;
Require(mmap_nid_postfix(nullptr, length, protection, flags, fd, offset) == failed);
Require(*__error_nid_postfix() == error);
};
reject(0, 3, 0x1002, -1, 0, 22);
reject(std::numeric_limits<std::size_t>::max(), 3, 0x1002, -1, 0, 22);
reject(page, 8, 0x1002, -1, 0, 22);
reject(page, 3, 0x1002, 0, 0, 22);
reject(page, 3, 0x1002, -1, 1, 22);
reject(page, 3, 0x1001, -1, 0, 45); // shared
reject(page, 3, 0x1012, -1, 0, 45); // fixed
reject(page, 3, 0x2, 0, 0, 45); // file-backed
reject(page, 3, 0x22, -1, 0, 45); // Linux MAP_ANON is not guest MAP_ANON
auto* memory = static_cast<unsigned char*>(mmap_nid_postfix(nullptr, page * 3 - 1, 3, 0x1002, -1, 0));
Require(memory != failed && (reinterpret_cast<std::uintptr_t>(memory) & (page - 1)) == 0);
for (std::size_t i = 0; i < page * 3; ++i) Require(memory[i] == 0);
memory[0] = 42;
memory[page * 2] = 73;
{
GuestAllocations::Mutation mutation;
const auto range = mutation.Find(memory);
Require(range.bytes == page * 3 && range.readable && range.writable);
}
Require(munmap_nid_postfix(memory + 1, page) == -1 && *__error_nid_postfix() == 22);
Require(munmap_nid_postfix(memory, 0) == -1 && *__error_nid_postfix() == 22);
Require(memory[0] == 42);
Require(munmap_nid_postfix(memory + page, 1) == 0); // round to one guest page
Require(memory[0] == 42 && memory[page * 2] == 73);
Require(munmap_nid_postfix(memory, page) == 0);
Require(memory[page * 2] == 73);
Require(munmap_nid_postfix(memory + page * 2, page) == 0);
Require(munmap_nid_postfix(memory, page) == -1);
for (int protection : {0, 1, 3, 5}) {
void* mapped = mmap_nid_postfix(memory, 1, protection, 0x1002, -1, 0);
Require(mapped != failed);
{
GuestAllocations::Mutation mutation;
const auto range = mutation.Find(mapped);
Require(range.readable == ((protection & 3) != 0));
Require(range.writable == ((protection & 2) != 0));
}
Require(munmap_nid_postfix(mapped, 1) == 0);
}
}

Some files were not shown because too many files have changed in this diff Show More