merge: sync archive directories with main

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
Adrià FranchandClaude Sonnet 5.5 committed 2026-10-26 10:50:37 +01:00
commit 654eadd7f5
282 files changed
+12242 -769

No files matched your search

+1 -1
View File
@@ -421,7 +421,7 @@ jobs:
linux:
needs: eligibility
if: ${{ (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.linux)) && needs.eligibility.outputs.full == 'true' }}
if: ${{ !github.event.repository.fork && (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.linux)) && needs.eligibility.outputs.full == 'true' }}
runs-on: [self-hosted, linux, x64, anyps5]
timeout-minutes: 60
env:
+29
View File
@@ -0,0 +1,29 @@
name: Input mapper
on:
push:
branches: [main]
pull_request:
branches: [main]
paths:
- '.github/workflows/input-mapper.yml'
- 'tools/input-mapper/**'
- 'core/libs/prx/libScePad/include/**'
- 'core/libs/prx/libSceVideoOut/src/InputMapping.cpp'
permissions:
contents: read
jobs:
input-mapper:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version: '22'
package-manager-cache: false
- run: npm --prefix tools/input-mapper test
+1
View File
@@ -18,6 +18,7 @@ concurrency:
jobs:
progress:
if: ${{ !github.event.repository.fork }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
+1 -1
View File
@@ -33,7 +33,7 @@ concurrency:
jobs:
pages:
if: github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success'
if: ${{ !github.event.repository.fork && (github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success') }}
runs-on: ubuntu-latest
environment:
name: github-pages
+3 -1
View File
@@ -290,7 +290,9 @@ if(BUILD_TESTING)
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_compile_preprocessor.py)
set_tests_properties(check_compile_preprocessor PROPERTIES TIMEOUT 20)
add_test(NAME check_compile COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_compile.py)
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_compile.py CheckCompileTests)
add_test(NAME check_compile_outputs COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_compile.py CheckCompileOutputTests)
add_test(NAME performance_compare COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_performance_compare.py)
set_tests_properties(performance_compare PROPERTIES TIMEOUT 20)
+35
View File
@@ -269,6 +269,15 @@ if(BUILD_TESTING)
set_tests_properties(guest_kernel_sys_sdk_version PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel_sys>")
endif()
add_test_executable(guest_kernel_sys_console_identity_tests tests/GuestKernelSysConsoleIdentity.cpp)
target_include_directories(guest_kernel_sys_console_identity_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_kernel_sys_console_identity_tests PRIVATE libkernel_sys libkernel libc)
configure_windows_unwind(guest_kernel_sys_console_identity_tests)
add_test(NAME guest_kernel_sys_console_identity COMMAND guest_kernel_sys_console_identity_tests)
if(WIN32)
set_tests_properties(guest_kernel_sys_console_identity PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel_sys>")
endif()
add_test_executable(nid_buffer_validation_tests nid/tests/BufferValidation.cpp)
target_include_directories(nid_buffer_validation_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/nid/include)
target_compile_features(nid_buffer_validation_tests PRIVATE cxx_std_20)
@@ -278,6 +287,32 @@ if(BUILD_TESTING)
add_test(NAME nid_buffer_validation COMMAND nid_buffer_validation_tests)
set_tests_properties(nid_buffer_validation PROPERTIES TIMEOUT 10)
add_test_executable(guest_dinkumware_fetch_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/GuestDinkumwareFetch.cpp
${CMAKE_CURRENT_SOURCE_DIR}/nid/src/NidCompute.cpp ${CMAKE_CURRENT_SOURCE_DIR}/nid/src/Sha1.cpp)
target_include_directories(guest_dinkumware_fetch_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/nid/include)
target_link_libraries(guest_dinkumware_fetch_tests PRIVATE ${CMAKE_DL_LIBS})
configure_windows_unwind(guest_dinkumware_fetch_tests)
if(MINGW)
target_link_libraries(guest_dinkumware_fetch_tests PRIVATE libc)
endif()
add_custom_target(guest_dinkumware_fetch_libraries DEPENDS ${LIBC_PATCHED})
add_dependencies(guest_dinkumware_fetch_tests guest_dinkumware_fetch_libraries)
foreach(operation IN ITEMS and or xor)
foreach(width IN ITEMS 1 2 4)
add_test(NAME guest_dinkumware_fetch_${operation}_${width} COMMAND guest_dinkumware_fetch_tests
$<TARGET_FILE_DIR:libc> unpatched ${operation} ${width})
add_test(NAME guest_dinkumware_fetch_${operation}_${width}_patched COMMAND guest_dinkumware_fetch_tests
${PATCHED_DIR} patched ${operation} ${width})
set_tests_properties(guest_dinkumware_fetch_${operation}_${width}
guest_dinkumware_fetch_${operation}_${width}_patched PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_dinkumware_fetch_${operation}_${width} PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
set_tests_properties(guest_dinkumware_fetch_${operation}_${width}_patched PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:${PATCHED_DIR}")
endif()
endforeach()
endforeach()
add_test_executable(pe_import_first_tests allocator/tests/PeImportFirstTests.cpp allocator/PeImportFirst.cpp)
target_include_directories(pe_import_first_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_features(pe_import_first_tests PRIVATE cxx_std_20)
+27 -3
View File
@@ -327,6 +327,7 @@ set(agcDriverSources
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/PipelineCache.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/PipelineJournal.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/Resources.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/DeferredDestruction.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/PlaceholderImages.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/Recorder.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/ParallelCompare.cpp
@@ -1279,6 +1280,20 @@ if(BUILD_TESTING)
endif()
endif()
add_test_executable(agc_deferred_destruction_tests EXCLUDE_FROM_ALL tests/DeferredDestruction.cpp)
target_include_directories(agc_deferred_destruction_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR})
target_link_libraries(agc_deferred_destruction_tests PRIVATE libSceAgcDriver libc)
set_target_properties(agc_deferred_destruction_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_deferred_destruction_tests)
if(BUILD_TESTING)
set_target_properties(agc_deferred_destruction_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_deferred_destruction COMMAND agc_deferred_destruction_tests)
if(WIN32)
set_tests_properties(agc_deferred_destruction PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
add_test_executable(agc_device_memory_fallback_tests EXCLUDE_FROM_ALL tests/DeviceMemoryFallback.cpp)
target_include_directories(agc_device_memory_fallback_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR})
target_link_libraries(agc_device_memory_fallback_tests PRIVATE libSceAgcDriver libc)
@@ -1572,8 +1587,12 @@ if(BUILD_TESTING)
set_tests_properties(agc_pipeline_library_off PROPERTIES ENVIRONMENT "APS5_NO_GPL=1")
add_test(NAME agc_pipeline_library_fast_only COMMAND agc_driver_recorder_tests --pipeline-library-only)
set_tests_properties(agc_pipeline_library_fast_only PROPERTIES ENVIRONMENT "APS5_NO_PIPELINE_LTO=1" SKIP_RETURN_CODE 77)
add_test(NAME agc_pipeline_library_lto_info COMMAND agc_driver_recorder_tests --pipeline-library-lto-info-only)
add_test(NAME agc_pipeline_library_lto_info_fast_only COMMAND agc_driver_recorder_tests --pipeline-library-lto-info-only)
set_tests_properties(agc_pipeline_library_lto_info agc_pipeline_library_lto_info_fast_only PROPERTIES SKIP_RETURN_CODE 77)
set_tests_properties(agc_pipeline_library_lto_info_fast_only PROPERTIES ENVIRONMENT "APS5_NO_PIPELINE_LTO=1")
if(WIN32)
set_tests_properties(agc_pipeline_library agc_pipeline_library_off agc_pipeline_library_fast_only PROPERTIES ENVIRONMENT_MODIFICATION
set_tests_properties(agc_pipeline_library agc_pipeline_library_off agc_pipeline_library_fast_only agc_pipeline_library_lto_info agc_pipeline_library_lto_info_fast_only PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
set_tests_properties(agc_pipeline_cache agc_pipeline_cache_file PROPERTIES SKIP_RETURN_CODE 77)
@@ -3467,8 +3486,7 @@ if(BUILD_TESTING)
endif()
endforeach()
option(ANYPS5_RUN_QUARANTINED_FRAME_REPLAY "Build and register agc_driver_frame_replay on Windows" OFF)
if(WIN32 AND ANYPS5_RUN_QUARANTINED_FRAME_REPLAY)
if(WIN32)
add_test_executable(agc_driver_frame_replay_tests tests/FrameReplay.cpp Execution/src/FrameCapture.cpp)
target_include_directories(agc_driver_frame_replay_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(agc_driver_frame_replay_tests PRIVATE libSceAgc libSceAgcDriver libkernel libc libzstd_static)
@@ -3481,6 +3499,12 @@ if(BUILD_TESTING)
TIMEOUT 120)
set_tests_properties(agc_driver_frame_replay PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
add_test(NAME agc_driver_frame_replay_concurrent COMMAND agc_driver_frame_replay_tests $<TARGET_FILE:agc_frame_replay> concurrent)
set_tests_properties(agc_driver_frame_replay_concurrent PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame-replay-concurrent-capture;APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=8"
TIMEOUT 120)
set_tests_properties(agc_driver_frame_replay_concurrent PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
@@ -33,6 +33,8 @@ extern "C" void AgcDriverSuspendPoint_nid_postfix();
extern "C" void AgcDriverSerialProgress_nid_postfix(std::uint64_t* accepted, std::uint64_t* completed);
extern "C" std::size_t AgcDriverAwaitedWaits_nid_postfix(AgcDriver::AwaitedWait* waits, std::size_t capacity);
extern "C" std::uint64_t AgcDriverDispatchesAdoptedAhead_nid_postfix();
extern "C" std::uint64_t AgcDriverDispatchesRejectedAhead_nid_postfix();
extern "C" void AgcDriverSetAheadAdoptHook_nid_postfix(void (*hook)());
extern "C" void AgcDriverRegisterShader_nid_postfix(const Shader* shader);
extern "C" void AgcDriverResolveShaderAbi_nid_postfix(const Shader* shader, std::span<const ShaderRegister> context, std::span<const ShaderRegister> primitive);
extern "C" void AgcDriverResolveGraphicsStagesAbi_nid_postfix(std::span<const Shader* const> stages, std::span<const ShaderRegister> context, std::span<const ShaderRegister> primitive);
@@ -59,6 +59,8 @@ public:
void Shutdown();
void WaitIdle();
std::uint64_t DispatchesAdoptedAhead() const;
std::uint64_t DispatchesRejectedAhead() const;
void SetAheadAdoptHook(void (*hook)());
void CheckFailure();
void ReportFailure(std::exception_ptr error);
void Submit(const Packet* packet, std::uint32_t queue, bool cpuFlip = false);
@@ -317,7 +319,7 @@ private:
std::chrono::steady_clock::time_point resolvedAt{};
};
struct GroupCaptureStats {
std::uint64_t runs = 0, resolved = 0, resolvedHits = 0, adopted = 0, addressMismatches = 0, keyMismatches = 0, failures = 0, stale = 0, labelOverlaps = 0, writtenBefore = 0, deviceRejects = 0, argumentMismatches = 0;
std::uint64_t runs = 0, resolved = 0, resolvedHits = 0, adopted = 0, addressMismatches = 0, keyMismatches = 0, memoryMismatches = 0, failures = 0, stale = 0, labelOverlaps = 0, writtenBefore = 0, deviceRejects = 0, argumentMismatches = 0;
double resolveMs = 0, resolveMaxMs = 0, ageMs = 0, ageMaxMs = 0;
std::chrono::steady_clock::time_point reported = std::chrono::steady_clock::now();
};
@@ -474,6 +476,8 @@ private:
std::atomic<std::uint64_t> evidenceReads{0};
std::atomic<std::uint64_t> evidenceValidations{0};
std::atomic<std::uint64_t> dispatchesAdoptedAhead{0};
std::atomic<std::uint64_t> dispatchesRejectedAhead{0};
std::atomic<void (*)()> aheadAdoptHook{nullptr};
AgcDriver::Mutex writtenBuffersMutex;
std::deque<WrittenBuffer> writtenBuffers;
@@ -10,6 +10,7 @@
#include <cstdint>
#include <deque>
#include <filesystem>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
@@ -35,10 +36,10 @@ public:
void ResolveGraphicsAbi(const Shader* vertex, const Shader* pixel, std::uint32_t primitiveType);
void RegisterVideoOutput(std::uint32_t handle);
void UnregisterVideoOutput(std::uint32_t handle);
void Submit(const Packet& packet, std::uint32_t queue);
void Flip(const FlipInfo& info);
void Submit(const Packet& packet, std::uint32_t queue, const std::function<void()>& accept);
void Flip(const FlipInfo& info, const std::function<void()>& accept);
void ReserveEopFlip(const FlipInfo& info, std::uint32_t token);
void Suspend();
void Suspend(const std::function<void()>& accept);
void PresentBuffer(const DisplayBuffer& buffer);
void FrameBoundary();
@@ -62,7 +63,8 @@ private:
};
FrameCall make(FrameCallKind kind);
void record(FrameCall call, bool prologueCall, std::span<const Shader* const> shaders = {});
void record(FrameCall call, bool prologueCall, std::span<const Shader* const> shaders = {}, const std::function<void()>& accept = {});
void recordLocked(FrameCall call, bool prologueCall, std::span<const Shader* const> shaders);
void emitDelta();
void emitShaderDelta(std::span<const Shader* const> shaders);
void writeDelta(std::span<const Span> spans, std::span<const std::uint64_t> hostPages, std::span<const Range> added = {});
@@ -72,6 +74,7 @@ private:
void snapshot(const Range& range);
void writeReferenceFrames();
bool abandonIfDiskLow();
void stop(const char* reason);
std::string header() const;
std::vector<Range> committedRanges();
std::vector<Range> newRanges();
@@ -46,6 +46,8 @@ private:
std::uint32_t sourceHeight = 0;
VkImage sourceImage = VK_NULL_HANDLE;
VkDeviceMemory sourceMemory = VK_NULL_HANDLE;
VkDeviceSize sourceBytes = 0;
std::uint32_t sourceMemoryType = 0;
VkImage blackImage = VK_NULL_HANDLE;
VkDeviceMemory blackMemory = VK_NULL_HANDLE;
};
@@ -275,8 +275,8 @@ public:
// blit-submit times after the flip packet; noted in-place reads checked
// (APS5_FLIP_READ_CHECK=1) and found overwritten before the blit's fence.
struct PresentStatistics {
std::uint64_t presents, gpuUnread, batchesAheadOfBlit, batchesAfterFlip, readsChecked, readsOverwritten, acquireDrops;
double gpuBusyMs, gpuGapMs, lastSubmitAfterFlipMs, blitSubmitAfterFlipMs;
std::uint64_t presents, gpuUnread, batchesAheadOfBlit, batchesAfterFlip, readsChecked, readsOverwritten, acquireDrops, keyWriterWaits;
double gpuBusyMs, gpuGapMs, lastSubmitAfterFlipMs, blitSubmitAfterFlipMs, keyWriterWaitMs;
};
static PresentStatistics PresentCounts();
struct PresyncStatistics {
@@ -16,6 +16,7 @@
#include "Optimization/ResourceProgram.hpp"
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <optional>
#include <stdexcept>
#include <utility>
@@ -57,6 +58,19 @@ void ReportDispatchItems(bool profile) {
std::fprintf(stderr, "[dispatch-item] %llu commit items (%llu recipes rebuilt on the committer); drained instead, cumulative:%s\n", static_cast<unsigned long long>(itemCommits.load(std::memory_order_relaxed)), static_cast<unsigned long long>(itemRebuilds.load(std::memory_order_relaxed)), drained.c_str());
}
bool CapturedCurrent(std::span<const ShaderRecompiler::MemoryRegion> captured, const ShaderSnapshot& code) {
for (const auto& region : captured) {
const auto address = region.guestAddress;
const auto bytes = region.bytes.size();
if (bytes == 0 || (address >= code.codeAddress && address < code.codeAddress + code.code.size() * sizeof(std::uint32_t)) || (!code.header.empty() && address >= code.headerAddress && address < code.headerAddress + code.header.size())) continue;
if (Driver::QueryPendingSpan(address, bytes, {}) != ShaderMemory::PendingWrite::None || !GuestMemory::Accessible(reinterpret_cast<const void*>(address), bytes)) return false;
GuestMemory::GuardRead(address, bytes);
GuestMemory::FlushGpuWrites(address, bytes);
if (std::memcmp(region.bytes.data(), reinterpret_cast<const void*>(address), bytes) != 0) return false;
}
return true;
}
}
bool Driver::pipelineDispatch() {
@@ -281,6 +295,14 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
fromAhead = false;
++groupStats.keyMismatches;
}
if (fromAhead) {
if (const auto hook = aheadAdoptHook.load(std::memory_order_acquire)) hook();
if (!CapturedCurrent(ahead.captured, snapshot)) {
fromAhead = false;
++dispatchesRejectedAhead;
++groupStats.memoryMismatches;
}
}
phaseTiming.Phase(PhaseKey);
timing.Mark("decode_key_device_setup");
bool readsClear = false;
@@ -42,6 +42,14 @@ std::uint64_t Driver::DispatchesAdoptedAhead() const {
return dispatchesAdoptedAhead.load(std::memory_order_relaxed);
}
std::uint64_t Driver::DispatchesRejectedAhead() const {
return dispatchesRejectedAhead.load(std::memory_order_relaxed);
}
void Driver::SetAheadAdoptHook(void (*hook)()) {
aheadAdoptHook.store(hook, std::memory_order_release);
}
namespace {
std::size_t& PreviewedUntil() {
@@ -326,9 +334,9 @@ void Driver::reportGroupCapture(std::uint32_t queue) {
auto& stats = groupCaptureStats();
const auto now = std::chrono::steady_clock::now();
if (now - stats.reported < std::chrono::seconds(10)) return;
AgcDriver::ProfilePrint_nid_no_patch("[groupcap] queue 0x%x (10 s): %llu runs resolved %llu dispatches (%llu cache hits) in %.1f ms (max %.2f ms); %llu adopted %.2f ms after the resolve on average, max %.2f ms; mismatches: address %llu key %llu; %llu resolve failures, %llu stale, %llu label overlaps, %llu read an earlier dispatch's output, %llu rejected by the device, %llu dropped because the argument re-read differed\n",
AgcDriver::ProfilePrint_nid_no_patch("[groupcap] queue 0x%x (10 s): %llu runs resolved %llu dispatches (%llu cache hits) in %.1f ms (max %.2f ms); %llu adopted %.2f ms after the resolve on average, max %.2f ms; mismatches: address %llu key %llu memory %llu; %llu resolve failures, %llu stale, %llu label overlaps, %llu read an earlier dispatch's output, %llu rejected by the device, %llu dropped because the argument re-read differed\n",
queue, static_cast<unsigned long long>(stats.runs), static_cast<unsigned long long>(stats.resolved), static_cast<unsigned long long>(stats.resolvedHits), stats.resolveMs, stats.resolveMaxMs,
static_cast<unsigned long long>(stats.adopted), stats.adopted != 0 ? stats.ageMs / static_cast<double>(stats.adopted) : 0.0, stats.ageMaxMs, static_cast<unsigned long long>(stats.addressMismatches), static_cast<unsigned long long>(stats.keyMismatches),
static_cast<unsigned long long>(stats.adopted), stats.adopted != 0 ? stats.ageMs / static_cast<double>(stats.adopted) : 0.0, stats.ageMaxMs, static_cast<unsigned long long>(stats.addressMismatches), static_cast<unsigned long long>(stats.keyMismatches), static_cast<unsigned long long>(stats.memoryMismatches),
static_cast<unsigned long long>(stats.failures), static_cast<unsigned long long>(stats.stale), static_cast<unsigned long long>(stats.labelOverlaps), static_cast<unsigned long long>(stats.writtenBefore), static_cast<unsigned long long>(stats.deviceRejects), static_cast<unsigned long long>(stats.argumentMismatches));
stats = GroupCaptureStats{};
stats.reported = now;
@@ -19,9 +19,8 @@ void Submit(const Packet* packet, std::uint32_t queue) try {
return;
}
GuestMemory::CheckRange(packet, sizeof(Packet), alignof(Packet));
recorder->Submit(*packet, queue);
const auto flips = driver.FlipSubmissions();
driver.Submit(packet, queue);
recorder->Submit(*packet, queue, [&] { driver.Submit(packet, queue); });
if (driver.FlipSubmissions() != flips) recorder->FrameBoundary();
} catch (const ProcessShutdown&) {
LibcAwaitExit_nid_postfix();
@@ -41,8 +40,12 @@ void RegisterShader(const Shader* shader) {
}
void SuspendPoint() {
if (auto* const recorder = FrameRecorder::Active()) recorder->Suspend();
DriverDetail::Driver::Get().SuspendPoint();
auto* const recorder = FrameRecorder::Active();
if (recorder == nullptr) {
DriverDetail::Driver::Get().SuspendPoint();
return;
}
recorder->Suspend([] { DriverDetail::Driver::Get().SuspendPoint(); });
}
void RegisterVideoOutput(std::uint32_t handle, const std::shared_ptr<IVideoOutput>& output) {
@@ -146,9 +149,12 @@ extern "C" void AgcDriverSerialProgress_nid_postfix(std::uint64_t* accepted, std
extern "C" void AgcDriverSubmitFlip_nid_postfix(const AgcDriver::FlipInfo& info) try {
auto* const recorder = AgcDriver::FrameRecorder::Active();
if (recorder != nullptr) recorder->Flip(info);
AgcDriver::DriverDetail::Driver::Get().SubmitFlip(info);
if (recorder != nullptr) recorder->FrameBoundary();
if (recorder == nullptr) {
AgcDriver::DriverDetail::Driver::Get().SubmitFlip(info);
return;
}
recorder->Flip(info, [&] { AgcDriver::DriverDetail::Driver::Get().SubmitFlip(info); });
recorder->FrameBoundary();
} catch (const ProcessShutdown&) {
LibcAwaitExit_nid_postfix();
}
@@ -157,6 +163,14 @@ extern "C" std::uint64_t AgcDriverDispatchesAdoptedAhead_nid_postfix() {
return AgcDriver::DriverDetail::Driver::Get().DispatchesAdoptedAhead();
}
extern "C" std::uint64_t AgcDriverDispatchesRejectedAhead_nid_postfix() {
return AgcDriver::DriverDetail::Driver::Get().DispatchesRejectedAhead();
}
extern "C" void AgcDriverSetAheadAdoptHook_nid_postfix(void (*hook)()) {
AgcDriver::DriverDetail::Driver::Get().SetAheadAdoptHook(hook);
}
extern "C" void AgcDriverRegisterVideoOutput_nid_postfix(std::uint32_t handle, const std::shared_ptr<AgcDriver::IVideoOutput>& output) {
AgcDriver::RegisterVideoOutput(handle, output);
}
@@ -206,7 +206,7 @@ void Driver::reportPresents(double waitedMs, std::size_t inFlight) {
const auto flips = flipsCounted.load() - previousFlips;
const double perFlip = flips != 0 ? 1.0 / static_cast<double>(flips) : 0.0;
const auto serial = flipSerial.load();
AgcDriver::ProfilePrint_nid_no_patch( "[present] %llu presents over 10 s (%llu dropped: no free swapchain image; %zu may trail on the GPU); %s p50 %.2f ms, p90 %.2f ms, max %.2f ms over %zu; per frame, presented or dropped: GPU busy %.1f ms, idle gaps %.1f ms (%llu batches without a completion record); per flip: %.1f batches recorded (%.1f unsignaled at the flip); per present: %.1f batches ahead of the blit, %.1f after it; after the flip packet: last submit %.1f ms, blit submit %.1f ms; in-place reads overwritten before execution: %llu of %llu checked\n", static_cast<unsigned long long>(presents), static_cast<unsigned long long>(dropped), inFlight, inFlight != 0 ? "inflight_wait" : "render_fence_wait", percentile(0.5), percentile(0.9), waits.empty() ? 0.0 : waits.back(), waits.size(), (counts.gpuBusyMs - previous.gpuBusyMs) * perFrame, (counts.gpuGapMs - previous.gpuGapMs) * perFrame, static_cast<unsigned long long>(counts.gpuUnread - previous.gpuUnread), static_cast<double>(serial - windowSerial) * perFlip, static_cast<double>(flipBatchesUnsignaled.load() - previousUnsignaled) * perFlip, static_cast<double>(counts.batchesAheadOfBlit - previous.batchesAheadOfBlit) * perPresent, static_cast<double>(counts.batchesAfterFlip - previous.batchesAfterFlip) * perPresent, (counts.lastSubmitAfterFlipMs - previous.lastSubmitAfterFlipMs) * perPresent, (counts.blitSubmitAfterFlipMs - previous.blitSubmitAfterFlipMs) * perPresent, static_cast<unsigned long long>(counts.readsOverwritten - previous.readsOverwritten), static_cast<unsigned long long>(counts.readsChecked - previous.readsChecked));
AgcDriver::ProfilePrint_nid_no_patch( "[present] %llu presents over 10 s (%llu dropped: no free swapchain image; %zu may trail on the GPU); %s p50 %.2f ms, p90 %.2f ms, max %.2f ms over %zu; per frame, presented or dropped: GPU busy %.1f ms, idle gaps %.1f ms (%llu batches without a completion record); per flip: %.1f batches recorded (%.1f unsignaled at the flip); per present: %.1f batches ahead of the blit, %.1f after it; after the flip packet: last submit %.1f ms, blit submit %.1f ms; in-place reads overwritten before execution: %llu of %llu checked; waits for the display keys' writer outside the GPU mutex: %llu, %.1f ms\n", static_cast<unsigned long long>(presents), static_cast<unsigned long long>(dropped), inFlight, inFlight != 0 ? "inflight_wait" : "render_fence_wait", percentile(0.5), percentile(0.9), waits.empty() ? 0.0 : waits.back(), waits.size(), (counts.gpuBusyMs - previous.gpuBusyMs) * perFrame, (counts.gpuGapMs - previous.gpuGapMs) * perFrame, static_cast<unsigned long long>(counts.gpuUnread - previous.gpuUnread), static_cast<double>(serial - windowSerial) * perFlip, static_cast<double>(flipBatchesUnsignaled.load() - previousUnsignaled) * perFlip, static_cast<double>(counts.batchesAheadOfBlit - previous.batchesAheadOfBlit) * perPresent, static_cast<double>(counts.batchesAfterFlip - previous.batchesAfterFlip) * perPresent, (counts.lastSubmitAfterFlipMs - previous.lastSubmitAfterFlipMs) * perPresent, (counts.blitSubmitAfterFlipMs - previous.blitSubmitAfterFlipMs) * perPresent, static_cast<unsigned long long>(counts.readsOverwritten - previous.readsOverwritten), static_cast<unsigned long long>(counts.readsChecked - previous.readsChecked), static_cast<unsigned long long>(counts.keyWriterWaits - previous.keyWriterWaits), counts.keyWriterWaitMs - previous.keyWriterWaitMs);
previous = counts;
previousFlips = flipsCounted.load();
previousUnsignaled = flipBatchesUnsignaled.load();
@@ -138,7 +138,7 @@ std::shared_ptr<const ShaderSnapshot> ReadRawShader(std::uint64_t address) {
cache.erase(found);
}
const auto bytes = result->code.size() * sizeof(std::uint32_t);
while (!cache.empty() && (cache.size() >= 64 || cacheBytes + bytes > 8 * 1024 * 1024)) {
while (!cache.empty() && (cache.size() >= 4096 || cacheBytes + bytes > 64 * 1024 * 1024)) {
cacheBytes -= cache.back()->code.size() * sizeof(std::uint32_t);
cache.pop_back();
}
@@ -163,20 +163,20 @@ void FrameRecorder::UnregisterVideoOutput(std::uint32_t handle) {
record(std::move(call), true);
}
void FrameRecorder::Submit(const Packet& packet, std::uint32_t queue) {
void FrameRecorder::Submit(const Packet& packet, std::uint32_t queue, const std::function<void()>& accept) {
auto call = make(FrameCallKind::Submit);
call.queue = queue;
call.address = pointer(packet.addr);
call.size = packet.dw_num;
call.flags = packet.flags;
record(std::move(call), false);
record(std::move(call), false, {}, accept);
}
void FrameRecorder::Flip(const FlipInfo& info) {
void FrameRecorder::Flip(const FlipInfo& info, const std::function<void()>& accept) {
auto call = make(FrameCallKind::Flip);
call.cpuFlip = true;
call.values = {info.handle, flipValue(info.index), info.mode, flipValue(info.argument)};
record(std::move(call), false);
record(std::move(call), false, {}, accept);
}
void FrameRecorder::ReserveEopFlip(const FlipInfo& info, std::uint32_t token) {
@@ -198,8 +198,8 @@ void FrameRecorder::ReserveEopFlip(const FlipInfo& info, std::uint32_t token) {
++recordedCalls;
}
void FrameRecorder::Suspend() {
record(make(FrameCallKind::Suspend), false);
void FrameRecorder::Suspend(const std::function<void()>& accept) {
record(make(FrameCallKind::Suspend), false, {}, accept);
}
void FrameRecorder::PresentBuffer(const DisplayBuffer& buffer) {
@@ -236,8 +236,13 @@ void FrameRecorder::writeReferenceFrames() {
}
}
void FrameRecorder::record(FrameCall call, bool prologueCall, std::span<const Shader* const> shaders) {
void FrameRecorder::record(FrameCall call, bool prologueCall, std::span<const Shader* const> shaders, const std::function<void()>& accept) {
std::lock_guard lock(mutex);
recordLocked(std::move(call), prologueCall, shaders);
if (accept) accept();
}
void FrameRecorder::recordLocked(FrameCall call, bool prologueCall, std::span<const Shader* const> shaders) {
if (state == State::Waiting) {
if (!prologueCall) return;
call.ordinal = ++ordinal;
@@ -360,10 +365,23 @@ void FrameRecorder::emitShaderDelta(std::span<const Shader* const> shaders) {
void FrameRecorder::writeDelta(std::span<const Span> spans, std::span<const std::uint64_t> hostPages, std::span<const Range> added) {
if (abandonIfDiskLow()) return;
const auto [accepted, completed] = DriverDetail::Driver::Get().SerialProgress();
std::vector<std::pair<std::uint64_t, std::uint64_t>> blocks;
std::vector<std::pair<std::uint64_t, std::uint64_t>> written;
for (const auto& span : spans) {
GuestMemory::CollectWrittenBlocks(span.address, static_cast<std::size_t>(span.bytes), span.since, written);
blocks.insert(blocks.end(), written.begin(), written.end());
}
auto& driver = DriverDetail::Driver::Get();
auto [accepted, completed] = driver.SerialProgress();
if (completed < accepted && (!added.empty() || !blocks.empty())) {
if (!driver.WaitIdleFor(std::chrono::seconds(10))) {
stop("stopped: the GPU work submitted before a memory delta did not finish within 10 s");
return;
}
std::tie(accepted, completed) = driver.SerialProgress();
}
const auto group = deltaGroups + 1;
bool any = false;
std::vector<std::pair<std::uint64_t, std::uint64_t>> written;
std::vector<std::byte> bytes;
for (const auto& range : added) {
writer->WriteRange(range.address, range.bytes, range.kind, range.protection, range.access);
@@ -397,15 +415,12 @@ void FrameRecorder::writeDelta(std::span<const Span> spans, std::span<const std:
deltaBytes += bytes.size();
any = true;
}
for (const auto& span : spans) {
GuestMemory::CollectWrittenBlocks(span.address, static_cast<std::size_t>(span.bytes), span.since, written);
for (const auto& [begin, end] : written) {
bytes.resize(static_cast<std::size_t>(end - begin));
GuestMemory::ReadCommittedUnsynced(begin, bytes);
writer->WriteDelta(group, completed, begin, bytes);
deltaBytes += bytes.size();
any = true;
}
for (const auto& [begin, end] : blocks) {
bytes.resize(static_cast<std::size_t>(end - begin));
GuestMemory::ReadCommittedUnsynced(begin, bytes);
writer->WriteDelta(group, completed, begin, bytes);
deltaBytes += bytes.size();
any = true;
}
if (!any) return;
deltaGroups = group;
@@ -436,8 +451,13 @@ bool FrameRecorder::abandonIfDiskLow() {
std::error_code error;
const auto space = std::filesystem::space(directory, error);
if (error || space.available >= MinimumFreeBytes) return false;
warnings.push_back("stopped: less than 4 GiB free on the capture volume");
std::fprintf(stderr, "[frame-capture] stopped: less than 4 GiB free on the capture volume\n");
stop("stopped: less than 4 GiB free on the capture volume");
return true;
}
void FrameRecorder::stop(const char* reason) {
warnings.push_back(reason);
std::fprintf(stderr, "[frame-capture] %s\n", reason);
try {
writer->Finish(header());
} catch (const std::exception& failure) {
@@ -445,7 +465,6 @@ bool FrameRecorder::abandonIfDiskLow() {
}
writer.reset();
state = State::Done;
return true;
}
void FrameRecorder::start() {
@@ -1825,12 +1825,26 @@ bool ImportWatched(std::uint64_t address, std::size_t bytes, const std::function
hashes[static_cast<std::size_t>((cursor - first) / page)] = HashPage(reinterpret_cast<const std::byte*>(cursor));
}
}
std::erase_if(spans, [&](const auto& span) { return !walkWrites(tracker, span.first, span.second, StampKind::Cpu, true); });
const auto saveBytes = [&](std::uint64_t begin, std::uint64_t end) {
lock.Suspend();
beforeUnwatch(begin, static_cast<std::size_t>(end - begin));
lock.Resume();
};
std::vector<std::pair<std::uint64_t, std::uint64_t>> leaving;
std::erase_if(spans, [&](const auto& span) {
if (walkWrites(tracker, span.first, span.second, StampKind::Cpu, true)) return false;
leaving.push_back(span);
return true;
});
for (const auto& [begin, end] : leaving) saveBytes(begin, end);
if (!runImport()) return false;
const WindowCopy window{first, hashes};
auto unwatchedFrom = first;
for (const auto& [begin, end] : spans) {
if (!walkWrites(tracker, begin, end, StampKind::Cpu, true, &window)) continue;
if (!walkWrites(tracker, begin, end, StampKind::Cpu, true, &window)) {
saveBytes(begin, end);
continue;
}
#ifdef _WIN32
GuestArena::GuestArenaCompareResidentWhenShared_nid_no_patch(static_cast<std::uintptr_t>(begin), static_cast<std::size_t>(end - begin));
#endif
@@ -1,5 +1,6 @@
#include "prx/libSceAgcDriver/Execution/include/PresentationScaler.hpp"
#include "prx/libSceAgcDriver/Execution/include/AspectFit.hpp"
#include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp"
namespace AgcDriver {
@@ -23,7 +24,7 @@ PresentationScaler::~PresentationScaler() {
void PresentationScaler::release() noexcept {
if (sourceImage != VK_NULL_HANDLE) context.Function<PFN_vkDestroyImage>("vkDestroyImage")(context.device, sourceImage, nullptr);
if (sourceMemory != VK_NULL_HANDLE) context.Function<PFN_vkFreeMemory>("vkFreeMemory")(context.device, sourceMemory, nullptr);
if (sourceMemory != VK_NULL_HANDLE) Graphics::ReleaseImageMemory(context, sourceMemory, sourceBytes, sourceMemoryType);
sourceImage = VK_NULL_HANDLE;
sourceMemory = VK_NULL_HANDLE;
sourceWidth = 0;
@@ -50,8 +51,9 @@ void PresentationScaler::EnsureSourceImage(std::uint32_t width, std::uint32_t he
context.Function<PFN_vkGetImageMemoryRequirements>("vkGetImageMemoryRequirements")(context.device, sourceImage, &requirements);
VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
allocation.allocationSize = requirements.size;
allocation.memoryTypeIndex = context.MemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
Graphics::Check(context.Function<PFN_vkAllocateMemory>("vkAllocateMemory")(context.device, &allocation, nullptr, &sourceMemory), "vkAllocateMemory presentation source");
allocation.memoryTypeIndex = context.DeviceMemoryType(requirements.memoryTypeBits, requirements.size, "presentation source");
sourceMemory = Graphics::AllocateImageMemory(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory presentation source", &sourceMemoryType, true);
sourceBytes = requirements.size;
Graphics::Check(context.Function<PFN_vkBindImageMemory>("vkBindImageMemory")(context.device, sourceImage, sourceMemory, 0), "vkBindImageMemory presentation source");
sourceWidth = width;
sourceHeight = height;
@@ -8,6 +8,7 @@
#include "SpirvBackend/SpirvOptimizer.hpp"
#endif
#include "prx/libSceAgcDriver/Graphics/include/PipelineJournal.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DeferredDestruction.hpp"
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Execution/include/FloatFlushProbe.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
@@ -369,6 +370,8 @@ struct VulkanDevice::State {
void* markerMapping = nullptr;
BufferMarkerLog markerLog;
bool deviceLostHook = false;
bool deferDestruction = false;
PFN_vkGetDeviceProcAddr contextDeviceProc = nullptr;
VkTimeDomainKHR hostTimeDomain = VK_TIME_DOMAIN_DEVICE_KHR;
VkPhysicalDeviceSubgroupProperties subgroup{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES};
std::vector<std::uint32_t> capabilities{1};
@@ -945,6 +948,7 @@ struct VulkanDevice::State {
destroyPool(device, pool, nullptr);
}
if (settlePool != VK_NULL_HANDLE) destroyPool(device, settlePool, nullptr);
Graphics::DestroyDeferredObjects(device);
destroyDevice(device, nullptr);
}
if (instance != VK_NULL_HANDLE) {
@@ -1283,6 +1287,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
VkPhysicalDeviceProperties2 driverProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &driver};
state->InstanceFunction<PFN_vkGetPhysicalDeviceProperties2>("vkGetPhysicalDeviceProperties2")(selected, &driverProperties);
driverId = driver.driverID;
state->deferDestruction = driver.driverID == VK_DRIVER_ID_MOLTENVK && std::getenv("APS5_NO_DEFERRED_DESTRUCTION") == nullptr;
state->driverInfo = std::string(driver.driverName) + " " + driver.driverInfo;
state->driverId = driver.driverID;
state->individuallyFreeDescriptorSets = driver.driverID == VK_DRIVER_ID_MESA_LLVMPIPE;
@@ -2095,6 +2100,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
check(state->InstanceFunction<PFN_vkCreateDevice>("vkCreateDevice")(selected, &deviceInfo, nullptr, &state->device), "vkCreateDevice");
state->shaderProfile = std::move(shaderProfile);
if (compileOnly) return;
if (state->deferDestruction) state->contextDeviceProc = Graphics::DeferDestructionToIdle(state->device, state->deviceProc);
if (calibratedTimestamps != nullptr) {
const bool khr = std::strcmp(calibratedTimestamps, VK_KHR_CALIBRATED_TIMESTAMPS_EXTENSION_NAME) == 0;
const auto domains = reinterpret_cast<PFN_vkGetPhysicalDeviceCalibrateableTimeDomainsKHR>(state->instanceProc(state->instance, khr ? "vkGetPhysicalDeviceCalibrateableTimeDomainsKHR" : "vkGetPhysicalDeviceCalibrateableTimeDomainsEXT"));
@@ -3357,6 +3363,15 @@ FrameDumps& Dumps() {
}
bool VulkanDevice::PresentDisplayBuffer(const DisplayBuffer& buffer) {
if (buffer.dccAddress != 0 && buffer.tilingMode == 0) {
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
const auto before = profile ? Graphics::Recorder::ThreadWaitedMs() : 0.0;
state->recorder->SyncThrough(buffer.dccAddress, DisplayBufferKeyBytes(buffer), true);
if (const auto waited = profile ? Graphics::Recorder::ThreadWaitedMs() - before : 0.0; waited > 0.0) {
++presentCounters.keyWriterWaits;
presentCounters.keyWriterWaitMs += waited;
}
}
static const bool storeAtFlip = Graphics::StorageTexture::StoreAtFlipRequested(std::getenv("APS5_STORE_AT_FLIP"));
if (storeAtFlip) Graphics::StorageTexture::FlushAllPending("flip");
else Graphics::StorageTexture::FlushDumpSelected("flip");
@@ -3977,7 +3992,7 @@ Graphics::Context VulkanDevice::buildContext() const {
state->physical,
state->queue,
state->pool,
state->deviceProc,
state->contextDeviceProc != nullptr ? state->contextDeviceProc : state->deviceProc,
state->InstanceFunction<PFN_vkGetPhysicalDeviceFormatProperties>("vkGetPhysicalDeviceFormatProperties"),
state->InstanceFunction<PFN_vkGetPhysicalDeviceImageFormatProperties>("vkGetPhysicalDeviceImageFormatProperties"),
state->memoryProperties,
@@ -0,0 +1,17 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_DEFERREDDESTRUCTION_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_DEFERREDDESTRUCTION_HPP
#include "prx/libSceAgcDriver/Graphics/include/Context.hpp"
#include <chrono>
#include <cstddef>
namespace AgcDriver::Graphics {
PFN_vkGetDeviceProcAddr DeferDestructionToIdle(VkDevice device, PFN_vkGetDeviceProcAddr resolver);
void DestroyDeferredAtIdle(const Context& context, std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now());
void DestroyDeferredObjects(VkDevice device) noexcept;
std::size_t DeferredObjects(VkDevice device);
}
#endif
@@ -0,0 +1,134 @@
#include "prx/libSceAgcDriver/Graphics/include/DeferredDestruction.hpp"
#include <atomic>
#include <cstring>
#include <mutex>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <vector>
namespace AgcDriver::Graphics {
namespace {
constexpr std::size_t DrainObjects = 512;
constexpr auto DrainAge = std::chrono::milliseconds(100);
struct Deferred {
PFN_vkDestroyBuffer destroyBuffer = nullptr;
PFN_vkFreeMemory freeMemory = nullptr;
std::vector<VkBuffer> buffers;
std::vector<VkDeviceMemory> memory;
std::chrono::steady_clock::time_point oldest{};
};
struct Registry {
std::mutex mutex;
std::unordered_map<VkDevice, Deferred> devices;
std::atomic<PFN_vkGetDeviceProcAddr> resolver{nullptr};
};
Registry& registry() {
static auto& state = *new Registry();
return state;
}
void destroyAll(VkDevice device, const Deferred& deferred) noexcept {
for (const auto buffer : deferred.buffers) deferred.destroyBuffer(device, buffer, nullptr);
for (const auto memory : deferred.memory) deferred.freeMemory(device, memory, nullptr);
}
template<typename THandle>
bool defer(VkDevice device, THandle handle, std::vector<THandle> Deferred::*list) noexcept {
try {
auto& state = registry();
std::lock_guard lock(state.mutex);
const auto found = state.devices.find(device);
if (found == state.devices.end()) return false;
auto& deferred = found->second;
if (deferred.buffers.empty() && deferred.memory.empty()) deferred.oldest = std::chrono::steady_clock::now();
(deferred.*list).push_back(handle);
return true;
} catch (...) {
return false;
}
}
VKAPI_ATTR void VKAPI_CALL deferredDestroyBuffer(VkDevice device, VkBuffer buffer, const VkAllocationCallbacks* allocator) {
if (buffer == VK_NULL_HANDLE || defer(device, buffer, &Deferred::buffers)) return;
reinterpret_cast<PFN_vkDestroyBuffer>(registry().resolver.load(std::memory_order_acquire)(device, "vkDestroyBuffer"))(device, buffer, allocator);
}
VKAPI_ATTR void VKAPI_CALL deferredFreeMemory(VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks* allocator) {
if (memory == VK_NULL_HANDLE || defer(device, memory, &Deferred::memory)) return;
reinterpret_cast<PFN_vkFreeMemory>(registry().resolver.load(std::memory_order_acquire)(device, "vkFreeMemory"))(device, memory, allocator);
}
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL deferringDeviceProc(VkDevice device, const char* name) {
if (std::strcmp(name, "vkDestroyBuffer") == 0) return reinterpret_cast<PFN_vkVoidFunction>(&deferredDestroyBuffer);
if (std::strcmp(name, "vkFreeMemory") == 0) return reinterpret_cast<PFN_vkVoidFunction>(&deferredFreeMemory);
return registry().resolver.load(std::memory_order_acquire)(device, name);
}
}
PFN_vkGetDeviceProcAddr DeferDestructionToIdle(VkDevice device, PFN_vkGetDeviceProcAddr resolver) {
if (device == VK_NULL_HANDLE || resolver == nullptr) throw std::invalid_argument("AGC graphics: deferred destruction needs a device and its resolver");
Deferred deferred;
deferred.destroyBuffer = reinterpret_cast<PFN_vkDestroyBuffer>(resolver(device, "vkDestroyBuffer"));
deferred.freeMemory = reinterpret_cast<PFN_vkFreeMemory>(resolver(device, "vkFreeMemory"));
if (deferred.destroyBuffer == nullptr || deferred.freeMemory == nullptr) throw std::runtime_error("AGC graphics: missing Vulkan function: vkDestroyBuffer or vkFreeMemory");
auto& state = registry();
std::lock_guard lock(state.mutex);
const auto current = state.resolver.load(std::memory_order_acquire);
if (current != nullptr && current != resolver) throw std::runtime_error("AGC graphics: deferred destruction found two different device resolvers");
if (!state.devices.emplace(device, std::move(deferred)).second) throw std::runtime_error("AGC graphics: deferred destruction was set up twice for one device");
state.resolver.store(resolver, std::memory_order_release);
return &deferringDeviceProc;
}
void DestroyDeferredAtIdle(const Context& context, std::chrono::steady_clock::time_point now) {
auto& state = registry();
{
std::lock_guard lock(state.mutex);
const auto found = state.devices.find(context.device);
if (found == state.devices.end()) return;
const auto objects = found->second.buffers.size() + found->second.memory.size();
if (objects == 0 || (objects < DrainObjects && now - found->second.oldest < DrainAge)) return;
}
const auto idle = context.Function<PFN_vkQueueWaitIdle>("vkQueueWaitIdle")(context.queue);
if (idle != VK_SUCCESS && idle != VK_ERROR_DEVICE_LOST) throw std::runtime_error("AGC graphics: vkQueueWaitIdle before destroying deferred objects: Vulkan result " + std::to_string(idle));
Deferred taken;
{
std::lock_guard lock(state.mutex);
const auto found = state.devices.find(context.device);
if (found == state.devices.end()) return;
taken.destroyBuffer = found->second.destroyBuffer;
taken.freeMemory = found->second.freeMemory;
taken.buffers.swap(found->second.buffers);
taken.memory.swap(found->second.memory);
}
destroyAll(context.device, taken);
}
void DestroyDeferredObjects(VkDevice device) noexcept {
Deferred taken;
{
auto& state = registry();
std::lock_guard lock(state.mutex);
const auto found = state.devices.find(device);
if (found == state.devices.end()) return;
taken = std::move(found->second);
state.devices.erase(found);
}
destroyAll(device, taken);
}
std::size_t DeferredObjects(VkDevice device) {
auto& state = registry();
std::lock_guard lock(state.mutex);
const auto found = state.devices.find(device);
return found == state.devices.end() ? 0 : found->second.buffers.size() + found->second.memory.size();
}
}
@@ -719,10 +719,30 @@ public:
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_MEMORY_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
context.Function<PFN_vkCmdCopyImageToBuffer>("vkCmdCopyImageToBuffer")(commands, storage->Image(), VK_IMAGE_LAYOUT_GENERAL, depthPlane.staging->Handle(), 1, &colorRegion);
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT);
context.Function<PFN_vkCmdCopyBufferToImage>("vkCmdCopyBufferToImage")(commands, depthPlane.staging->Handle(), image, VK_IMAGE_LAYOUT_GENERAL, 1, &depthRegion);
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT);
if (target.stencilAddress == 0) {
VkImageMemoryBarrier discard{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
discard.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
discard.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
discard.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
discard.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
discard.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
discard.image = image;
discard.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1};
const auto barrier = context.Resolved(&DeviceFunctions::cmdPipelineBarrier, "vkCmdPipelineBarrier");
barrier(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &discard);
context.Function<PFN_vkCmdCopyBufferToImage>("vkCmdCopyBufferToImage")(commands, depthPlane.staging->Handle(), image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &depthRegion);
VkImageMemoryBarrier toGeneral = discard;
toGeneral.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
toGeneral.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
toGeneral.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
toGeneral.newLayout = VK_IMAGE_LAYOUT_GENERAL;
barrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &toGeneral);
} else {
context.Function<PFN_vkCmdCopyBufferToImage>("vkCmdCopyBufferToImage")(commands, depthPlane.staging->Handle(), image, VK_IMAGE_LAYOUT_GENERAL, 1, &depthRegion);
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT);
}
if (batch) batch->SubmitAndWait();
else Recorder::CountBarriers(Recorder::CommandClass::Copy, 3);
else Recorder::CountBarriers(Recorder::CommandClass::Copy, target.stencilAddress == 0 ? 4 : 3);
}
std::shared_ptr<StorageTexture> Mirror(const GuestTextureResource& resource) {
@@ -1493,8 +1493,32 @@ bool evictingRefusalCurrent(const HostImports& state, const EvictingRefusal& ref
return refusal.epoch == state.epoch && refusal.liveBytes == state.liveBytes && budget <= refusal.budget && refusal.window == importWindow.load(std::memory_order_relaxed) && state.useCounter < refusal.ready && (state.buildFloor <= refusal.floor || state.useCounter < refusal.readyHeld);
}
void noteRetiredUnwatched(std::vector<std::pair<std::uint64_t, std::uint64_t>>& spans, const HostImport& entry, const GuestAllocations::Lease& lease) {
if (!entry.unwatched) return;
for (const auto& range : entry.ranges) {
if (leasedRangeOwner(lease, range->address) != range) continue;
const auto begin = std::max(range->address, entry.base);
const auto end = std::min(range->address + range->bytes, entry.base + entry.bytes);
if (begin < end) spans.emplace_back(begin, end);
}
}
void rewatchRetired(const HostImports& state, std::vector<std::pair<std::uint64_t, std::uint64_t>> spans) {
std::erase_if(spans, [&](const auto& span) {
auto it = state.imports.upper_bound(span.first);
if (it != state.imports.begin()) --it;
for (; it != state.imports.end() && it->first < span.second; ++it) {
if (it->second.unwatched && span.first < it->first + it->second.bytes) return true;
}
const std::array<std::pair<std::uint64_t, std::uint64_t>, 1> one{{span}};
return StorageTexture::AnyLiveOverlaps(one);
});
if (!spans.empty()) GuestMemory::Rewatch(spans);
}
std::uint64_t retireLeastRecent(const Context& context, HostImports& state, const GuestAllocations::Lease& lease, std::uint64_t wanted) {
std::uint64_t freed = 0;
std::vector<std::pair<std::uint64_t, std::uint64_t>> unwatched;
while (freed < wanted) {
auto oldest = state.imports.end();
for (auto it = state.imports.begin(); it != state.imports.end(); ++it) {
@@ -1502,8 +1526,10 @@ std::uint64_t retireLeastRecent(const Context& context, HostImports& state, cons
}
if (oldest == state.imports.end()) break;
freed += oldest->second.pinnedBytes;
noteRetiredUnwatched(unwatched, oldest->second, lease);
retireImport(context.device, state, oldest, lease);
}
rewatchRetired(state, std::move(unwatched));
return freed;
}
@@ -2088,6 +2114,7 @@ void refreshImports(const Context& context, HostImports& state, const GuestAlloc
std::erase_if(state.sparseRunEnds, [&](const auto& entry) { return !starts(entry.first); });
std::erase_if(state.sparseRunJoins, [&](const auto& entry) { return !starts(entry.first); });
const auto budget = effectiveBudget(context, state);
std::vector<std::pair<std::uint64_t, std::uint64_t>> unwatched;
for (auto it = state.imports.begin(); it != state.imports.end();) {
if (sameRange(it->second, lease, budget)) {
if (it->second.unwatched) GuestMemory::Unwatch(it->first, it->second.bytes);
@@ -2097,9 +2124,11 @@ void refreshImports(const Context& context, HostImports& state, const GuestAlloc
static const bool trace = std::getenv("APS5_TRACE_HOST_IMPORT") != nullptr;
if (trace && leasedRangeOwner(lease, it->first, it->first + it->second.bytes) != nullptr) std::fprintf(stderr, "[gpu] host import of 0x%llx+0x%llx retired: the range was mapped again\n", static_cast<unsigned long long>(it->first), static_cast<unsigned long long>(it->second.bytes));
const auto next = std::next(it);
noteRetiredUnwatched(unwatched, it->second, lease);
retireImport(context.device, state, it, lease);
it = next;
}
rewatchRetired(state, std::move(unwatched));
for (auto it = state.failed.begin(); it != state.failed.end();) it = sameRange(it->second, lease, budget) ? std::next(it) : state.failed.erase(it);
for (auto it = state.failedJoins.begin(); it != state.failedJoins.end();) it = sameRun(it->second, joinedRun(lease, it->second.base, it->second.base + it->second.bytes)) ? std::next(it) : state.failedJoins.erase(it);
for (auto it = state.failedBlocks.begin(); it != state.failedBlocks.end();) it = sameRun(it->second, exactRun(lease, it->first.first, it->first.second)) ? std::next(it) : state.failedBlocks.erase(it);
@@ -185,7 +185,8 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip
std::vector<VkPipeline> libraries;
for (std::size_t part = 0; part < flags.size(); ++part) {
if (part == 0 && mesh) continue;
const auto key = Combined(*partKeys[part], *contextKeys[part]);
auto key = Combined(*partKeys[part], *contextKeys[part]);
key.push_back(static_cast<std::byte>(optimize));
auto& cache = device.parts[part];
if (const auto found = cache.find(key); found != cache.end()) {
libraries.push_back(found->second);
@@ -195,7 +196,7 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip
VkGraphicsPipelineLibraryCreateInfoEXT library{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_LIBRARY_CREATE_INFO_EXT, flags[part] == VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT ? &rendering : &shaderRendering};
library.flags = flags[part];
VkGraphicsPipelineCreateInfo create{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, &library};
create.flags = VK_PIPELINE_CREATE_LIBRARY_BIT_KHR | VK_PIPELINE_CREATE_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT;
create.flags = optimize ? VK_PIPELINE_CREATE_LIBRARY_BIT_KHR | VK_PIPELINE_CREATE_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT : VK_PIPELINE_CREATE_LIBRARY_BIT_KHR;
create.pDynamicState = info.pDynamicState;
create.layout = partLayouts[part];
switch (flags[part]) {
@@ -8,6 +8,7 @@
#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp"
#include "prx/libSceAgcDriver/Graphics/include/ParallelCompare.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DeferredDestruction.hpp"
#include "prx/libSceAgcDriver/Graphics/shaders/MeshArguments_spv.h"
#include "prx/libSceAgcDriver/Graphics/shaders/SampleCounter_spv.h"
#include "prx/libSceAgcDriver/Graphics/shaders/SampleDumps_spv.h"
@@ -3679,6 +3680,7 @@ void Recorder::Submit() {
submission.pSignalSemaphores = &timeline;
}
if (hold != VK_NULL_HANDLE || timeline != VK_NULL_HANDLE) submission.pNext = &timelineInfo;
DestroyDeferredAtIdle(context);
const auto submitStart = std::chrono::steady_clock::now();
const auto result = function(queueSubmit, "vkQueueSubmit")(context.queue, 1, &submission, batch->fence);
if (result == VK_ERROR_DEVICE_LOST) {
@@ -1,5 +1,6 @@
#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp"
#include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DeferredDestruction.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
@@ -381,6 +382,7 @@ void CommandBatch::Submit() {
submission.commandBufferCount = 1;
submission.pCommandBuffers = &commands;
timing.Mark("command_end");
DestroyDeferredAtIdle(context);
Check(context.Function<PFN_vkQueueSubmit>("vkQueueSubmit")(context.queue, 1, &submission, fence), "vkQueueSubmit graphics");
pending = true;
submitted = true;
@@ -1189,9 +1189,9 @@ std::shared_ptr<StorageTexture> cachedStorageTexture(const Context& context, std
const auto viewed = ArrayLayerWindow(requested, context.limits.maxImageArrayLayers);
if (viewed.baseAddress != requested.baseAddress) guestBytes = 0;
Require(viewed.dimension != TextureDimension::k2DMsaa, "storage image access to a multisampled texture is not implemented");
RequireNoMultisampleTarget(viewed.baseAddress, guestBytes != 0 ? static_cast<std::size_t>(guestBytes) : 1u, "a storage image access");
if (auto depth = DepthSurfaceStorage(context, words, viewed, mip)) return depth;
const auto viewedBytes = guestBytes != 0 ? guestBytes : DescribeSurface(viewed).guestBytes;
RequireNoMultisampleTarget(viewed.baseAddress, static_cast<std::size_t>(viewedBytes), "a storage image access");
if (auto depth = DepthSurfaceStorage(context, words, viewed, mip)) return depth;
if (viewedBytes >= (1ull << 30u)) {
static std::atomic<std::uint64_t> large{0};
const auto count = large.fetch_add(1) + 1;
@@ -2084,9 +2084,10 @@ void ShaderResources::buildPrepare(std::span<const CompiledShader> shaders, cons
const bool classified = element < binding.bufferWritten.size() && element < binding.bufferRead.size() && element < binding.bufferAtomic.size();
allocations[index].writeOnly = classified && allocations[index].guest && allocations[index].written && binding.bufferWritten[element] && !read && !atomic;
const auto& push = shader.program->pushConstants;
if (!push.empty()) {
const auto pushDataBytes = shader.program->stencilReferences.empty() ? push.size() : shader.program->stencilReferenceDword * sizeof(std::uint32_t);
if (pushDataBytes != 0u) {
const auto position = shader.program->memoryOffsetDword * 4u + element;
Require(position < push.size(), "guest buffer offset lies outside the shader's push constants");
Require(position < pushDataBytes, "guest buffer offset lies outside the shader's push constants");
allocations[index].pushByte = static_cast<std::int32_t>(shader.pushConstantOffset + position);
} else {
allocations[index].dataByte = shader.program->memoryOffsetDword * 4u + element;
@@ -315,13 +315,33 @@ VkStencilOpState stencilFace(std::uint32_t compare, std::uint32_t ops, std::uint
auto fixed = compare == 0 || compare == 7 ? 0u : face.compareMask;
std::uint32_t invertMask = 0;
std::uint32_t inverted = 0;
std::uint32_t logicMask = 0;
const auto logic = [](std::uint32_t op) { return op == 10 || op == 11 || op == 12 || op == 15; };
const auto convert = [&](std::uint32_t op, bool testFailed) {
if (face.writeMask == 0) return VK_STENCIL_OP_KEEP;
char detail[112];
if (opValExport && ((op == 4 && !opValReference) || op == 5 || op == 6 || op == 8 || op == 9)) {
if (opValExport && ((op == 4 && !opValReference) || (op >= 5 && op != 7))) {
std::snprintf(detail, sizeof(detail), "AGC graphics: stencil operation %u with a pixel shader that exports the stencil operation value is unsupported", op);
throw std::runtime_error(detail);
}
if (logic(op)) {
const auto bits = (op == 11 || op == 12 ? opValue : ~opValue) & face.writeMask;
if (bits == 0) return VK_STENCIL_OP_KEEP;
if (logicMask != 0 && logicMask != bits) {
std::snprintf(detail, sizeof(detail), "AGC graphics: stencil logic operations on write masks 0x%02x and 0x%02x are unsupported", logicMask, bits);
throw std::runtime_error(detail);
}
logicMask = bits;
if (op != 11) return op == 10 ? VK_STENCIL_OP_ZERO : VK_STENCIL_OP_INVERT;
if (testValExport) throw std::runtime_error("AGC graphics: stencil OR is unsupported with a pixel shader that exports the stencil test value");
if ((~reference & fixed & bits) != 0) {
std::snprintf(detail, sizeof(detail), "AGC graphics: stencil OR of 0x%02x against test value 0x%02x (masks 0x%02x/0x%02x) is unsupported", opValue, test, face.compareMask, face.writeMask);
throw std::runtime_error(detail);
}
reference |= bits;
fixed |= bits;
return VK_STENCIL_OP_REPLACE;
}
switch (op) {
case 0: return VK_STENCIL_OP_KEEP;
case 1: return VK_STENCIL_OP_ZERO;
@@ -378,6 +398,15 @@ VkStencilOpState stencilFace(std::uint32_t compare, std::uint32_t ops, std::uint
if (inverted != active) throw std::runtime_error("AGC graphics: a stencil replacement drawn as an inversion beside another stencil operation is unsupported");
face.writeMask = invertMask;
}
if (logicMask != 0) {
const auto other = [&](std::uint32_t op) { return op != 0 && !logic(op); };
if ((other(ops & 0xfu) || other((ops >> 4u) & 0xfu) || other((ops >> 8u) & 0xfu)) && logicMask != face.writeMask) {
char detail[128];
std::snprintf(detail, sizeof(detail), "AGC graphics: stencil operations 0x%03x combine a logic operation on write mask 0x%02x with write mask 0x%02x", ops & 0xfffu, logicMask, face.writeMask);
throw std::runtime_error(detail);
}
face.writeMask = logicMask;
}
face.reference = reference;
Require(!opValReference || compare == 0 || compare == 7 || face.compareMask == 0, "a stencil compare against the test value is unsupported with a pixel shader that exports the stencil operation value");
return face;
@@ -2360,6 +2360,64 @@ void importedHeapMirrorTests(const Context& context, const BdaTestAccess& access
GuestArena::GuestArenaRelease_nid_postfix(block, bytes);
}
void retiredImportWatchTests(const Context& context) {
#ifdef _WIN32
if (!GuestArena::GuestArenaAvailable_nid_postfix() || !GuestArena::GuestArenaWriteWatched_nid_postfix()) {
std::cout << "guest arena unavailable or not write-watched: the write watch of retired imports not tested\n";
return;
}
constexpr std::size_t piece = 65536;
constexpr std::size_t bytes = 2 * piece;
void* block = GuestArena::GuestArenaAllocate_nid_postfix(bytes, bytes);
GuestArena::GuestArenaCommit_nid_postfix(block, bytes, PAGE_READWRITE, bytes);
std::memset(block, 0x5a, bytes);
auto* guest = static_cast<std::uint8_t*>(block);
const auto address = reinterpret_cast<std::uintptr_t>(block);
auto importing = context;
importing.hostImportAlignment = piece;
SetHostImportWindow(bytes);
SetHostImportDeferredSettles(false);
ClearHostImports(importing.device);
const auto decided = PrepareImportWatch(importing);
SetImportWatch(importing, ImportWatch::Unwatch);
const auto registry = [&](std::size_t offset, bool add) {
auto* mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix();
if (add) GuestAllocations::GuestAllocationsAdd_nid_postfix(mutation, guest + offset, piece, true, true, true);
else GuestAllocations::GuestAllocationsRemove_nid_postfix(mutation, guest + offset);
GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation);
};
const auto build = [&] {
GuestBufferMemory leased(importing);
leased.AcquireRegistered();
leased.Upload(true);
leased.WriteBack();
};
registry(0, true);
build();
Require(HostImportCovers(importing, address, piece), "the first 64 KiB piece was not imported on its own");
Require(!AgcDriver::GuestMemory::Watched(address, piece), "an unwatched 64 KiB import kept its piece in the write watch");
registry(piece, true);
SetHostImportBudget(4096);
const auto before = MirrorCounters();
build();
Require(!HostImportCovers(importing, address, piece), "the piece's import outlived the run that grew past it, or a 128 KiB block fit a 4 KiB budget");
Require(AgcDriver::GuestMemory::Watched(address, piece), "a 64 KiB piece whose import retired when its run grew into a block stayed outside the write watch");
Require(MirrorCounters().heapMirrors == before.heapMirrors + 2, "a 64 KiB piece whose import retired when its run grew into a block was copied, not heap mirrored, once no import fit");
registry(piece, false);
registry(0, false);
build();
SetImportWatch(importing, decided);
ClearHostImports(importing.device);
SetHostImportBudget(0);
SetHostImportWindow(0);
SetHostImportDeferredSettles(true);
GuestArena::GuestArenaReset_nid_postfix(block, bytes);
GuestArena::GuestArenaRelease_nid_postfix(block, bytes);
#else
static_cast<void>(context);
#endif
}
void importedFreshTests(const Context& context) {
#ifndef _WIN32
if (!GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) {
@@ -4168,6 +4226,7 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) {
heapMirrorTests(context, access);
partialHeapImportBudgetTests(context, access);
importedHeapMirrorTests(context, access);
retiredImportWatchTests(context);
importWatchTests();
importedFreshTests(context);
gpuMappingTests(context);
@@ -0,0 +1,131 @@
#include "prx/libSceAgcDriver/Graphics/include/DeferredDestruction.hpp"
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <exception>
#include <string>
#include <vector>
using namespace AgcDriver::Graphics;
namespace {
int failures = 0;
void Expect(bool condition, const std::string& what) {
if (condition) return;
std::fprintf(stderr, "FAIL: %s\n", what.c_str());
++failures;
}
std::vector<std::string> events;
VkResult idleResult = VK_SUCCESS;
template<typename THandle>
THandle Handle(std::uintptr_t value) {
return reinterpret_cast<THandle>(value);
}
std::string Name(const char* kind, const void* handle) {
return std::string(kind) + std::to_string(reinterpret_cast<std::uintptr_t>(handle));
}
VKAPI_ATTR void VKAPI_CALL mockDestroyBuffer(VkDevice device, VkBuffer buffer, const VkAllocationCallbacks*) {
events.push_back(Name("buffer ", reinterpret_cast<const void*>(buffer)) + Name(" on ", device));
}
VKAPI_ATTR void VKAPI_CALL mockFreeMemory(VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks*) {
events.push_back(Name("memory ", reinterpret_cast<const void*>(memory)) + Name(" on ", device));
}
VKAPI_ATTR VkResult VKAPI_CALL mockQueueWaitIdle(VkQueue queue) {
events.push_back(Name("idle ", queue));
return idleResult;
}
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL mockProc(VkDevice, const char* name) {
if (std::strcmp(name, "vkDestroyBuffer") == 0) return reinterpret_cast<PFN_vkVoidFunction>(&mockDestroyBuffer);
if (std::strcmp(name, "vkFreeMemory") == 0) return reinterpret_cast<PFN_vkVoidFunction>(&mockFreeMemory);
if (std::strcmp(name, "vkQueueWaitIdle") == 0) return reinterpret_cast<PFN_vkVoidFunction>(&mockQueueWaitIdle);
return nullptr;
}
Context ContextFor(VkDevice device, PFN_vkGetDeviceProcAddr proc) {
Context context{};
context.device = device;
context.queue = Handle<VkQueue>(0x900);
context.deviceProc = proc;
return context;
}
void DeferredUntilTheQueueIsIdle() {
events.clear();
const auto device = Handle<VkDevice>(0x100);
const auto proc = DeferDestructionToIdle(device, mockProc);
const auto context = ContextFor(device, proc);
Expect(context.Function<PFN_vkQueueWaitIdle>("vkQueueWaitIdle") == &mockQueueWaitIdle, "a function other than vkDestroyBuffer and vkFreeMemory was not passed through");
context.Function<PFN_vkFreeMemory>("vkFreeMemory")(device, Handle<VkDeviceMemory>(0x21), nullptr);
context.Function<PFN_vkDestroyBuffer>("vkDestroyBuffer")(device, Handle<VkBuffer>(0x11), nullptr);
context.Function<PFN_vkDestroyBuffer>("vkDestroyBuffer")(device, VK_NULL_HANDLE, nullptr);
Expect(events.empty() && DeferredObjects(device) == 2, "a destroy or free was not deferred, or a null handle was queued");
const auto start = std::chrono::steady_clock::now();
DestroyDeferredAtIdle(context, start);
Expect(events.empty() && DeferredObjects(device) == 2, "deferred objects younger than the drain age were destroyed, or the queue was waited for");
DestroyDeferredAtIdle(context, start + std::chrono::seconds(1));
const std::vector<std::string> drained{"idle 2304", "buffer 17 on 256", "memory 33 on 256"};
Expect(events == drained && DeferredObjects(device) == 0, "old deferred objects were not destroyed after a queue wait, buffers before memory");
events.clear();
for (std::uintptr_t index = 0; index < 512; ++index) context.Function<PFN_vkDestroyBuffer>("vkDestroyBuffer")(device, Handle<VkBuffer>(0x1000 + index), nullptr);
DestroyDeferredAtIdle(context, std::chrono::steady_clock::now());
Expect(events.size() == 513 && events.front() == "idle 2304" && DeferredObjects(device) == 0, "512 young deferred objects were not destroyed after a queue wait");
events.clear();
idleResult = VK_ERROR_OUT_OF_HOST_MEMORY;
context.Function<PFN_vkFreeMemory>("vkFreeMemory")(device, Handle<VkDeviceMemory>(0x22), nullptr);
bool threw = false;
try {
DestroyDeferredAtIdle(context, std::chrono::steady_clock::now() + std::chrono::seconds(1));
} catch (const std::exception&) {
threw = true;
}
Expect(threw && DeferredObjects(device) == 1 && events.size() == 1, "a failed queue wait did not throw, or destroyed the deferred objects");
idleResult = VK_SUCCESS;
events.clear();
DestroyDeferredObjects(device);
Expect(events == std::vector<std::string>{"memory 34 on 256"} && DeferredObjects(device) == 0, "the deferred objects were not destroyed when the device went, or the queue was waited for");
events.clear();
context.Function<PFN_vkDestroyBuffer>("vkDestroyBuffer")(device, Handle<VkBuffer>(0x12), nullptr);
Expect(events == std::vector<std::string>{"buffer 18 on 256"}, "a destroy after the device went was deferred");
}
void OtherDevicesDestroyAtOnce() {
events.clear();
const auto device = Handle<VkDevice>(0x101);
const auto proc = DeferDestructionToIdle(device, mockProc);
const auto other = Handle<VkDevice>(0x102);
reinterpret_cast<PFN_vkFreeMemory>(proc(other, "vkFreeMemory"))(other, Handle<VkDeviceMemory>(0x23), nullptr);
Expect(events == std::vector<std::string>{"memory 35 on 258"} && DeferredObjects(other) == 0, "a free on a device without deferred destruction was deferred");
bool threw = false;
try {
DeferDestructionToIdle(device, mockProc);
} catch (const std::exception&) {
threw = true;
}
Expect(threw, "deferred destruction was set up twice for one device");
DestroyDeferredObjects(device);
}
}
int main() {
try {
DeferredUntilTheQueueIsIdle();
OtherDevicesDestroyAtOnce();
} catch (const std::exception& error) {
std::fprintf(stderr, "FAIL: %s\n", error.what());
return 1;
}
if (failures != 0) return 1;
std::puts("deferred destruction tests passed");
return 0;
}
@@ -17,6 +17,7 @@
#include <memory>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>
#ifdef _WIN32
#include <windows.h>
@@ -100,14 +101,18 @@ std::vector<std::byte> pattern(std::uint8_t seed) {
return bytes;
}
void copyFrame(std::uint32_t* words, const Layout& layout, std::byte* target) {
CommandBuffer buffer{words, words + CommandBytes / sizeof(std::uint32_t), words, words + CommandBytes / sizeof(std::uint32_t), nullptr, nullptr, 0};
sceAgcDcbDmaData(&buffer, 0, DmaMemory, 0, at(target), DmaMemory, 0, at(layout.source), DataBytes, 0, 1, 0);
sceAgcDcbWriteData(&buffer, GraphicsMemory, 0, at(layout.label), &Done, 1, 0, 1);
void copy(std::uint32_t* words, std::size_t commandBytes, const std::byte* source, std::byte* target, std::uint32_t* label) {
CommandBuffer buffer{words, words + commandBytes / sizeof(std::uint32_t), words, words + commandBytes / sizeof(std::uint32_t), nullptr, nullptr, 0};
sceAgcDcbDmaData(&buffer, 0, DmaMemory, 0, at(target), DmaMemory, 0, at(source), DataBytes, 0, 1, 0);
sceAgcDcbWriteData(&buffer, GraphicsMemory, 0, at(label), &Done, 1, 0, 1);
Packet packet{words, static_cast<std::uint32_t>(buffer.cursor_up - words), 0, {}};
check(sceAgcDriverSubmitDcb(&packet) == 0, "submission failed");
}
void copyFrame(std::uint32_t* words, const Layout& layout, std::byte* target) {
copy(words, CommandBytes, layout.source, target, layout.label);
}
void flip() {
AgcDriverSubmitFlip_nid_postfix(AgcDriver::FlipInfo{Handle, -1, 1, 0});
}
@@ -187,15 +192,75 @@ void testCaptureAndReplay(const std::filesystem::path& tool) {
check(readFile(secondDump) == second, "the replay did not reproduce the second copy");
}
void testConcurrentCopies(const std::filesystem::path& tool) {
constexpr std::size_t Copies = 64;
constexpr std::size_t Frames = 8;
constexpr std::size_t CopyCommandBytes = 1024;
const char* directory = std::getenv("APS5_FRAME_CAPTURE");
check(directory != nullptr && *directory != '\0', "APS5_FRAME_CAPTURE is not set");
const std::filesystem::path capture(directory);
const auto layout = allocate();
const auto other = allocate();
auto* const otherSource = other.base + 128 * 1024;
auto* const targets = otherSource + DataBytes;
auto* const otherLabel = reinterpret_cast<std::uint32_t*>(other.base + 512 * 1024);
const auto copied = pattern(57);
std::memcpy(otherSource, copied.data(), copied.size());
auto output = std::make_shared<Output>();
AgcDriverRegisterVideoOutput_nid_postfix(Handle, output);
flip();
std::thread copier([&] {
for (std::size_t index = 0; index < Copies; ++index) {
copy(reinterpret_cast<std::uint32_t*>(other.base + index * CopyCommandBytes), CopyCommandBytes, otherSource, targets + index * DataBytes, otherLabel);
}
});
for (std::size_t frame = 0; frame + 1 < Frames; ++frame) {
const auto bytes = pattern(static_cast<std::uint8_t>(frame));
std::memcpy(layout.source, bytes.data(), bytes.size());
copyFrame(layout.firstCommands, layout, layout.firstTarget);
flip();
}
copier.join();
flip();
AgcDriverWaitIdle_nid_postfix();
const FrameCaptureReader reader(capture);
check(!reader.Truncated(), "the capture is truncated");
std::uint64_t base = 0;
std::size_t submits = 0, memory = 0;
for (const auto& call : reader.Calls()) {
if (call.kind == FrameCallKind::FrameStart) base = call.completed;
if (call.kind == FrameCallKind::Memory) {
++memory;
check(call.completed >= base + submits, "a memory delta was read with " + std::to_string(base + submits - call.completed) + " of the " + std::to_string(submits) + " submissions recorded before it unfinished");
}
submits += call.kind == FrameCallKind::Submit;
}
check(submits == Copies + Frames - 1, "the capture holds " + std::to_string(submits) + " submits, not " + std::to_string(Copies + Frames - 1));
check(memory != 0, "the capture holds no memory deltas");
const auto dump = capture / "targets.bin";
const auto command = "\"\"" + tool.string() + "\" \"" + capture.string() + "\" --strict --dump " + std::to_string(at(targets)) + ":" + std::to_string(Copies * DataBytes) + ":\"" + dump.string() + "\"\"";
const auto code = std::system(command.c_str());
check(code == 0, "agc_frame_replay exited with " + std::to_string(code));
const auto replayed = readFile(dump);
check(replayed.size() == Copies * DataBytes, "the replay dumped " + std::to_string(replayed.size()) + " bytes of the copy targets");
for (std::size_t index = 0; index < Copies; ++index) {
check(std::memcmp(replayed.data() + index * DataBytes, copied.data(), DataBytes) == 0, "the replay did not reproduce copy " + std::to_string(index) + " of the second thread");
}
}
}
int main(int argc, char** argv) {
if (argc != 2) {
std::fprintf(stderr, "usage: agc_driver_frame_replay_tests <agc_frame_replay>\n");
if (argc != 2 && !(argc == 3 && std::string(argv[2]) == "concurrent")) {
std::fprintf(stderr, "usage: agc_driver_frame_replay_tests <agc_frame_replay> [concurrent]\n");
return 2;
}
try {
testCaptureAndReplay(argv[1]);
if (argc == 3) testConcurrentCopies(argv[1]);
else testCaptureAndReplay(argv[1]);
} catch (const std::exception& error) {
std::fprintf(stderr, "FAILED: %s\n", error.what());
return 1;
@@ -9235,7 +9235,7 @@ void stencilRefExportTests() {
Require(AgcDriver::Graphics::DrawRejection(unused, false).empty(), "the precheck rejected STENCIL_OP_VAL_EXPORT_ENABLE without a stencil test in DB_SHADER_CONTROL " + std::to_string(shaderControl));
Require(AgcDriver::Graphics::DecodePixelStageInfo(unused.context, IdentityExports).stencilRefExportEnable == ((shaderControl & 2u) != 0), "STENCIL_OP_VAL_EXPORT_ENABLE without a stencil test exported the stencil reference");
const bool replaces = (shaderControl & 6u) == 4u;
for (const auto [ops, writeMask, compare, accepted] : {std::tuple{0x000u, 0xffu, 7u, true}, std::tuple{0x737u, 0xffu, 7u, true}, std::tuple{0x110u, 0xffu, 7u, true}, std::tuple{0x050u, 0x00u, 7u, true}, std::tuple{0x040u, 0xffu, 7u, replaces}, std::tuple{0x40040u, 0xffu, 7u, replaces}, std::tuple{0x404u, 0x0fu, 0u, replaces}, std::tuple{0x040u, 0xffu, 2u, false}, std::tuple{0x340u, 0xffu, 7u, false}, std::tuple{0x042u, 0xffu, 7u, false}, std::tuple{0x050u, 0xffu, 7u, false}, std::tuple{0x600u, 0xffu, 7u, false}, std::tuple{0x008u, 0xffu, 7u, false}, std::tuple{0x900u, 0xffu, 7u, false}, std::tuple{0x5000u, 0xffu, 7u, false}}) {
for (const auto [ops, writeMask, compare, accepted] : {std::tuple{0x000u, 0xffu, 7u, true}, std::tuple{0x737u, 0xffu, 7u, true}, std::tuple{0x110u, 0xffu, 7u, true}, std::tuple{0x050u, 0x00u, 7u, true}, std::tuple{0x040u, 0xffu, 7u, replaces}, std::tuple{0x40040u, 0xffu, 7u, replaces}, std::tuple{0x404u, 0x0fu, 0u, replaces}, std::tuple{0x040u, 0xffu, 2u, false}, std::tuple{0x340u, 0xffu, 7u, false}, std::tuple{0x042u, 0xffu, 7u, false}, std::tuple{0x050u, 0xffu, 7u, false}, std::tuple{0x600u, 0xffu, 7u, false}, std::tuple{0x008u, 0xffu, 7u, false}, std::tuple{0x900u, 0xffu, 7u, false}, std::tuple{0x5000u, 0xffu, 7u, false}, std::tuple{0x0c0u, 0xffu, 7u, false}, std::tuple{0x00bu, 0xffu, 7u, false}}) {
auto stenciled = stencilRefExportQueue(shaderControl, zFormat);
for (const auto [offset, word] : {std::pair{0x010u, 0x80000180u}, std::pair{0x011u, 0x20000181u}, std::pair{0x012u, 0x00001000u}, std::pair{0x013u, 0x00002000u}, std::pair{0x015u, 0x00002000u}, std::pair{0x007u, 0x003f003fu}, std::pair{0x000u, 0u}, std::pair{0x002u, 0u}, std::pair{0x00au, 0u}, std::pair{0x00bu, 0x3f800000u}, std::pair{0x200u, 0x007007b3u}}) stenciled.context[offset] = word;
stenciled.context[0x10b] = ops;
@@ -9545,6 +9545,99 @@ void stencilReferenceUnusedTests() {
std::cout << "Unused stencil reference sample equivalence passed: " << accepted << " accepted states, " << countermodels << " dependent countermodels\n";
}
void stencilLogicOperationTests() {
const auto queueFor = [](std::uint32_t ops, std::uint32_t front, std::uint32_t back, std::uint32_t depthControl) {
auto queue = makeState();
for (const auto [offset, word] : {std::pair{0x000u, 0u}, std::pair{0x002u, 0u}, std::pair{0x007u, (1u << 16u) | 3u}, std::pair{0x00au, 0u}, std::pair{0x00bu, 0u}, std::pair{0x010u, 0x22900983u}, std::pair{0x011u, 0x20000181u}, std::pair{0x012u, 0x100u}, std::pair{0x014u, 0x100u}, std::pair{0x013u, 0x200u}, std::pair{0x015u, 0x200u}, std::pair{0x005u, 0x300u}, std::pair{0x01eu, 0x1u}}) queue.context[offset] = word;
queue.context[0x10b] = ops;
queue.context[0x10c] = front;
queue.context[0x10d] = back;
queue.context[0x200] = depthControl;
return queue;
};
const auto refMaskOf = [](std::uint32_t test, std::uint32_t writeMask, std::uint32_t value) { return test | 0xff00u | (writeMask << 16u) | (value << 24u); };
const auto apply = [](std::uint32_t op, std::uint32_t stencil, std::uint32_t value) {
switch (op) {
case 1: return 0u;
case 10: return stencil & value;
case 11: return stencil | value;
case 12: return stencil ^ value;
case 15: return ~(stencil ^ value);
default: return stencil;
}
};
const auto matches = [&](const AgcDriver::Graphics::State& state, const VkStencilOpState& face, std::uint32_t ops, std::uint32_t compare, std::uint32_t refMask, const std::string& name) {
const auto test = refMask & 0xffu;
const auto compareMask = (refMask >> 8u) & 0xffu;
const auto writeMask = (refMask >> 16u) & 0xffu;
const auto value = refMask >> 24u;
for (std::uint32_t stencil = 0; stencil < 256u; ++stencil) {
for (const bool depthPass : {false, true}) {
const bool stencilPass = stencilComparison(static_cast<VkCompareOp>(compare), test & compareMask, stencil & compareMask);
const auto op = (ops >> (!stencilPass ? 0u : !depthPass ? 8u : 4u)) & 0xfu;
const auto expected = ((stencil & ~writeMask) | (apply(op, stencil, value) & writeMask)) & 0xffu;
const auto [actual, passed] = stencilReferenceOutcome(state, face, face.reference, stencil, depthPass, true);
Require(actual == expected && passed == (stencilPass && depthPass), name + ": stencil " + std::to_string(stencil) + (depthPass ? " passing" : " failing") + " the depth test became " + std::to_string(actual) + ", expected " + std::to_string(expected));
}
}
};
std::size_t accepted = 0;
for (const auto op : {10u, 11u, 12u, 15u}) {
for (const auto placement : {0x010u, 0x001u, 0x100u, 0x111u}) {
for (const auto compare : {7u, 0u, 2u, 3u}) {
for (const auto [test, writeMask, value] : {std::tuple{0x00u, 0xffu, 0x5au}, std::tuple{0xa5u, 0xffu, 0x21u}, std::tuple{0xa5u, 0x3cu, 0xffu}, std::tuple{0xffu, 0xf0u, 0x0fu}, std::tuple{0x5au, 0x81u, 0x00u}}) {
const auto ops = op * placement;
const auto refMask = refMaskOf(test, writeMask, value);
const auto queue = queueFor(ops, refMask, refMask, 0x13u | (compare << 8u));
const auto name = "stencil operation " + std::to_string(op) + " in DB_STENCIL_CONTROL " + std::to_string(ops) + " with compare " + std::to_string(compare) + " and DB_STENCILREFMASK " + std::to_string(refMask);
const auto bits = (op == 11u || op == 12u ? value : ~value) & writeMask;
if (op == 11u && compare != 0u && compare != 7u && (~test & bits) != 0u) {
expectFailure([&] { static_cast<void>(AgcDriver::Graphics::DecodeState(queue)); }, "stencil OR of");
continue;
}
const auto state = AgcDriver::Graphics::DecodeState(queue);
Require(state.stencilTest && state.depthTest && (state.stencilFront.writeMask & 0xffu) == (bits != 0u ? bits : writeMask), "the write mask is not narrowed to the bits changed by " + name);
matches(state, state.stencilFront, ops, compare, refMask, name);
++accepted;
}
}
}
}
for (const auto [ops, refMask, name] : {std::tuple{0x0c1u, refMaskOf(0x80u, 0xffu, 0xffu), "XOR of every written bit beside ZERO"}, std::tuple{0x0cbu, refMaskOf(0x8fu, 0xffu, 0x0fu), "OR and XOR of the same bits"}, std::tuple{0x0fau, refMaskOf(0x80u, 0xffu, 0xf0u), "AND and XNOR of the same bits"}, std::tuple{0xb0bu, refMaskOf(0x30u, 0x3cu, 0xf0u), "OR on stencil fail and depth fail"}}) {
const auto queue = queueFor(ops, refMask, refMask, 0x13u | (3u << 8u));
const auto state = AgcDriver::Graphics::DecodeState(queue);
matches(state, state.stencilFront, ops, 3u, refMask, name);
++accepted;
}
{
const auto front = refMaskOf(0x11u, 0xffu, 0x0fu);
const auto back = refMaskOf(0x22u, 0x3cu, 0x30u);
const auto queue = queueFor(0x0c0u | (0x0a0u << 12u), front, back, 0x93u | (7u << 8u) | (5u << 20u));
const auto state = AgcDriver::Graphics::DecodeState(queue);
Require(state.stencilFront.passOp == VK_STENCIL_OP_INVERT && state.stencilFront.writeMask == 0x0fu && state.stencilBack.passOp == VK_STENCIL_OP_ZERO && state.stencilBack.writeMask == 0x0cu, "front XOR and back AND did not keep their own write masks");
matches(state, state.stencilFront, 0x0c0u, 7u, front, "front XOR beside a back AND");
matches(state, state.stencilBack, 0x0a0u, 5u, back, "back AND beside a front XOR");
accepted += 2;
}
for (const auto [ops, refMask, reason] : {std::tuple{0x0d0u, refMaskOf(0u, 0xffu, 0x0fu), "stencil operation 13 is unsupported"}, std::tuple{0x0e0u, refMaskOf(0u, 0xffu, 0x0fu), "stencil operation 14 is unsupported"}, std::tuple{0x0cau, refMaskOf(0u, 0xffu, 0x0fu), "stencil logic operations on write masks"}, std::tuple{0x0fcu, refMaskOf(0u, 0xffu, 0x0fu), "stencil logic operations on write masks"},std::tuple{0x0c1u, refMaskOf(0u, 0xffu, 0x0fu), "combine a logic operation"}, std::tuple{0x5c0u, refMaskOf(0u, 0xffu, 0x01u), "combine a logic operation"}}) {
const auto queue = queueFor(ops, refMask, refMask, 0x13u | (7u << 8u));
expectFailure([&] { static_cast<void>(AgcDriver::Graphics::DecodeState(queue)); }, reason);
}
for (const auto ops : {0x0c0u, 0x0b0u}) {
auto exported = stencilRefExportQueue(0x12u, 9u);
for (const auto [offset, word] : {std::pair{0x010u, 0x80000180u}, std::pair{0x011u, 0x20000181u}, std::pair{0x012u, 0x00001000u}, std::pair{0x013u, 0x00002000u}, std::pair{0x015u, 0x00002000u}, std::pair{0x007u, 0x003f003fu}, std::pair{0x000u, 0u}, std::pair{0x002u, 0u}, std::pair{0x00au, 0u}, std::pair{0x00bu, 0x3f800000u}, std::pair{0x200u, 0x007007b3u}}) exported.context[offset] = word;
exported.context[0x10b] = ops;
exported.context[0x10c] = exported.context[0x10d] = refMaskOf(0u, 0xffu, 0x0fu);
if (ops == 0x0c0u) {
const auto state = AgcDriver::Graphics::DecodeState(exported);
Require(state.stencilFront.passOp == VK_STENCIL_OP_INVERT && state.stencilFront.writeMask == 0x0fu, "a stencil test value export changed a stencil XOR");
} else {
expectFailure([&] { static_cast<void>(AgcDriver::Graphics::DecodeState(exported)); }, "stencil OR is unsupported with a pixel shader that exports the stencil test value");
}
}
std::cout << "Stencil logic operations matched AND, OR, XOR and XNOR under the write mask for " << accepted << " register states\n";
}
std::set<std::string> bufferViews(std::span<const std::uint32_t> words) {
std::set<std::string> views;
for (std::size_t at = 5; at < words.size() && (words[at] >> 16u) != 0; at += words[at] >> 16u) {
@@ -12377,6 +12470,7 @@ int main(int argc, char** argv) {
orderedPixelShaderTests();
stencilRefExportTests();
stencilReferenceUnusedTests();
stencilLogicOperationTests();
aliasedBufferViewTests();
LineStripTests();
ShaderModuleKeyTests();
@@ -32,7 +32,7 @@ struct LoadCase {
std::uint32_t components = 1;
};
std::vector<std::uint32_t> emitLoad(const LoadCase& test, bool active, std::uint32_t offset, std::uint32_t records, bool robust) {
std::vector<std::uint32_t> emitLoad(const LoadCase& test, bool active, std::uint32_t offset, std::uint32_t records, bool robust, bool valid = true) {
IrProgram program;
program.SetWaveSize(32);
program.Resources().stage = IrShaderStage::Compute;
@@ -76,7 +76,8 @@ std::vector<std::uint32_t> emitLoad(const LoadCase& test, bool active, std::uint
program.Metadata().bindings.pushDataStartDword = 0;
program.Metadata().bindings.memoryOffsetCount = 1;
auto& size = constant(records);
auto& handle = emit(IrOpcode::GetBufferResource, IrType::BufferResource, {&zero, &zero, &size, &zero});
auto& base = constant(valid ? 0x100u : 0u);
auto& handle = emit(IrOpcode::GetBufferResource, IrType::BufferResource, {&base, &zero, &size, &zero});
emit(test.opcode, test.type, {&handle, &zero, &address, &zero, &enabled});
} else {
if (test.kind == ResourceKind::Gds) program.Metadata().bindings.descriptors.push_back({DescriptorBindingKind::Gds, {0}});
@@ -104,8 +105,8 @@ std::vector<std::uint32_t> emitLoad(const LoadCase& test, bool active, std::uint
class AccessTrace {
public:
AccessTrace(const std::vector<std::uint32_t>& module, std::uint32_t misalignment = 0, bool valid = true)
: words(module), misalignment(misalignment), valid(valid) {
AccessTrace(const std::vector<std::uint32_t>& module, std::uint32_t misalignment = 0)
: words(module), misalignment(misalignment) {
require(words.size() >= 5, "SPIR-V module header is missing");
for (std::size_t offset = 5; offset < words.size();) {
const auto count = words[offset] >> 16;
@@ -206,7 +207,6 @@ private:
const auto spec = specializations.at(id);
if (spec == PipelineSpecialization::BufferBase) return 0;
if (spec == PipelineSpecialization::BufferBase + 1) return 0x30001000;
if (spec == PipelineSpecialization::BufferBase + 2) return valid;
if (spec == PipelineSpecialization::BufferBase + 3) return misalignment;
if (spec == PipelineSpecialization::PushDataOffset) return 0;
throw std::runtime_error("unexpected specialization in access fixture");
@@ -250,7 +250,6 @@ private:
const std::vector<std::uint32_t>& words;
std::uint32_t misalignment;
bool valid;
std::uint32_t entryFunction = 0;
std::map<std::uint32_t, std::span<const std::uint32_t>> definitions;
std::map<std::uint32_t, std::string> names;
@@ -281,8 +280,8 @@ std::uint32_t ldsLength(const std::vector<std::uint32_t>& words) {
}
void expectReads(const LoadCase& test, bool robust, bool active, std::uint32_t offset, std::vector<std::uint32_t> expected, std::uint32_t records = 64, std::uint32_t misalignment = 0, bool valid = true) {
const auto module = emitLoad(test, active, offset, records, robust);
const auto actual = AccessTrace(module, misalignment, valid).Run();
const auto module = emitLoad(test, active, offset, records, robust, valid);
const auto actual = AccessTrace(module, misalignment).Run();
if (actual != expected) {
std::string detail = std::string(robust ? "robust" : "plain") + " guest reads:";
for (const auto index : actual) detail += " " + std::to_string(index);
@@ -568,6 +568,64 @@ void residentPresentTests(const Context& context) {
blitLetterboxMatchesClear(context);
}
PFN_vkGetDeviceProcAddr realDeviceProc = nullptr;
PFN_vkAllocateMemory realAllocateMemory = nullptr;
const VkPhysicalDeviceMemoryProperties* refusingMemory = nullptr;
std::uint32_t refusedAllocations = 0;
std::uint32_t lastAllocatedType = UINT32_MAX;
VKAPI_ATTR VkResult VKAPI_CALL refuseDeviceLocalAllocation(VkDevice device, const VkMemoryAllocateInfo* info, const VkAllocationCallbacks* callbacks, VkDeviceMemory* memory) {
const auto heap = refusingMemory->memoryTypes[info->memoryTypeIndex].heapIndex;
if ((refusingMemory->memoryHeaps[heap].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0) {
++refusedAllocations;
return VK_ERROR_OUT_OF_DEVICE_MEMORY;
}
lastAllocatedType = info->memoryTypeIndex;
return realAllocateMemory(device, info, callbacks, memory);
}
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL refusingDeviceProc(VkDevice device, const char* name) {
if (std::strcmp(name, "vkAllocateMemory") == 0) return reinterpret_cast<PFN_vkVoidFunction>(&refuseDeviceLocalAllocation);
return realDeviceProc(device, name);
}
void sourceImageOutsideDeviceMemoryTests(const Context& base) {
VkImageCreateInfo probeInfo{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
probeInfo.imageType = VK_IMAGE_TYPE_2D;
probeInfo.format = VK_FORMAT_B8G8R8A8_UNORM;
probeInfo.extent = {64u, 64u, 1u};
probeInfo.mipLevels = 1;
probeInfo.arrayLayers = 1;
probeInfo.samples = VK_SAMPLE_COUNT_1_BIT;
probeInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
probeInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
VkImage probe = VK_NULL_HANDLE;
Check(base.Function<PFN_vkCreateImage>("vkCreateImage")(base.device, &probeInfo, nullptr, &probe), "vkCreateImage probe");
VkMemoryRequirements requirements{};
base.Function<PFN_vkGetImageMemoryRequirements>("vkGetImageMemoryRequirements")(base.device, probe, &requirements);
base.Function<PFN_vkDestroyImage>("vkDestroyImage")(base.device, probe, nullptr);
if (!base.HostMemoryType(requirements.memoryTypeBits)) {
std::printf("presentation source in host memory: skipped, the device offers no host memory type for the image\n");
return;
}
auto context = base;
context.bufferPool.reset();
realDeviceProc = base.deviceProc;
realAllocateMemory = base.Function<PFN_vkAllocateMemory>("vkAllocateMemory");
refusingMemory = &context.memory;
refusedAllocations = 0;
lastAllocatedType = UINT32_MAX;
context.deviceProc = refusingDeviceProc;
{
PresentationScaler scaler(context, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_B8G8R8A8_UNORM);
scaler.EnsureSourceImage(64u, 64u);
Require(refusedAllocations != 0, "the presentation source never asked for device-local memory");
Require(lastAllocatedType != UINT32_MAX && (context.memory.memoryHeaps[context.memory.memoryTypes[lastAllocatedType].heapIndex].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) == 0, "the presentation source was not placed in host memory when device-local memory was refused");
Require(scaler.SourceWidth() == 64u && scaler.SourceHeight() == 64u, "the presentation source in host memory lost its extent");
}
context.bufferPool.reset();
}
}
int main() {
@@ -582,6 +640,7 @@ int main() {
return VulkanTestSkipped;
}
residentPresentTests(device->GetContext());
sourceImageOutsideDeviceMemoryTests(device->GetContext());
std::cout << "resident present format, presentation pass and letterbox tests passed\n";
return 0;
} catch (const std::exception& error) {
@@ -11058,6 +11058,97 @@ void denormalClearTests(const Device& device, Recorder& recorder) {
recorder.Sync();
}
class LibraryCreateFlags {
public:
explicit LibraryCreateFlags(const Context& context) : resolver(context.deviceProc), device(context.device) {
create = function<PFN_vkCreateGraphicsPipelines>("vkCreateGraphicsPipelines");
active = this;
}
~LibraryCreateFlags() { active = nullptr; }
LibraryCreateFlags(const LibraryCreateFlags&) = delete;
LibraryCreateFlags& operator=(const LibraryCreateFlags&) = delete;
PFN_vkGetDeviceProcAddr Resolver() const { return resolve; }
std::uint32_t Libraries() const { return libraries.load(); }
std::uint32_t Retaining() const { return retaining.load(); }
private:
template<typename TFunction>
TFunction function(const char* name) const { return reinterpret_cast<TFunction>(resolver(device, name)); }
static VKAPI_ATTR VkResult VKAPI_CALL createPipelines(VkDevice device, VkPipelineCache cache, std::uint32_t count, const VkGraphicsPipelineCreateInfo* infos, const VkAllocationCallbacks* callbacks, VkPipeline* pipelines) {
const auto result = active->create(device, cache, count, infos, callbacks, pipelines);
for (std::uint32_t index = 0; index < count; ++index) {
if (pipelines[index] == VK_NULL_HANDLE || (infos[index].flags & VK_PIPELINE_CREATE_LIBRARY_BIT_KHR) == 0) continue;
active->libraries.fetch_add(1);
if ((infos[index].flags & VK_PIPELINE_CREATE_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT) != 0) active->retaining.fetch_add(1);
}
return result;
}
static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL resolve(VkDevice device, const char* name) {
if (std::strcmp(name, "vkCreateGraphicsPipelines") == 0) return reinterpret_cast<PFN_vkVoidFunction>(createPipelines);
return active->resolver(device, name);
}
PFN_vkGetDeviceProcAddr resolver;
VkDevice device;
PFN_vkCreateGraphicsPipelines create = nullptr;
std::atomic<std::uint32_t> libraries{0};
std::atomic<std::uint32_t> retaining{0};
static inline LibraryCreateFlags* active = nullptr;
};
void pipelineLibraryLtoInfoTests(const Device& device) {
std::lock_guard gpu(GpuMutex());
const auto& context = device.GetContext();
LibraryCreateFlags created(context);
struct Cleanup {
VkDevice device;
~Cleanup() { ClearCachedPipelines(device); }
} cleanup{context.device};
ClearCachedPipelines(context.device);
if (!context.graphicsPipelineLibrary) {
std::cout << "graphics pipeline libraries unavailable or disabled: no library retains link-time optimization info\n";
return;
}
ShaderRecompiler::RecompileResult vertex, fragment;
vertex.variantId = 41;
fragment.variantId = 42;
vertex.spirv = std::vector<std::uint32_t>(std::begin(TRIANGLE_vert_SPV), std::end(TRIANGLE_vert_SPV));
fragment.spirv = std::vector<std::uint32_t>(std::begin(TRIANGLE_frag_SPV), std::end(TRIANGLE_frag_SPV));
const std::array shaders{CompiledShader{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, CompiledShader{ShaderRecompiler::ShaderStage::Fragment, &fragment, 0}};
State state{};
state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
state.hasColorTarget = true;
state.color.format = VK_FORMAT_R8G8B8A8_UNORM;
state.colors.push_back(state.color);
state.blend.colorWriteMask = 15;
state.blends.push_back(state.blend);
ShaderResources resources(context, shaders, state.color, static_cast<std::uint32_t>(state.colors.size()), 0, 0);
VertexInputLayout input;
auto fast = context;
fast.deviceProc = created.Resolver();
fast.pipelineLinkTimeOptimization = false;
auto optimizing = fast;
optimizing.pipelineLinkTimeOptimization = true;
const bool lto = std::getenv("APS5_NO_PIPELINE_LTO") == nullptr;
const auto fastPipeline = CachedPipeline(fast, state, input, resources, shaders, VK_IMAGE_LAYOUT_GENERAL);
const auto fastLibraries = created.Libraries();
Require(fastLibraries != 0, "the pipeline was not linked from libraries created through the traced device");
Require(created.Retaining() == 0, std::to_string(created.Retaining()) + " of the " + std::to_string(fastLibraries) + " libraries made for a context without optimized links were created to retain link-time optimization info");
WaitForOptimizedPipelines(context.device);
Require(!fastPipeline->Optimized(), "a pipeline of a context without optimized links was optimized in the background");
const auto optimizedPipeline = CachedPipeline(optimizing, state, input, resources, shaders, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
Require(optimizedPipeline != fastPipeline, "the two attachment layouts shared one cached pipeline");
const auto libraries = created.Libraries() - fastLibraries;
if (lto) {
Require(libraries == fastLibraries, "a link that will be optimized made " + std::to_string(libraries) + " libraries, expected " + std::to_string(fastLibraries) + ": it reused libraries made without link-time optimization info");
Require(created.Retaining() == libraries, std::to_string(created.Retaining()) + " of the " + std::to_string(libraries) + " libraries of a link that will be optimized were created to retain link-time optimization info");
} else {
Require(libraries == 0 && created.Retaining() == 0, "with APS5_NO_PIPELINE_LTO set, forcing pipelineLinkTimeOptimization changed the libraries or their create flags");
}
WaitForOptimizedPipelines(context.device);
Require(optimizedPipeline->Optimized() == lto, lto ? "the pipeline linked from libraries that retain link-time optimization info was not optimized in the background" : "a pipeline was optimized with APS5_NO_PIPELINE_LTO set");
std::cout << "Pipeline library link-time optimization info tests passed: " << fastLibraries << " libraries without the info, " << created.Retaining() << " with it\n";
}
void pipelineLibraryTests(const Device& device) {
std::lock_guard gpu(GpuMutex());
const auto& context = device.GetContext();
@@ -11438,6 +11529,10 @@ int main(int argc, char** argv) {
}
return sparseRunImportTests(device, true) ? 0 : 77;
}
if (argc == 2 && std::string_view(argv[1]) == "--pipeline-library-lto-info-only") {
pipelineLibraryLtoInfoTests(device);
return 0;
}
if (argc == 2 && std::string_view(argv[1]) == "--pipeline-library-only") {
pipelineLibraryTests(device);
return 0;
@@ -952,7 +952,8 @@ int main(int argc, char** argv) {
});
for (auto& reader : readers) reader.join();
for (const auto& snapshot : concurrent) check(snapshot == branched, "concurrent raw compute readers lost snapshot reuse");
alignas(256) std::array<std::array<std::uint32_t, 64>, 65> programs{};
struct alignas(256) RawProgram : std::array<std::uint32_t, 64> {};
std::vector<RawProgram> programs(4097);
for (auto& program : programs) program[0] = 0xbf810000;
const auto firstAddress = reinterpret_cast<std::uintptr_t>(programs.front().data());
readers.clear();
@@ -962,6 +963,9 @@ int main(int argc, char** argv) {
for (auto& reader : readers) reader.join();
for (const auto& snapshot : concurrent) check(snapshot == concurrent.front(), "concurrent raw compute misses duplicated snapshots");
const auto evicted = AgcDriver::DriverDetail::ReadRawShader(firstAddress);
for (std::size_t i = 1; i <= 400; ++i)
AgcDriver::DriverDetail::ReadRawShader(reinterpret_cast<std::uintptr_t>(programs[i].data()));
check(AgcDriver::DriverDetail::ReadRawShader(firstAddress) == evicted, "raw shader cache evicted a program while a title used only 401");
for (std::size_t i = 1; i < programs.size(); ++i)
AgcDriver::DriverDetail::ReadRawShader(reinterpret_cast<std::uintptr_t>(programs[i].data()));
check(AgcDriver::DriverDetail::ReadRawShader(firstAddress) != evicted && evicted->code[0] == 0xbf810000,
@@ -1718,6 +1718,80 @@ void CheckUnwatchedImport() {
#endif
}
struct HookCall {
std::uint64_t address;
std::size_t bytes;
bool watched;
};
std::vector<HookCall> hookCalls;
std::uint64_t hookProbe = 0;
std::size_t hookProbeBytes = 0;
void RecordHook(std::uint64_t address, std::size_t bytes) {
hookCalls.push_back({address, bytes, Watched(hookProbe, hookProbeBytes)});
}
struct RecordedHook {
RecordedHook() {
hookCalls.clear();
SetUnwatchHook(&RecordHook);
}
~RecordedHook() { SetUnwatchHook(nullptr); }
RecordedHook(const RecordedHook&) = delete;
RecordedHook& operator=(const RecordedHook&) = delete;
};
void RequireHookOver(std::uint64_t address, std::size_t bytes, const std::string& path) {
Require(hookCalls.size() == 1, path + " did not run the unwatch hook once");
const auto& call = hookCalls.front();
Require(call.address <= address && address + bytes <= call.address + call.bytes, path + " gave the unwatch hook a range that misses its own");
Require(call.watched, path + " ran the unwatch hook after its range left the write watch");
}
void CheckUnwatchHooks() {
void* memory = AllocateWatched(8 * Block);
const auto base = reinterpret_cast<std::uint64_t>(memory);
std::memset(memory, 0x11, 8 * Block);
Require(CollectWritesUncached(base, 8 * Block) != 0, "the hooked block is not collected");
const RecordedHook recorded;
hookProbe = base;
hookProbeBytes = Block;
Unwatch(base, Block);
RequireHookOver(base, Block, "Unwatch");
Require(!Watched(base, Block), "Unwatch left its range watched");
hookCalls.clear();
hookProbe = base + Block;
bool hookedBeforeImport = false;
Require(ImportWatched(base + Block, Block, [&] {
hookedBeforeImport = hookCalls.size() == 1;
return true;
}, false), "an import that does not stay watched failed");
RequireHookOver(base + Block, Block, "an import that does not stay watched");
Require(hookedBeforeImport, "an import that does not stay watched ran the unwatch hook after the import");
Require(!Watched(base + Block, Block), "an import that does not stay watched left its range watched");
hookCalls.clear();
Require(ImportWatched(base + 2 * Block, Block, [] { return true; }), "an import that stays watched failed");
Require(hookCalls.empty() && Watched(base + 2 * Block, Block), "an import whose page walk vouched for its pages ran the unwatch hook or gave up its range");
#ifndef _WIN32
hookCalls.clear();
const auto late = base + 4 * Block;
hookProbe = late;
bool hookedBeforeLoss = false;
Require(ImportWatched(late, 3 * Block, [&] {
GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(reinterpret_cast<const void*>(late + 2 * Block), Block);
hookedBeforeLoss = !hookCalls.empty();
return true;
}), "an import whose range lost a block during the import failed");
RequireHookOver(late, 3 * Block, "an import whose page walk failed after the import");
Require(!hookedBeforeLoss, "an import whose page walk failed after the import ran the unwatch hook before it");
Require(!Watched(late, Block), "an import whose page walk failed after the import left its range watched");
#endif
}
class PartlyCommitted {
public:
static constexpr std::size_t Bytes = 4 * Block;
@@ -1948,6 +2022,7 @@ int main() {
CheckStoreAcrossImports();
CheckImportWindowFootprint();
CheckUnwatchedImport();
CheckUnwatchHooks();
CheckCommittedRanges();
CheckAdoptLatestEpoch();
} catch (const std::exception& error) {
@@ -0,0 +1,208 @@
#include "VulkanTestDevice.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Submit/include/Acb.hpp"
#include <array>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <initializer_list>
#include <iostream>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>
namespace {
void Require(bool condition, const std::string& message) {
if (!condition) throw std::runtime_error(message);
}
alignas(256) std::array<std::uint32_t, 7> CounterCode{0xf4080200u, 0xfa000000u, 0xbf8cc07fu, 0x7e000281u, 0xe0c80000u, 0x80020000u, 0xbf810000u};
alignas(256) std::array<std::uint32_t, 7> StoreCode{0xf4080200u, 0xfa000000u, 0xbf8cc07fu, 0x7e000202u, 0xe0700000u, 0x80020000u, 0xbf810000u};
alignas(256) std::array<std::uint32_t, 15> SpinCode{0xf4080200u, 0xfa000000u, 0xbe840381u, 0x9304ff04u, 0x0019660du, 0x8004ff04u, 0x3c6ef35fu, 0x80828102u, 0xbf078002u, 0xbf85fff9u, 0xbf8cc07fu, 0x7e000204u, 0xe0700000u, 0x80020000u, 0xbf810000u};
constexpr std::uint32_t Compute = 0x20u;
constexpr std::uint32_t OtherCompute = 0x28u;
constexpr std::uint32_t SpinIterations = 1u << 20u;
alignas(256) std::array<std::uint32_t, 4> Stale{};
alignas(256) std::array<std::uint32_t, 4> Fresh{};
alignas(256) std::array<std::uint32_t, 4> Target{};
alignas(256) std::array<std::uint32_t, 4> TargetDescriptor{};
alignas(256) std::array<std::uint32_t, 4> Spun{};
alignas(256) std::array<std::uint32_t, 4> SpunDescriptor{};
alignas(256) volatile std::uint32_t Gate = 1;
alignas(256) volatile std::uint32_t Done = 0;
alignas(256) volatile std::uint32_t OtherDone = 0;
enum class Window { None, CpuStore, QueuedGpuStore };
std::atomic<Window> Armed{Window::None};
std::uint32_t SpinDispatches = 16;
std::vector<std::uint32_t> OtherWords;
bool SawPending = false;
std::uint32_t Low(const volatile void* pointer) {
return static_cast<std::uint32_t>(reinterpret_cast<std::uintptr_t>(pointer));
}
std::uint32_t High(const volatile void* pointer) {
return static_cast<std::uint32_t>(static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(pointer)) >> 32u);
}
std::array<std::uint32_t, 4> Descriptor(const void* pointer, std::uint32_t bytes) {
return {Low(pointer), High(pointer) & 0xffffu, bytes, 0x31016facu};
}
void SetShaderRegister(std::vector<std::uint32_t>& words, std::uint32_t offset, std::uint32_t value) {
words.insert(words.end(), {0xc0017600u, offset, value});
}
void Program(std::vector<std::uint32_t>& words, const void* code, std::initializer_list<std::uint32_t> userData) {
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(code));
SetShaderRegister(words, 0x20c, static_cast<std::uint32_t>(address >> 8u));
SetShaderRegister(words, 0x20d, static_cast<std::uint32_t>(address >> 40u) & 0xffu);
for (const std::uint32_t offset : {0x207u, 0x208u, 0x209u}) SetShaderRegister(words, offset, 1);
SetShaderRegister(words, 0x212, 0);
SetShaderRegister(words, 0x213, static_cast<std::uint32_t>(userData.size()) << 1u);
std::uint32_t offset = 0x240;
for (const auto value : userData) SetShaderRegister(words, offset++, value);
}
void Dispatch(std::vector<std::uint32_t>& words, const void* code, std::initializer_list<std::uint32_t> userData) {
Program(words, code, userData);
words.insert(words.end(), {0xc0031500u, 1, 1, 1, 0x8041u});
}
void WriteData(std::vector<std::uint32_t>& words, const volatile std::uint32_t* target, std::uint32_t value) {
words.insert(words.end(), {0xc0033700u, 0x00100200u, Low(target), High(target), value});
}
void Submit(std::uint32_t queue, std::vector<std::uint32_t>& words) {
Packet packet{words.data(), static_cast<std::uint32_t>(words.size()), 0, {}};
Require(sceAgcDriverSubmitAcb(queue, &packet) == 0, "compute submit failed");
}
void QueueStoreAhead() {
OtherWords.clear();
for (std::uint32_t index = 0; index < SpinDispatches; ++index) Dispatch(OtherWords, SpinCode.data(), {Low(SpunDescriptor.data()), High(SpunDescriptor.data()), SpinIterations});
Dispatch(OtherWords, StoreCode.data(), {Low(TargetDescriptor.data()), High(TargetDescriptor.data()), Low(Fresh.data())});
WriteData(OtherWords, &OtherDone, 1);
Submit(OtherCompute, OtherWords);
const auto start = std::chrono::steady_clock::now();
while (std::chrono::steady_clock::now() - start < std::chrono::seconds(5) && OtherDone == 0) {
if (AgcDriver::DriverDetail::Driver::QueryPendingSpan(reinterpret_cast<std::uintptr_t>(Target.data()), sizeof(Target), {}) != AgcDriver::ShaderMemory::PendingWrite::None) {
SawPending = true;
return;
}
std::this_thread::yield();
}
}
void BeforeAdoption() {
const auto window = Armed.exchange(Window::None);
if (window == Window::CpuStore) reinterpret_cast<volatile std::uint32_t&>(Target[0]) = Low(Fresh.data());
else if (window == Window::QueuedGpuStore) QueueStoreAhead();
}
void WaitIdle() {
AgcDriverWaitIdle_nid_postfix();
for (const auto* buffer : {&Stale, &Fresh, &Target}) AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast<std::uintptr_t>(buffer->data()), sizeof(*buffer));
}
void Reset() {
WaitIdle();
Stale = {};
Fresh = {};
Target = Descriptor(Stale.data(), sizeof(Stale));
Done = 0;
OtherDone = 0;
SawPending = false;
}
void Gated(Window window) {
Reset();
std::vector<std::uint32_t> words;
words.insert(words.end(), {0xc0053c00u, 0x13u, Low(&Gate), High(&Gate), 1u, 0xffffffffu, 0x19u});
Dispatch(words, CounterCode.data(), {Low(Target.data()), High(Target.data())});
WriteData(words, &Done, 1);
Armed = window;
Submit(Compute, words);
WaitIdle();
Armed = Window::None;
Require(Done == 1, "the gated submission did not finish");
}
std::string Counts() {
return "(stale " + std::to_string(Stale[0]) + ", fresh " + std::to_string(Fresh[0]) + ")";
}
}
int main() {
try {
auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
device.reset();
Require(High(Stale.data()) == High(Fresh.data()), "the stale and fresh buffers differ in their high address word");
TargetDescriptor = Descriptor(Target.data(), sizeof(Target));
SpunDescriptor = Descriptor(Spun.data(), sizeof(Spun));
AgcDriverSetAheadAdoptHook_nid_postfix(&BeforeAdoption);
Reset();
std::vector<std::uint32_t> warmUp;
Dispatch(warmUp, CounterCode.data(), {Low(Target.data()), High(Target.data())});
WriteData(warmUp, &Done, 1);
Submit(Compute, warmUp);
std::vector<std::uint32_t> otherWarmUp;
Dispatch(otherWarmUp, SpinCode.data(), {Low(SpunDescriptor.data()), High(SpunDescriptor.data()), SpinIterations});
Dispatch(otherWarmUp, StoreCode.data(), {Low(TargetDescriptor.data()), High(TargetDescriptor.data()), Low(Fresh.data())});
Submit(OtherCompute, otherWarmUp);
WaitIdle();
Require(Done == 1 && Target[0] == Low(Fresh.data()), "the warm-up dispatches did not run");
Gated(Window::None);
Require(Stale[0] == 1 && Fresh[0] == 0, "the gated dispatch without a store in the window did not count into the stale buffer " + Counts());
const auto adopted = AgcDriverDispatchesAdoptedAhead_nid_postfix();
Require(adopted != 0, "the gated dispatch was not resolved ahead and adopted");
auto rejected = AgcDriverDispatchesRejectedAhead_nid_postfix();
Gated(Window::CpuStore);
Require(Fresh[0] == 1 && Stale[0] == 0, "a dispatch resolved ahead whose descriptor the CPU replaced before its adoption used the old descriptor " + Counts());
Require(AgcDriverDispatchesRejectedAhead_nid_postfix() == rejected + 1, "the CPU store in the adoption window was not caught by the adoption check");
std::cout << "cpu store before adoption: passed\n";
bool conclusive = false;
for (int attempt = 0; attempt < 4 && !conclusive; ++attempt, SpinDispatches *= 4) {
rejected = AgcDriverDispatchesRejectedAhead_nid_postfix();
Gated(Window::QueuedGpuStore);
conclusive = SawPending;
if (!conclusive) {
std::cout << "queued gpu store before adoption: the store finished before the adoption with " << SpinDispatches << " spin dispatches, retrying\n";
continue;
}
Require(Fresh[0] == 1 && Stale[0] == 0, "a dispatch resolved ahead whose descriptor a GPU store queued on another queue replaces before its adoption used the old descriptor " + Counts());
Require(AgcDriverDispatchesRejectedAhead_nid_postfix() == rejected + 1, "the queued GPU store in the adoption window was not caught by the adoption check");
}
AgcDriverSetAheadAdoptHook_nid_postfix(nullptr);
if (!conclusive) {
std::cout << "queued gpu store before adoption: never observed the store pending at the adoption\n";
AgcDriverShutdown_nid_postfix();
return VulkanTestSkipped;
}
std::cout << "queued gpu store before adoption: passed\n";
std::cout << "dispatch ahead adopt check test passed\n";
AgcDriverShutdown_nid_postfix();
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
try {
AgcDriverShutdown_nid_postfix();
} catch (const std::exception& shutdownError) {
std::cerr << shutdownError.what() << '\n';
}
return 1;
}
}
@@ -0,0 +1,185 @@
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#endif
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <span>
#include <string>
#include <vector>
namespace {
using AgcDriver::Graphics::Require;
using ShaderRecompiler::ShaderStage;
constexpr std::uint32_t Threads = 32;
constexpr std::uint32_t Width = 32;
constexpr std::uint32_t Type1D = 8;
constexpr std::uint32_t IdentitySwizzle = 0xfacu;
constexpr std::size_t TexelBytes = 4096;
constexpr std::size_t BlockBytes = 65536;
alignas(256) std::array<std::uint32_t, Threads * 4> Input{};
alignas(256) constexpr std::array<std::uint32_t, 17> StoreCode{
0x34020084, 0xe0381000, 0x80001401, 0xbf8c3f70, 0x7e3c0300, 0x7e3e0281, 0x7e400280, 0xf0201f28, 0x0001141e,
0x7e3e0280, 0x7e4002c1, 0xf0201528, 0x0001141e, 0x7e400281, 0xf0201528, 0x0001141e, 0xbf810000,
};
class GuestBlock {
public:
GuestBlock() {
#ifdef _WIN32
block = static_cast<std::uint8_t*>(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
#else
block = static_cast<std::uint8_t*>(std::aligned_alloc(BlockBytes, BlockBytes));
#endif
Require(block != nullptr, "image store line through plane array: cannot allocate the guest block");
GuestAllocations::Mutation().Add(block, BlockBytes, true, true, true);
}
~GuestBlock() {
GuestAllocations::Mutation().Remove(block);
#ifdef _WIN32
VirtualFree(block, 0, MEM_RELEASE);
#else
std::free(block);
#endif
}
GuestBlock(const GuestBlock&) = delete;
GuestBlock& operator=(const GuestBlock&) = delete;
std::uint8_t* Data() { return block; }
private:
std::uint8_t* block = nullptr;
};
struct Format {
const char* name;
std::uint32_t format;
std::uint32_t components;
};
constexpr std::array<Format, 2> Formats{{
{"32_SINT", 21u, 1u},
{"32_32_32_32_SINT", 76u, 4u},
}};
std::string Hex(std::uint32_t value) {
char text[16];
std::snprintf(text, sizeof(text), "0x%08x", value);
return text;
}
std::uint32_t Value(std::uint32_t tid, std::uint32_t index) {
return (tid * 4u + index + 1u) * 0x9e3779b1u;
}
std::uint64_t TexelOffset(const Format& format, std::uint32_t x) {
const auto mip = AgcDriver::Graphics::ComputeMipLayout(AgcDriver::Graphics::TextureTileMode::kLinear, format.format, Width, 1u, 1u).at(0);
return mip.tiledOffset + static_cast<std::uint64_t>(x) * format.components * 4u;
}
std::vector<std::uint8_t> Initial() {
std::vector<std::uint8_t> bytes(TexelBytes);
for (std::size_t index = 0; index < bytes.size(); ++index) bytes[index] = static_cast<std::uint8_t>(0x80u + 37u * index + 11u * (index >> 4u));
return bytes;
}
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t bytes) {
const auto address = reinterpret_cast<std::uintptr_t>(data);
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu), bytes, 0x31016facu};
}
std::array<std::uint32_t, 8> TextureDescriptor(const void* data, std::uint32_t format) {
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(data));
return {
static_cast<std::uint32_t>(address >> 8u),
static_cast<std::uint32_t>((address >> 40u) & 0xffu) | (format << 20u) | (((Width - 1u) & 3u) << 30u),
(Width - 1u) >> 2u,
IdentitySwizzle | (Type1D << 28u),
0u,
0u,
0u,
0u,
};
}
void Dispatch(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std::span<const std::uint32_t> code, std::uint32_t format) {
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
for (std::uint32_t index = 0; index < 4u; ++index) Input[tid * 4u + index] = Value(tid, index);
}
const auto initial = Initial();
std::copy(initial.begin(), initial.end(), texels);
std::vector<std::uint32_t> userData(16, 0u);
const auto buffer = BufferDescriptor(Input.data(), static_cast<std::uint32_t>(Input.size() * 4u));
const auto texture = TextureDescriptor(texels, format);
std::copy(buffer.begin(), buffer.end(), userData.begin());
std::copy(texture.begin(), texture.end(), userData.begin() + 4);
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1};
ShaderRecompiler::RecompileRequest request{
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
{32, 0, userData, compute, std::nullopt, std::nullopt, memory},
device.Target(),
{0, 0, 0, 128}
};
request.useCache = false;
const auto result = ShaderRecompiler::Recompile(request);
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
device.WaitIdle();
AgcDriver::Graphics::StorageTexture::FlushPending(reinterpret_cast<std::uintptr_t>(texels), TexelBytes, nullptr, "test");
device.WaitIdle();
}
void CheckTexels(const std::uint8_t* texels, const Format& format) {
const auto initial = Initial();
for (std::uint32_t x = 0; x < Width; ++x) {
const auto offset = TexelOffset(format, x);
for (std::uint32_t component = 0; component < format.components; ++component) {
std::uint32_t actual = 0;
std::memcpy(&actual, texels + offset + component * 4u, 4u);
const auto wanted = Value(x, component);
Require(actual == wanted, std::string("image_store 2d_array through a 1D ") + format.name + " T#: texel " + std::to_string(x) + " component " + std::to_string(component) + " is " + Hex(actual) + ", expected the y = 1, slice = 0 store " + Hex(wanted) + " and the stores to slices 0xffffffff and 1 dropped");
}
}
const auto end = TexelOffset(format, Width);
Require(std::equal(texels + end, texels + TexelBytes, initial.begin() + static_cast<std::ptrdiff_t>(end)), std::string("image_store 2d_array through a 1D ") + format.name + " T#: bytes past the texture changed");
}
}
int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
GuestBlock block;
auto* texels = block.Data();
for (const auto& format : Formats) {
Dispatch(*device, texels, StoreCode, format.format);
CheckTexels(texels, format);
}
std::puts("image store line through plane array tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}
@@ -704,6 +704,48 @@ void CheckDepthAttachmentAlias() {
std::puts("native depth attachment alias changes layout while retaining held GPU views");
}
void CheckStorageAliasRetirement() {
const auto device = OpenVulkanTestDevice();
Require(device != nullptr, "native depth storage alias requires Vulkan");
const auto context = device->graphicsContext();
constexpr VkExtent2D extent{5u, 3u};
constexpr std::uint32_t samples = 4u;
constexpr std::size_t prefix = 65536u;
const auto bytes = static_cast<std::size_t>(MultisampledDepthBytes_nid_no_patch(extent, 4u, samples));
TargetMemory memory(prefix + bytes), metadata(65536u);
std::memset(memory.Data(), 0x3c, prefix + bytes);
std::memset(metadata.Data(), 0, 65536u);
struct Cleanup {
AgcDriver::VulkanDevice& device;
~Cleanup() { device.WaitIdle(); ClearCachedTextures(device.Device()); ClearDepthSurfaces(device.Device()); }
} cleanup{*device};
GuestTextureResource alias{};
alias.baseAddress = memory.Address();
alias.width = 256u;
alias.height = static_cast<std::uint32_t>((prefix + bytes) / 1024u);
alias.mipCount = alias.allocatedMipCount = 1u;
alias.dimension = TextureDimension::k2D;
alias.tileMode = TextureTileMode::kLinear;
alias.format = 22u;
alias.dstSelX = 0u;
alias.dstSelY = alias.dstSelZ = 4u;
alias.dstSelW = 5u;
auto cached = CachedStorageSurface(context, alias);
Require(cached != nullptr, "storage alias was not cached before native depth creation");
DepthTarget depth{memory.Address() + prefix, 0u, extent, VK_FORMAT_D32_SFLOAT, 0.0f, 0u, metadata.Address(), false, samples};
depth.depthSwizzle = 24u;
depth.iterate256 = depth.htilePipeAligned = depth.htileDepthOnly = true;
Require(DepthSurfaceView(context, depth) != VK_NULL_HANDLE && DepthSurfaceAt(depth.address), "storage alias fixture has no native depth owner");
auto disjoint = alias;
disjoint.height = static_cast<std::uint32_t>(prefix / 1024u);
Require(CachedStorageSurface(context, disjoint) != nullptr, "disjoint storage alias was not created");
Require(DepthSurfaceAt(depth.address), "storage access ending at native depth retired a disjoint owner");
Require(CachedStorageSurface(context, alias) != nullptr, "overlapping cached storage alias was not returned");
Require(!DepthSurfaceAt(depth.address), "a cached storage view kept a native depth owner inside its range");
for (std::size_t offset = 0u; offset < prefix + bytes; ++offset) Require(memory.Data()[offset] == std::byte{0x3c}, "retiring unwritten native depth changed guest bytes");
std::puts("cached storage span retires only intersecting native depth owners");
}
enum class ClearFault { None, PartialStore, CpuWrite, DepthWrite, BadMetadata, UnprovenDispatch, PendingWrite, Overlap, PossibleStore, CompletedPossibleStore, DriverDispatch, ImportWindowWrite };
void CheckClearDispatch() {
@@ -1337,6 +1379,7 @@ int main(int argc, char** argv) {
CheckDrawFeedback();
const auto context = device->GetContext();
if (argc == 1) CheckDepthAttachmentAlias();
if (argc == 1) CheckStorageAliasRetirement();
std::unique_lock lock(AgcDriver::GuestMemory::GpuMutex());
Require(DepthSupported(context, VK_FORMAT_D32_SFLOAT, 4u), "the native depth regression requires sampled D32 with four samples");
if (argc == 2 && std::string_view(argv[1]) == "--clear-refusals") {
@@ -64,6 +64,8 @@ struct PipelineTrace {
std::mutex mutex;
std::condition_variable released;
bool allowOptimization = false;
std::uint32_t libraries = 0;
std::uint32_t retaining = 0;
std::vector<VkPipeline> fast;
std::vector<VkPipeline> optimized;
VkCommandBuffer boundCommands = VK_NULL_HANDLE;
@@ -90,7 +92,12 @@ VKAPI_ATTR VkResult VKAPI_CALL CreatePipelines(VkDevice device, VkPipelineCache
const auto result = Trace->create(device, cache, count, infos, allocator, pipelines);
std::lock_guard lock(Trace->mutex);
for (std::uint32_t index = 0; index < count; ++index) {
if (pipelines[index] == VK_NULL_HANDLE || (infos[index].flags & VK_PIPELINE_CREATE_LIBRARY_BIT_KHR) != 0) continue;
if (pipelines[index] == VK_NULL_HANDLE) continue;
if ((infos[index].flags & VK_PIPELINE_CREATE_LIBRARY_BIT_KHR) != 0) {
++Trace->libraries;
if ((infos[index].flags & VK_PIPELINE_CREATE_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT) != 0) ++Trace->retaining;
continue;
}
auto& handles = (infos[index].flags & VK_PIPELINE_CREATE_LINK_TIME_OPTIMIZATION_BIT_EXT) != 0 ? Trace->optimized : Trace->fast;
handles.push_back(pipelines[index]);
}
@@ -217,12 +224,17 @@ void Run(AgcDriver::VulkanDevice& device, Context context, PipelineTrace& trace,
CheckPixels(masked, phase);
};
render(false, "fast link");
{
std::lock_guard lock(trace.mutex);
Require(trace.libraries != 0, "the readback pipeline's libraries were not created through the traced device");
Require(trace.retaining == (optimize ? trace.libraries : 0u), std::to_string(trace.retaining) + " of the " + std::to_string(trace.libraries) + " libraries were created to retain link-time optimization info, expected " + (optimize ? "all of them" : "none, since no optimized link will be made"));
}
trace.ReleaseOptimization();
WaitForOptimizedPipelines(context.device);
Require(PipelineLibraryCountersOf(context.device).linked == 1, "the readback pipeline was not linked from graphics libraries");
Require(trace.optimized.size() == (optimize ? 1u : 0u), "the optimized pipeline creation count differs from the requested mode");
render(optimize, "completed link");
std::printf("pipeline readback passed: fast draws %u, optimized draws %u, optimized creations %zu\n", optimize ? 1u : 2u, optimize ? 1u : 0u, trace.optimized.size());
std::printf("pipeline readback passed: fast draws %u, optimized draws %u, optimized creations %zu, libraries %u (%u retaining link-time optimization info)\n", optimize ? 1u : 2u, optimize ? 1u : 0u, trace.optimized.size(), trace.libraries, trace.retaining);
}
}
@@ -0,0 +1,202 @@
#include "prx/libSceAgcDriver/Execution/include/DisplayBuffer.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#endif
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <exception>
#include <iostream>
#include <mutex>
#include <optional>
#include <span>
#include <string>
#include <vector>
namespace {
using AgcDriver::Graphics::Require;
using ShaderRecompiler::ShaderStage;
constexpr std::uint32_t Threads = 32;
constexpr std::uint32_t Side = 128;
constexpr std::uint64_t DisplayFormat = 0x8000000000000000ull;
constexpr std::size_t SurfaceBytes = 65536;
constexpr std::size_t KeysOffset = SurfaceBytes;
constexpr std::size_t KeysCapacity = 32768;
constexpr std::size_t CopiedKeys = 256;
constexpr std::size_t SourceKeysOffset = KeysOffset + KeysCapacity;
constexpr std::size_t BlockBytes = 2 * 65536;
constexpr std::uint8_t Uncompressed = 0xff;
constexpr std::uint8_t Cleared = 0x00;
alignas(256) constexpr std::array<std::uint32_t, 7> CopyKeysCode{
0x34020083, 0xe0341000, 0x80000201, 0xbf8c3f70, 0xe0741000, 0x80010201, 0xbf810000,
};
std::uint64_t AddressOf(const void* data) {
return static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(data));
}
class GuestBlock {
public:
GuestBlock() {
#ifdef _WIN32
block = static_cast<std::uint8_t*>(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
#else
block = static_cast<std::uint8_t*>(std::aligned_alloc(65536, BlockBytes));
#endif
Require(block != nullptr, "present key writer wait: cannot allocate the guest block");
GuestAllocations::Mutation().Add(block, BlockBytes, true, true, true);
}
~GuestBlock() {
GuestAllocations::Mutation().Remove(block);
#ifdef _WIN32
VirtualFree(block, 0, MEM_RELEASE);
#else
std::free(block);
#endif
}
GuestBlock(const GuestBlock&) = delete;
GuestBlock& operator=(const GuestBlock&) = delete;
std::uint8_t* Data() { return block; }
private:
std::uint8_t* block = nullptr;
};
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t bytes) {
const auto address = AddressOf(data);
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu), bytes, 0x31016facu};
}
ShaderRecompiler::RecompileResult Compile(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> code, const std::vector<std::uint32_t>& userData) {
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1};
ShaderRecompiler::RecompileRequest request{
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
{32, 0, userData, compute, std::nullopt, std::nullopt, memory},
device.Target(),
{0, 0, 0, 128}
};
request.useCache = false;
return ShaderRecompiler::Recompile(request);
}
struct Scenario {
std::uint8_t* surface;
std::uint8_t* keys;
std::uint8_t* source;
std::size_t keyBytes;
AgcDriver::DisplayBuffer buffer;
std::vector<std::uint32_t> copyData;
};
Scenario Prepare(std::uint8_t* block) {
Scenario scenario{block, block + KeysOffset, block + SourceKeysOffset, 0, {AddressOf(block), DisplayFormat, Side, Side}, std::vector<std::uint32_t>(16, 0u)};
scenario.buffer.dccAddress = AddressOf(scenario.keys);
scenario.keyBytes = AgcDriver::DisplayBufferKeyBytes(scenario.buffer);
Require(AgcDriver::DisplayBufferSize(scenario.buffer) <= SurfaceBytes && scenario.keyBytes >= CopiedKeys && scenario.keyBytes <= KeysCapacity, "present key writer wait: the display buffer does not fit the guest block");
const auto source = BufferDescriptor(scenario.source, CopiedKeys);
const auto destination = BufferDescriptor(scenario.keys, CopiedKeys);
std::copy(source.begin(), source.end(), scenario.copyData.begin());
std::copy(destination.begin(), destination.end(), scenario.copyData.begin() + 4);
return scenario;
}
void Reset(const Scenario& scenario) {
std::memset(scenario.surface, 0x55, SurfaceBytes);
std::memset(scenario.keys, Uncompressed, scenario.keyBytes);
std::memset(scenario.keys, Cleared, CopiedKeys);
std::memset(scenario.source, Uncompressed, CopiedKeys);
AgcDriver::GuestMemory::BumpCollectEpoch();
}
std::string PresentFailure(AgcDriver::VulkanDevice& device, const AgcDriver::DisplayBuffer& buffer) {
try {
device.PresentDisplayBuffer(buffer);
} catch (const std::exception& error) {
return error.what();
}
return "the headless present succeeded";
}
const std::string NoSwapchain = "Vulkan presentation: device has no swapchain";
bool CanCopyKeys(AgcDriver::VulkanDevice& device, const Scenario& scenario) {
const std::array<std::uint32_t, 4> pattern{0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu};
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
const bool filled = device.FillBuffer(AddressOf(scenario.keys), CopiedKeys, pattern);
device.WaitIdle();
return filled;
}
void RunPendingWriter(AgcDriver::VulkanDevice& device, const Scenario& scenario) {
Reset(scenario);
const auto copy = Compile(device, CopyKeysCode, scenario.copyData);
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
Require(AgcDriver::GuestMemory::GpuMutex().DepthOnThisThread() == 1, "present key writer wait: the presenter's hold is not the outermost");
device.Dispatch(copy, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(CopyKeysCode.data()));
Require(AgcDriver::Graphics::DccKeyWritersPending(scenario.buffer.dccAddress, scenario.keyBytes), "a recorded kernel that copies data over the display keys is not seen as a pending key writer");
const auto holds = AgcDriver::GuestMemory::GpuHoldSerial();
const auto failure = PresentFailure(device, scenario.buffer);
const auto acquired = AgcDriver::GuestMemory::GpuHoldSerial() - holds;
Require(failure == NoSwapchain, "a present behind a pending writer of the display keys stopped before the swapchain: " + failure);
Require(acquired != 0, "the presenter synchronized with the display keys' writer without releasing the GPU mutex");
Require(!AgcDriver::Graphics::DccKeyWritersPending(scenario.buffer.dccAddress, scenario.keyBytes), "the display keys' writer is still pending after the present");
Require(scenario.keys[0] == Uncompressed && scenario.keys[CopiedKeys - 1] == Uncompressed, "the present read the display keys before their writer landed");
}
void RunNoWriter(AgcDriver::VulkanDevice& device, const Scenario& scenario) {
std::memset(scenario.keys, Uncompressed, scenario.keyBytes);
AgcDriver::GuestMemory::BumpCollectEpoch();
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
device.WaitIdle();
Require(!AgcDriver::Graphics::DccKeyWritersPending(scenario.buffer.dccAddress, scenario.keyBytes), "the idle device still reports a pending writer of the display keys");
const auto holds = AgcDriver::GuestMemory::GpuHoldSerial();
const auto failure = PresentFailure(device, scenario.buffer);
const auto acquired = AgcDriver::GuestMemory::GpuHoldSerial() - holds;
Require(failure == NoSwapchain, "a present with no pending writer of the display keys stopped before the swapchain: " + failure);
Require(acquired == 0, "a present with no pending writer of the display keys released the GPU mutex");
}
}
int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
if (!device->CanWaitUnlocked()) {
std::puts("skipped, the device has no timeline semaphores: the presenter cannot wait without the GPU mutex");
return VulkanTestSkipped;
}
GuestBlock block;
const auto scenario = Prepare(block.Data());
if (!CanCopyKeys(*device, scenario)) {
std::puts("skipped, host imports are unavailable: the key writer of the test is a copied writer");
return VulkanTestSkipped;
}
RunPendingWriter(*device, scenario);
RunNoWriter(*device, scenario);
std::puts("present key writer wait tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}
@@ -0,0 +1,202 @@
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp"
#include "prx/libSceAgcDriver/Graphics/include/State.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#include <algorithm>
#include <array>
#include <bit>
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <span>
#include <string>
#include <utility>
#include <vector>
namespace {
using AgcDriver::Graphics::Require;
using ShaderRecompiler::ShaderStage;
constexpr std::uint32_t Width = 64;
constexpr std::uint32_t Height = 16;
constexpr std::uint32_t WaveSize = 64;
constexpr std::uint32_t PlainControl = 0x50u;
constexpr std::byte Kept{0x40};
alignas(256) std::array<std::byte, Width * Height * 4> Pixels{};
alignas(256) std::array<std::byte, 128 * 128 * 4> DepthMemory{};
alignas(256) std::array<std::byte, 256 * 256> StencilMemory{};
alignas(256) constexpr std::array<std::uint32_t, 6> VertexCode{
0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000,
};
alignas(256) constexpr std::array<std::uint32_t, 4> PixelCode{
0x7e0e02f2, 0xf800180f, 0x07070707, 0xbf810000,
};
alignas(256) constexpr std::array<std::array<float, 4>, 3> Vertices{{
{-1.0f, -1.0f, 0.25f, 1.0f}, {3.0f, -1.0f, 0.25f, 1.0f}, {-1.0f, 3.0f, 0.25f, 1.0f},
}};
struct Shaders {
ShaderRecompiler::RecompileResult vertex;
ShaderRecompiler::RecompileResult pixel;
std::uint32_t pixelPush = 0;
};
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) {
const auto address = reinterpret_cast<std::uintptr_t>(data);
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu};
}
ShaderRecompiler::ShaderPixelStageInfo PixelInfo() {
const AgcDriver::Registers context{
{0x1b3, 0x700u}, {0x1b4, 0x700u}, {0x1b6, 0u}, {0x1c5, 9u}, {0x203, PlainControl},
};
return AgcDriver::Graphics::DecodePixelStageInfo(context, {0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u});
}
Shaders Compile(AgcDriver::VulkanDevice& device) {
const auto target = device.Target();
std::vector<std::uint32_t> vertexUserData(4, 0u);
const auto vertexBuffer = BufferDescriptor(Vertices.data(), 16u, static_cast<std::uint32_t>(Vertices.size()));
std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin());
const std::array<ShaderRecompiler::MemoryRegion, 1> vertexMemory{{{reinterpret_cast<std::uintptr_t>(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}};
ShaderRecompiler::RecompileRequest vertex{
{ShaderStage::Vertex, reinterpret_cast<std::uintptr_t>(VertexCode.data()), VertexCode, 0, {}},
{WaveSize, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory},
target,
{0, 0, 0, 64}
};
vertex.useCache = false;
Shaders shaders{ShaderRecompiler::Recompile(vertex), {}, 0};
const auto vertexPush = static_cast<std::uint32_t>(shaders.vertex.pushConstants.size());
std::vector<std::uint32_t> pixelUserData(8, 0u);
const std::array<ShaderRecompiler::MemoryRegion, 1> pixelMemory{{{reinterpret_cast<std::uintptr_t>(PixelCode.data()), std::as_bytes(std::span(PixelCode))}}};
ShaderRecompiler::RecompileRequest fragment{
{ShaderStage::Fragment, reinterpret_cast<std::uintptr_t>(PixelCode.data()), PixelCode, 0, {}},
{WaveSize, 0, pixelUserData, std::nullopt, PixelInfo(), std::nullopt, pixelMemory},
target,
PixelPushLayout(vertexPush, target)
};
fragment.useCache = false;
shaders.pixel = ShaderRecompiler::Recompile(fragment);
shaders.pixelPush = PixelPushOffset(vertexPush, target);
return shaders;
}
AgcDriver::QueueState StencilQueue(std::uint32_t ops, std::uint32_t refMask, std::uint32_t depthControl) {
AgcDriver::QueueState queue;
queue.userConfig[0x242] = 4;
queue.context = {
{0x2d5, 0x2000},
{0x1b6, 0}, {0x1b8, 0}, {0x207, 0}, {0x203, 0x800},
{0x2dc, 0xaa00}, {0x2f8, 0}, {0x292, 2}, {0x293, 0},
{0x80, 0}, {0x8d, 0}, {0x83, 0xffff}, {0x8c, 0xa},
{0x2f9, 0x2d}, {0x313, 0x6000}, {0x30e, 0xffffffff}, {0x30f, 0xffffffff},
{0x206, 0x43f}, {0x204, 0x80000}, {0x205, 0x240},
{0x8e, 0xf}, {0x8f, 0xf}, {0x202, 0xcc0010},
{0x1c4, 0}, {0x1c5, 9}, {0x1c3, 4}, {0x31c, 0x28028},
{0x31b, 0}, {0x31d, 0}, {0x3b0, (63u << 14u) | 3u},
{0x3b8, 0x9000000}, {0x1e0, 0},
{0xc, 0}, {0xd, 0x40040},
{0x81, 0x80000000}, {0x82, 0x40040},
{0x90, 0x80000000}, {0x91, 0x40040},
{0x94, 0x80000000}, {0x95, 0x40040},
{0x000, 0}, {0x002, 0}, {0x007, (1u << 16u) | 3u}, {0x00a, 0}, {0x00b, 0}, {0x010, 0x22900983u}, {0x011, 0x20000181u},
{0x012, 0x100}, {0x014, 0x100}, {0x013, 0x200}, {0x015, 0x200}, {0x005, 0x300}, {0x01e, 0x1},
{0x10b, ops}, {0x10c, refMask}, {0x10d, refMask}, {0x200, depthControl}
};
const auto address = reinterpret_cast<std::uintptr_t>(Pixels.data());
queue.context[0x318] = static_cast<std::uint32_t>(address >> 8u);
queue.context[0x390] = static_cast<std::uint32_t>(address >> 40u);
queue.context[0x10f] = std::bit_cast<std::uint32_t>(32.0f);
queue.context[0x110] = std::bit_cast<std::uint32_t>(32.0f);
queue.context[0x111] = std::bit_cast<std::uint32_t>(-2.0f);
queue.context[0x112] = std::bit_cast<std::uint32_t>(2.0f);
queue.context[0x113] = std::bit_cast<std::uint32_t>(1.0f);
queue.context[0x114] = 0;
queue.context[0xb4] = 0;
queue.context[0xb5] = std::bit_cast<std::uint32_t>(1.0f);
queue.shader[0x008] = 0x100;
queue.shader[0x009] = 0;
return queue;
}
void Draw(AgcDriver::VulkanDevice& device, const Shaders& shaders, const VkStencilOpState& stencil) {
Pixels.fill(Kept);
const std::array<AgcDriver::Graphics::CompiledShader, 2> stages{{
{ShaderStage::Vertex, &shaders.vertex, 0},
{ShaderStage::Fragment, &shaders.pixel, shaders.pixelPush}
}};
AgcDriver::Graphics::State state{};
state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, WaveSize, WaveSize, std::nullopt, std::nullopt};
state.color = {reinterpret_cast<std::uintptr_t>(Pixels.data()), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, Pixels.size(), 0xe4u};
state.colors = {state.color};
state.hasColorTarget = true;
state.depth = AgcDriver::Graphics::DepthTarget{reinterpret_cast<std::uintptr_t>(DepthMemory.data()), reinterpret_cast<std::uintptr_t>(StencilMemory.data()), {Width, Height}, VK_FORMAT_D32_SFLOAT_S8_UINT, 1.0f, 0};
state.depthTest = false;
state.depthCompare = VK_COMPARE_OP_ALWAYS;
state.stencilTest = true;
state.stencilFront = stencil;
state.stencilBack = stencil;
state.renderExtent = {Width, Height};
state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
state.viewport = {0, static_cast<float>(Height), static_cast<float>(Width), -static_cast<float>(Height), 0, 1};
state.negativeOneToOne = false;
state.scissor = {{0, 0}, {Width, Height}};
state.cullMode = VK_CULL_MODE_NONE;
state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
state.blend.colorWriteMask = 15;
state.blends = {state.blend};
state.blendConstants = {};
device.Draw(state, AgcDriver::Pm4::DrawParameters{0, 3u, 0, 1u, 0, false}, stages);
device.WaitIdle();
}
std::uint32_t Expected(std::uint32_t op, std::uint32_t stored, std::uint32_t value, std::uint32_t writeMask) {
const auto result = op == 10u ? stored & value : op == 11u ? stored | value : op == 12u ? stored ^ value : ~(stored ^ value);
return ((stored & ~writeMask) | (result & writeMask)) & 0xffu;
}
}
int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
const auto shaders = Compile(*device);
const auto replace = [](std::uint32_t value) { return VkStencilOpState{VK_STENCIL_OP_REPLACE, VK_STENCIL_OP_REPLACE, VK_STENCIL_OP_REPLACE, VK_COMPARE_OP_ALWAYS, 0xffu, 0xffu, value}; };
const auto equal = [](std::uint32_t value) { return VkStencilOpState{VK_STENCIL_OP_KEEP, VK_STENCIL_OP_KEEP, VK_STENCIL_OP_KEEP, VK_COMPARE_OP_EQUAL, 0xffu, 0u, value}; };
std::size_t checked = 0;
for (const auto stored : {0xa5u, 0x3cu}) {
for (const auto op : {10u, 11u, 12u, 15u}) {
for (const auto [writeMask, value] : {std::pair{0xffu, 0x3cu}, std::pair{0x0fu, 0x96u}, std::pair{0xf6u, 0xffu}}) {
for (const auto [ops, compare] : {std::pair{op << 4u, 7u}, std::pair{op, 0u}}) {
const auto refMask = 0xff00u | (writeMask << 16u) | (value << 24u);
const auto face = AgcDriver::Graphics::DecodeState(StencilQueue(ops, refMask, 1u | (compare << 8u))).stencilFront;
const auto expected = Expected(op, stored, value, writeMask);
const auto name = "stencil operation " + std::to_string(op) + " in DB_STENCIL_CONTROL " + std::to_string(ops) + " with DB_STENCILREFMASK " + std::to_string(refMask) + " on stored value " + std::to_string(stored);
Draw(*device, shaders, replace(stored));
Draw(*device, shaders, face);
Draw(*device, shaders, equal(expected));
for (std::uint32_t pixel = 0; pixel < Width * Height; ++pixel) {
Require(Pixels[pixel * 4u] == std::byte{255}, name + ": pixel " + std::to_string(pixel) + " does not hold the expected stencil value " + std::to_string(expected));
}
Draw(*device, shaders, equal(expected ^ 0x80u));
Require(std::ranges::all_of(std::span(Pixels).first(4), [](std::byte value) { return value == Kept; }), name + ": the stencil value also equals " + std::to_string(expected ^ 0x80u));
++checked;
}
}
}
}
std::printf("stencil logic operation tests passed: %zu states\n", checked);
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}
@@ -88,7 +88,7 @@ ShaderRecompiler::ShaderPixelStageInfo PixelInfo(std::uint32_t shaderControl) {
return AgcDriver::Graphics::DecodePixelStageInfo(context, {0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u});
}
void Draw(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> pixelCode, std::uint32_t shaderControl, const VkStencilOpState& stencil, std::uint32_t vertices = 3u, std::uint32_t instances = 1u, bool depthFail = false, const std::function<void(AgcDriver::Graphics::State&)>& alter = {}, const std::function<void(AgcDriver::Pm4::DrawParameters&)>& alterDraw = {}, std::span<const std::array<float, 4>> vertexData = Vertices, std::span<const std::uint32_t> vertexCode = VertexCode, std::span<const std::uint32_t> pixelData = {}, std::span<const ShaderRecompiler::MemoryRegion> pixelSrt = {}, const std::function<void(ShaderRecompiler::RecompileResult&)>& alterVertex = {}) {
void Draw(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> pixelCode, std::uint32_t shaderControl, const VkStencilOpState& stencil, std::uint32_t vertices = 3u, std::uint32_t instances = 1u, bool depthFail = false, const std::function<void(AgcDriver::Graphics::State&)>& alter = {}, const std::function<void(AgcDriver::Pm4::DrawParameters&)>& alterDraw = {}, std::span<const std::array<float, 4>> vertexData = Vertices, std::span<const std::uint32_t> vertexCode = VertexCode, std::span<const std::uint32_t> pixelData = {}, std::span<const ShaderRecompiler::MemoryRegion> pixelSrt = {}, const std::function<void(ShaderRecompiler::RecompileResult&)>& alterVertex = {}, std::uint32_t pixelPushBytes = 0u) {
Pixels.fill(Kept);
auto target = device.Target();
std::vector<std::uint32_t> capabilities(target.supportedCapabilities.begin(), target.supportedCapabilities.end());
@@ -113,11 +113,13 @@ void Draw(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> pixelC
auto pixelUserData = pixelData.empty() ? std::vector<std::uint32_t>(8, 0u) : std::vector<std::uint32_t>(pixelData.begin(), pixelData.end());
std::vector<ShaderRecompiler::MemoryRegion> pixelMemory{{reinterpret_cast<std::uintptr_t>(pixelCode.data()), std::as_bytes(pixelCode)}};
pixelMemory.insert(pixelMemory.end(), pixelSrt.begin(), pixelSrt.end());
auto pixelLayout = PixelPushLayout(vertexPush, target);
if (pixelPushBytes != 0u) pixelLayout.pushConstantSizeBytes = pixelPushBytes;
ShaderRecompiler::RecompileRequest fragment{
{ShaderStage::Fragment, reinterpret_cast<std::uintptr_t>(pixelCode.data()), pixelCode, 0, {}},
{WaveSize, 0, pixelUserData, std::nullopt, PixelInfo(shaderControl), std::nullopt, pixelMemory},
target,
PixelPushLayout(vertexPush, target)
pixelLayout
};
fragment.useCache = false;
const auto pixelResult = ShaderRecompiler::Recompile(fragment);
@@ -350,6 +352,14 @@ int main() {
const std::array<std::uint32_t, 1> colorData{0x3f800000u};
Draw(*device, userColor, exported, replace(), 3u, 1u, false, {}, {}, Vertices, VertexCode, colorData);
Check(all, "dynamic reference preserves the adjacent shader-data push payload");
auto bufferColor = std::vector<std::uint32_t>(alternating.begin(), alternating.end());
*std::find(bufferColor.begin(), bufferColor.end(), 0x7e0e02f2u) = 0x7e0e0208u;
bufferColor.insert(bufferColor.begin(), {0xf4200200u, 0xfa000000u, 0xbf8cc07fu});
alignas(256) const std::array<std::uint32_t, 4> colorBuffer{0x3f800000u, 0u, 0u, 0u};
const auto colorDescriptor = BufferDescriptor(colorBuffer.data(), 4u, static_cast<std::uint32_t>(colorBuffer.size()));
const std::array<ShaderRecompiler::MemoryRegion, 1> colorMemory{{{reinterpret_cast<std::uintptr_t>(colorBuffer.data()), std::as_bytes(std::span(colorBuffer))}}};
Draw(*device, bufferColor, exported, replace(), 3u, 1u, false, {}, {}, Vertices, VertexCode, colorDescriptor, colorMemory, {}, sizeof(std::uint32_t));
Check(all, "dynamic reference keeps guest buffer offsets in shader data outside the push constants");
auto guarded = std::vector<std::uint32_t>(alternating.begin(), alternating.end());
*std::find(guarded.begin(), guarded.end(), 0x7e0e02f2u) = 0x7e0e020cu;
guarded.insert(guarded.begin(), {0xf4040280u, 0xfa000018u, 0xf4000305u, 0xfa000000u, 0xbf8cc07fu});
@@ -369,6 +379,29 @@ int main() {
catch (const std::exception& error) { faultThrown = std::string(error.what()).find("BDA") != std::string::npos; }
Require(faultThrown, "poisoned readonly SRT replay did not throw a host fault");
Require(std::ranges::all_of(Pixels, [](std::byte value) { return value == Kept; }), "poisoned replay published guest color results before its fault");
alignas(256) std::array<std::uint32_t, Width + 1u> columnValues{};
alignas(256) std::array<float, Width> columnTexels{};
columnValues[0] = 0x2fu;
for (std::uint32_t x = 0; x < Width; ++x) {
columnValues[x + 1u] = 0x30u + x;
columnTexels[x] = 1.0f;
}
constexpr std::array<std::uint32_t, 15> bufferImageCode{
0x7e060f00u, 0x7e080280u, 0xf0001108u, 0x00010503u, 0xe0302000u, 0x80000603u, 0xe0302000u, 0x80030803u,
0xbf8c3f70u, 0x7e0e02f2u, 0xf8000082u, 0x00000600u, 0xf800180fu, 0x07050805u, 0xbf810000u,
};
const auto columnBuffer = BufferDescriptor(columnValues.data() + 1, 4u, Width);
const auto alignedBuffer = BufferDescriptor(columnValues.data(), 4u, Width + 1u);
const auto texelAddress = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(columnTexels.data()));
const std::array<std::uint32_t, 8> columnTexture{static_cast<std::uint32_t>(texelAddress >> 8u), static_cast<std::uint32_t>((texelAddress >> 40u) & 0xffu) | (22u << 20u) | (((Width - 1u) & 3u) << 30u), (Width - 1u) >> 2u, 0xfacu | (9u << 28u), 0u, 0u, 0u, 0u};
std::vector<std::uint32_t> bufferImageData(columnBuffer.begin(), columnBuffer.end());
bufferImageData.insert(bufferImageData.end(), columnTexture.begin(), columnTexture.end());
bufferImageData.insert(bufferImageData.end(), alignedBuffer.begin(), alignedBuffer.end());
Draw(*device, PlainPixelCode, plain, replace(0u));
Draw(*device, bufferImageCode, exported, replace(), 3u, 1u, false, {}, {}, Vertices, VertexCode, bufferImageData);
Check(all, "a replayed export beside an image draws the image's texels");
Draw(*device, PlainPixelCode, plain, equal(0x35u));
Check([](std::uint32_t x, std::uint32_t) { return x == 5u; }, "a replayed export beside an image reads its guest buffer at a 4-byte offset");
std::puts("stencil replay tests passed");
return 0;
} catch (const std::exception& error) {
@@ -663,6 +663,10 @@ int APS5_VABI sceAudioOutSysOpen(int userId, std::uint32_t mode) {
return allocatePort(PORT_TYPE_SYS, SYS_PORT_SAMPLES, SYS_PORT_FRAME_RATE, Format::S16_8Ch);
}
int APS5_VABI sceAudioOutSetMainOutput() {
return 0;
}
int APS5_VABI sceAudioOutSetMixLevelPadSpk(int handle, std::uint32_t mixLevel) {
std::lock_guard<std::mutex> lock(g_mutex);
Port* port = getPort(handle);
@@ -129,11 +129,6 @@ int APS5_VABI sceAudioOutGetFocusEnablePid(void) {
return 0;
}
int APS5_VABI sceAudioOutSetMainOutput(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutSetVolumeDown(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -46,6 +46,10 @@ int main() {
Require(Throws([] { sceAudioOutSysConfigureOutput(1, 0, 4, 1, 0); }), "another mode throws");
Require(Throws([] { sceAudioOutSysConfigureOutput(1, 0, 5, 2, 0); }), "another target throws");
int mainOutput = -1;
Require(!Throws([&mainOutput] { mainOutput = sceAudioOutSetMainOutput(); }), "sceAudioOutSetMainOutput does not throw");
Require(mainOutput == 0, "sceAudioOutSetMainOutput returns 0");
Require(sceAudioOutSysClose(truehd) == 0, "close the first Sys port");
Require(Throws([truehd] { sceAudioOutSysClose(truehd); }), "a closed Sys port throws");
const int ex = sceAudioOutExOpen(SYSTEM_USER, 0);
+34 -32
View File
@@ -210,10 +210,11 @@ struct JisChange {
uint16_t code;
};
constexpr JisChange JIS_X0208_CHANGES[6] = {{0x2141, 0x301C}, {0x2142, 0x2016}, {0x215D, 0x2212}, {0x2171, 0x00A2}, {0x2172, 0x00A3}, {0x224C, 0x00AC}};
constexpr uint16_t JIS_X0208_UNSETTLED_CELL = 0x2140;
constexpr uint16_t JIS_X0208_UNSETTLED_CODE = 0xFF3C;
constexpr const char* JIS_X0208_UNSETTLED = "libSceCesCs: the console's mapping of JIS X 0208 cell 0x2140 (U+005C or U+FF3C) is unknown";
constexpr JisChange JIS_X0208_CHANGES[7] = {{0x213D, 0x2014}, {0x2141, 0x301C}, {0x2142, 0x2016}, {0x215D, 0x2212}, {0x2171, 0x00A2}, {0x2172, 0x00A3}, {0x224C, 0x00AC}};
constexpr uint16_t JIS_X0208_BACKSLASH_CELL = 0x2140;
constexpr JisChange JIS_X0208_EUC_FALLBACKS[2] = {{0x216F, 0x00A5}, {0x2131, 0x203E}};
enum class JisStrict { None, Sjis, Euc };
constexpr uint32_t jisIndex(uint16_t jis) {
return ((jis >> 8) - 0x21) * 94 + (jis & 0xFF) - 0x21;
@@ -223,22 +224,21 @@ bool isCp932ExtensionRow(uint32_t row) {
return row == 13 || (row >= 89 && row <= 92);
}
uint16_t strictJisX0208(uint32_t index) {
if (isCp932ExtensionRow(index / 94 + 1) || index == jisIndex(JIS_X0208_UNSETTLED_CELL)) return 0;
uint16_t strictJisX0208(JisStrict strict, uint32_t index) {
if (isCp932ExtensionRow(index / 94 + 1)) return 0;
if (index == jisIndex(JIS_X0208_BACKSLASH_CELL)) return strict == JisStrict::Sjis ? 0x005C : 0xFF3C;
for (const JisChange& change : JIS_X0208_CHANGES) {
if (jisIndex(change.jis) == index) return change.code;
}
return JisX0208::TO_UCS2[index];
}
uint32_t jisX0208ToUnicode(bool strict, uint32_t index) {
if (!strict) return JisX0208::TO_UCS2[index];
if (index == jisIndex(JIS_X0208_UNSETTLED_CELL)) throw std::runtime_error(JIS_X0208_UNSETTLED);
return strictJisX0208(index);
uint32_t jisX0208ToUnicode(JisStrict strict, uint32_t index) {
return strict == JisStrict::None ? JisX0208::TO_UCS2[index] : strictJisX0208(strict, index);
}
void requireSettledSjisX0208(uint32_t code) {
if (code == 0x5C || code == 0x7E || code == 0xA5 || code == 0x203E) throw std::runtime_error("libSceCesCs: the console's mapping of Shift-JIS bytes 0x5C and 0x7E (ASCII or JIS X 0201 Roman) is unknown");
uint32_t sjisX0208Single(uint8_t byte) {
return byte == 0x5C ? 0x00A5 : byte == 0x7E ? 0x203E : byte;
}
constexpr uint8_t EUC_CN_TAG[8] = {'E', 'U', 'C', 'C', 'N', '9', '3', '6'};
@@ -507,7 +507,7 @@ const void* contextProfile(const void* context) {
constexpr uint8_t EUC_KR_TAG[8] = {'E', 'U', 'C', 'K', 'R', '9', '4', '9'};
constexpr uint8_t UHC_TAG[8] = {'U', 'H', 'C', 'C', 'P', '9', '4', '9'};
constexpr uint8_t BIG5_TAG[8] = {'B', 'I', 'G', '5', 'C', '9', '5', '0'};
constexpr uint16_t BIG5_LAST_CELL[6] = {0x2550, 0x255E, 0x2561, 0x256A, 0x5341, 0x5345};
constexpr uint16_t BIG5_LAST_CELL[2] = {0x5341, 0x5345};
bool isEucKr(const void* profile) {
return profile != nullptr && std::memcmp(profile, EUC_KR_TAG, sizeof(EUC_KR_TAG)) == 0;
@@ -674,7 +674,7 @@ int decodeEucJp(const void* profile, const uint8_t* mbcs, uint32_t mbcsMax, uint
if (mbcsMax < 2) return needBytes(2, length);
if (mbcs[1] < 0xA1 || mbcs[1] > 0xFE) return rejectTrail(mbcs[1], 2, length);
length = 2;
code = jisX0208ToUnicode(isEucJpX0208(profile), (lead - 0xA1) * 94 + (mbcs[1] - 0xA1));
code = jisX0208ToUnicode(isEucJpX0208(profile) ? JisStrict::Euc : JisStrict::None, (lead - 0xA1) * 94 + (mbcs[1] - 0xA1));
return code != 0 ? 0 : CES_ERROR_UNASSIGNED_CODE;
}
if (lead == 0x8F && hasSs3(profile)) {
@@ -701,9 +701,8 @@ int decodeMbcs(const void* profile, const uint8_t* mbcs, uint32_t mbcsMax, uint3
if (isSjis(profile)) {
const bool strict = isSjisX0208(profile);
if (lead < 0x80) {
if (strict) requireSettledSjisX0208(lead);
length = 1;
code = lead;
code = strict ? sjisX0208Single(lead) : lead;
return 0;
}
if (lead >= 0xA1 && lead <= 0xDF) {
@@ -738,7 +737,7 @@ int decodeMbcs(const void* profile, const uint8_t* mbcs, uint32_t mbcsMax, uint3
int cell = 0;
sjisToJis(lead, trail, row, cell);
if (row < 1 || row > 94 || cell < 1 || cell > 94) return unassigned(2, length);
code = jisX0208ToUnicode(strict, (row - 1) * 94 + (cell - 1));
code = jisX0208ToUnicode(strict ? JisStrict::Sjis : JisStrict::None, (row - 1) * 94 + (cell - 1));
if (code == 0) return unassigned(2, length);
length = 2;
return 0;
@@ -754,13 +753,16 @@ struct JisCell {
bool supplementary;
};
std::vector<JisCell> buildJisReverse(bool strict) {
std::vector<JisCell> buildJisReverse(JisStrict strict) {
std::vector<JisCell> entries;
entries.reserve(2 * 94 * 94);
for (uint32_t cell = 0; cell < 94 * 94; ++cell) {
const uint16_t code = strict ? strictJisX0208(cell) : JisX0208::TO_UCS2[cell];
const uint16_t code = static_cast<uint16_t>(jisX0208ToUnicode(strict, cell));
if (code != 0) entries.push_back({code, static_cast<uint16_t>(cell), false});
}
if (strict == JisStrict::Euc) {
for (const JisChange& fallback : JIS_X0208_EUC_FALLBACKS) entries.push_back({fallback.code, static_cast<uint16_t>(jisIndex(fallback.jis)), false});
}
for (uint32_t cell = 0; cell < 94 * 94; ++cell) {
if (JisX0212::TO_UCS2[cell] != 0) entries.push_back({JisX0212::TO_UCS2[cell], static_cast<uint16_t>(cell), true});
}
@@ -769,13 +771,11 @@ std::vector<JisCell> buildJisReverse(bool strict) {
return entries;
}
const std::vector<JisCell>& jisReverse(bool strict) {
if (strict) {
static const auto strictReverse = buildJisReverse(true);
return strictReverse;
}
static const auto reverse = buildJisReverse(false);
return reverse;
const std::vector<JisCell>& jisReverse(JisStrict strict) {
static const auto reverse = buildJisReverse(JisStrict::None);
static const auto sjisReverse = buildJisReverse(JisStrict::Sjis);
static const auto eucReverse = buildJisReverse(JisStrict::Euc);
return strict == JisStrict::Sjis ? sjisReverse : strict == JisStrict::Euc ? eucReverse : reverse;
}
uint32_t jisAlias(uint32_t code) {
@@ -804,8 +804,12 @@ int encodeMbcs(const void* profile, uint32_t code, uint8_t* mbcs, uint32_t& leng
}
if (isSjis(profile)) {
const bool strict = isSjisX0208(profile);
if (strict) requireSettledSjisX0208(code);
if (code < 0x80) {
if (strict && (code == 0x00A5 || code == 0x203E)) {
length = 1;
mbcs[0] = code == 0x00A5 ? 0x5C : 0x7E;
return 0;
}
if (code < 0x80 && !(strict && (code == 0x5C || code == 0x7E))) {
length = 1;
mbcs[0] = static_cast<uint8_t>(code);
return 0;
@@ -822,8 +826,7 @@ int encodeMbcs(const void* profile, uint32_t code, uint8_t* mbcs, uint32_t& leng
return 0;
}
if (code > 0xFFFF) return CES_ERROR_UNASSIGNED_CODE;
if (strict && code == JIS_X0208_UNSETTLED_CODE) throw std::runtime_error(JIS_X0208_UNSETTLED);
const auto& reverse = jisReverse(strict);
const auto& reverse = jisReverse(strict ? JisStrict::Sjis : JisStrict::None);
const auto found = std::lower_bound(reverse.begin(), reverse.end(), static_cast<uint16_t>(code), [](const JisCell& entry, uint16_t value) { return entry.code < value; });
if (found == reverse.end() || found->code != code || found->supplementary) return CES_ERROR_UNASSIGNED_CODE;
jisToSjis(found->cell / 94 + 1, found->cell % 94 + 1, mbcs[0], mbcs[1]);
@@ -850,8 +853,7 @@ int encodeMbcs(const void* profile, uint32_t code, uint8_t* mbcs, uint32_t& leng
return 0;
}
if (code > 0xFFFF) return CES_ERROR_UNASSIGNED_CODE;
if (strict && code == JIS_X0208_UNSETTLED_CODE) throw std::runtime_error(JIS_X0208_UNSETTLED);
const auto& reverse = jisReverse(strict);
const auto& reverse = jisReverse(strict ? JisStrict::Euc : JisStrict::None);
const auto found = std::lower_bound(reverse.begin(), reverse.end(), static_cast<uint16_t>(code), [](const JisCell& entry, uint16_t value) { return entry.code < value; });
if (found == reverse.end() || found->code != code || (found->supplementary && !hasSs3(profile))) return CES_ERROR_UNASSIGNED_CODE;
if (found->supplementary) {
@@ -35,7 +35,7 @@ constexpr std::uint32_t LookUp(std::int32_t keyboardType, std::int32_t japaneseV
constexpr std::uint32_t traditionalChinese[] = {26, 27, 28, 29, 39};
return traditionalChineseVariant >= 0 && traditionalChineseVariant < 5 ? traditionalChinese[traditionalChineseVariant] : 0;
}
const std::int32_t index = keyboardType - kFirstMappedType;
const std::int64_t index = std::int64_t{keyboardType} - kFirstMappedType;
return index >= 0 && index < kMappedTypeCount ? kImeTypeByKeyboardType[index] : 0;
}
+3 -1
View File
@@ -8,9 +8,11 @@ if(BUILD_TESTING)
target_include_directories(fiber_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(fiber_tests PRIVATE libSceFiber libc)
add_test(NAME fiber COMMAND fiber_tests)
add_test(NAME fiber_contextless COMMAND fiber_tests --contextless)
add_asan_test_options(fiber detect_stack_use_after_return=0)
add_asan_test_options(fiber_contextless detect_stack_use_after_return=0)
if(WIN32)
set_tests_properties(fiber PROPERTIES ENVIRONMENT_MODIFICATION
set_tests_properties(fiber fiber_contextless PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceFiber>")
endif()
endif()
+29 -17
View File
@@ -106,14 +106,17 @@ static bool TraceFibers() {
static void CompletePendingSuspend() {
auto& thread = ThreadState();
if (thread.pendingSuspend) {
thread.pendingSuspend->state.store(FiberState::Suspended, std::memory_order_release);
auto* fiber = thread.pendingSuspend;
const auto state = fiber->context ? FiberState::Suspended : FiberState::Idle;
if (!fiber->context) fiber->savedStack = nullptr;
fiber->state.store(state, std::memory_order_release);
thread.pendingSuspend = nullptr;
}
}
#ifdef _WIN32
extern "C" void Aps5FiberSwitchStack_nid_no_patch(void** save, void* load);
extern "C" void Aps5FiberSwitchStack_nid_no_patch(void** save, void** load, Fiber* start);
extern "C" void Aps5FiberTrampoline_nid_no_patch();
asm(R"(
@@ -143,7 +146,12 @@ Aps5FiberSwitchStack_nid_no_patch:
stmxcsr 0xa0(%rsp)
fnstcw 0xa4(%rsp)
mov %rsp, (%rcx)
mov %rdx, %rsp
mov (%rdx), %rsp
test %r8, %r8
jz 1f
mov %r8, %r12
jmp Aps5FiberTrampoline_nid_no_patch
1:
movaps 0x00(%rsp), %xmm6
movaps 0x10(%rsp), %xmm7
movaps 0x20(%rsp), %xmm8
@@ -214,7 +222,7 @@ static void UnpinStack(const void* context, std::uint64_t bytes) {
#else
extern "C" void Aps5FiberSwitchStack_nid_no_patch(void** save, void* load);
extern "C" void Aps5FiberSwitchStack_nid_no_patch(void** save, void** load, Fiber* start);
extern "C" void Aps5FiberTrampoline_nid_no_patch();
asm(".text\n"
@@ -229,7 +237,12 @@ asm(".text\n"
stmxcsr 0(%rsp)
fnstcw 4(%rsp)
mov %rsp, (%rdi)
mov %rsi, %rsp
mov (%rsi), %rsp
test %rdx, %rdx
jz 1f
mov %rdx, %r12
jmp )" APS5_ASM_SYMBOL("Aps5FiberTrampoline_nid_no_patch") R"(
1:
ldmxcsr 0(%rsp)
fldcw 4(%rsp)
add $8, %rsp
@@ -337,6 +350,7 @@ static std::uint8_t* StackTop(const Fiber* fiber) {
}
static std::uint64_t* SizedWindow(const Fiber* fiber) {
if (!fiber->context) return nullptr;
return reinterpret_cast<std::uint64_t*>(StackTop(fiber) - fiber->contextSize);
}
@@ -362,6 +376,10 @@ static Fiber* AsFiber(FiberObject* object) {
extern "C" [[noreturn]] void Aps5FiberMain_nid_no_patch(Fiber* fiber) {
CompletePendingSuspend();
if (!fiber->context && fiber->setFpuRegisters) {
_mm_setcsr(FIBER_DEFAULT_MXCSR);
asm volatile("fldcw %0" : : "m"(FIBER_DEFAULT_FPU_CONTROL));
}
fiber->entry(fiber->argOnInitialize, ThreadState().transfer);
throw std::runtime_error(std::string("sceFiber: entry function of fiber '") + fiber->name + "' returned");
}
@@ -392,13 +410,7 @@ static bool AcquireForResume(Fiber* target) {
}
if (state != FiberState::Idle && state != FiberState::Suspended) return false;
if (target->state.compare_exchange_weak(state, FiberState::Running, std::memory_order_acq_rel)) {
if (state == FiberState::Idle) {
if (!target->context) {
target->state.store(FiberState::Idle, std::memory_order_release);
throw std::runtime_error(std::string("sceFiber: fiber '") + target->name + "' has no context; running a fiber on the stack of its caller is not supported");
}
PrepareInitialStack(target);
}
if (state == FiberState::Idle && target->context) PrepareInitialStack(target);
return true;
}
}
@@ -446,7 +458,7 @@ static int32_t AcquireWithContext(Fiber* target, void* addr_context, uint64_t si
static void Resume(Fiber* target, void** save, std::uint64_t argOnRun) {
const auto* frame = static_cast<const InitialFrame*>(target->savedStack);
if (frame->returnAddress == 0) {
if (target->context && frame->returnAddress == 0) {
std::fprintf(stderr, "[fiber] resuming '%s' with a wiped context: saved=%p context=%p size=0x%llx\n", target->name, target->savedStack,
static_cast<void*>(target->context), static_cast<unsigned long long>(target->contextSize));
const auto* words = static_cast<const std::uint64_t*>(target->savedStack);
@@ -456,8 +468,8 @@ static void Resume(Fiber* target, void** save, std::uint64_t argOnRun) {
}
ThreadState().current = target;
ThreadState().transfer = argOnRun;
SetBounds(FiberBounds(target));
Aps5FiberSwitchStack_nid_no_patch(save, target->savedStack);
SetBounds(target->context ? FiberBounds(target) : ThreadState().threadBounds);
Aps5FiberSwitchStack_nid_no_patch(save, target->context ? &target->savedStack : &ThreadState().threadStack, target->context ? nullptr : target);
}
static int32_t InitializeFiber(FiberObject* object, const char* name, GuestFiberEntry entry, uint64_t arg_on_initialize, void* addr_context, uint64_t size_context, uint32_t flags) {
@@ -558,7 +570,7 @@ int32_t APS5_VABI sceFiberReturnToThread(uint64_t arg_on_return, uint64_t* arg_o
ThreadState().current = nullptr;
ThreadState().transfer = arg_on_return;
SetBounds(ThreadState().threadBounds);
Aps5FiberSwitchStack_nid_no_patch(&self->savedStack, ThreadState().threadStack);
Aps5FiberSwitchStack_nid_no_patch(&self->savedStack, &ThreadState().threadStack, nullptr);
CompletePendingSuspend();
if (arg_on_run) *arg_on_run = ThreadState().transfer;
return SCE_OK;
@@ -581,7 +593,7 @@ int32_t APS5_VABI sceFiberGetInfo(FiberObject* object, FiberInfo* fiber_info) {
fiber_info->size_context = fiber->contextSize;
std::memcpy(fiber_info->name, fiber->name, sizeof(fiber_info->name));
fiber_info->size_context_margin = static_cast<uint64_t>(-1);
if (fiber->contextSizeCheck) {
if (fiber->contextSizeCheck && fiber->context) {
const auto* words = SizedWindow(fiber);
const auto* end = words + fiber->contextSize / sizeof(std::uint64_t);
fiber_info->size_context_margin = static_cast<uint64_t>(std::find_if(words, end, [](std::uint64_t word) { return word != FIBER_CONTEXT_FILL; }) - words) * sizeof(std::uint64_t);
+4 -1
View File
@@ -64,6 +64,7 @@ struct FontWritingData {
float penY = 0.0f;
bool hasAggregateMetrics = false;
bool hasCurrentStep = false;
bool hasCurrentCharacter = false;
bool maskFormatCharacters = true;
bool ownsStorage = false;
bool hasRawGlyphMetrics = false;
@@ -684,6 +685,7 @@ const FontWritingStep* APS5_VABI sceFontWritingRefersRenderStep(FontWriting* fon
if (!PopulateWritingStep(*writing, writing->nextIndex)) return nullptr;
writing->currentIndex = writing->nextIndex;
writing->hasCurrentStep = true;
writing->hasCurrentCharacter = true;
AccumulateWritingMetrics(*writing);
++writing->nextIndex;
return &writing->step;
@@ -692,7 +694,8 @@ const FontWritingStep* APS5_VABI sceFontWritingRefersRenderStep(FontWriting* fon
FontTextCharacter* APS5_VABI sceFontWritingRefersRenderStepCharacter(FontWriting* fontWriting, const FontWritingLetterStep** pLetterStep) {
if (pLetterStep) *pLetterStep = nullptr;
auto* writing = GetWritingData(fontWriting);
if (!writing || !writing->hasCurrentStep || !writing->storage || writing->currentIndex >= writing->storage->characters.size()) return nullptr;
if (!writing || !writing->hasCurrentStep || !writing->hasCurrentCharacter || !writing->storage || writing->currentIndex >= writing->storage->characters.size()) return nullptr;
writing->hasCurrentCharacter = false;
auto& letterStep = writing->letterStep;
letterStep = {};
letterStep.x = writing->step.x;
@@ -6,6 +6,9 @@
extern "C" {
std::int32_t APS5_VABI FT_Outline_GetInsideBorder_nid_postfix(const FreeType::GuestOutline* outline);
std::int32_t APS5_VABI FT_Outline_GetOutsideBorder_nid_postfix(const FreeType::GuestOutline* outline);
int APS5_VABI FT_Init_FreeType_nid_postfix(FreeType::GuestLibrary** alibrary);
int APS5_VABI FT_Done_FreeType_nid_postfix(FreeType::GuestLibrary* library);
int APS5_VABI FT_New_Face_nid_postfix(FreeType::GuestLibrary* library, const char* filepathname, std::int64_t faceIndex, FreeType::GuestFaceRec** aface);
@@ -0,0 +1,68 @@
#include "prx/libSceFreeType/include/Api.hpp"
#include <algorithm>
#include <bit>
#include <cstdint>
#include <stdexcept>
namespace {
bool Clockwise(const FreeType::GuestOutline* outline) {
if (!outline || outline->n_points == 0) return true;
if (!outline->points) throw std::invalid_argument("FreeType outline border: null points");
auto minimum = outline->points[0];
auto maximum = minimum;
for (unsigned i = 1; i < outline->n_points; ++i) {
const auto point = outline->points[i];
minimum.x = std::min(minimum.x, point.x);
minimum.y = std::min(minimum.y, point.y);
maximum.x = std::max(maximum.x, point.x);
maximum.y = std::max(maximum.y, point.y);
}
if (minimum.x == maximum.x || minimum.y == maximum.y) return false;
constexpr std::int64_t limit = 0x1000000;
if (minimum.x < -limit || minimum.y < -limit || maximum.x > limit || maximum.y > limit) return false;
if (outline->n_contours == 0) return false;
if (!outline->contours) throw std::invalid_argument("FreeType outline border: null contours");
unsigned first = 0;
for (unsigned contour = 0; contour < outline->n_contours; ++contour) {
const unsigned last = outline->contours[contour];
if (last < first || last >= outline->n_points)
throw std::invalid_argument("FreeType outline border: invalid contour endpoint");
first = last + 1;
}
const auto magnitude = [](std::int64_t value) { return static_cast<std::uint32_t>(value < 0 ? -value : value); };
const auto shift = [](std::uint32_t value) { const auto bits = std::bit_width(value); return bits > 15 ? bits - 15 : 0; };
const auto xShift = shift(magnitude(maximum.x) | magnitude(minimum.x));
const auto yShift = shift(static_cast<std::uint32_t>(maximum.y - minimum.y));
std::int64_t area = 0;
first = 0;
for (unsigned contour = 0; contour < outline->n_contours; ++contour) {
const unsigned last = outline->contours[contour];
auto previous = outline->points[last];
previous.x >>= xShift;
previous.y >>= yShift;
for (unsigned i = first; i <= last; ++i) {
auto current = outline->points[i];
current.x >>= xShift;
current.y >>= yShift;
area += (current.y - previous.y) * (current.x + previous.x);
previous = current;
}
first = last + 1;
}
return area < 0;
}
}
extern "C" {
std::int32_t APS5_VABI FT_Outline_GetInsideBorder_nid_postfix(const FreeType::GuestOutline* outline) {
return Clockwise(outline) ? 1 : 0;
}
std::int32_t APS5_VABI FT_Outline_GetOutsideBorder_nid_postfix(const FreeType::GuestOutline* outline) {
return Clockwise(outline) ? 0 : 1;
}
}
@@ -0,0 +1,38 @@
add_library(libSceFreeTypeOl SHARED EXCLUDE_FROM_ALL Export.cpp ../libSceFreeType/src/OutlineBorders.cpp)
target_include_directories(libSceFreeTypeOl PRIVATE ${LIBS_INCLUDE_DIR})
configure_windows_unwind(libSceFreeTypeOl)
if(BUILD_TESTING)
add_test_executable(guest_freetype_outline_border_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeOutlineBorder.cpp)
target_include_directories(guest_freetype_outline_border_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_freetype_outline_border_tests PRIVATE freetype ${CMAKE_DL_LIBS})
if(WIN32)
target_link_libraries(guest_freetype_outline_border_tests PRIVATE libc)
endif()
configure_windows_unwind(guest_freetype_outline_border_tests)
add_custom_target(freetype_outline_border_patched DEPENDS
${PATCHED_DIR}/libSceFreeTypeOl.prx ${PATCHED_DIR}/libSceFreeTypeOt.prx ${PATCHED_DIR}/libkernel.prx ${LIBC_PATCHED})
add_dependencies(guest_freetype_outline_border_tests freetype_outline_border_patched)
add_test(NAME guest_freetype_outline_border_reference COMMAND guest_freetype_outline_border_tests reference)
foreach(module IN ITEMS Ol Ot)
foreach(border IN ITEMS inside outside)
add_test(NAME guest_freetype_outline_border_${module}_${border}
COMMAND guest_freetype_outline_border_tests $<TARGET_FILE:libSceFreeType${module}> ${border} raw)
add_test(NAME guest_freetype_outline_border_${module}_${border}_patched
COMMAND guest_freetype_outline_border_tests ${PATCHED_DIR}/libSceFreeType${module}.prx ${border} patched)
set_tests_properties(guest_freetype_outline_border_${module}_${border}
guest_freetype_outline_border_${module}_${border}_patched PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_freetype_outline_border_${module}_${border} PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceFreeTypeOl>")
set_tests_properties(guest_freetype_outline_border_${module}_${border}_patched PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:${PATCHED_DIR}")
endif()
endforeach()
endforeach()
set_tests_properties(guest_freetype_outline_border_reference PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_freetype_outline_border_reference PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceFreeTypeOl>")
endif()
endif()
@@ -1 +1,3 @@
#pragma once
#include "prx/libSceFreeType/include/Api.hpp"
@@ -0,0 +1,3 @@
add_library(libSceFreeTypeOt SHARED EXCLUDE_FROM_ALL Export.cpp ../libSceFreeType/src/OutlineBorders.cpp)
target_include_directories(libSceFreeTypeOt PRIVATE ${LIBS_INCLUDE_DIR})
configure_windows_unwind(libSceFreeTypeOt)
+740 -1
View File
@@ -5,6 +5,745 @@
extern "C" {
APS5_DUMMY_FUN
int APS5_VABI sceFreeTypeOtUnknown_UD7IFu7ML2U_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("UD7IFu7ML2U", sceFreeTypeOtUnknown_UD7IFu7ML2U_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_2T7lbS_CSzA_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("2T7lbS+CSzA", sceFreeTypeOtUnknown_2T7lbS_CSzA_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_MGEIQsujKaY_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("MGEIQsujKaY", sceFreeTypeOtUnknown_MGEIQsujKaY_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_mkmgcRnAJog_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("mkmgcRnAJog", sceFreeTypeOtUnknown_mkmgcRnAJog_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_m18bTUNF_0g_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("m18bTUNF-0g", sceFreeTypeOtUnknown_m18bTUNF_0g_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_vTZ34rw1sfg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("vTZ34rw1sfg", sceFreeTypeOtUnknown_vTZ34rw1sfg_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_PvCNusrp9xQ_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("PvCNusrp9xQ", sceFreeTypeOtUnknown_PvCNusrp9xQ_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_dlM4ASZdcjk_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("dlM4ASZdcjk", sceFreeTypeOtUnknown_dlM4ASZdcjk_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_Ev3BqJ_U1Fg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Ev3BqJ-U1Fg", sceFreeTypeOtUnknown_Ev3BqJ_U1Fg_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_ZhtAXOoLNyo_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("ZhtAXOoLNyo", sceFreeTypeOtUnknown_ZhtAXOoLNyo_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_73vg_nRH68g_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("73vg+nRH68g", sceFreeTypeOtUnknown_73vg_nRH68g_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_wBKAYO7yEXw_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("wBKAYO7yEXw", sceFreeTypeOtUnknown_wBKAYO7yEXw_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_nSIux4OQyJM_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("nSIux4OQyJM", sceFreeTypeOtUnknown_nSIux4OQyJM_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_Dr3F3qrSQ2c_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Dr3F3qrSQ2c", sceFreeTypeOtUnknown_Dr3F3qrSQ2c_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_avLzN0tTxLI_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("avLzN0tTxLI", sceFreeTypeOtUnknown_avLzN0tTxLI_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_0rKmI1ZbMAc_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("0rKmI1ZbMAc", sceFreeTypeOtUnknown_0rKmI1ZbMAc_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_BBRhYfYBDgU_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("BBRhYfYBDgU", sceFreeTypeOtUnknown_BBRhYfYBDgU_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_c1nbr8oSWlg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("c1nbr8oSWlg", sceFreeTypeOtUnknown_c1nbr8oSWlg_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_AVEEb57HhUw_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("AVEEb57HhUw", sceFreeTypeOtUnknown_AVEEb57HhUw_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_x8y0qzLiYOU_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("x8y0qzLiYOU", sceFreeTypeOtUnknown_x8y0qzLiYOU_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_VW4GD_TaIUc_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("VW4GD-TaIUc", sceFreeTypeOtUnknown_VW4GD_TaIUc_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_GiuNQjoj4fs_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("GiuNQjoj4fs", sceFreeTypeOtUnknown_GiuNQjoj4fs_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_5qqbXOGlHw4_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("5qqbXOGlHw4", sceFreeTypeOtUnknown_5qqbXOGlHw4_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_4LY5pyZ5IQk_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("4LY5pyZ5IQk", sceFreeTypeOtUnknown_4LY5pyZ5IQk_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_zE0V_0aouZ4_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("zE0V+0aouZ4", sceFreeTypeOtUnknown_zE0V_0aouZ4_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_LsyswTUNTRs_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("LsyswTUNTRs", sceFreeTypeOtUnknown_LsyswTUNTRs_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_AfL_nqNC5fY_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("AfL-nqNC5fY", sceFreeTypeOtUnknown_AfL_nqNC5fY_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_WIvTHYs7ZQQ_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("WIvTHYs7ZQQ", sceFreeTypeOtUnknown_WIvTHYs7ZQQ_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_yrtsLmTfa48_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("yrtsLmTfa48", sceFreeTypeOtUnknown_yrtsLmTfa48_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_tCMhTh7h8D4_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("tCMhTh7h8D4", sceFreeTypeOtUnknown_tCMhTh7h8D4_nid_no_patch);
int APS5_VABI sceFreeTypeOtNewSize_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("zyezaHweY3U", sceFreeTypeOtNewSize_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_C_PpdS5zris_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("C-PpdS5zris", sceFreeTypeOtUnknown_C_PpdS5zris_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_g8eLxL9VAhU_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("g8eLxL9VAhU", sceFreeTypeOtUnknown_g8eLxL9VAhU_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_OeNH41MA2Vs_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("OeNH41MA2Vs", sceFreeTypeOtUnknown_OeNH41MA2Vs_nid_no_patch);
int APS5_VABI sceFreeTypeOtOutlineCopy_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Bhm8Injl0KA", sceFreeTypeOtOutlineCopy_nid_no_patch);
int APS5_VABI sceFreeTypeOtOutlineDecompose_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("++lgV9pRsO4", sceFreeTypeOtOutlineDecompose_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_BqGJsJcFYaM_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("BqGJsJcFYaM", sceFreeTypeOtUnknown_BqGJsJcFYaM_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_umIqupy8UU0_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("umIqupy8UU0", sceFreeTypeOtUnknown_umIqupy8UU0_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_l6Cf9rb9fZQ_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("l6Cf9rb9fZQ", sceFreeTypeOtUnknown_l6Cf9rb9fZQ_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_BgUMBGvhATU_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("BgUMBGvhATU", sceFreeTypeOtUnknown_BgUMBGvhATU_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_0Aal_TIC4so_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("0Aal-TIC4so", sceFreeTypeOtUnknown_0Aal_TIC4so_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_4RRjK_Wd1lU_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("4RRjK-Wd1lU", sceFreeTypeOtUnknown_4RRjK_Wd1lU_nid_no_patch);
int APS5_VABI sceFreeTypeOtOutlineGetOrientation_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("weVsBCT+TTQ", sceFreeTypeOtOutlineGetOrientation_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_QH2emcgeFck_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("QH2emcgeFck", sceFreeTypeOtUnknown_QH2emcgeFck_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_NG7OgYsgcF8_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("NG7OgYsgcF8", sceFreeTypeOtUnknown_NG7OgYsgcF8_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_9km02mPqPJE_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("9km02mPqPJE", sceFreeTypeOtUnknown_9km02mPqPJE_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_0Lv6K1d47jg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("0Lv6K1d47jg", sceFreeTypeOtUnknown_0Lv6K1d47jg_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_8fAMdZCnewA_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("8fAMdZCnewA", sceFreeTypeOtUnknown_8fAMdZCnewA_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_LsB538_IPrE_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("LsB538+IPrE", sceFreeTypeOtUnknown_LsB538_IPrE_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_hz_J3hohXW8_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("hz-J3hohXW8", sceFreeTypeOtUnknown_hz_J3hohXW8_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_KVbmKCGGowg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("KVbmKCGGowg", sceFreeTypeOtUnknown_KVbmKCGGowg_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_hF3stKUWbWg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("hF3stKUWbWg", sceFreeTypeOtUnknown_hF3stKUWbWg_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_p5GNJaXO2yE_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("p5GNJaXO2yE", sceFreeTypeOtUnknown_p5GNJaXO2yE_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_8cnVx1yH9_I_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("8cnVx1yH9-I", sceFreeTypeOtUnknown_8cnVx1yH9_I_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_HVSaZbxYi2s_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("HVSaZbxYi2s", sceFreeTypeOtUnknown_HVSaZbxYi2s_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_qAPXM5DN62Q_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("qAPXM5DN62Q", sceFreeTypeOtUnknown_qAPXM5DN62Q_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_5ipBcLYVc2k_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("5ipBcLYVc2k", sceFreeTypeOtUnknown_5ipBcLYVc2k_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_8A0cAU4yBVY_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("8A0cAU4yBVY", sceFreeTypeOtUnknown_8A0cAU4yBVY_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_z9Wsaf34JA4_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("z9Wsaf34JA4", sceFreeTypeOtUnknown_z9Wsaf34JA4_nid_no_patch);
int APS5_VABI sceFreeTypeOtRequestSize_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("PaDk9bdmdhU", sceFreeTypeOtRequestSize_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_Z8KG6700sw4_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Z8KG6700sw4", sceFreeTypeOtUnknown_Z8KG6700sw4_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_uK7vGxAQVxM_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("uK7vGxAQVxM", sceFreeTypeOtUnknown_uK7vGxAQVxM_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_zetC8Tp1S8w_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("zetC8Tp1S8w", sceFreeTypeOtUnknown_zetC8Tp1S8w_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_DAmwG6U7YIc_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("DAmwG6U7YIc", sceFreeTypeOtUnknown_DAmwG6U7YIc_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_Yu9oXRVmTTo_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Yu9oXRVmTTo", sceFreeTypeOtUnknown_Yu9oXRVmTTo_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_ihIHD8BTNxI_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("ihIHD8BTNxI", sceFreeTypeOtUnknown_ihIHD8BTNxI_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_yCEJIsjIOO8_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("yCEJIsjIOO8", sceFreeTypeOtUnknown_yCEJIsjIOO8_nid_no_patch);
int APS5_VABI sceFreeTypeOtSin_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("L1uHkWTazkI", sceFreeTypeOtSin_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_C7N2GuwqTA0_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("C7N2GuwqTA0", sceFreeTypeOtUnknown_C7N2GuwqTA0_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_JgtSc0g3G6E_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("JgtSc0g3G6E", sceFreeTypeOtUnknown_JgtSc0g3G6E_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_gVf7xRLdWm8_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("gVf7xRLdWm8", sceFreeTypeOtUnknown_gVf7xRLdWm8_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_FHdeCFVf_f8_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("FHdeCFVf-f8", sceFreeTypeOtUnknown_FHdeCFVf_f8_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_Qmgc0XACzRg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Qmgc0XACzRg", sceFreeTypeOtUnknown_Qmgc0XACzRg_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_0rGx_oFMLyk_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("0rGx-oFMLyk", sceFreeTypeOtUnknown_0rGx_oFMLyk_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_thA48N_0VwQ_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("thA48N-0VwQ", sceFreeTypeOtUnknown_thA48N_0VwQ_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_jl0VxYZb8_o_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("jl0VxYZb8-o", sceFreeTypeOtUnknown_jl0VxYZb8_o_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_Er0Lin_u20o_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Er0Lin-u20o", sceFreeTypeOtUnknown_Er0Lin_u20o_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_KHj92a3WDn4_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("KHj92a3WDn4", sceFreeTypeOtUnknown_KHj92a3WDn4_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_tDssQCH2syY_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("tDssQCH2syY", sceFreeTypeOtUnknown_tDssQCH2syY_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_dXJK59vXRxs_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("dXJK59vXRxs", sceFreeTypeOtUnknown_dXJK59vXRxs_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_BB4gl7SgLdk_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("BB4gl7SgLdk", sceFreeTypeOtUnknown_BB4gl7SgLdk_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_Fv8MWCUH8k8_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Fv8MWCUH8k8", sceFreeTypeOtUnknown_Fv8MWCUH8k8_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_RzgDJTjQst4_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("RzgDJTjQst4", sceFreeTypeOtUnknown_RzgDJTjQst4_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_4yt9cD1qcEI_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("4yt9cD1qcEI", sceFreeTypeOtUnknown_4yt9cD1qcEI_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_u6Xquj1bFwc_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("u6Xquj1bFwc", sceFreeTypeOtUnknown_u6Xquj1bFwc_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown__Xm1cLEpBec_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("-Xm1cLEpBec", sceFreeTypeOtUnknown__Xm1cLEpBec_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_uDXTnGbAz44_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("uDXTnGbAz44", sceFreeTypeOtUnknown_uDXTnGbAz44_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_iruPsrXCN7U_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("iruPsrXCN7U", sceFreeTypeOtUnknown_iruPsrXCN7U_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_a7RlARfexoo_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("a7RlARfexoo", sceFreeTypeOtUnknown_a7RlARfexoo_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_jX1zq1i8KfI_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("jX1zq1i8KfI", sceFreeTypeOtUnknown_jX1zq1i8KfI_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_U_0WKVAePww_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("U+0WKVAePww", sceFreeTypeOtUnknown_U_0WKVAePww_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_dlbnv6Wass8_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("dlbnv6Wass8", sceFreeTypeOtUnknown_dlbnv6Wass8_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_cmd41395g7Y_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("cmd41395g7Y", sceFreeTypeOtUnknown_cmd41395g7Y_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_SX5I_XzH8HI_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("SX5I+XzH8HI", sceFreeTypeOtUnknown_SX5I_XzH8HI_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_Bmi8PdvsM3M_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Bmi8PdvsM3M", sceFreeTypeOtUnknown_Bmi8PdvsM3M_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_jJh4SMt8NFQ_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("jJh4SMt8NFQ", sceFreeTypeOtUnknown_jJh4SMt8NFQ_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_1z6bBg1qBfU_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("1z6bBg1qBfU", sceFreeTypeOtUnknown_1z6bBg1qBfU_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_mF0B64O954g_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("mF0B64O954g", sceFreeTypeOtUnknown_mF0B64O954g_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_c002A4zTOgA_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("c002A4zTOgA", sceFreeTypeOtUnknown_c002A4zTOgA_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_mcT796Qgj_g_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("mcT796Qgj+g", sceFreeTypeOtUnknown_mcT796Qgj_g_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_b5gZp_r3WWI_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("b5gZp-r3WWI", sceFreeTypeOtUnknown_b5gZp_r3WWI_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_S_n0Md8Rjfw_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("S+n0Md8Rjfw", sceFreeTypeOtUnknown_S_n0Md8Rjfw_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_6a1p3QgXvo0_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("6a1p3QgXvo0", sceFreeTypeOtUnknown_6a1p3QgXvo0_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_JFSCOXTEvfg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("JFSCOXTEvfg", sceFreeTypeOtUnknown_JFSCOXTEvfg_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_xejRyEbsLcw_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("xejRyEbsLcw", sceFreeTypeOtUnknown_xejRyEbsLcw_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_R1mZutnlTiM_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("R1mZutnlTiM", sceFreeTypeOtUnknown_R1mZutnlTiM_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_4J4Baf8ana8_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("4J4Baf8ana8", sceFreeTypeOtUnknown_4J4Baf8ana8_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_Az4H9FlfxCA_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Az4H9FlfxCA", sceFreeTypeOtUnknown_Az4H9FlfxCA_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_xj3tNJptfLI_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("xj3tNJptfLI", sceFreeTypeOtUnknown_xj3tNJptfLI_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_TPKhRt1QIeE_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("TPKhRt1QIeE", sceFreeTypeOtUnknown_TPKhRt1QIeE_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_6_yXRlVrjbc_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("6+yXRlVrjbc", sceFreeTypeOtUnknown_6_yXRlVrjbc_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown__RLa_6fbshA_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("+RLa-6fbshA", sceFreeTypeOtUnknown__RLa_6fbshA_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_pBqh_Uh_gns_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("pBqh-Uh-gns", sceFreeTypeOtUnknown_pBqh_Uh_gns_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_2rcvKZfm5rI_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("2rcvKZfm5rI", sceFreeTypeOtUnknown_2rcvKZfm5rI_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_7cIS2cFG3PE_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("7cIS2cFG3PE", sceFreeTypeOtUnknown_7cIS2cFG3PE_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_LfyUi_LkdCk_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("LfyUi-LkdCk", sceFreeTypeOtUnknown_LfyUi_LkdCk_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_rzIWh7FJnmU_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("rzIWh7FJnmU", sceFreeTypeOtUnknown_rzIWh7FJnmU_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_cKndiZnt0EA_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("cKndiZnt0EA", sceFreeTypeOtUnknown_cKndiZnt0EA_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_eqGCaeizvWM_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("eqGCaeizvWM", sceFreeTypeOtUnknown_eqGCaeizvWM_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_RvFx7dv9ThU_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("RvFx7dv9ThU", sceFreeTypeOtUnknown_RvFx7dv9ThU_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_Po8kyd3DlIw_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Po8kyd3DlIw", sceFreeTypeOtUnknown_Po8kyd3DlIw_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_B3uU5aDZn94_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("B3uU5aDZn94", sceFreeTypeOtUnknown_B3uU5aDZn94_nid_no_patch);
int APS5_VABI sceFreeTypeOtTan_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("dojyaLKzKN0", sceFreeTypeOtTan_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_08_mhD_AAds_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("08-mhD-AAds", sceFreeTypeOtUnknown_08_mhD_AAds_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_64XMq6sDxUo_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("64XMq6sDxUo", sceFreeTypeOtUnknown_64XMq6sDxUo_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_4xG87W9dTSg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("4xG87W9dTSg", sceFreeTypeOtUnknown_4xG87W9dTSg_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_4_SScYDT5Pg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("4+SScYDT5Pg", sceFreeTypeOtUnknown_4_SScYDT5Pg_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_2rmvbvOCyjY_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("2rmvbvOCyjY", sceFreeTypeOtUnknown_2rmvbvOCyjY_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_1jmC2QcpRl0_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("1jmC2QcpRl0", sceFreeTypeOtUnknown_1jmC2QcpRl0_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_HMcNe70D9TI_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("HMcNe70D9TI", sceFreeTypeOtUnknown_HMcNe70D9TI_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_6wJwUIVwAxg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("6wJwUIVwAxg", sceFreeTypeOtUnknown_6wJwUIVwAxg_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_PrZtZyFwM6c_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("PrZtZyFwM6c", sceFreeTypeOtUnknown_PrZtZyFwM6c_nid_no_patch);
int APS5_VABI sceFreeTypeOtVectorUnit_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("3WpTQrvGUcU", sceFreeTypeOtVectorUnit_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_P6WOAeb0lJ8_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("P6WOAeb0lJ8", sceFreeTypeOtUnknown_P6WOAeb0lJ8_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_XGVABnpNksQ_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("XGVABnpNksQ", sceFreeTypeOtUnknown_XGVABnpNksQ_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_Ln9I6YfmYJU_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Ln9I6YfmYJU", sceFreeTypeOtUnknown_Ln9I6YfmYJU_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_7cVMwHmN308_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("7cVMwHmN308", sceFreeTypeOtUnknown_7cVMwHmN308_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_AEU2tkjdZ2w_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("AEU2tkjdZ2w", sceFreeTypeOtUnknown_AEU2tkjdZ2w_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_2lSRg9FfIOY_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("2lSRg9FfIOY", sceFreeTypeOtUnknown_2lSRg9FfIOY_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_fPrLu1YGOaw_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("fPrLu1YGOaw", sceFreeTypeOtUnknown_fPrLu1YGOaw_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_t3vCgnT5YL0_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("t3vCgnT5YL0", sceFreeTypeOtUnknown_t3vCgnT5YL0_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_8bkQ_YRnpOE_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("8bkQ+YRnpOE", sceFreeTypeOtUnknown_8bkQ_YRnpOE_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_q4qhGebYuGI_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("q4qhGebYuGI", sceFreeTypeOtUnknown_q4qhGebYuGI_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_ni_K_leoQNg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("ni-K-leoQNg", sceFreeTypeOtUnknown_ni_K_leoQNg_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_XfcgJB6V2a0_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("XfcgJB6V2a0", sceFreeTypeOtUnknown_XfcgJB6V2a0_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_TWzf5iykiGw_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("TWzf5iykiGw", sceFreeTypeOtUnknown_TWzf5iykiGw_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown__b4iVKArt94_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("-b4iVKArt94", sceFreeTypeOtUnknown__b4iVKArt94_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_uSNqxrARpEQ_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("uSNqxrARpEQ", sceFreeTypeOtUnknown_uSNqxrARpEQ_nid_no_patch);
}
@@ -1 +1,3 @@
#pragma once
#include "prx/libSceFreeType/include/Api.hpp"
+92 -13
View File
@@ -1,7 +1,9 @@
#include "prx/libSceGameUpdate/include/Api.hpp"
#include <array>
#include <atomic>
#include <cstdint>
#include <cstddef>
#include <mutex>
#include <stdexcept>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
@@ -11,20 +13,58 @@ static constexpr int SCE_GAME_UPDATE_ERROR_ALREADY_INITIALIZED = static_cast<int
static constexpr int SCE_GAME_UPDATE_ERROR_INVALID_ARGUMENT = static_cast<int>(0x80412803);
static constexpr int SCE_GAME_UPDATE_ERROR_INVALID_SIZE = static_cast<int>(0x80412804);
static constexpr int SCE_GAME_UPDATE_ERROR_REQUEST_NOT_FOUND = static_cast<int>(0x80412805);
static constexpr int SCE_GAME_UPDATE_ERROR_TOO_MANY_REQUESTS = static_cast<int>(0x80412806);
static constexpr int SCE_GAME_UPDATE_ERROR_ABORTED = static_cast<int>(0x80412807);
static constexpr int FIRST_REQUEST_ID = 0x20000000;
static constexpr int LAST_REQUEST_ID = 0x2FFFFFFF;
static_assert(sizeof(GameUpdateCheckParam) == 0x30);
static_assert(sizeof(GameUpdateCheckResult) == 0x30);
static std::atomic<bool> initialized{false};
struct Request {
int id;
bool aborted;
};
static int checkRequestId(int requestId) {
static std::mutex stateMutex;
static bool initialized = false;
static int nextRequestId = FIRST_REQUEST_ID;
static std::array<Request, 8> requests{};
static Request* findRequest(int requestId) {
for (Request& request : requests) {
if (request.id != 0 && request.id == requestId) {
return &request;
}
}
return nullptr;
}
static int followingRequestId(int requestId) {
return requestId == LAST_REQUEST_ID ? FIRST_REQUEST_ID : requestId + 1;
}
static int takeRequestId() {
int requestId = nextRequestId;
while (findRequest(requestId) != nullptr) {
requestId = followingRequestId(requestId);
}
nextRequestId = followingRequestId(requestId);
return requestId;
}
static int checkRequestId(int requestId, Request** request) {
if (requestId <= 0) {
return SCE_GAME_UPDATE_ERROR_REQUEST_NOT_FOUND;
}
if (!initialized.load()) {
if (!initialized) {
return SCE_GAME_UPDATE_ERROR_NOT_INITIALIZED;
}
return SCE_GAME_UPDATE_ERROR_REQUEST_NOT_FOUND;
*request = findRequest(requestId);
if (*request == nullptr) {
return SCE_GAME_UPDATE_ERROR_REQUEST_NOT_FOUND;
}
return 0;
}
static bool hasNonZeroReserved(const GameUpdateCheckParam* param) {
@@ -39,7 +79,13 @@ static bool hasNonZeroReserved(const GameUpdateCheckParam* param) {
extern "C" {
int APS5_VABI sceGameUpdateAbortRequest(int requestId) {
return checkRequestId(requestId);
std::lock_guard lock(stateMutex);
Request* request = nullptr;
if (int error = checkRequestId(requestId, &request); error != 0) {
return error;
}
request->aborted = true;
return 0;
}
int APS5_VABI sceGameUpdateCheck(int requestId, const GameUpdateCheckParam* param, GameUpdateCheckResult* result) {
@@ -55,18 +101,43 @@ int APS5_VABI sceGameUpdateCheck(int requestId, const GameUpdateCheckParam* para
if (param->option != 0 || hasNonZeroReserved(param)) {
return SCE_GAME_UPDATE_ERROR_INVALID_ARGUMENT;
}
return checkRequestId(requestId);
std::lock_guard lock(stateMutex);
Request* request = nullptr;
if (int error = checkRequestId(requestId, &request); error != 0) {
return error;
}
if (request->aborted) {
return SCE_GAME_UPDATE_ERROR_ABORTED;
}
result->found = false;
result->addcont_found = false;
return 0;
}
int APS5_VABI sceGameUpdateCreateRequest(void) {
if (!initialized.load()) {
std::lock_guard lock(stateMutex);
if (!initialized) {
return SCE_GAME_UPDATE_ERROR_NOT_INITIALIZED;
}
throw std::runtime_error("sceGameUpdateCreateRequest: patch service request creation is not implemented");
for (Request& request : requests) {
if (request.id != 0) {
continue;
}
const int requestId = takeRequestId();
request = {requestId, false};
return requestId;
}
return SCE_GAME_UPDATE_ERROR_TOO_MANY_REQUESTS;
}
int APS5_VABI sceGameUpdateDeleteRequest(int requestId) {
return checkRequestId(requestId);
std::lock_guard lock(stateMutex);
Request* request = nullptr;
if (int error = checkRequestId(requestId, &request); error != 0) {
return error;
}
*request = {};
return 0;
}
int APS5_VABI sceGameUpdateGetAddcontLatestVersion(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, GameUpdateAddcontVersionInfo* info) {
@@ -77,23 +148,31 @@ int APS5_VABI sceGameUpdateGetAddcontLatestVersion(uint32_t serviceLabel, const
if (entitlementLabel == nullptr) {
return SCE_GAME_UPDATE_ERROR_INVALID_ARGUMENT;
}
if (!initialized.load()) {
return SCE_GAME_UPDATE_ERROR_NOT_INITIALIZED;
{
std::lock_guard lock(stateMutex);
if (!initialized) {
return SCE_GAME_UPDATE_ERROR_NOT_INITIALIZED;
}
}
throw std::runtime_error("sceGameUpdateGetAddcontLatestVersion: entitlement version lookup is not implemented");
}
int APS5_VABI sceGameUpdateInitialize(void) {
if (initialized.exchange(true)) {
std::lock_guard lock(stateMutex);
if (initialized) {
return SCE_GAME_UPDATE_ERROR_ALREADY_INITIALIZED;
}
initialized = true;
return 0;
}
int APS5_VABI sceGameUpdateTerminate(void) {
if (!initialized.exchange(false)) {
std::lock_guard lock(stateMutex);
if (!initialized) {
return SCE_GAME_UPDATE_ERROR_NOT_INITIALIZED;
}
initialized = false;
requests = {};
return 0;
}
@@ -175,17 +175,15 @@ int GetKey2Char(std::int32_t handle, std::int32_t arrange, std::uint32_t ledStat
{
std::lock_guard lock(keyboardMutex);
if (!initialized) return KEYBOARD_ERROR_NOT_INITIALIZED;
if (keyCode < 4 || keyCode > 231) return KEYBOARD_ERROR_INVALID_ARG;
if (handle != KEYBOARD_HANDLE || !opened) return KEYBOARD_ERROR_INVALID_HANDLE;
}
if (charData == nullptr) return KEYBOARD_ERROR_INVALID_ARG;
if (arrange != KEYBOARD_ARRANGEMENT_101 && arrange != KEYBOARD_ARRANGEMENT_106) return KEYBOARD_ERROR_INVALID_ARG;
if (arrange == KEYBOARD_ARRANGEMENT_106 && (ledState & KEYBOARD_LED_KANA) != 0) {
NotImplemented_nid_no_patch(__func__);
return KEYBOARD_ERROR_INVALID_ARG;
}
*charData = {};
charData->char_code = character(arrange == KEYBOARD_ARRANGEMENT_106, ledState, modifierKey, keyCode);
charData->processed = charData->char_code != 0;
const std::uint16_t converted = character(arrange == KEYBOARD_ARRANGEMENT_106, ledState, modifierKey, keyCode);
charData->char_code = converted != 0 ? converted : keyCode;
charData->processed = converted != 0;
charData->length = charData->processed ? 1 : 0;
return KEYBOARD_OK;
}
@@ -5,10 +5,18 @@
#include "SDL_events.h"
#include "SDL_keyboard.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <initializer_list>
#include <source_location>
#include <stdexcept>
static void Require(bool value) { if (!value) std::abort(); }
static void Require(bool value, std::source_location location = std::source_location::current()) {
if (value) return;
std::fprintf(stderr, "%s:%u: keyboard assertion failed\n", location.file_name(), location.line());
std::abort();
}
static SDL_Event KeyEvent(Uint32 type, SDL_Scancode scancode, Uint16 mod = 0, Uint8 repeat = 0) {
SDL_Event event{};
@@ -23,14 +31,63 @@ static SDL_Event KeyEvent(Uint32 type, SDL_Scancode scancode, Uint16 mod = 0, Ui
static std::uint16_t Char(std::uint32_t led, std::uint32_t modifierKey, std::uint16_t keyCode, std::int32_t arrange = KEYBOARD_ARRANGEMENT_101) {
KeyboardCharData data{};
Require(sceKeyboardGetKey2Char(KEYBOARD_HANDLE, arrange, led, modifierKey, keyCode, &data) == KEYBOARD_OK);
Require(data.processed == (data.char_code != 0) && data.length == (data.processed ? 1 : 0));
return data.char_code;
Require(data.processed ? data.char_code != 0 : data.char_code == keyCode);
Require(data.length == (data.processed ? 1 : 0));
return data.processed ? data.char_code : 0;
}
static std::uint16_t Jis(std::uint32_t modifierKey, std::uint16_t keyCode, std::uint32_t led = 0) {
return Char(led, modifierKey, keyCode, KEYBOARD_ARRANGEMENT_106);
}
static void CheckInvalidKeys(bool opened) {
constexpr std::uint16_t invalidKeys[] = {0, 1, 2, 3, 232, 255, 256, 65535};
for (const auto key : invalidKeys) {
for (const auto handle : {KEYBOARD_HANDLE, 42}) {
KeyboardCharData data;
std::memset(&data, 0xa5, sizeof(data));
unsigned char before[sizeof(data)];
std::memcpy(before, &data, sizeof(data));
Require(sceKeyboardGetKey2Char(handle, KEYBOARD_ARRANGEMENT_101, 0, 0, key, &data) == KEYBOARD_ERROR_INVALID_ARG);
Require(std::memcmp(&data, before, sizeof(data)) == 0);
Require(sceKeyboardGetKey2Char(handle, KEYBOARD_ARRANGEMENT_106, KEYBOARD_LED_KANA, 0, key, nullptr) == KEYBOARD_ERROR_INVALID_ARG);
}
}
for (const auto key : {std::uint16_t{4}, std::uint16_t{231}}) {
KeyboardCharData data{};
const auto expected = opened ? KEYBOARD_OK : KEYBOARD_ERROR_INVALID_HANDLE;
Require(sceKeyboardGetKey2Char(KEYBOARD_HANDLE, KEYBOARD_ARRANGEMENT_101, 0, 0, key, &data) == expected);
}
}
static void CheckUnconvertedKeys() {
constexpr std::uint16_t unconvertedKeys[] = {0x29, 0x39, 0x3a, 0x4f, 0x59, 0x88, 0xe0, 0xe7};
for (const auto arrange : {KEYBOARD_ARRANGEMENT_101, KEYBOARD_ARRANGEMENT_106}) {
for (const auto key : unconvertedKeys) {
KeyboardCharData data{};
Require(sceKeyboardGetKey2Char(KEYBOARD_HANDLE, arrange, 0, 0, key, &data) == KEYBOARD_OK);
Require(!data.processed && data.char_code == key);
}
}
}
static void CheckKanaLed() {
for (const auto arrange : {KEYBOARD_ARRANGEMENT_101, KEYBOARD_ARRANGEMENT_106}) {
for (const auto modifier : {std::uint8_t{0}, std::uint8_t{KEYBOARD_MOD_LEFT_SHIFT}, std::uint8_t{KEYBOARD_MOD_RIGHT_SHIFT}}) {
for (const auto led : {std::uint8_t{0}, std::uint8_t{KEYBOARD_LED_NUM_LOCK | KEYBOARD_LED_CAPS_LOCK}}) {
for (std::uint16_t key = 4; key <= 231; ++key) {
KeyboardCharData withoutKana{};
KeyboardCharData withKana{};
Require(sceKeyboardGetKey2Char(KEYBOARD_HANDLE, arrange, led, modifier, key, &withoutKana) == KEYBOARD_OK);
Require(sceKeyboardGetKey2Char(KEYBOARD_HANDLE, arrange, led | KEYBOARD_LED_KANA, modifier, key, &withKana) == KEYBOARD_OK);
Require(withKana.processed == withoutKana.processed);
Require(withKana.length == withoutKana.length && withKana.char_code == withoutKana.char_code);
}
}
}
}
}
int main() {
KeyboardData data[16]{};
KeyboardCharData charData{};
@@ -39,11 +96,15 @@ int main() {
Require(sceKeyboardGetConnection(KEYBOARD_HANDLE, &connected) == KEYBOARD_ERROR_NOT_INITIALIZED);
Require(sceKeyboardInit() == KEYBOARD_OK);
Require(sceKeyboardInit() == KEYBOARD_OK);
CheckInvalidKeys(false);
Require(sceKeyboardOpen(1, 0, 1, nullptr) == KEYBOARD_ERROR_INVALID_ARG);
Require(sceKeyboardOpen(1, 1, 0, nullptr) == KEYBOARD_ERROR_INVALID_ARG);
Require(sceKeyboardGetKey2Char(KEYBOARD_HANDLE, KEYBOARD_ARRANGEMENT_101, 0, 0, 0x04, &charData) == KEYBOARD_ERROR_INVALID_HANDLE);
Require(sceKeyboardGetConnection(KEYBOARD_HANDLE, &connected) == KEYBOARD_ERROR_INVALID_HANDLE);
Require(sceKeyboardOpen(1, 0, 0, nullptr) == KEYBOARD_HANDLE);
CheckInvalidKeys(true);
CheckUnconvertedKeys();
CheckKanaLed();
Require(sceKeyboardOpen(1, 0, 0, nullptr) == KEYBOARD_ERROR_ALREADY_OPENED);
Require(sceKeyboardGetConnection(42, &connected) == KEYBOARD_ERROR_INVALID_HANDLE);
Require(sceKeyboardGetConnection(KEYBOARD_HANDLE, nullptr) == KEYBOARD_ERROR_INVALID_ARG);
@@ -78,6 +78,7 @@ if(BUILD_TESTING)
configure_windows_unwind(guest_posix_spawn_tests)
add_test(NAME guest_posix_spawn COMMAND guest_posix_spawn_tests)
set_tests_properties(guest_posix_spawn PROPERTIES TIMEOUT 20)
add_asan_test_options(guest_posix_spawn handle_sigbus=0)
if(WIN32)
set_tests_properties(guest_posix_spawn PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceLibcInternal>")
+3 -3
View File
@@ -87,17 +87,17 @@ std::int64_t APS5_VABI ASN1_INTEGER_get_nid_postfix(const Asn1String* value) {
}
unsigned char* APS5_VABI ASN1_STRING_data_nid_postfix(Asn1String* value) {
if (!value) NotImplemented_nid_no_patch(__func__);
if (!value) APS5_INVALID_ARG_EX;
return value->data;
}
int APS5_VABI ASN1_STRING_length_nid_postfix(const Asn1String* value) {
if (!value) NotImplemented_nid_no_patch(__func__);
if (!value) APS5_INVALID_ARG_EX;
return value->length;
}
int APS5_VABI ASN1_STRING_type_nid_postfix(Asn1String* value) {
if (!value) NotImplemented_nid_no_patch(__func__);
if (!value) APS5_INVALID_ARG_EX;
return value->type;
}
+13 -3
View File
@@ -36,6 +36,16 @@ void RequireThrows(TAction action, const char* message) {
Require(false, message);
}
template<typename TAction>
void RequireInvalidArgument(TAction action, const char* message) {
try {
action();
} catch (const std::invalid_argument&) {
return;
}
Require(false, message);
}
void CheckConversion(int type, std::span<const unsigned char> input, std::span<const unsigned char> expected, int length = -2) {
std::vector<unsigned char> storage(input.begin(), input.end());
storage.push_back(0);
@@ -65,9 +75,9 @@ void Accessors() {
Require(ASN1_STRING_data_nid_postfix(&value) == nullptr, "null data is preserved");
Require(ASN1_STRING_length_nid_postfix(&value) == -9, "length is returned without normalization");
Require(ASN1_STRING_type_nid_postfix(&value) == 0x7fffffff, "type is returned without normalization");
RequireThrows([] { ASN1_STRING_data_nid_postfix(nullptr); }, "null data accessor input");
RequireThrows([] { ASN1_STRING_length_nid_postfix(nullptr); }, "null length accessor input");
RequireThrows([] { ASN1_STRING_type_nid_postfix(nullptr); }, "null type accessor input");
RequireInvalidArgument([] { ASN1_STRING_data_nid_postfix(nullptr); }, "null data accessor input");
RequireInvalidArgument([] { ASN1_STRING_length_nid_postfix(nullptr); }, "null length accessor input");
RequireInvalidArgument([] { ASN1_STRING_type_nid_postfix(nullptr); }, "null type accessor input");
}
void Integers() {
@@ -291,6 +291,7 @@ static void WriteOutput(const Ngs2RenderBufferInfo& output, const std::vector<fl
}
auto* pcm = static_cast<std::int16_t*>(output.buffer);
for (std::size_t i = 0; i < mix.size(); i++) {
if (std::isnan(mix[i])) throw std::runtime_error("NGS2: a NaN sample in a PCM_I16L render buffer is not implemented");
pcm[i] = static_cast<std::int16_t>(std::min(std::lrint(std::clamp(mix[i], -1.0f, 1.0f) * 32768.0f), 32767L));
}
}
@@ -20,6 +20,7 @@ static constexpr std::uint8_t COMMAND_TYPE_INT = 3;
static constexpr std::uint8_t COMMAND_TYPE_UINT = 4;
static constexpr std::uint8_t COMMAND_TYPE_FLOAT_ARRAY = 0x11;
static constexpr float MAX_MATRIX_LEVEL = 4.0f;
static constexpr float MAX_MASTERING_GAIN = 32.0f;
struct Ngs2ReturnCode {
int code;
@@ -394,9 +395,8 @@ static void ApplyParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param) {
}
if (param.id == SCE_NGS2_MASTERING_VOICE_PARAM_GAIN || param.id == SCE_NGS2_CUSTOM_MASTERING_VOICE_PARAM_GAIN) {
const auto& gain = ParamAs<Ngs2MasteringVoiceGainParam>(param);
if (!std::isfinite(gain.fbw_level) || !std::isfinite(gain.lfe_level)) APS5_INVALID_ARG_EX;
voice.fbwLevel = gain.fbw_level;
voice.lfeLevel = gain.lfe_level;
voice.fbwLevel = std::clamp(gain.fbw_level, -MAX_MASTERING_GAIN, MAX_MASTERING_GAIN);
voice.lfeLevel = std::clamp(gain.lfe_level, -MAX_MASTERING_GAIN, MAX_MASTERING_GAIN);
return;
}
if (param.id == SCE_NGS2_MASTERING_VOICE_PARAM_OUTPUT || param.id == SCE_NGS2_CUSTOM_MASTERING_VOICE_PARAM_OUTPUT) {
@@ -9,6 +9,7 @@
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <deque>
#include <iterator>
#include <numbers>
#include <stdexcept>
@@ -1008,9 +1009,6 @@ static void TestMasteringGain() {
Control(toQuad, SCE_NGS2_VOICE_PARAM_PORT_MATRIX, Ngs2VoicePortMatrixParam{{}, 0, 0});
Patch(toQuad, quad);
Event(toQuad, SCE_NGS2_VOICE_EVENT_PLAY);
bool rejected = false;
try { Control(stereo, SCE_NGS2_MASTERING_VOICE_PARAM_GAIN, Ngs2MasteringVoiceGainParam{{}, NAN, 1.0f}); } catch (const std::exception&) { rejected = true; }
Require(rejected);
std::vector<float> outStereo(Grain * 2, -1.0f);
std::vector<float> outSurround(Grain * 6, -1.0f);
@@ -1044,6 +1042,80 @@ static void TestMasteringGain() {
Require(sceNgs2SystemDestroy(loudSystem, nullptr) == SCE_NGS2_OK);
}
static void TestMasteringGainClampedLikePs5() {
std::deque<std::vector<std::int16_t>> waveforms;
const auto chain = [&waveforms](std::uint32_t channels, std::uint32_t grains, uintptr_t& master) {
const auto system = CreateSystem();
master = Mastering(system, channels);
const auto& pcm = waveforms.emplace_back(Grain * grains, 16384);
const auto source = Sampler(system, pcm, 0);
const std::vector<float> levels(channels, 1.0f);
Control(source, SCE_NGS2_VOICE_PARAM_MATRIX_LEVELS, Ngs2VoiceMatrixLevelsParam{{}, 0, channels, levels.data()});
Control(source, SCE_NGS2_VOICE_PARAM_PORT_MATRIX, Ngs2VoicePortMatrixParam{{}, 0, 0});
Patch(source, master);
Event(source, SCE_NGS2_VOICE_EVENT_PLAY);
return system;
};
const auto render = [](uintptr_t system, std::uint32_t channels) {
std::vector<float> out(Grain * channels, -1.0f);
const Ngs2RenderBufferInfo info{out.data(), out.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, channels};
Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK);
return out;
};
const auto expect = [](const std::vector<float>& out, std::uint32_t channels, float fbwOut, float lfeOut) {
for (std::uint32_t i = 0; i < Grain; i++) {
for (std::uint32_t channel = 0; channel < channels; channel++) {
const float level = channel == 3 && channels >= 6 ? lfeOut : fbwOut;
const float sample = out[i * channels + channel];
Require(std::isnan(level) ? std::isnan(sample) : sample == 0.5f * level);
}
}
};
const auto gain = [&](std::uint32_t channels, float fbw, float lfe, float fbwOut, float lfeOut) {
uintptr_t master = 0;
const auto system = chain(channels, 1, master);
Control(master, SCE_NGS2_MASTERING_VOICE_PARAM_GAIN, Ngs2MasteringVoiceGainParam{{}, fbw, lfe});
expect(render(system, channels), channels, fbwOut, lfeOut);
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
};
const float inf = INFINITY;
for (const std::uint32_t channels : {6u, 8u}) {
gain(channels, 32.0f, 1.0f, 32.0f, 1.0f);
gain(channels, 33.0f, 1.0f, 32.0f, 1.0f);
gain(channels, 64.0f, 1.0f, 32.0f, 1.0f);
gain(channels, 1e30f, 1.0f, 32.0f, 1.0f);
gain(channels, inf, 1.0f, 32.0f, 1.0f);
gain(channels, -32.0f, 1.0f, -32.0f, 1.0f);
gain(channels, -33.0f, 1.0f, -32.0f, 1.0f);
gain(channels, -inf, 1.0f, -32.0f, 1.0f);
gain(channels, NAN, 1.0f, NAN, 1.0f);
gain(channels, 1.0f, 32.0f, 1.0f, 32.0f);
gain(channels, 1.0f, 33.0f, 1.0f, 32.0f);
gain(channels, 1.0f, 1e30f, 1.0f, 32.0f);
gain(channels, 1.0f, inf, 1.0f, 32.0f);
gain(channels, 1.0f, -33.0f, 1.0f, -32.0f);
gain(channels, 1.0f, -inf, 1.0f, -32.0f);
gain(channels, 1.0f, NAN, 1.0f, NAN);
}
gain(1, 0.5f, 2.0f, 0.5f, 0.5f);
gain(1, inf, NAN, 32.0f, 32.0f);
gain(2, -inf, 1e30f, -32.0f, -32.0f);
gain(2, 2.0f, 0.5f, 2.0f, 2.0f);
uintptr_t master = 0;
const auto system = chain(6, 4, master);
expect(render(system, 6), 6, 1.0f, 1.0f);
Control(master, SCE_NGS2_MASTERING_VOICE_PARAM_GAIN, Ngs2MasteringVoiceGainParam{{}, 0.5f, 0.25f});
expect(render(system, 6), 6, 0.5f, 0.25f);
Event(master, SCE_NGS2_VOICE_EVENT_STOP_IMM);
Event(master, SCE_NGS2_VOICE_EVENT_PLAY);
expect(render(system, 6), 6, 0.5f, 0.25f);
Control(master, SCE_NGS2_MASTERING_VOICE_PARAM_SETUP, Ngs2MasteringVoiceSetupParam{{}, 6, 0});
Event(master, SCE_NGS2_VOICE_EVENT_PLAY);
expect(render(system, 6), 6, 1.0f, 1.0f);
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
}
static uintptr_t RackVoice(uintptr_t rack, std::uint32_t index) {
uintptr_t voice = 0;
Require(sceNgs2RackGetVoiceHandle(rack, index, &voice) == SCE_NGS2_OK && voice != 0);
@@ -1729,6 +1801,9 @@ int main() {
TestReverbSetup();
TestReverbConversion();
TestSampleRate();
TestMasteringGainClampedLikePs5();
TestSystemOptionFlags();
TestUserData();
TestPcmCallbackReset();
+4 -4
View File
@@ -5,10 +5,10 @@
extern "C" {
int APS5_VABI sceNotificationSend(int type, std::uint8_t flag, std::uint8_t* message) {
(void)type;
(void)flag;
(void)message;
int APS5_VABI sceNotificationSend(int userId, bool logged, const char* payload) {
(void)userId;
(void)logged;
(void)payload;
NotImplemented_nid_no_patch(__func__);
return 0;
}
@@ -4,7 +4,7 @@
extern "C" {
int APS5_VABI sceNotificationSend(int type, std::uint8_t flag, std::uint8_t* message);
int APS5_VABI sceNotificationSend(int userId, bool logged, const char* payload);
int APS5_VABI sceNotificationSendById(int type, std::uint8_t flag, std::uint8_t* id, std::uint8_t* jsonData);
int APS5_VABI sceNotificationShowPsButtonPersistentBanner(std::uint8_t* jsonParam);
void APS5_VABI sceNotificationHidePsButtonPersistentBanner();
+30 -15
View File
@@ -1,4 +1,5 @@
#include "prx/libSceNpGameIntent/include/Api.hpp"
#include <atomic>
#include <cstdint>
#include <cstddef>
#include <cstring>
@@ -8,43 +9,57 @@
namespace {
constexpr int NpGameIntentErrorNotInitialized = -2141898750;
constexpr int NpGameIntentErrorInvalidArgument = -2141898748;
constexpr int NpGameIntentErrorIntentNotFound = -2141898746;
constexpr int NpGameIntentErrorValueNotFound = -2141898745;
constexpr int NpGameIntentUserIdInvalid = -1;
std::atomic_bool& NpGameIntentInitialized() {
static std::atomic_bool initialized{false};
return initialized;
}
}
extern "C" {
int APS5_VABI sceNpGameIntentGetPropertyValueString(const NpGameIntentData* intentData, const char* key, char* valueBuf, size_t bufSize) {
if (intentData == nullptr || key == nullptr || valueBuf == nullptr || bufSize == 0) {
APS5_INVALID_ARG_EX;
}
if (!NpGameIntentInitialized()) {
return NpGameIntentErrorNotInitialized;
}
if (intentData == nullptr || key == nullptr || key[0] == '\0' || valueBuf == nullptr || bufSize == 0) {
return NpGameIntentErrorInvalidArgument;
}
valueBuf[0] = '\0';
return NpGameIntentErrorValueNotFound;
valueBuf[0] = '\0';
return NpGameIntentErrorValueNotFound;
}
int APS5_VABI sceNpGameIntentInitialize(const void* initParam) {
(void)initParam;
return 0;
(void)initParam;
NpGameIntentInitialized() = true;
return 0;
}
int APS5_VABI sceNpGameIntentReceiveIntent(NpGameIntentInfo* intentInfo) {
if (intentInfo == nullptr) {
APS5_INVALID_ARG_EX;
}
if (!NpGameIntentInitialized()) {
return NpGameIntentErrorNotInitialized;
}
if (intentInfo == nullptr || intentInfo->size != 0x4140) {
return NpGameIntentErrorInvalidArgument;
}
intentInfo->user_id = NpGameIntentUserIdInvalid;
std::memset(intentInfo->intent_type, 0, sizeof(intentInfo->intent_type));
std::memset(&intentInfo->intent_data, 0, sizeof(intentInfo->intent_data));
intentInfo->user_id = NpGameIntentUserIdInvalid;
std::memset(intentInfo->intent_type, 0, sizeof(intentInfo->intent_type));
std::memset(&intentInfo->intent_data, 0, sizeof(intentInfo->intent_data));
return NpGameIntentErrorIntentNotFound;
return NpGameIntentErrorIntentNotFound;
}
int APS5_VABI sceNpGameIntentTerminate(void) {
return 0;
NpGameIntentInitialized() = false;
return 0;
}
int APS5_VABI sceNpGameIntentInitParamInit(NpGameIntentInitParam* param) {
+8 -11
View File
@@ -49,9 +49,8 @@ int APS5_VABI sceNpPartyGetId(void* id) {
return notInParty(id);
}
int APS5_VABI sceNpPartyGetMemberInfo(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceNpPartyGetMemberInfo(std::uint16_t, void* memberInfo) {
return notInParty(memberInfo);
}
int APS5_VABI sceNpPartyGetMemberInfoA(std::uint16_t, void* memberInfo) {
@@ -66,9 +65,8 @@ int APS5_VABI sceNpPartyGetMemberVoiceInfo(std::uint16_t, std::uint16_t, std::ui
return notInParty(memberVoiceState);
}
int APS5_VABI sceNpPartyGetMembers(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceNpPartyGetMembers(void* memberList) {
return notInParty(memberList);
}
int APS5_VABI sceNpPartyGetMembersA(void* memberList) {
@@ -106,17 +104,16 @@ int APS5_VABI sceNpPartyLeave(void) {
return 0;
}
int APS5_VABI sceNpPartyRegisterHandler(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpPartyRegisterHandlerA(const void* handlers, void*) {
if (!g_initialized) return NP_PARTY_ERROR_NOT_INITIALIZED;
if (!handlers) return NP_PARTY_ERROR_INVALID_ARGUMENT;
return 0;
}
int APS5_VABI sceNpPartyRegisterHandler(const void* handlers, void* userdata) {
return sceNpPartyRegisterHandlerA(handlers, userdata);
}
int APS5_VABI sceNpPartyRegisterPrivateHandler(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
+3 -3
View File
@@ -9,11 +9,11 @@ int APS5_VABI sceNpPartyCheckCallback(void);
int APS5_VABI sceNpPartyCreate(void);
int APS5_VABI sceNpPartyCreateA(void);
int APS5_VABI sceNpPartyGetId(void* id);
int APS5_VABI sceNpPartyGetMemberInfo(void);
int APS5_VABI sceNpPartyGetMemberInfo(std::uint16_t, void* memberInfo);
int APS5_VABI sceNpPartyGetMemberInfoA(std::uint16_t, void* memberInfo);
int APS5_VABI sceNpPartyGetMemberSessionInfo(std::uint16_t, void* memberGameSessionInfo);
int APS5_VABI sceNpPartyGetMemberVoiceInfo(std::uint16_t, std::uint16_t, std::uint8_t* memberVoiceState);
int APS5_VABI sceNpPartyGetMembers(void);
int APS5_VABI sceNpPartyGetMembers(void* memberList);
int APS5_VABI sceNpPartyGetMembersA(void* memberList);
int APS5_VABI sceNpPartyGetStateAsUser(void);
int APS5_VABI sceNpPartyGetStateAsUserA(std::int32_t, std::uint16_t* state);
@@ -21,7 +21,7 @@ int APS5_VABI sceNpPartyGetVoiceChatPriority(void);
int APS5_VABI sceNpPartyInitialize(const void* param);
int APS5_VABI sceNpPartyJoin(void);
int APS5_VABI sceNpPartyLeave(void);
int APS5_VABI sceNpPartyRegisterHandler(void);
int APS5_VABI sceNpPartyRegisterHandler(const void* handlers, void* userdata);
int APS5_VABI sceNpPartyRegisterHandlerA(const void* handlers, void*);
int APS5_VABI sceNpPartyRegisterPrivateHandler(void);
int APS5_VABI sceNpPartySendBinaryMessage(const void* sendParams);
+20 -29
View File
@@ -347,11 +347,6 @@ int APS5_VABI sceNpTusGetDataForCrossSaveVUserAsync(void) {
return 0;
}
int APS5_VABI sceNpTusGetMultiUserVariableForCrossSave(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpTusGetFriendsVariableA(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -694,6 +689,10 @@ int APS5_VABI sceNpTusGetMultiUserVariableForCrossSaveAsync(int reqId, const std
return StartOffline(reqId);
}
int APS5_VABI sceNpTusGetMultiUserVariableForCrossSave(int reqId, const std::uint64_t* targetAccountIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option) {
return Complete(reqId, sceNpTusGetMultiUserVariableForCrossSaveAsync(reqId, targetAccountIdArray, slotId, variableArray, variableArraySize, arrayLen, option));
}
int APS5_VABI sceNpTusGetMultiUserVariableForCrossSaveVUserAsync(int reqId, const NpOnlineId* targetVirtualUserIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option) {
(void)variableArraySize;
if (!targetVirtualUserIdArray || !variableArray) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT;
@@ -1007,44 +1006,36 @@ int APS5_VABI sceNpTusCreateNpTitleCtx() {
return 0;
}
int APS5_VABI sceNpTusGetData() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceNpTusGetData(int reqId, const void* targetNpId, std::int32_t slotId, void* dataStatus, std::uint64_t dataStatusSize, void* data, std::uint64_t recvSize, void* option) {
return Complete(reqId, sceNpTusGetDataAsync(reqId, targetNpId, slotId, dataStatus, dataStatusSize, data, recvSize, option));
}
int APS5_VABI sceNpTusGetDataVUser() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceNpTusGetDataVUser(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, void* dataStatus, std::uint64_t dataStatusSize, void* data, std::uint64_t recvSize, void* option) {
return Complete(reqId, sceNpTusGetDataVUserAsync(reqId, targetVirtualUserId, slotId, dataStatus, dataStatusSize, data, recvSize, option));
}
int APS5_VABI sceNpTusGetMultiSlotVariable() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceNpTusGetMultiSlotVariable(int reqId, const void* targetNpId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option) {
return Complete(reqId, sceNpTusGetMultiSlotVariableAsync(reqId, targetNpId, slotIds, variableArray, variableArraySize, arrayLen, option));
}
int APS5_VABI sceNpTusGetMultiSlotVariableVUser() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceNpTusGetMultiSlotVariableVUser(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option) {
return Complete(reqId, sceNpTusGetMultiSlotVariableVUserAsync(reqId, targetVirtualUserId, slotIds, variableArray, variableArraySize, arrayLen, option));
}
int APS5_VABI sceNpTusSetData() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceNpTusSetData(int reqId, const void* targetNpId, std::int32_t slotId, std::uint64_t totalSize, std::uint64_t sendSize, const void* data, const void* info, std::uint64_t infoStructSize, const void* isLastChangedAuthor, const void* isLastChangedDate, void* option) {
return Complete(reqId, sceNpTusSetDataAsync(reqId, targetNpId, slotId, totalSize, sendSize, data, info, infoStructSize, isLastChangedAuthor, isLastChangedDate, option));
}
int APS5_VABI sceNpTusSetDataVUser() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceNpTusSetDataVUser(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, std::uint64_t totalSize, std::uint64_t sendSize, const void* data, const void* info, std::uint64_t infoStructSize, const void* isLastChangedAuthor, const void* isLastChangedDate, void* option) {
return Complete(reqId, sceNpTusSetDataVUserAsync(reqId, targetVirtualUserId, slotId, totalSize, sendSize, data, info, infoStructSize, isLastChangedAuthor, isLastChangedDate, option));
}
int APS5_VABI sceNpTusSetMultiSlotVariable() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceNpTusSetMultiSlotVariable(int reqId, const void* targetNpId, const std::int32_t* slotIds, const std::int64_t* variableArray, int arrayLen, void* option) {
return Complete(reqId, sceNpTusSetMultiSlotVariableAsync(reqId, targetNpId, slotIds, variableArray, arrayLen, option));
}
int APS5_VABI sceNpTusSetMultiSlotVariableVUser() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceNpTusSetMultiSlotVariableVUser(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, const std::int64_t* variableArray, int arrayLen, void* option) {
return Complete(reqId, sceNpTusSetMultiSlotVariableVUserAsync(reqId, targetVirtualUserId, slotIds, variableArray, arrayLen, option));
}
}
+9 -9
View File
@@ -45,7 +45,7 @@ int APS5_VABI sceNpTssGetStorageAsync(void);
int APS5_VABI sceNpTssGetDataAsync(void);
int APS5_VABI sceNpTusGetDataForCrossSaveVUser(void);
int APS5_VABI sceNpTusGetDataForCrossSaveVUserAsync(void);
int APS5_VABI sceNpTusGetMultiUserVariableForCrossSave(void);
int APS5_VABI sceNpTusGetMultiUserVariableForCrossSave(int reqId, const std::uint64_t* targetAccountIdArray, std::int32_t slotId, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option);
int APS5_VABI sceNpTusGetFriendsVariableA(void);
int APS5_VABI sceNpTusGetFriendsVariableAAsync(void);
int APS5_VABI sceNpTusGetFriendsVariableForCrossSave(void);
@@ -138,13 +138,13 @@ int APS5_VABI sceNpTusTryAndSetVariableForCrossSaveVUser(int reqId, const NpOnli
int APS5_VABI sceNpTusTryAndSetVariableVUser(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, std::int32_t opeType, std::int64_t variable, const std::int64_t* compareValue, const void* isLastChangedAuthor, const void* isLastChangedDate, void* resultVariable, std::uint64_t resultVariableSize, void* option);
int APS5_VABI sceNpTssGetData();
int APS5_VABI sceNpTusCreateNpTitleCtx();
int APS5_VABI sceNpTusGetData();
int APS5_VABI sceNpTusGetDataVUser();
int APS5_VABI sceNpTusGetMultiSlotVariable();
int APS5_VABI sceNpTusGetMultiSlotVariableVUser();
int APS5_VABI sceNpTusSetData();
int APS5_VABI sceNpTusSetDataVUser();
int APS5_VABI sceNpTusSetMultiSlotVariable();
int APS5_VABI sceNpTusSetMultiSlotVariableVUser();
int APS5_VABI sceNpTusGetData(int reqId, const void* targetNpId, std::int32_t slotId, void* dataStatus, std::uint64_t dataStatusSize, void* data, std::uint64_t recvSize, void* option);
int APS5_VABI sceNpTusGetDataVUser(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, void* dataStatus, std::uint64_t dataStatusSize, void* data, std::uint64_t recvSize, void* option);
int APS5_VABI sceNpTusGetMultiSlotVariable(int reqId, const void* targetNpId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option);
int APS5_VABI sceNpTusGetMultiSlotVariableVUser(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, void* variableArray, std::uint64_t variableArraySize, int arrayLen, void* option);
int APS5_VABI sceNpTusSetData(int reqId, const void* targetNpId, std::int32_t slotId, std::uint64_t totalSize, std::uint64_t sendSize, const void* data, const void* info, std::uint64_t infoStructSize, const void* isLastChangedAuthor, const void* isLastChangedDate, void* option);
int APS5_VABI sceNpTusSetDataVUser(int reqId, const NpOnlineId* targetVirtualUserId, std::int32_t slotId, std::uint64_t totalSize, std::uint64_t sendSize, const void* data, const void* info, std::uint64_t infoStructSize, const void* isLastChangedAuthor, const void* isLastChangedDate, void* option);
int APS5_VABI sceNpTusSetMultiSlotVariable(int reqId, const void* targetNpId, const std::int32_t* slotIds, const std::int64_t* variableArray, int arrayLen, void* option);
int APS5_VABI sceNpTusSetMultiSlotVariableVUser(int reqId, const NpOnlineId* targetVirtualUserId, const std::int32_t* slotIds, const std::int64_t* variableArray, int arrayLen, void* option);
}
+95 -47
View File
@@ -1,6 +1,7 @@
#include "prx/libSceNpUtility/include/Api.hpp"
#include <cstdint>
#include <cstddef>
#include <condition_variable>
#include <cstring>
#include <mutex>
#include <unordered_map>
@@ -16,6 +17,7 @@ static constexpr int SCE_NP_COMMUNITY_ERROR_ABORTED = static_cast<int>(0x8055070
static constexpr int SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT = static_cast<int>(0x8055070C);
static constexpr int SCE_NP_COMMUNITY_ERROR_INVALID_ID = static_cast<int>(0x8055070E);
static constexpr int SCE_NP_COMMUNITY_ERROR_INVALID_TYPE = static_cast<int>(0x80550711);
static constexpr int SCE_NP_LOOKUP_ERROR_SYNCHRONOUS_REQUEST = static_cast<int>(0x80550307);
static constexpr int POLL_FINISHED = 0;
static constexpr int POLL_RUNNING = 1;
static constexpr std::size_t MAX_TITLE_CONTEXTS = 32;
@@ -23,7 +25,9 @@ static constexpr std::size_t MAX_REQUESTS = 32;
static constexpr int WORD_FILTER_TITLE_CTX_ID_OFFSET = 0x01000000;
static constexpr int WORD_FILTER_REQUEST_ID_OFFSET = 0x02000000;
static constexpr std::size_t WORD_FILTER_COMMENT_MAX_LENGTH = 0x400;
static constexpr std::uint64_t WORD_FILTER_ASYNC_PARAMETER_SIZE = 0x18;
static constexpr std::uint64_t ASYNC_REQUEST_PARAMETER_SIZE = 0x18;
static constexpr std::uint32_t RESOLVE_TIMEOUT_MIN = 1000000;
static constexpr std::uint32_t TRANSFER_TIMEOUT_MIN = 10000000;
namespace {
struct Request {
@@ -38,6 +42,7 @@ struct Request {
struct Service {
Service(int firstTitleContext, int firstRequest) : nextTitleContext(firstTitleContext), nextRequest(firstRequest) {}
std::mutex mutex;
std::condition_variable finished;
std::unordered_set<int> titleContexts;
std::unordered_map<int, Request> requests;
int nextTitleContext;
@@ -60,6 +65,7 @@ int DeleteTitleContext(Service& service, int titleCtxId) {
std::lock_guard lock(service.mutex);
if (!service.titleContexts.erase(titleCtxId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ID;
std::erase_if(service.requests, [titleCtxId](const auto& entry) { return entry.second.titleCtxId == titleCtxId; });
service.finished.notify_all();
return 0;
}
@@ -74,7 +80,9 @@ int CreateRequest(Service& service, int titleCtxId, bool async) {
int DeleteRequest(Service& service, int reqId) {
std::lock_guard lock(service.mutex);
return service.requests.erase(reqId) ? 0 : SCE_NP_COMMUNITY_ERROR_INVALID_ID;
if (!service.requests.erase(reqId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ID;
service.finished.notify_all();
return 0;
}
int StartOffline(Service& service, int reqId) {
@@ -86,9 +94,62 @@ int StartOffline(Service& service, int reqId) {
request->second.used = true;
request->second.finished = true;
request->second.result = SCE_NP_ERROR_SIGNED_OUT;
service.finished.notify_all();
return request->second.async ? 0 : SCE_NP_ERROR_SIGNED_OUT;
}
int AbortRequest(Service& service, int reqId) {
std::lock_guard lock(service.mutex);
const auto request = service.requests.find(reqId);
if (request == service.requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID;
request->second.aborted = true;
if (!request->second.finished) {
request->second.finished = true;
request->second.result = SCE_NP_COMMUNITY_ERROR_ABORTED;
service.finished.notify_all();
}
return 0;
}
int PollAsync(Service& service, int reqId, int* result, int synchronousError) {
if (!result) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT;
std::lock_guard lock(service.mutex);
const auto request = service.requests.find(reqId);
if (request == service.requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID;
if (!request->second.async) return synchronousError;
if (!request->second.finished) return POLL_RUNNING;
*result = request->second.result;
return POLL_FINISHED;
}
int WaitAsync(Service& service, int reqId, int* result, int synchronousError) {
if (!result) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT;
std::unique_lock lock(service.mutex);
auto request = service.requests.find(reqId);
if (request == service.requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID;
if (!request->second.async) return synchronousError;
service.finished.wait(lock, [&] {
request = service.requests.find(reqId);
return request == service.requests.end() || request->second.finished;
});
if (request == service.requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID;
*result = request->second.result;
return 0;
}
bool ValidTimeout(std::uint32_t timeout, std::uint32_t minimum) {
return timeout == 0 || timeout >= minimum;
}
int SetTimeout(Service& service, int id, int resolveRetry, std::uint32_t resolveTimeout, std::uint32_t connTimeout, std::uint32_t sendTimeout, std::uint32_t recvTimeout) {
if (resolveRetry < 0 || !ValidTimeout(resolveTimeout, RESOLVE_TIMEOUT_MIN) || !ValidTimeout(connTimeout, TRANSFER_TIMEOUT_MIN) ||
!ValidTimeout(sendTimeout, TRANSFER_TIMEOUT_MIN) || !ValidTimeout(recvTimeout, TRANSFER_TIMEOUT_MIN))
return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT;
if (resolveRetry == 0 && resolveTimeout == 0 && connTimeout == 0 && sendTimeout == 0 && recvTimeout == 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT;
std::lock_guard lock(service.mutex);
return service.titleContexts.contains(id) || service.requests.contains(id) ? 0 : SCE_NP_COMMUNITY_ERROR_INVALID_ID;
}
int CheckComment(const char* comment) {
return strnlen(comment, WORD_FILTER_COMMENT_MAX_LENGTH + 1) > WORD_FILTER_COMMENT_MAX_LENGTH ? SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT : 0;
}
@@ -237,14 +298,6 @@ int APS5_VABI sceNpBandwidthTestShutdownWithDetailedInfo() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpLookupAbortRequest() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpLookupCreateAsyncRequest() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpLookupCreateTitleCtxA() {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -313,18 +366,6 @@ int APS5_VABI sceNpLookupNetTerm() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpLookupPollAsync() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpLookupSetTimeout() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpLookupWaitAsync() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpServiceChecker2IntAbortRequest() {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -421,14 +462,6 @@ int APS5_VABI sceNpWordFilterCreateTitleCtxA() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWordFilterSetTimeout() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWordFilterWaitAsync() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
extern "C" {
@@ -438,6 +471,15 @@ int APS5_VABI sceNpBandwidthTestInitStart() {
return 0;
}
int APS5_VABI sceNpLookupAbortRequest(int reqId) {
return AbortRequest(lookup, reqId);
}
int APS5_VABI sceNpLookupCreateAsyncRequest(int titleCtxId, const std::uint64_t* param) {
if (!param || *param != ASYNC_REQUEST_PARAMETER_SIZE) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT;
return CreateRequest(lookup, titleCtxId, true);
}
int APS5_VABI sceNpLookupCreateRequest(int titleCtxId) {
return CreateRequest(lookup, titleCtxId, false);
}
@@ -460,16 +502,20 @@ int APS5_VABI sceNpLookupNpId(int reqId, const NpOnlineId* onlineId, void* npId,
return StartOffline(lookup, reqId);
}
int APS5_VABI sceNpLookupPollAsync(int reqId, int* result) {
return PollAsync(lookup, reqId, result, SCE_NP_LOOKUP_ERROR_SYNCHRONOUS_REQUEST);
}
int APS5_VABI sceNpLookupSetTimeout(int id, int resolveRetry, std::uint32_t resolveTimeout, std::uint32_t connTimeout, std::uint32_t sendTimeout, std::uint32_t recvTimeout) {
return SetTimeout(lookup, id, resolveRetry, resolveTimeout, connTimeout, sendTimeout, recvTimeout);
}
int APS5_VABI sceNpLookupWaitAsync(int reqId, int* result) {
return WaitAsync(lookup, reqId, result, SCE_NP_LOOKUP_ERROR_SYNCHRONOUS_REQUEST);
}
int APS5_VABI sceNpWordFilterAbortRequest(int reqId) {
std::lock_guard lock(wordFilter.mutex);
const auto request = wordFilter.requests.find(reqId);
if (request == wordFilter.requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID;
request->second.aborted = true;
if (!request->second.finished) {
request->second.finished = true;
request->second.result = SCE_NP_COMMUNITY_ERROR_ABORTED;
}
return 0;
return AbortRequest(wordFilter, reqId);
}
int APS5_VABI sceNpWordFilterCensorComment(int reqId, const char* comment, void* option) {
@@ -480,7 +526,7 @@ int APS5_VABI sceNpWordFilterCensorComment(int reqId, const char* comment, void*
}
int APS5_VABI sceNpWordFilterCreateAsyncRequest(int titleCtxId, const std::uint64_t* param) {
if (!param || *param != WORD_FILTER_ASYNC_PARAMETER_SIZE) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT;
if (!param || *param != ASYNC_REQUEST_PARAMETER_SIZE) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT;
return CreateRequest(wordFilter, titleCtxId, true);
}
@@ -501,13 +547,7 @@ int APS5_VABI sceNpWordFilterDeleteTitleCtx(int titleCtxId) {
}
int APS5_VABI sceNpWordFilterPollAsync(int reqId, int* result) {
if (!result) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT;
std::lock_guard lock(wordFilter.mutex);
const auto request = wordFilter.requests.find(reqId);
if (request == wordFilter.requests.end() || !request->second.async) return SCE_NP_COMMUNITY_ERROR_INVALID_ID;
if (!request->second.finished) return POLL_RUNNING;
*result = request->second.result;
return POLL_FINISHED;
return PollAsync(wordFilter, reqId, result, SCE_NP_COMMUNITY_ERROR_INVALID_ID);
}
int APS5_VABI sceNpWordFilterSanitizeComment(int reqId, const char* comment, char* sanitizedComment, void* option) {
@@ -517,4 +557,12 @@ int APS5_VABI sceNpWordFilterSanitizeComment(int reqId, const char* comment, cha
return StartOffline(wordFilter, reqId);
}
int APS5_VABI sceNpWordFilterSetTimeout(int id, int resolveRetry, std::uint32_t resolveTimeout, std::uint32_t connTimeout, std::uint32_t sendTimeout, std::uint32_t recvTimeout) {
return SetTimeout(wordFilter, id, resolveRetry, resolveTimeout, connTimeout, sendTimeout, recvTimeout);
}
int APS5_VABI sceNpWordFilterWaitAsync(int reqId, int* result) {
return WaitAsync(wordFilter, reqId, result, SCE_NP_COMMUNITY_ERROR_INVALID_ID);
}
}
@@ -23,6 +23,13 @@ int APS5_VABI sceNpWordFilterCreateTitleCtx(const void* selfNpId);
int APS5_VABI sceNpWordFilterDeleteRequest(int reqId);
int APS5_VABI sceNpWordFilterDeleteTitleCtx(int titleCtxId);
int APS5_VABI sceNpWordFilterPollAsync(int reqId, int* result);
int APS5_VABI sceNpLookupAbortRequest(int reqId);
int APS5_VABI sceNpLookupCreateAsyncRequest(int titleCtxId, const std::uint64_t* param);
int APS5_VABI sceNpLookupPollAsync(int reqId, int* result);
int APS5_VABI sceNpLookupSetTimeout(int id, int resolveRetry, std::uint32_t resolveTimeout, std::uint32_t connTimeout, std::uint32_t sendTimeout, std::uint32_t recvTimeout);
int APS5_VABI sceNpLookupWaitAsync(int reqId, int* result);
int APS5_VABI sceNpWordFilterSetTimeout(int id, int resolveRetry, std::uint32_t resolveTimeout, std::uint32_t connTimeout, std::uint32_t sendTimeout, std::uint32_t recvTimeout);
int APS5_VABI sceNpWordFilterWaitAsync(int reqId, int* result);
int APS5_VABI sceNpWordFilterSanitizeComment(int reqId, const char* comment, char* sanitizedComment, void* option);
int APS5_VABI sceNpAppInfoIntAbortRequest();
@@ -53,8 +60,6 @@ int APS5_VABI sceNpAppLaunchLinkIntGetCompatibleTitleIdList();
int APS5_VABI sceNpAppLaunchLinkIntGetCompatibleTitleIdNum();
int APS5_VABI sceNpAppLaunchLinkIntInitialize();
int APS5_VABI sceNpBandwidthTestShutdownWithDetailedInfo();
int APS5_VABI sceNpLookupAbortRequest();
int APS5_VABI sceNpLookupCreateAsyncRequest();
int APS5_VABI sceNpLookupCreateTitleCtxA();
int APS5_VABI sceNpLookupNetAbortRequest();
int APS5_VABI sceNpLookupNetCensorComment();
@@ -72,9 +77,6 @@ int APS5_VABI sceNpLookupNetNpId();
int APS5_VABI sceNpLookupNetSanitizeComment();
int APS5_VABI sceNpLookupNetSetTimeout();
int APS5_VABI sceNpLookupNetTerm();
int APS5_VABI sceNpLookupPollAsync();
int APS5_VABI sceNpLookupSetTimeout();
int APS5_VABI sceNpLookupWaitAsync();
int APS5_VABI sceNpServiceChecker2IntAbortRequest();
int APS5_VABI sceNpServiceChecker2IntCreateRequest();
int APS5_VABI sceNpServiceChecker2IntDestroyRequest();
@@ -99,7 +101,5 @@ int APS5_VABI sceNpTitleMetadataIntGetNpTitleId();
int APS5_VABI sceNpUtilityInit();
int APS5_VABI sceNpUtilityTerm();
int APS5_VABI sceNpWordFilterCreateTitleCtxA();
int APS5_VABI sceNpWordFilterSetTimeout();
int APS5_VABI sceNpWordFilterWaitAsync();
}
+153 -28
View File
@@ -1,51 +1,133 @@
#include "prx/libSceNpWebApi/include/Api.hpp"
#include "SceTypes.hpp"
#include "prx/libc/include/ApplicationHeap.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libSceUserService/UserService.hpp"
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <mutex>
#include <stdexcept>
#include <unordered_map>
#include <unordered_set>
static constexpr int SCE_NP_WEBAPI_ERROR_INVALID_ARGUMENT = static_cast<int>(0x80552902);
static constexpr int SCE_NP_WEBAPI_ERROR_INVALID_LIB_CONTEXT_ID = static_cast<int>(0x80552903);
static constexpr int SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND = static_cast<int>(0x80552904);
static constexpr int SCE_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND = static_cast<int>(0x80552905);
static constexpr int SCE_NP_WEBAPI_ERROR_REQUEST_NOT_FOUND = static_cast<int>(0x80552906);
static constexpr int SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN = static_cast<int>(0x80552907);
static constexpr int SCE_NP_WEBAPI_ERROR_INVALID_CONTENT_PARAMETER = static_cast<int>(0x80552908);
static constexpr int SCE_NP_WEBAPI_ERROR_ABORTED = static_cast<int>(0x80552909);
static constexpr int SCE_NP_WEBAPI_ERROR_USER_CONTEXT_ALREADY_EXIST = static_cast<int>(0x8055290A);
static constexpr int SCE_NP_WEBAPI_ERROR_PUSH_EVENT_FILTER_NOT_FOUND = static_cast<int>(0x8055290B);
static constexpr int SCE_NP_WEBAPI_ERROR_PUSH_EVENT_CALLBACK_NOT_FOUND = static_cast<int>(0x8055290C);
static constexpr int SCE_NP_WEBAPI_ERROR_HANDLE_NOT_FOUND = static_cast<int>(0x8055290D);
static constexpr int SCE_NP_WEBAPI_ERROR_SERVICE_PUSH_EVENT_FILTER_NOT_FOUND = static_cast<int>(0x8055290E);
static constexpr int SCE_NP_WEBAPI_ERROR_SERVICE_PUSH_EVENT_CALLBACK_NOT_FOUND = static_cast<int>(0x8055290F);
static constexpr int SCE_NP_WEBAPI_ERROR_SIGNED_IN_USER_NOT_FOUND = static_cast<int>(0x80552910);
static constexpr int SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_MAX = static_cast<int>(0x8055291A);
static constexpr int SCE_NP_WEBAPI_HTTP_METHOD_DELETE = 3;
static constexpr std::uint32_t SCE_NP_INVALID_SERVICE_LABEL = 0xFFFFFFFF;
static constexpr std::uint32_t SDK_VERSION_1_00 = 0x01000000;
static constexpr std::uint32_t SDK_VERSION_2_00 = 0x02000000;
static constexpr std::uint32_t SDK_VERSION_2_50 = 0x02500000;
namespace {
struct Request {
bool aborted;
};
struct UserContext {
int userId;
std::unordered_map<std::int64_t, Request> requests;
std::unordered_set<int> pushEventCallbacks;
std::unordered_set<int> servicePushEventCallbacks;
};
struct LibraryContext {
int libHttpCtxId;
std::size_t poolSize;
std::unordered_set<int> handles;
std::unordered_set<int> pushEventFilters;
std::unordered_map<int, UserContext> userContexts;
};
struct UserContextLookup {
int error;
LibraryContext* library;
UserContext* user;
};
struct RequestLookup {
int error;
UserContext* user;
Request* request;
};
std::mutex contextsMutex;
std::unordered_map<int, LibraryContext> contexts;
int lastContext = 0;
int lastObject = 0;
int lastUserContext = 0;
std::int64_t lastRequest = 0;
int nextObject() {
lastObject = lastObject % 0xefffffff + 1;
return lastObject;
}
std::uint32_t compiledSdkVersion() {
const auto* process = static_cast<const std::byte*>(ApplicationProcessParameters_nid_no_patch());
std::uint32_t version;
std::memcpy(&version, process + 16, sizeof(version));
return version;
}
UserContextLookup findUserContext(int titleUserCtxId) {
const auto library = contexts.find(titleUserCtxId >> 16);
if (library == contexts.end()) return {SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND, nullptr, nullptr};
const auto user = library->second.userContexts.find(titleUserCtxId);
if (user == library->second.userContexts.end()) return {SCE_NP_WEBAPI_ERROR_USER_CONTEXT_NOT_FOUND, &library->second, nullptr};
return {0, &library->second, &user->second};
}
RequestLookup findRequest(std::int64_t requestId) {
const auto lookup = findUserContext(static_cast<int>(requestId >> 32));
if (lookup.error != 0) return {lookup.error, nullptr, nullptr};
const auto request = lookup.user->requests.find(requestId);
if (request == lookup.user->requests.end()) return {SCE_NP_WEBAPI_ERROR_REQUEST_NOT_FOUND, lookup.user, nullptr};
return {0, lookup.user, &request->second};
}
}
extern "C" {
int APS5_VABI sceNpWebApiAbortRequest() {
NotImplemented_nid_no_patch(__func__);
int APS5_VABI sceNpWebApiAbortRequest(std::int64_t requestId) {
std::lock_guard lock(contextsMutex);
const auto lookup = findRequest(requestId);
if (lookup.error != 0) return lookup.error;
lookup.request->aborted = true;
return 0;
}
int APS5_VABI sceNpWebApiCreateContext() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceNpWebApiCreateContext(int libCtxId, const void* onlineId) {
if (libCtxId >= 0x8000) return SCE_NP_WEBAPI_ERROR_INVALID_LIB_CONTEXT_ID;
if (!onlineId) return SCE_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
std::lock_guard lock(contextsMutex);
const auto context = contexts.find(libCtxId);
if (context == contexts.end()) return SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
auto& userContexts = context->second.userContexts;
for (const auto& [id, user] : userContexts) {
if (user.userId == USER_SERVICE_INITIAL_USER_ID) return SCE_NP_WEBAPI_ERROR_USER_CONTEXT_ALREADY_EXIST;
}
lastUserContext = lastUserContext % 0xffff + 1;
const int titleUserCtxId = (libCtxId << 16) | lastUserContext;
userContexts.emplace(titleUserCtxId, UserContext{USER_SERVICE_INITIAL_USER_ID, {}, {}, {}});
return titleUserCtxId;
}
int APS5_VABI sceNpWebApiCreateHandle(int libCtxId) {
@@ -67,14 +149,26 @@ int APS5_VABI sceNpWebApiCreatePushEventFilter(int libCtxId, const void* filterP
return filter;
}
int APS5_VABI sceNpWebApiCreateRequest() {
NotImplemented_nid_no_patch(__func__);
int APS5_VABI sceNpWebApiCreateRequest(int titleUserCtxId, const char* apiGroup, const char* path, int method, const NpWebApiContentParameter* contentParameter, std::int64_t* requestId) {
if (!apiGroup || !path) return SCE_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
if (contentParameter && contentParameter->contentLength != 0 && !contentParameter->contentType) return SCE_NP_WEBAPI_ERROR_INVALID_CONTENT_PARAMETER;
if (method > SCE_NP_WEBAPI_HTTP_METHOD_DELETE && compiledSdkVersion() >= SDK_VERSION_2_50) return SCE_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
std::lock_guard lock(contextsMutex);
const auto lookup = findUserContext(titleUserCtxId);
if (lookup.error != 0) return lookup.error;
lastRequest = lastRequest % 0xffffffff + 1;
const std::int64_t base = static_cast<std::int64_t>(titleUserCtxId) << 32;
std::int64_t id = base | lastRequest;
while (lookup.user->requests.contains(id)) --id;
if (id <= base) throw std::runtime_error("sceNpWebApiCreateRequest: every request id of the user context is in use");
lookup.user->requests.emplace(id, Request{false});
if (requestId) *requestId = id;
return 0;
}
int APS5_VABI sceNpWebApiCreateServicePushEventFilter(int libCtxId, int handleId, const char* npServiceName, std::uint32_t npServiceLabel, const void* filterParam, std::size_t filterParamNum) {
(void)npServiceLabel;
if (!npServiceName || !filterParam || filterParamNum == 0) return SCE_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
if (npServiceLabel == SCE_NP_INVALID_SERVICE_LABEL && compiledSdkVersion() >= SDK_VERSION_2_00) return SCE_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
std::lock_guard lock(contextsMutex);
const auto context = contexts.find(libCtxId);
if (context == contexts.end()) return SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
@@ -82,8 +176,11 @@ int APS5_VABI sceNpWebApiCreateServicePushEventFilter(int libCtxId, int handleId
return SCE_NP_WEBAPI_ERROR_SIGNED_IN_USER_NOT_FOUND;
}
int APS5_VABI sceNpWebApiDeleteContext() {
NotImplemented_nid_no_patch(__func__);
int APS5_VABI sceNpWebApiDeleteContext(int titleUserCtxId) {
std::lock_guard lock(contextsMutex);
const auto lookup = findUserContext(titleUserCtxId);
if (lookup.error != 0) return lookup.error;
lookup.library->userContexts.erase(titleUserCtxId);
return 0;
}
@@ -101,8 +198,11 @@ int APS5_VABI sceNpWebApiDeletePushEventFilter(int libCtxId, int filterId) {
return context->second.pushEventFilters.erase(filterId) != 0 ? 0 : SCE_NP_WEBAPI_ERROR_PUSH_EVENT_FILTER_NOT_FOUND;
}
int APS5_VABI sceNpWebApiDeleteRequest() {
NotImplemented_nid_no_patch(__func__);
int APS5_VABI sceNpWebApiDeleteRequest(std::int64_t requestId) {
std::lock_guard lock(contextsMutex);
const auto lookup = findRequest(requestId);
if (lookup.error != 0) return lookup.error;
lookup.user->requests.erase(requestId);
return 0;
}
@@ -133,7 +233,7 @@ int APS5_VABI sceNpWebApiInitialize(int libHttpCtxId, std::size_t poolSize) {
for (int attempt = 0; attempt < 0x7fff; ++attempt) {
lastContext = lastContext % 0x7fff + 1;
if (contexts.contains(lastContext)) continue;
contexts[lastContext] = {libHttpCtxId, poolSize, {}, {}};
contexts[lastContext] = {libHttpCtxId, poolSize, {}, {}, {}};
return lastContext;
}
return SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_MAX;
@@ -144,19 +244,40 @@ int APS5_VABI sceNpWebApiReadData() {
return 0;
}
int APS5_VABI sceNpWebApiRegisterPushEventCallback() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceNpWebApiRegisterPushEventCallback(int titleUserCtxId, int filterId, void* callback, void* userArg) {
(void)userArg;
if (!callback && compiledSdkVersion() >= SDK_VERSION_1_00) return SCE_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
std::lock_guard lock(contextsMutex);
const auto lookup = findUserContext(titleUserCtxId);
if (lookup.error != 0) return lookup.error;
if (!lookup.library->pushEventFilters.contains(filterId) && compiledSdkVersion() >= SDK_VERSION_2_50) return SCE_NP_WEBAPI_ERROR_PUSH_EVENT_FILTER_NOT_FOUND;
const auto callbackId = nextObject();
lookup.user->pushEventCallbacks.insert(callbackId);
return callbackId;
}
int APS5_VABI sceNpWebApiRegisterServicePushEventCallback() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceNpWebApiRegisterServicePushEventCallback(int titleUserCtxId, int filterId, void* callback, void* userArg) {
(void)filterId;
(void)userArg;
if (!callback && compiledSdkVersion() >= SDK_VERSION_1_00) return SCE_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
std::lock_guard lock(contextsMutex);
const auto lookup = findUserContext(titleUserCtxId);
if (lookup.error != 0) return lookup.error;
if (compiledSdkVersion() >= SDK_VERSION_2_50) return SCE_NP_WEBAPI_ERROR_SERVICE_PUSH_EVENT_FILTER_NOT_FOUND;
if (!callback) return SCE_NP_WEBAPI_ERROR_INVALID_ARGUMENT;
const auto callbackId = nextObject();
lookup.user->servicePushEventCallbacks.insert(callbackId);
return callbackId;
}
int APS5_VABI sceNpWebApiSendRequest() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceNpWebApiSendRequest(std::int64_t requestId, const void* data, std::size_t dataSize) {
(void)data;
(void)dataSize;
std::lock_guard lock(contextsMutex);
const auto lookup = findRequest(requestId);
if (lookup.error != 0) return lookup.error;
if (lookup.request->aborted) return SCE_NP_WEBAPI_ERROR_ABORTED;
return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN;
}
int APS5_VABI sceNpWebApiTerminate(int libCtxId) {
@@ -164,14 +285,18 @@ int APS5_VABI sceNpWebApiTerminate(int libCtxId) {
return contexts.erase(libCtxId) != 0 ? 0 : SCE_NP_WEBAPI_ERROR_LIB_CONTEXT_NOT_FOUND;
}
int APS5_VABI sceNpWebApiUnregisterPushEventCallback() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceNpWebApiUnregisterPushEventCallback(int titleUserCtxId, int callbackId) {
std::lock_guard lock(contextsMutex);
const auto lookup = findUserContext(titleUserCtxId);
if (lookup.error != 0) return lookup.error;
return lookup.user->pushEventCallbacks.erase(callbackId) != 0 ? 0 : SCE_NP_WEBAPI_ERROR_PUSH_EVENT_CALLBACK_NOT_FOUND;
}
int APS5_VABI sceNpWebApiUnregisterServicePushEventCallback() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceNpWebApiUnregisterServicePushEventCallback(int titleUserCtxId, int callbackId) {
std::lock_guard lock(contextsMutex);
const auto lookup = findUserContext(titleUserCtxId);
if (lookup.error != 0) return lookup.error;
return lookup.user->servicePushEventCallbacks.erase(callbackId) != 0 ? 0 : SCE_NP_WEBAPI_ERROR_SERVICE_PUSH_EVENT_CALLBACK_NOT_FOUND;
}
int APS5_VABI sceNpWebApiUtilityParseNpId() {
+18 -10
View File
@@ -2,30 +2,38 @@
#include "SceTypes.hpp"
struct NpWebApiContentParameter {
std::uint64_t contentLength;
const char* contentType;
std::uint8_t reserved[16];
};
static_assert(sizeof(NpWebApiContentParameter) == 32);
extern "C" {
int APS5_VABI sceNpWebApiAbortRequest();
int APS5_VABI sceNpWebApiCreateContext();
int APS5_VABI sceNpWebApiAbortRequest(std::int64_t requestId);
int APS5_VABI sceNpWebApiCreateContext(int libCtxId, const void* onlineId);
int APS5_VABI sceNpWebApiCreateHandle(int libCtxId);
int APS5_VABI sceNpWebApiCreatePushEventFilter(int libCtxId, const void* filterParam, std::size_t filterParamNum);
int APS5_VABI sceNpWebApiCreateRequest();
int APS5_VABI sceNpWebApiCreateRequest(int titleUserCtxId, const char* apiGroup, const char* path, int method, const NpWebApiContentParameter* contentParameter, std::int64_t* requestId);
int APS5_VABI sceNpWebApiCreateServicePushEventFilter(int libCtxId, int handleId, const char* npServiceName, std::uint32_t npServiceLabel, const void* filterParam, std::size_t filterParamNum);
int APS5_VABI sceNpWebApiDeleteContext();
int APS5_VABI sceNpWebApiDeleteContext(int titleUserCtxId);
int APS5_VABI sceNpWebApiDeleteHandle(int libCtxId, int handleId);
int APS5_VABI sceNpWebApiDeletePushEventFilter(int libCtxId, int filterId);
int APS5_VABI sceNpWebApiDeleteRequest();
int APS5_VABI sceNpWebApiDeleteRequest(std::int64_t requestId);
int APS5_VABI sceNpWebApiDeleteServicePushEventFilter(int libCtxId, int filterId);
int APS5_VABI sceNpWebApiGetHttpResponseHeaderValue();
int APS5_VABI sceNpWebApiGetHttpResponseHeaderValueLength();
int APS5_VABI sceNpWebApiGetHttpStatusCode();
int APS5_VABI sceNpWebApiInitialize(int libHttpCtxId, std::size_t poolSize);
int APS5_VABI sceNpWebApiReadData();
int APS5_VABI sceNpWebApiRegisterPushEventCallback();
int APS5_VABI sceNpWebApiRegisterServicePushEventCallback();
int APS5_VABI sceNpWebApiSendRequest();
int APS5_VABI sceNpWebApiRegisterPushEventCallback(int titleUserCtxId, int filterId, void* callback, void* userArg);
int APS5_VABI sceNpWebApiRegisterServicePushEventCallback(int titleUserCtxId, int filterId, void* callback, void* userArg);
int APS5_VABI sceNpWebApiSendRequest(std::int64_t requestId, const void* data, std::size_t dataSize);
int APS5_VABI sceNpWebApiTerminate(int libCtxId);
int APS5_VABI sceNpWebApiUnregisterPushEventCallback();
int APS5_VABI sceNpWebApiUnregisterServicePushEventCallback();
int APS5_VABI sceNpWebApiUnregisterPushEventCallback(int titleUserCtxId, int callbackId);
int APS5_VABI sceNpWebApiUnregisterServicePushEventCallback(int titleUserCtxId, int callbackId);
int APS5_VABI sceNpWebApiUtilityParseNpId();
}
+4 -5
View File
@@ -145,10 +145,8 @@ int32_t APS5_VABI scePngDecDecode(void* handle, const PngDecDecodeParam* param,
const std::optional<Decoder::Png::Header> header = Decoder::Png::ParseHeader(png);
if (!header) return SCE_PNG_DEC_ERROR_INVALID_DATA;
if (imageInfo) fillImageInfo(*header, imageInfo);
if (header->bitDepth == 16 && context->attribute == ATTRIBUTE_BIT_DEPTH_16) {
throw std::runtime_error("scePngDecDecode: 16-bit output is not implemented");
}
const bool packedDimensions = header->width <= MAX_PACKED_DIMENSION && header->height <= MAX_PACKED_DIMENSION;
if (!imageInfo && !packedDimensions) return SCE_PNG_DEC_ERROR_INVALID_ADDR;
const std::uint64_t rowSize = static_cast<std::uint64_t>(header->width) * BYTES_PER_PIXEL;
const std::uint64_t pitch = param->image_pitch == 0 ? rowSize : param->image_pitch;
if (param->image_pitch != 0 && (pitch < rowSize || (pitch & 3u) != 0)) return SCE_PNG_DEC_ERROR_INVALID_PARAM;
@@ -160,6 +158,7 @@ int32_t APS5_VABI scePngDecDecode(void* handle, const PngDecDecodeParam* param,
const std::uint64_t alignedWidth = (static_cast<std::uint64_t>(header->width) + 7u) & ~std::uint64_t{7u};
const std::uint64_t alignedMaxWidth = (static_cast<std::uint64_t>(context->maxImageWidth) + 7u) & ~std::uint64_t{7u};
if (alignedWidth > alignedMaxWidth) return SCE_PNG_DEC_ERROR_INVALID_WORK_MEMORY;
if (header->bitDepth > (8u << (context->attribute & 31u))) return SCE_PNG_DEC_ERROR_INVALID_WORK_MEMORY;
const std::optional<Decoder::Png::Image> image = Decoder::Png::Decode(png);
if (!image || image->width != header->width || image->height != header->height) return SCE_PNG_DEC_ERROR_DECODE_ERROR;
@@ -179,7 +178,7 @@ int32_t APS5_VABI scePngDecDecode(void* handle, const PngDecDecodeParam* param,
}
}
if (header->width > MAX_PACKED_DIMENSION || header->height > MAX_PACKED_DIMENSION) return 0;
if (!packedDimensions) return 0;
return static_cast<std::int32_t>(header->width << 16 | header->height);
}
+80 -9
View File
@@ -1,22 +1,93 @@
#include "prx/libSceRegMgr/include/Api.hpp"
#include <cstdint>
#include <cstddef>
#include <cstring>
#include <mutex>
#include <string>
#include <unordered_map>
#include <vector>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
namespace {
constexpr int SCE_REG_MGR_ERROR_INVALID_PARAM = static_cast<int>(0x80820001);
constexpr int SCE_REG_MGR_ERROR_NOT_FOUND = static_cast<int>(0x80820002);
constexpr int SCE_REG_MGR_ERROR_BUFFER_TOO_SMALL = static_cast<int>(0x80820004);
constexpr int SCE_REG_MGR_ERROR_INVALID_TYPE = static_cast<int>(0x80820006);
enum EntryType {
TypeInt = 0,
TypeStr = 1,
TypeBin = 2,
};
struct RegEntry {
EntryType type;
int32_t intVal = 0;
std::string strVal;
std::vector<uint8_t> binVal;
};
class RegistryStore {
public:
RegistryStore() = default;
int getInt(int64_t key, int32_t* out) {
if (out == nullptr) return SCE_REG_MGR_ERROR_INVALID_PARAM;
std::lock_guard<std::mutex> lock(mutex_);
auto it = entries_.find(key);
if (it == entries_.end()) return SCE_REG_MGR_ERROR_NOT_FOUND;
if (it->second.type != TypeInt) return SCE_REG_MGR_ERROR_INVALID_TYPE;
*out = it->second.intVal;
return 0;
}
int getStr(int64_t key, char* out, int len) {
if (len == 0) NotImplemented_nid_no_patch(__func__);
if (out == nullptr || len < 0) return SCE_REG_MGR_ERROR_INVALID_PARAM;
std::lock_guard<std::mutex> lock(mutex_);
auto it = entries_.find(key);
if (it == entries_.end()) return SCE_REG_MGR_ERROR_NOT_FOUND;
if (it->second.type != TypeStr) return SCE_REG_MGR_ERROR_INVALID_TYPE;
if (static_cast<size_t>(len) <= it->second.strVal.size()) return SCE_REG_MGR_ERROR_BUFFER_TOO_SMALL;
std::memcpy(out, it->second.strVal.c_str(), it->second.strVal.size() + 1);
return 0;
}
int getBin(int64_t key, void* out, int size) {
if (size == 0) NotImplemented_nid_no_patch(__func__);
if (out == nullptr || size < 0) return SCE_REG_MGR_ERROR_INVALID_PARAM;
std::lock_guard<std::mutex> lock(mutex_);
auto it = entries_.find(key);
if (it == entries_.end()) return SCE_REG_MGR_ERROR_NOT_FOUND;
if (it->second.type != TypeBin) return SCE_REG_MGR_ERROR_INVALID_TYPE;
if (static_cast<size_t>(size) < it->second.binVal.size()) return SCE_REG_MGR_ERROR_BUFFER_TOO_SMALL;
std::memcpy(out, it->second.binVal.data(), it->second.binVal.size());
return 0;
}
private:
std::mutex mutex_;
std::unordered_map<int64_t, RegEntry> entries_;
};
RegistryStore gStore;
} // namespace
extern "C" {
int APS5_VABI sceRegMgrGetBin(std::uint32_t optionId, void* data, std::size_t size) {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceRegMgrGetBin(std::int64_t optionId, void* data, int size) {
return gStore.getBin(optionId, data, size);
}
int APS5_VABI sceRegMgrGetInt(int32_t optionId, int32_t* outInt) {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceRegMgrGetInt(std::int64_t optionId, int32_t* outInt) {
return gStore.getInt(optionId, outInt);
}
int APS5_VABI sceRegMgrGetStr(int32_t optionId, char* outString, std::size_t stringLength) {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceRegMgrGetStr(std::int64_t optionId, char* outString, int stringLength) {
return gStore.getStr(optionId, outString, stringLength);
}
int APS5_VABI sceRegMgrSetBin(std::uint32_t optionId, const void* data, std::size_t size) {
NotImplemented_nid_no_patch(__func__);
+5 -3
View File
@@ -1,12 +1,14 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include "SceTypes.hpp"
extern "C" {
int APS5_VABI sceRegMgrGetBin(std::uint32_t optionId, void* data, std::size_t size);
int APS5_VABI sceRegMgrGetInt(int32_t optionId, int32_t* outInt);
int APS5_VABI sceRegMgrGetStr(int32_t optionId, char* outString, std::size_t stringLength);
int APS5_VABI sceRegMgrGetBin(std::int64_t optionId, void* data, int size);
int APS5_VABI sceRegMgrGetInt(std::int64_t optionId, int32_t* outInt);
int APS5_VABI sceRegMgrGetStr(std::int64_t optionId, char* outString, int stringLength);
int APS5_VABI sceRegMgrSetBin(std::uint32_t optionId, const void* data, std::size_t size);
int APS5_VABI sceRegMgrSetInt(int32_t optionId, int32_t inInt);
int APS5_VABI sceRegMgrSetStr(int32_t optionId, const char* inStirng, std::size_t stringLength);
@@ -26,6 +26,7 @@ if(BUILD_TESTING)
add_test_executable(guest_savedata_convert_tests ${LIBS_INCLUDE_DIR}/tests/GuestSaveDataConvert.cpp)
target_include_directories(guest_savedata_convert_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_savedata_convert_tests PRIVATE libSceSaveData.native libc)
configure_windows_unwind(guest_savedata_convert_tests)
add_test(NAME guest_savedata_convert COMMAND guest_savedata_convert_tests)
if(WIN32)
set_tests_properties(guest_savedata_convert PROPERTIES ENVIRONMENT_MODIFICATION
+11
View File
@@ -41,6 +41,17 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceUlt>")
endif()
endforeach()
add_test_executable(guest_ult_finalize_allocation_tests ${LIBS_INCLUDE_DIR}/tests/UltFinalizeAllocation.cpp Export.cpp src/ReaderWriterLock.cpp)
target_include_directories(guest_ult_finalize_allocation_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_ult_finalize_allocation_tests PRIVATE libc libkernel)
configure_windows_unwind(guest_ult_finalize_allocation_tests)
add_test(NAME guest_ult_finalize_allocation COMMAND guest_ult_finalize_allocation_tests)
set_tests_properties(guest_ult_finalize_allocation PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_ult_finalize_allocation PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test_executable(guest_ult_sync_tests ${LIBS_INCLUDE_DIR}/tests/GuestUltSync.cpp)
target_include_directories(guest_ult_sync_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_ult_sync_tests PRIVATE libSceUlt libc)
+1 -1
View File
@@ -149,6 +149,7 @@ int APS5_VABI sceUltFinalize() {
return ULT_ERROR_BUSY;
}
semaphores.reserve(gSemaphores.size());
mutexes.reserve(gMutexes.size());
for (auto& entry : gSemaphores) {
auto& state = entry.second;
std::lock_guard<std::mutex> stateLock(state->_mutex);
@@ -156,7 +157,6 @@ int APS5_VABI sceUltFinalize() {
semaphores.push_back(state);
}
gSemaphores.clear();
mutexes.reserve(gMutexes.size());
for (auto& entry : gMutexes) {
auto& state = entry.second;
std::lock_guard<std::mutex> stateLock(state->_mutex);
+29 -5
View File
@@ -345,11 +345,35 @@ int APS5_VABI sceVdecswSyncDecodeOutput(std::uint64_t handle, OutputInfo* output
}
int APS5_VABI sceVdecswGetPictureInfo(const OutputInfo* output, void* first, void* second) {
(void)output;
(void)first;
(void)second;
NotImplemented_nid_no_patch(__func__);
return 0;
static_cast<void>(second);
if (output == nullptr || (output->thisSize | 8u) != sizeof(OutputInfo)) throw std::runtime_error("Vdecsw: invalid output info");
if (output->pictureCount == 0) return 0;
if (output->pictureCount > 1) NotImplemented_nid_no_patch(__func__);
if (first == nullptr) return 0;
std::uint64_t size;
std::memcpy(&size, first, sizeof(size));
if (size < sizeof(size)) throw std::runtime_error("Vdecsw: picture info is smaller than its size field");
const auto copy = [first, size](const auto& source) {
const auto bytes = std::min<std::uint64_t>(size, sizeof(source)) - sizeof(size);
std::memcpy(static_cast<std::uint8_t*>(first) + sizeof(size), reinterpret_cast<const std::uint8_t*>(&source) + sizeof(size), bytes);
};
std::vector<std::shared_ptr<Decoder>> all;
{
std::lock_guard lock(decodersMutex);
for (const auto& [handle, decoder] : decoders) all.push_back(decoder);
}
for (const auto& decoder : all) {
std::lock_guard lock(decoder->mutex);
if (const auto found = decoder->delivered.find(output->frameBuffer); found != decoder->delivered.end()) {
std::visit(copy, found->second);
return 0;
}
if (const auto found = decoder->deliveredVp9.find(output->frameBuffer); found != decoder->deliveredVp9.end()) {
copy(found->second);
return 0;
}
}
throw std::runtime_error("Vdecsw: picture info requested for a frame buffer no decoder returned");
}
int APS5_VABI sceVdecswGetAvcPictureInfo(const OutputInfo* output, AvcPictureInfo* first, AvcPictureInfo* second) {
@@ -38,34 +38,8 @@ int APS5_VABI sceVideoOutCursorSetPosition(int32_t, int32_t, uint32_t, uint32_t)
return 0;
}
int APS5_VABI sceVideoOutAdjustColor_(int handle, const VideoOutColorSettings* settings, uint32_t settings_size) {
if (settings_size < sizeof(VideoOutColorSettings)) throw std::runtime_error("sceVideoOutAdjustColor_: VIDEO_OUT_ERROR_INVALID_VALUE");
return sceVideoOutAdjustColor(handle, settings);
}
int APS5_VABI sceVideoOutColorSettingsSetGamma_(VideoOutColorSettings* settings, float gamma, uint32_t settings_size) {
if (settings_size < sizeof(VideoOutColorSettings)) throw std::runtime_error("sceVideoOutColorSettingsSetGamma_: VIDEO_OUT_ERROR_INVALID_VALUE");
return sceVideoOutColorSettingsSetGamma(settings, gamma);
}
int APS5_VABI sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion_(VideoOutColorSpaceConversionSettings* settings, uint32_t settings_size) {
if (settings_size < sizeof(VideoOutColorSpaceConversionSettings)) throw std::runtime_error("sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion_: VIDEO_OUT_ERROR_INVALID_VALUE");
return sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion(settings);
}
int APS5_VABI sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion_(VideoOutColorSpaceConversionSettings* settings, float lumaScale, uint32_t option, uint32_t settings_size) {
if (settings_size < sizeof(VideoOutColorSpaceConversionSettings)) throw std::runtime_error("sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion_: VIDEO_OUT_ERROR_INVALID_VALUE");
return sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion(settings, lumaScale, option);
}
int APS5_VABI sceVideoOutAdjustColorSpaceConversion_(int handle, const VideoOutColorSpaceConversionSettings* settings, uint32_t settings_size) {
if (settings_size < sizeof(VideoOutColorSpaceConversionSettings)) throw std::runtime_error("sceVideoOutAdjustColorSpaceConversion_: VIDEO_OUT_ERROR_INVALID_VALUE");
return sceVideoOutAdjustColorSpaceConversion(handle, settings);
}
int APS5_VABI sceDbgVideoOutAddOutputModeVr60Privilege() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
@@ -371,6 +371,31 @@ int APS5_VABI sceVideoOutAdjustColorSpaceConversion(int handle, const VideoOutCo
LibcAwaitExit_nid_postfix();
}
int APS5_VABI sceVideoOutAdjustColor_(int handle, const VideoOutColorSettings* settings, uint32_t settings_size) {
if (settings_size < sizeof(VideoOutColorSettings)) throw std::runtime_error("sceVideoOutAdjustColor_: VIDEO_OUT_ERROR_INVALID_VALUE");
return sceVideoOutAdjustColor(handle, settings);
}
int APS5_VABI sceVideoOutColorSettingsSetGamma_(VideoOutColorSettings* settings, float gamma, uint32_t settings_size) {
if (settings_size < sizeof(VideoOutColorSettings)) throw std::runtime_error("sceVideoOutColorSettingsSetGamma_: VIDEO_OUT_ERROR_INVALID_VALUE");
return sceVideoOutColorSettingsSetGamma(settings, gamma);
}
int APS5_VABI sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion_(VideoOutColorSpaceConversionSettings* settings, uint32_t settings_size) {
if (settings_size < sizeof(VideoOutColorSpaceConversionSettings)) throw std::runtime_error("sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion_: VIDEO_OUT_ERROR_INVALID_VALUE");
return sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion(settings);
}
int APS5_VABI sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion_(VideoOutColorSpaceConversionSettings* settings, float lumaScale, uint32_t option, uint32_t settings_size) {
if (settings_size < sizeof(VideoOutColorSpaceConversionSettings)) throw std::runtime_error("sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion_: VIDEO_OUT_ERROR_INVALID_VALUE");
return sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion(settings, lumaScale, option);
}
int APS5_VABI sceVideoOutAdjustColorSpaceConversion_(int handle, const VideoOutColorSpaceConversionSettings* settings, uint32_t settings_size) {
if (settings_size < sizeof(VideoOutColorSpaceConversionSettings)) throw std::runtime_error("sceVideoOutAdjustColorSpaceConversion_: VIDEO_OUT_ERROR_INVALID_VALUE");
return sceVideoOutAdjustColorSpaceConversion(handle, settings);
}
int APS5_VABI sceVideoOutSetGlobalBlendSpace(int handle, uint32_t space, uint32_t option) try {
auto cfg = VideoOutDriver::Get().GetConfig(handle);
if (cfg == nullptr) {
@@ -2,6 +2,7 @@
#include "prx/libSceVideoOut/include/VideoOutDriver.hpp"
#include "prx/libSceVideoOut/include/Buffer.hpp"
#include "prx/libSceVideoOut/include/Output.hpp"
#include "prx/libSceVideoOut/include/VideoOut.hpp"
#include "prx/libSceVideoOut/include/Event.hpp"
#include "prx/libkernel/Equeue/Equeue.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
@@ -445,6 +446,22 @@ void testControls() {
}
VideoOutColorSettings settings{};
expectFailure([&] { sceVideoOutColorSettingsSetGamma(&settings, std::numeric_limits<float>::quiet_NaN()); });
expectFailure([&] { sceVideoOutColorSettingsSetGamma_(&settings, 1.0f, sizeof(VideoOutColorSettings) - 1); });
check(sceVideoOutColorSettingsSetGamma_(&settings, 1.2f, sizeof(VideoOutColorSettings)) == 0, "gamma wrapper rejected valid size");
check(settings.gamma == 1.2f, "gamma wrapper failed to forward value");
expectFailure([&] { sceVideoOutAdjustColor_(handle, &settings, sizeof(VideoOutColorSettings) - 1); });
check(sceVideoOutAdjustColor_(handle, &settings, sizeof(VideoOutColorSettings)) == 0, "adjust color wrapper rejected valid size");
VideoOutColorSpaceConversionSettings cscSettings{};
expectFailure([&] { sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion_(&cscSettings, sizeof(VideoOutColorSpaceConversionSettings) - 1); });
check(sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion_(&cscSettings, sizeof(VideoOutColorSpaceConversionSettings)) == 0, "default SDR to HDR wrapper rejected valid size");
expectFailure([&] { sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion_(&cscSettings, 1.0f, 0, sizeof(VideoOutColorSpaceConversionSettings) - 1); });
check(sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion_(&cscSettings, 1.0f, 0, sizeof(VideoOutColorSpaceConversionSettings)) == 0, "SDR to HDR conversion wrapper rejected valid size");
expectFailure([&] { sceVideoOutAdjustColorSpaceConversion_(handle, &cscSettings, sizeof(VideoOutColorSpaceConversionSettings) - 1); });
check(sceVideoOutAdjustColorSpaceConversion_(handle, &cscSettings, sizeof(VideoOutColorSpaceConversionSettings)) == 0, "adjust color space conversion wrapper rejected valid size");
KernelEqueue queue = 0;
check(sceKernelCreateEqueue(&queue, "VideoOut test") == 0, "event queue creation failed");
auto owner = EqueuePin_nid_postfix(queue);
+9
View File
@@ -929,6 +929,15 @@ if(BUILD_TESTING)
set_tests_properties(guest_bad_optional_access PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test_executable(guest_underflow_error_tests ${LIBS_INCLUDE_DIR}/tests/GuestUnderflowError.cpp)
target_include_directories(guest_underflow_error_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_underflow_error_tests PRIVATE libc)
configure_windows_unwind(guest_underflow_error_tests)
add_test(NAME guest_underflow_error COMMAND guest_underflow_error_tests)
if(WIN32)
set_tests_properties(guest_underflow_error PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
if(UNIX)
add_test_executable(exception_personality_tests ${LIBS_INCLUDE_DIR}/tests/ExceptionPersonality.cpp)
target_include_directories(exception_personality_tests PRIVATE ${LIBS_INCLUDE_DIR})
+24
View File
@@ -163,6 +163,15 @@ int APS5_VABI _Atomic_compare_exchange_strong_8_nid_postfix(volatile std::uint64
int APS5_VABI _Atomic_compare_exchange_weak_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t* expected, std::uint8_t desired, int, int);
int APS5_VABI _Atomic_compare_exchange_weak_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t* expected, std::uint16_t desired, int, int);
int APS5_VABI _Atomic_compare_exchange_weak_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t* expected, std::uint64_t desired, int, int);
std::uint8_t APS5_VABI _Fetch_and_seq_cst_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value);
std::uint16_t APS5_VABI _Fetch_and_seq_cst_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value);
std::uint32_t APS5_VABI _Fetch_and_seq_cst_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value);
std::uint8_t APS5_VABI _Fetch_or_seq_cst_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value);
std::uint16_t APS5_VABI _Fetch_or_seq_cst_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value);
std::uint32_t APS5_VABI _Fetch_or_seq_cst_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value);
std::uint8_t APS5_VABI _Fetch_xor_seq_cst_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value);
std::uint16_t APS5_VABI _Fetch_xor_seq_cst_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value);
std::uint32_t APS5_VABI _Fetch_xor_seq_cst_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value);
std::uint8_t APS5_VABI _Atomic_fetch_add_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int);
std::uint16_t APS5_VABI _Atomic_fetch_add_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int);
std::uint64_t APS5_VABI _Atomic_fetch_add_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int order);
@@ -219,6 +228,7 @@ int APS5_VABI strerror_r_nid_postfix(int error, char* buffer, std::size_t length
void APS5_VABI perror_nid_postfix(const char* prefix);
FileStream* APS5_VABI fdopen_nid_postfix(int descriptor, const char* mode);
FileStream* APS5_VABI freopen_nid_postfix(const char* filename, const char* mode, FileStream* stream);
int APS5_VABI freopen_s_nid_postfix(FileStream** result, const char* filename, const char* mode, FileStream* stream);
FileStream* APS5_VABI fopen_nid_postfix(const char* filename, const char* mode);
int APS5_VABI fopen_s_nid_postfix(FileStream** result, const char* filename, const char* mode);
int APS5_VABI fclose_nid_postfix(FileStream* stream);
@@ -355,6 +365,13 @@ int APS5_VABI puts_nid_postfix(const char* s);
int APS5_VABI vfscanf_nid_postfix(FileStream* stream, const char* format, VaList* args);
int APS5_VABI vscanf_nid_postfix(const char* format, VaList* args);
int APS5_VABI scanf_nid_postfix(const char* format, ...);
int APS5_VABI vfprintf_s_nid_postfix(FileStream* stream, const char* format, VaList* args);
int APS5_VABI vprintf_s_nid_postfix(const char* format, VaList* args);
int APS5_VABI fprintf_s_nid_postfix(FileStream* stream, const char* format, ...);
int APS5_VABI vfscanf_s_nid_postfix(FileStream* stream, const char* format, VaList* args);
int APS5_VABI vscanf_s_nid_postfix(const char* format, VaList* args);
int APS5_VABI fscanf_s_nid_postfix(FileStream* stream, const char* format, ...);
int APS5_VABI scanf_s_nid_postfix(const char* format, ...);
int APS5_VABI vswprintf_nid_postfix(char16_t* buffer, std::size_t size, const char16_t* format, VaList* args);
int APS5_VABI swprintf_nid_postfix(char16_t* buffer, std::size_t size, const char16_t* format, ...);
int APS5_VABI snwprintf_s_nid_postfix(char16_t* buffer, std::size_t size, const char16_t* format, ...);
@@ -364,6 +381,10 @@ int APS5_VABI vsnwprintf_s_nid_postfix(char16_t* buffer, std::size_t size, const
int APS5_VABI wprintf_nid_postfix(const char16_t* format, ...);
int APS5_VABI vfwprintf_nid_postfix(FileStream* stream, const char16_t* format, VaList* args);
int APS5_VABI fwprintf_nid_postfix(FileStream* stream, const char16_t* format, ...);
int APS5_VABI vfwprintf_s_nid_postfix(FileStream* stream, const char16_t* format, VaList* args);
int APS5_VABI vwprintf_s_nid_postfix(const char16_t* format, VaList* args);
int APS5_VABI fwprintf_s_nid_postfix(FileStream* stream, const char16_t* format, ...);
int APS5_VABI wprintf_s_nid_postfix(const char16_t* format, ...);
int APS5_VABI vwprintf_nid_postfix(const char16_t* format, VaList* args);
int APS5_VABI fputwc_nid_postfix(char16_t value, FileStream* stream);
int APS5_VABI fputws_nid_postfix(const char16_t* str, FileStream* stream);
@@ -604,6 +625,8 @@ float APS5_VABI __powisf2_nid_postfix(float base, int exponent);
double APS5_VABI __powidf2_nid_postfix(double base, int exponent);
float APS5_VABI expf_nid_postfix(float x);
float APS5_VABI exp2f_nid_postfix(float x);
short APS5_VABI _Exp_nid_postfix(double* value, double y, short offset);
short APS5_VABI _FExp_nid_postfix(float* value, float y, short offset);
float APS5_VABI logf_nid_postfix(float x);
float APS5_VABI log2f_nid_postfix(float x);
double APS5_VABI log10_nid_postfix(double x);
@@ -1268,6 +1291,7 @@ void APS5_VABI _ZNSt11range_errorD1Ev_nid_postfix(LibcException::ExceptionObject
void APS5_VABI _ZNSt11range_errorD2Ev_nid_postfix(LibcException::ExceptionObject* self);
void APS5_VABI _ZNSt11range_errorD0Ev_nid_postfix(LibcException::ExceptionObject* self);
void APS5_VABI _ZNSt14overflow_errorD1Ev_nid_postfix(LibcException::ExceptionObject* self);
void APS5_VABI _ZNSt15underflow_errorD1Ev_nid_postfix(LibcException::ExceptionObject* self);
void APS5_VABI _ZNSt8ios_base7failureD1Ev_nid_postfix(LibcException::ExceptionObject* self);
void APS5_VABI _ZNSt8ios_base7failureD2Ev_nid_postfix(LibcException::ExceptionObject* self);
void APS5_VABI _ZNSt8ios_base7failureD0Ev_nid_postfix(LibcException::ExceptionObject* self);
@@ -63,6 +63,18 @@ int LockedCompareExchange(volatile unsigned char* flag, std::size_t size, volati
extern "C" {
std::uint8_t APS5_VABI _Fetch_and_seq_cst_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value) { return __atomic_fetch_and(target, value, __ATOMIC_SEQ_CST); }
std::uint16_t APS5_VABI _Fetch_and_seq_cst_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value) { return __atomic_fetch_and(target, value, __ATOMIC_SEQ_CST); }
std::uint32_t APS5_VABI _Fetch_and_seq_cst_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value) { return __atomic_fetch_and(target, value, __ATOMIC_SEQ_CST); }
std::uint8_t APS5_VABI _Fetch_or_seq_cst_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value) { return __atomic_fetch_or(target, value, __ATOMIC_SEQ_CST); }
std::uint16_t APS5_VABI _Fetch_or_seq_cst_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value) { return __atomic_fetch_or(target, value, __ATOMIC_SEQ_CST); }
std::uint32_t APS5_VABI _Fetch_or_seq_cst_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value) { return __atomic_fetch_or(target, value, __ATOMIC_SEQ_CST); }
std::uint8_t APS5_VABI _Fetch_xor_seq_cst_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value) { return __atomic_fetch_xor(target, value, __ATOMIC_SEQ_CST); }
std::uint16_t APS5_VABI _Fetch_xor_seq_cst_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value) { return __atomic_fetch_xor(target, value, __ATOMIC_SEQ_CST); }
std::uint32_t APS5_VABI _Fetch_xor_seq_cst_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value) { return __atomic_fetch_xor(target, value, __ATOMIC_SEQ_CST); }
void APS5_VABI _Atomic_store_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { Store(target, value); }
void APS5_VABI _Atomic_store_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { Store(target, value); }
void APS5_VABI _Atomic_store_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int) { Store(target, value); }
@@ -1,9 +1,13 @@
#include "prx/libc/include/Api.hpp"
#include <algorithm>
#include <cctype>
#include <cerrno>
#include <cfenv>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <limits>
#include <stdexcept>
#include <string>
#include "prx/libc/include/General.hpp"
@@ -73,6 +77,67 @@ TInteger ParsedInteger(const char* text, char** end, int base, int* error, TInte
return value;
}
constexpr short Denormal = -2, Finite = -1, Zero = 0, Infinite = 1, NotANumber = 2;
void RaiseLibcError(int exceptions) {
if ((exceptions & (FE_OVERFLOW | FE_UNDERFLOW)) != 0) exceptions |= FE_INEXACT;
std::feraiseexcept(exceptions);
const bool range = (exceptions & FE_INVALID) == 0 &&
(exceptions & (FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW)) != 0;
errno = range ? ERANGE : EDOM;
}
template<typename TFloat>
short ClassifyValue(TFloat value) {
switch (std::fpclassify(value)) {
case FP_NAN:
return NotANumber;
case FP_INFINITE:
return Infinite;
case FP_ZERO:
return Zero;
case FP_SUBNORMAL:
return Denormal;
default:
return Finite;
}
}
template<typename TFloat>
short ScaleProduct(TFloat& value, TFloat factor, TFloat multiplier, long exponent) {
std::fexcept_t saved;
std::fegetexceptflag(&saved, FE_ALL_EXCEPT);
const int savedErrno = errno;
TFloat result = factor * multiplier;
if (!std::isfinite(result) || result == static_cast<TFloat>(0)) {
int factorExponent = 0;
int multiplierExponent = 0;
const TFloat factorMantissa = std::frexp(factor, &factorExponent);
const TFloat multiplierMantissa = std::frexp(multiplier, &multiplierExponent);
const long long combinedExponent = static_cast<long long>(factorExponent) + multiplierExponent + exponent;
result = std::scalbn(factorMantissa * multiplierMantissa, static_cast<int>(combinedExponent));
} else {
result = std::scalbn(result, static_cast<int>(exponent));
}
value = result;
const short kind = ClassifyValue(result);
const int raised = std::fetestexcept(FE_ALL_EXCEPT) & ~static_cast<int>(saved) & FE_ALL_EXCEPT;
std::fesetexceptflag(&saved, FE_ALL_EXCEPT);
errno = savedErrno;
if (kind == Infinite) {
RaiseLibcError(FE_OVERFLOW);
return Infinite;
}
if (kind == Zero) {
RaiseLibcError(FE_UNDERFLOW);
return Zero;
}
if (kind == Denormal) return Finite;
if (kind == NotANumber) return NotANumber;
if ((raised & FE_INEXACT) != 0) std::feraiseexcept(FE_INEXACT);
return Finite;
}
}
extern "C" {
@@ -131,6 +196,200 @@ long long APS5_VABI _Stoll_nid_postfix(const char* text, char** end, int base) {
return _Stollx_nid_postfix(text, end, base, nullptr);
}
short APS5_VABI _Exp_nid_postfix(double* value, double y, short offset) {
if (value == nullptr) throw std::invalid_argument("_Exp: null value");
std::fexcept_t entryFlags;
std::fegetexceptflag(&entryFlags, FE_ALL_EXCEPT);
const int entryErrno = errno;
const double x = *value;
constexpr double HugeExp = 1842.0;
constexpr double Eps = std::numeric_limits<double>::epsilon();
constexpr double InverseLn2 = 1.4426950408889634073599246810018921;
constexpr double Ln2High = 22713.0 / 32768.0;
constexpr double Ln2Low = 1.4286068203094172321214581765680755e-6;
constexpr double P1 = 420.30235984910635;
constexpr double P2 = 15132.70094680474802;
constexpr double Q0 = 30.01511290683317;
constexpr double Q1 = 3362.72154416553028;
constexpr double Q2 = 30265.40189360949691;
constexpr double E = 0x1.5bf0a8b145769p+1;
const short xKind = ClassifyValue(x);
const short yKind = ClassifyValue(y);
if (xKind == NotANumber) return NotANumber;
if (yKind == NotANumber) return NotANumber;
if (xKind == Infinite) {
if (x > 0.0) {
if (yKind == Zero) {
*value = std::numeric_limits<double>::quiet_NaN();
RaiseLibcError(FE_INVALID);
return NotANumber;
}
if (yKind == Infinite) {
*value = y;
} else {
*value = std::copysign(std::numeric_limits<double>::infinity(), y);
}
return Infinite;
}
if (yKind == Infinite) {
*value = std::numeric_limits<double>::quiet_NaN();
RaiseLibcError(FE_INVALID);
return NotANumber;
}
*value = std::copysign(0.0, y);
return Zero;
}
if (yKind == Zero) {
*value = y;
return Zero;
}
if (yKind == Infinite) {
*value = y;
return Infinite;
}
if (xKind == Zero) {
if (offset == 0) {
*value = y;
return Finite;
}
const short result = ScaleProduct(*value, 1.0, y, offset);
if (result == Finite && std::fpclassify(*value) == FP_SUBNORMAL) {
std::fesetexceptflag(&entryFlags, FE_ALL_EXCEPT);
errno = entryErrno;
}
return result;
}
if (offset == 0 && x == 1.0 && y == 1.0) {
*value = E;
return Finite;
}
if (x < -HugeExp) {
*value = std::copysign(0.0, y);
return Zero;
}
if (x > HugeExp) {
*value = std::copysign(std::numeric_limits<double>::infinity(), y);
RaiseLibcError(FE_OVERFLOW);
return Infinite;
}
double g = x * InverseLn2;
long exponent = static_cast<long>(g + (g < 0.0 ? -0.5 : 0.5));
g = static_cast<double>(exponent);
g = (x - g * Ln2High) - g * Ln2Low;
double factor;
if (-Eps < g && g < Eps) {
factor = 1.0;
} else {
const double z = g * g;
const double w = (Q0 * z + Q1) * z + Q2;
g *= (z + P1) * z + P2;
factor = (w + g) / (w - g) * 2.0;
--exponent;
}
const short result = ScaleProduct(*value, factor, y, exponent + offset);
if (result == Finite && std::fpclassify(*value) == FP_SUBNORMAL) {
std::fesetexceptflag(&entryFlags, FE_ALL_EXCEPT);
errno = entryErrno;
}
return result;
}
short APS5_VABI _FExp_nid_postfix(float* value, float y, short offset) {
if (value == nullptr) throw std::invalid_argument("_FExp: null value");
std::fexcept_t entryFlags;
std::fegetexceptflag(&entryFlags, FE_ALL_EXCEPT);
const int entryErrno = errno;
const float x = *value;
constexpr float HugeExp = 229.0f;
constexpr float Eps = std::numeric_limits<float>::epsilon();
constexpr float InverseLn2 = 1.4426950216f;
constexpr float Ln2High = 0.69287109375f;
constexpr float Ln2Low = 0.00027608682f;
constexpr float Q = 0.09997485578f;
constexpr float P0 = 0.5f;
constexpr float P1 = 0.0083208261f;
constexpr float E = 0x1.5bf0a8p+1f;
const short xKind = ClassifyValue(x);
const short yKind = ClassifyValue(y);
if (xKind == NotANumber) return NotANumber;
if (yKind == NotANumber) return NotANumber;
if (xKind == Infinite) {
if (x > 0.0f) {
if (yKind == Zero) {
*value = std::numeric_limits<float>::quiet_NaN();
RaiseLibcError(FE_INVALID);
return NotANumber;
}
if (yKind == Infinite) {
*value = y;
} else {
*value = std::copysign(std::numeric_limits<float>::infinity(), y);
}
return Infinite;
}
if (yKind == Infinite) {
*value = std::numeric_limits<float>::quiet_NaN();
RaiseLibcError(FE_INVALID);
return NotANumber;
}
*value = std::copysign(0.0f, y);
return Zero;
}
if (yKind == Zero) {
*value = y;
return Zero;
}
if (yKind == Infinite) {
*value = y;
return Infinite;
}
if (xKind == Zero) {
if (offset == 0) {
*value = y;
return Finite;
}
const short result = ScaleProduct(*value, 1.0f, y, offset);
if (result == Finite && std::fpclassify(*value) == FP_SUBNORMAL) {
std::fesetexceptflag(&entryFlags, FE_ALL_EXCEPT);
errno = entryErrno;
}
return result;
}
if (offset == 0 && x == 1.0f && y == 1.0f) {
*value = E;
return Finite;
}
if (x < -HugeExp) {
*value = std::copysign(0.0f, y);
return Zero;
}
if (x > HugeExp) {
*value = std::copysign(std::numeric_limits<float>::infinity(), y);
RaiseLibcError(FE_OVERFLOW);
return Infinite;
}
float g = x * InverseLn2;
long exponent = static_cast<long>(g + (g < 0.0f ? -0.5f : 0.5f));
g = static_cast<float>(exponent);
g = (x - g * Ln2High) - g * Ln2Low;
float factor;
if (-Eps < g && g < Eps) {
factor = 1.0f;
} else {
const float z = g * g;
const float w = Q * z + 1.0f;
g *= P0 + P1 * z;
factor = (w + g) / (w - g) * 2.0f;
--exponent;
}
const short result = ScaleProduct(*value, factor, y, exponent + offset);
if (result == Finite && std::fpclassify(*value) == FP_SUBNORMAL) {
std::fesetexceptflag(&entryFlags, FE_ALL_EXCEPT);
errno = entryErrno;
}
return result;
}
double APS5_VABI _Sin_nid_postfix(double x, unsigned int quadrant) {
return quadrant != 0 ? cos_nid_postfix(x) : sin_nid_postfix(x);
}
+9
View File
@@ -465,6 +465,15 @@ int APS5_VABI fopen_s_nid_postfix(FileStream** result, const char* filename, con
return *result ? 0 : errno;
}
int APS5_VABI freopen_s_nid_postfix(FileStream** result, const char* filename, const char* mode, FileStream* stream) {
constexpr int GuestEinval = 22;
if (!result) return GuestEinval;
*result = nullptr;
if (!mode || !stream) return GuestEinval;
*result = freopen_nid_postfix(filename, mode, stream);
return *result ? 0 : errno;
}
int APS5_VABI fclose_nid_postfix(FileStream* stream) {
if (stream && !stream->IsOpen()) {
std::unique_ptr<FileStream> owner(stream->IsDynamic() ? stream : nullptr);
+185 -21
View File
@@ -125,10 +125,69 @@ int HostVfscanf(std::FILE* stream, const char* format, std::va_list args) {
}
#endif
template <typename NextPointer, typename NextCapacity>
int ScanGuest(const char* buffer, const char* format, bool secure, NextPointer nextPointer, NextCapacity nextCapacity) {
if (buffer == nullptr || format == nullptr) return EOF;
const char* input = buffer;
int HostFscanf(std::FILE* stream, const char* format, ...) {
std::va_list args;
va_start(args, format);
#ifdef _WIN32
const int result = std::vfscanf(stream, format, args);
#else
const int result = HostVfscanf(stream, format, args);
#endif
va_end(args);
return result;
}
class StringSource {
const char* const start;
const char* position;
public:
static constexpr bool Contiguous = true;
explicit StringSource(const char* text) : start(text), position(text) {}
int Peek() const { return *position == '\0' ? EOF : static_cast<unsigned char>(*position); }
void Skip() { ++position; }
void Advance(std::ptrdiff_t count) { position += count; }
std::ptrdiff_t Consumed() const { return position - start; }
const char* Remaining() const { return position; }
int Scan(const char* directive, bool suppress, void* target, int* consumed) const {
return suppress ? HostSscanf(position, directive, consumed) : HostSscanf(position, directive, target, consumed);
}
};
class StreamSource {
std::FILE* const file;
std::ptrdiff_t consumed = 0;
public:
static constexpr bool Contiguous = false;
explicit StreamSource(std::FILE* native) : file(native) {}
int Peek() const {
const int next = std::fgetc(file);
if (next != EOF) std::ungetc(next, file);
return next;
}
void Skip() {
std::fgetc(file);
++consumed;
}
void Advance(std::ptrdiff_t count) { consumed += count; }
std::ptrdiff_t Consumed() const { return consumed; }
int Scan(const char* directive, bool suppress, void* target, int* count) const {
return suppress ? HostFscanf(file, directive, count) : HostFscanf(file, directive, target, count);
}
};
template <typename TSource, typename NextPointer, typename NextCapacity>
int ScanSource(TSource source, const char* format, bool secure, NextPointer nextPointer, NextCapacity nextCapacity) {
if (format == nullptr) return EOF;
int assigned = 0;
bool converted = false;
const char* cursor = format;
@@ -136,15 +195,15 @@ int ScanGuest(const char* buffer, const char* format, bool secure, NextPointer n
while (*cursor != '\0') {
if (std::isspace(static_cast<unsigned char>(*cursor))) {
while (std::isspace(static_cast<unsigned char>(*cursor))) ++cursor;
while (std::isspace(static_cast<unsigned char>(*input))) ++input;
while (std::isspace(source.Peek())) source.Skip();
continue;
}
if (*cursor != '%' || cursor[1] == '%') {
const char expected = *cursor == '%' ? '%' : *cursor;
cursor += *cursor == '%' ? 2 : 1;
if (expected == '%') while (std::isspace(static_cast<unsigned char>(*input))) ++input;
if (*input != expected) return finish(!converted && *input == '\0' ? EOF : assigned);
++input;
if (expected == '%') while (std::isspace(source.Peek())) source.Skip();
if (source.Peek() != static_cast<unsigned char>(expected)) return finish(!converted && source.Peek() == EOF ? EOF : assigned);
source.Skip();
continue;
}
++cursor;
@@ -171,7 +230,7 @@ int ScanGuest(const char* buffer, const char* format, bool secure, NextPointer n
if (!suppress) {
void* target = nextPointer();
if (target == nullptr) return EOF;
const auto count = input - buffer;
const auto count = source.Consumed();
if (length == "hh") *static_cast<signed char*>(target) = static_cast<signed char>(count);
else if (length == "h") *static_cast<short*>(target) = static_cast<short>(count);
else if (length.empty()) *static_cast<int*>(target) = static_cast<int>(count);
@@ -183,7 +242,8 @@ int ScanGuest(const char* buffer, const char* format, bool secure, NextPointer n
continue;
}
#ifndef _WIN32
if (IsWideScan(length, conversion)) {
if constexpr (TSource::Contiguous) if (IsWideScan(length, conversion)) {
const char* const input = source.Remaining();
const char kind = conversion == 'S' ? 's' : conversion == 'C' ? 'c' : conversion;
const char* field = input;
if (kind == 's') {
@@ -229,15 +289,14 @@ int ScanGuest(const char* buffer, const char* format, bool secure, NextPointer n
if (kind != 'c') target[text.size()] = u'\0';
++assigned;
}
input = field;
source.Advance(field - input);
converted = true;
continue;
}
#endif
if (conversion != 'c' && conversion != '[') {
const char* probe = input;
while (std::isspace(static_cast<unsigned char>(*probe))) ++probe;
if (*probe == '\0') return finish(converted ? assigned : EOF);
while (std::isspace(source.Peek())) source.Skip();
if (source.Peek() == EOF) return finish(converted ? assigned : EOF);
}
const bool sized = secure && (conversion == 's' || conversion == 'c' || conversion == '[');
bool capped = false;
@@ -264,29 +323,50 @@ int ScanGuest(const char* buffer, const char* format, bool secure, NextPointer n
directive += specifier;
directive += "%n";
int consumed = -1;
const int matched = suppress ? HostSscanf(input, directive.c_str(), &consumed) : HostSscanf(input, directive.c_str(), target, &consumed);
if (consumed < 0 || (!suppress && matched != 1)) return finish(!converted && *input == '\0' ? EOF : assigned);
const int matched = source.Scan(directive.c_str(), suppress, target, &consumed);
if (consumed < 0 || (!suppress && matched != 1)) return finish(!converted && source.Peek() == EOF ? EOF : assigned);
source.Advance(consumed);
if (capped && conversion != 'c') {
const char next = input[consumed];
const int next = source.Peek();
bool overflow = false;
if (conversion == 's') overflow = next != '\0' && !std::isspace(static_cast<unsigned char>(next));
else if (next != '\0') {
if (conversion == 's') overflow = next != EOF && !std::isspace(next);
else if (next != EOF) {
char probe[2];
const char text[2] = {static_cast<char>(next), '\0'};
const std::string test = "%1" + specifier;
overflow = std::sscanf(input + consumed, test.c_str(), probe) == 1;
overflow = std::sscanf(text, test.c_str(), probe) == 1;
}
if (overflow) {
static_cast<char*>(target)[0] = '\0';
return finish(assigned);
}
}
input += consumed;
converted = true;
if (!suppress) ++assigned;
}
return finish(assigned);
}
template <typename NextPointer, typename NextCapacity>
int ScanGuest(const char* buffer, const char* format, bool secure, NextPointer nextPointer, NextCapacity nextCapacity) {
if (buffer == nullptr) return EOF;
return ScanSource(StringSource(buffer), format, secure, nextPointer, nextCapacity);
}
template <typename NextPointer, typename NextCapacity>
int ScanGuestStream(FileStream* stream, const char* format, NextPointer nextPointer, NextCapacity nextCapacity) {
if (stream == nullptr || format == nullptr) return EOF;
if (!stream->IsOpen()) { errno = 9; return EOF; }
auto* native = GetNativeStream(stream);
if (!stream->SwitchToReading()) return EOF;
#ifndef _WIN32
RejectGuestWideScan(format);
#endif
const int result = ScanSource(StreamSource(native), format, true, nextPointer, nextCapacity);
stream->SyncStatus();
return result;
}
}
extern "C" {
@@ -577,6 +657,47 @@ int APS5_VABI vsnprintf_s_nid_postfix(char* buffer, size_t size, const char* for
return FormatSecure(buffer, size, format, args);
}
static int WriteSecure(FileStream* stream, const char* format, const void* args) {
if (stream == nullptr || format == nullptr || args == nullptr) return -1;
if (!stream->IsOpen()) { errno = 9; return -1; }
auto* native = GetNativeStream(stream);
std::string text;
int count;
try {
count = LibcDetail::FormatWindows(nullptr, 0, format, args, &text, true);
} catch (const LibcDetail::FormatConstraintViolation&) {
return -1;
}
const int result = std::fwrite(text.data(), 1, static_cast<size_t>(count), native) == static_cast<size_t>(count) ? count : -1;
stream->SyncStatus();
return result;
}
int APS5_VABI vfprintf_s_nid_postfix(FileStream* stream, const char* format, VaList* args) {
return WriteSecure(stream, format, args);
}
int APS5_VABI vprintf_s_nid_postfix(const char* format, VaList* args) {
return WriteSecure(&_Stdout_nid_postfix, format, args);
}
int APS5_VABI fprintf_s_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_s_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 vsscanf_s_nid_postfix(const char* buffer, const char* format, VaList* args) {
@@ -690,4 +811,47 @@ int APS5_VABI scanf_nid_postfix(const char* format, ...) {
return result;
}
int APS5_VABI vfscanf_s_nid_postfix(FileStream* stream, const char* format, VaList* args) {
LibcDetail::FormatArguments arguments(args);
return ScanGuestStream(stream, format, [&] { return arguments.Next<void*>(); }, [&] { return arguments.Next<unsigned int>(); });
}
int APS5_VABI vscanf_s_nid_postfix(const char* format, VaList* args) {
return vfscanf_s_nid_postfix(__stdinp_nid_postfix, format, args);
}
int APS5_VABI fscanf_s_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 = vfscanf_s_nid_postfix(stream, format, reinterpret_cast<VaList*>(args));
#ifdef _WIN32
__builtin_sysv_va_end(args);
#else
va_end(args);
#endif
return result;
}
int APS5_VABI scanf_s_nid_postfix(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 = vfscanf_s_nid_postfix(__stdinp_nid_postfix, format, reinterpret_cast<VaList*>(args));
#ifdef _WIN32
__builtin_sysv_va_end(args);
#else
va_end(args);
#endif
return result;
}
}
+33
View File
@@ -361,6 +361,39 @@ int APS5_VABI vwprintf_nid_postfix(const char16_t* format, VaList* args) {
return vfwprintf_nid_postfix(__stdoutp_nid_postfix, format, args);
}
int APS5_VABI vfwprintf_s_nid_postfix(FileStream* stream, const char16_t* format, VaList* args) {
if (stream == nullptr || format == nullptr || args == nullptr) return -1;
if (!stream->IsOpen()) { errno = 9; return -1; }
int result = -1;
try {
const std::u16string text = FormatWide(format, args, true);
const std::string bytes = ToUtf8(text);
if (text.size() <= static_cast<std::size_t>(INT_MAX) &&
std::fwrite(bytes.data(), 1, bytes.size(), GetNativeStream(stream)) == bytes.size()) result = static_cast<int>(text.size());
} catch (const std::invalid_argument&) {
}
stream->SyncStatus();
return result;
}
int APS5_VABI vwprintf_s_nid_postfix(const char16_t* format, VaList* args) {
return vfwprintf_s_nid_postfix(__stdoutp_nid_postfix, format, args);
}
int APS5_VABI fwprintf_s_nid_postfix(FileStream* stream, const char16_t* format, ...) {
APS5_VA_BEGIN(format);
const int result = vfwprintf_s_nid_postfix(stream, format, reinterpret_cast<VaList*>(args));
APS5_VA_END();
return result;
}
int APS5_VABI wprintf_s_nid_postfix(const char16_t* format, ...) {
APS5_VA_BEGIN(format);
const int result = vfwprintf_s_nid_postfix(__stdoutp_nid_postfix, format, reinterpret_cast<VaList*>(args));
APS5_VA_END();
return result;
}
int APS5_VABI fputwc_nid_postfix(char16_t value, FileStream* stream) {
if (stream == nullptr) { errno = 22; return -1; }
if (!stream->IsOpen()) { errno = 9; return -1; }
+7 -1
View File
@@ -853,9 +853,15 @@ extern "C" std::filesystem::path RelinkedModulePath_nid_no_patch(const std::file
const auto relinked = RelinkedModuleName(path);
std::error_code error;
if (std::filesystem::is_regular_file(relinked, error)) return relinked;
const auto parent = relinked.parent_path().empty() ? std::filesystem::path(".") : relinked.parent_path();
NameMatch names[] = {{relinked.filename().string()}, {path.filename().string(), std::nullopt, false, true}};
MatchNames(relinked.parent_path().empty() ? "." : relinked.parent_path(), names);
MatchNames(parent, names);
if (names[0].ambiguous || names[0].path) return MatchedModuleOrPath(path, relinked, names[0]);
for (const char* directory : {"prx", "sce_module", "sce_modules"}) {
NameMatch nested[] = {{relinked.filename().string()}};
MatchNames(parent / directory, nested);
if (nested[0].ambiguous || nested[0].path) return MatchedModuleOrPath(path, relinked, nested[0]);
}
return names[1].path ? *names[1].path : path;
}
+1 -1
View File
@@ -6,7 +6,7 @@
#include <cstring>
extern "C" int APS5_VABI gethostname_nid_postfix(char* name, std::size_t length) {
constexpr char hostname[] = "AnyPS5";
constexpr char hostname[] = "";
if (name == nullptr) return 0;
const int savedError = errno;
const auto count = std::min(length, sizeof(hostname));
Loaded 100 of 282 files, more files were not shown because too many files have changed in this diff. Show more