mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-28 12:05:06 +00:00
Merge main into fix/libscepad-open-checks-measured-ps5
Main limits the standard port to logged-in users, so scePadOpen now checks the port type alone first, then the user and index, and leaves the standard port's user lookup to PortForUser. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
commit
199fd82e3d
147 files changed
+6949
-508
No files matched your search
@@ -102,6 +102,18 @@ uint32_t* APS5_VABI sceAgcAcbPushMarker(CommandBuffer* buf, const char* str, uin
|
||||
return Agc::Marker::Push(buf, str, __func__);
|
||||
}
|
||||
|
||||
uint32_t* APS5_VABI sceAgcAcbPushMarkerSpan(CommandBuffer* buf, const char* str, uint32_t length, uint32_t color) {
|
||||
(void)color;
|
||||
return Agc::Marker::PushSpan(buf, str, length, __func__);
|
||||
}
|
||||
|
||||
uint32_t* APS5_VABI sceAgcAcbSetMarkerSpan(CommandBuffer* buf, const char* str, uint32_t length, uint32_t color) {
|
||||
(void)color;
|
||||
auto* packet = Agc::Marker::PushSpan(buf, str, length, __func__);
|
||||
Agc::Marker::Pop(buf, __func__);
|
||||
return packet;
|
||||
}
|
||||
|
||||
uint32_t* APS5_VABI sceAgcAcbPopMarker(CommandBuffer* buf) {
|
||||
return Agc::Marker::Pop(buf, __func__);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
namespace Agc::Marker {
|
||||
|
||||
std::uint32_t* Push(CommandBuffer* buf, const char* str, const char* function);
|
||||
std::uint32_t* PushSpan(CommandBuffer* buf, const char* str, std::uint32_t length, const char* function);
|
||||
std::uint32_t* Pop(CommandBuffer* buf, const char* function);
|
||||
|
||||
}
|
||||
|
||||
@@ -22,13 +22,21 @@ namespace Agc::Marker {
|
||||
std::uint32_t* Push(CommandBuffer* buf, const char* str, const char* function) {
|
||||
Agc::Command::Require(buf != nullptr, function, "null command buffer");
|
||||
const char* text = str ? str : "";
|
||||
const std::size_t bytes = std::strlen(text) + 1;
|
||||
const std::size_t length = std::strlen(text);
|
||||
Agc::Command::Require(length < 0x10000u, function, "marker text too long");
|
||||
return PushSpan(buf, text, static_cast<std::uint32_t>(length), function);
|
||||
}
|
||||
|
||||
std::uint32_t* PushSpan(CommandBuffer* buf, const char* str, std::uint32_t length, const char* function) {
|
||||
Agc::Command::Require(buf != nullptr, function, "null command buffer");
|
||||
Agc::Command::Require(str != nullptr || length == 0, function, "null marker text");
|
||||
const std::size_t bytes = std::size_t{length} + 1;
|
||||
const auto payload = static_cast<std::uint32_t>((bytes + 3) / 4);
|
||||
Agc::Command::Require(payload <= 0x4000u, function, "marker text too long");
|
||||
auto* packet = Agc::Command::Allocate(buf, payload + 1, function);
|
||||
packet[0] = Agc::Command::Header(OpcodeNop, payload + 1, CustomPushMarker << 2);
|
||||
std::memset(packet + 1, 0, payload * sizeof(std::uint32_t));
|
||||
std::memcpy(packet + 1, text, bytes);
|
||||
if (length != 0) std::memcpy(packet + 1, str, length);
|
||||
return packet;
|
||||
}
|
||||
|
||||
@@ -60,4 +68,16 @@ uint32_t* APS5_VABI sceAgcDcbPushMarker(CommandBuffer* buf, const char* str, uin
|
||||
return Agc::Marker::Push(buf, str, __func__);
|
||||
}
|
||||
|
||||
uint32_t* APS5_VABI sceAgcDcbPushMarkerSpan(CommandBuffer* buf, const char* str, uint32_t length, uint32_t color) {
|
||||
(void)color;
|
||||
return Agc::Marker::PushSpan(buf, str, length, __func__);
|
||||
}
|
||||
|
||||
uint32_t* APS5_VABI sceAgcDcbSetMarkerSpan(CommandBuffer* buf, const char* str, uint32_t length, uint32_t color) {
|
||||
(void)color;
|
||||
auto* packet = Agc::Marker::PushSpan(buf, str, length, __func__);
|
||||
Agc::Marker::Pop(buf, __func__);
|
||||
return packet;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,31 +5,11 @@
|
||||
|
||||
extern "C" {
|
||||
|
||||
std::uint32_t* APS5_VABI sceAgcAcbPushMarkerSpan(CommandBuffer* buffer, const char* text, std::uint32_t length, std::uint32_t color) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::uint32_t* APS5_VABI sceAgcAcbSetMarkerSpan(CommandBuffer* buffer, const char* text, std::uint32_t length, std::uint32_t color) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAgcAcquireMemSetEngine(std::uint32_t* packet, std::uint8_t engine) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::uint32_t* APS5_VABI sceAgcDcbPushMarkerSpan(CommandBuffer* buffer, const char* text, std::uint32_t length, std::uint32_t color) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::uint32_t* APS5_VABI sceAgcDcbSetMarkerSpan(CommandBuffer* buffer, const char* text, std::uint32_t length, std::uint32_t color) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::uint32_t* APS5_VABI sceAgcCbMemsetExclusive(CommandBuffer* buffer, void* destination, const void* pattern, std::uint64_t size) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
|
||||
@@ -48,6 +48,10 @@ extern "C" std::uint32_t* APS5_VABI sceAgcAcbSetWorkloadStreamInactive(CommandBu
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcDcbSetMarker(CommandBuffer* buf, const char* str, std::uint32_t color);
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcAcbPushMarker(CommandBuffer* buf, const char* str, std::uint32_t color);
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcAcbPopMarker(CommandBuffer* buf);
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcAcbPushMarkerSpan(CommandBuffer* buf, const char* str, std::uint32_t length, std::uint32_t color);
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcAcbSetMarkerSpan(CommandBuffer* buf, const char* str, std::uint32_t length, std::uint32_t color);
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcDcbPushMarkerSpan(CommandBuffer* buf, const char* str, std::uint32_t length, std::uint32_t color);
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcDcbSetMarkerSpan(CommandBuffer* buf, const char* str, std::uint32_t length, std::uint32_t color);
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcAcbSetMarker(CommandBuffer* buf, const char* str, std::uint32_t color);
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcDcbSetIndexBuffer(CommandBuffer* buf, std::uint64_t indexAddress);
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcDcbSetIndexIndirectArgs(CommandBuffer* buf, std::uint64_t attributeAddress, std::uint32_t attributeIndex);
|
||||
@@ -225,6 +229,29 @@ void testMarkers() {
|
||||
expectFailure([] { sceAgcAcbSetMarker(nullptr, "frame", 0); });
|
||||
}
|
||||
|
||||
void testMarkerSpans() {
|
||||
Storage dcb;
|
||||
Storage acb;
|
||||
const char text[] = "framebuffer";
|
||||
const auto* acbPush = sceAgcAcbPushMarkerSpan(&acb.buffer, text, 5, 0x00ff00u);
|
||||
const auto* dcbPush = sceAgcDcbPushMarkerSpan(&dcb.buffer, text, 5, 0xff0000u);
|
||||
check(acbPush == acb.words.data() && acbPush[0] == Agc::Command::Header(0x10, 3, 0x0bu << 2u), "ACB push marker span header mismatch");
|
||||
check(std::strcmp(reinterpret_cast<const char*>(acbPush + 1), "frame") == 0, "ACB push marker span does not stop at its length");
|
||||
check(dcbPush == dcb.words.data(), "DCB push marker span did not return its packet");
|
||||
const auto* acbSet = sceAgcAcbSetMarkerSpan(&acb.buffer, nullptr, 0, 0);
|
||||
const auto* dcbSet = sceAgcDcbSetMarkerSpan(&dcb.buffer, nullptr, 0, 0);
|
||||
check(acbSet == acb.words.data() + 3 && dcbSet == dcb.words.data() + 3, "set marker span did not return its push packet");
|
||||
check(acbSet[0] == Agc::Command::Header(0x10, 2, 0x0bu << 2u) && acbSet[1] == 0 && acbSet[2] == Agc::Command::Header(0x10, 2, 0x0cu << 2u), "ACB set marker span is not a push and pop pair");
|
||||
check(dcb.words == acb.words && acb.buffer.cursor_up == acb.words.data() + 7, "ACB and DCB marker spans differ");
|
||||
Storage plain;
|
||||
sceAgcAcbPushMarker(&plain.buffer, "frame", 0);
|
||||
check(std::equal(plain.words.begin(), plain.words.begin() + 3, acb.words.begin()), "marker span differs from the same text pushed whole");
|
||||
expectFailure([] { sceAgcAcbPushMarkerSpan(nullptr, "frame", 5, 0); });
|
||||
expectFailure([] { sceAgcAcbPushMarker(nullptr, reinterpret_cast<const char*>(std::uintptr_t{0x10}), 0); });
|
||||
expectFailure([] { sceAgcDcbSetMarkerSpan(nullptr, "frame", 5, 0); });
|
||||
expectFailure([&] { sceAgcDcbPushMarkerSpan(&dcb.buffer, nullptr, 4, 0); });
|
||||
}
|
||||
|
||||
void testWorkloads() {
|
||||
Storage dcb;
|
||||
const std::uint32_t ids[2] = {3, 4};
|
||||
@@ -822,6 +849,7 @@ int main(int argc, char** argv) {
|
||||
testClearState();
|
||||
testIndexedIndirectDraws();
|
||||
testMarkers();
|
||||
testMarkerSpans();
|
||||
testWorkloads();
|
||||
testWorkloadStreams();
|
||||
testIndexBuffer();
|
||||
|
||||
@@ -2164,6 +2164,9 @@ if(NOT ANYPS5_AGC_EXECUTION_TESTS OR "PooledBufferRange" IN_LIST ANYPS5_AGC_EXEC
|
||||
endif()
|
||||
|
||||
target_link_libraries(agc_driver_gpu_map_on_fiber_tests PRIVATE libSceFiber)
|
||||
if(ANYPS5_ENABLE_SPIRV_TOOLS)
|
||||
target_link_libraries(agc_driver_optimize_on_fiber_tests PRIVATE libSceFiber shader_recompiler_agc_driver)
|
||||
endif()
|
||||
|
||||
target_link_libraries(agc_driver_rect_list_corner_tests PRIVATE shader_recompiler_agc_driver)
|
||||
target_link_libraries(agc_driver_queue_zero_late_interrupt_tests PRIVATE libkernel)
|
||||
@@ -2304,6 +2307,8 @@ target_include_directories(agc_driver_packed_unorm4444_multisample_partial_write
|
||||
|
||||
target_include_directories(agc_driver_color1555_target_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
|
||||
target_include_directories(agc_driver_color_logic_op_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
|
||||
target_include_directories(agc_driver_color565_target_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
|
||||
target_include_directories(agc_driver_depth_range_remap_draw_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
@@ -2500,6 +2505,14 @@ if(BUILD_TESTING)
|
||||
set_tests_properties(agc_driver_sparse_indirect_indices_unreadable PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test(NAME agc_driver_draw_cache_relocation_evicted COMMAND agc_driver_draw_cache_relocation_tests --evicted)
|
||||
add_test(NAME agc_driver_draw_cache_relocation_kept COMMAND agc_driver_draw_cache_relocation_tests --kept)
|
||||
set_tests_properties(agc_driver_draw_cache_relocation_evicted agc_driver_draw_cache_relocation_kept PROPERTIES
|
||||
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_DRAW_CACHE_ENTRIES=2" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_driver_draw_cache_relocation_evicted agc_driver_draw_cache_relocation_kept PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
set_tests_properties(agc_driver_pixel_implicit_derivatives PROPERTIES TIMEOUT 30)
|
||||
set_tests_properties(agc_driver_storage_file_write PROPERTIES TIMEOUT 30 ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_SUSPEND_DRAIN=1")
|
||||
set_tests_properties(agc_driver_clamped_global_fill PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_LABEL_READBACK_STORE=1")
|
||||
|
||||
@@ -416,7 +416,7 @@ private:
|
||||
std::unordered_set<std::uint64_t> traceShapes, traceKeysSeen, traceKeysEvicted;
|
||||
std::list<std::uint64_t> drawOrder;
|
||||
std::unordered_map<std::uint64_t, std::shared_ptr<const DrawRelocation>> drawRelocations;
|
||||
std::unordered_map<std::uint64_t, std::shared_ptr<DrawEntry>> drawAliases;
|
||||
std::unordered_map<std::uint64_t, std::uint64_t> drawAliases;
|
||||
DrawRelocationPending drawRelocationPending;
|
||||
std::unordered_map<std::uint64_t, DrawRelocationTrial> drawRelocationTrials;
|
||||
DrawRelocationCounters drawRelocationCounters;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Pm4.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp"
|
||||
#include "Optimization/ResourceProgram.hpp"
|
||||
#include "prx/libc/include/HostThreadLocal.hpp"
|
||||
#include <atomic>
|
||||
@@ -46,7 +47,8 @@ struct DrawTail {
|
||||
std::shared_ptr<DrawEntry> dataEntry;
|
||||
std::uint64_t shapeKey = 0;
|
||||
ShaderRecompiler::ShaderPixelStageInfo pixel{};
|
||||
std::vector<DrawProgram> programs;
|
||||
std::vector<DrawProgram> writablePrograms;
|
||||
const std::vector<DrawProgram>* programs = nullptr;
|
||||
std::vector<ShaderRecompiler::MemoryRegion> memory;
|
||||
std::vector<ShaderRecompiler::LinkedProgram> linked;
|
||||
std::vector<std::optional<ShaderRecompiler::ShaderVertexStageInfo>> vertexInfos;
|
||||
@@ -94,7 +96,7 @@ void noteFinisherHandOff(bool handed) {
|
||||
if (!stats) return;
|
||||
const auto tenSeconds = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now().time_since_epoch()).count() / 10;
|
||||
if (reportedAt.exchange(tenSeconds, std::memory_order_relaxed) == tenSeconds) return;
|
||||
std::fprintf(stderr, "[finisher] draws (10 s): %llu handed to the draw finisher, %llu finished on the worker%s\n", static_cast<unsigned long long>(handedOff.exchange(0)), static_cast<unsigned long long>(kept.exchange(0)), DrawFinisher::DrainText().c_str());
|
||||
AgcDriver::ProfilePrint_nid_no_patch("[finisher] draws (10 s): %llu handed to the draw finisher, %llu finished on the worker%s\n", static_cast<unsigned long long>(handedOff.exchange(0)), static_cast<unsigned long long>(kept.exchange(0)), DrawFinisher::DrainText().c_str());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -147,7 +149,7 @@ DrawFinisher::Pending heldWrites(const DrawTail& tail) {
|
||||
noteHeld(HeldReason::NoDecode);
|
||||
return held;
|
||||
}
|
||||
if (tail.matched.size() != tail.programs.size()) {
|
||||
if (tail.matched.size() != tail.programs->size()) {
|
||||
noteHeld(HeldReason::Size);
|
||||
return held;
|
||||
}
|
||||
@@ -157,10 +159,10 @@ DrawFinisher::Pending heldWrites(const DrawTail& tail) {
|
||||
const auto& variant = tail.matched[i];
|
||||
if (variant == nullptr || variant->compiled == nullptr) {
|
||||
const bool candidates = !tail.drawHit && i < tail.dataCandidates.size() && !tail.dataCandidates[i].empty();
|
||||
const auto& program = tail.programs[i];
|
||||
const auto& program = (*tail.programs)[i];
|
||||
if (!candidates && program.snapshot != nullptr && !ProgramWritesMemory(*program.snapshot, program.codeOffset) && (program.tailContinuation == nullptr || !ProgramWritesMemory(*program.tailContinuation, 0))) continue;
|
||||
if (!candidates) {
|
||||
const auto stage = tail.programs[i].binary.stage;
|
||||
const auto stage = (*tail.programs)[i].binary.stage;
|
||||
noteHeld(stage == ShaderRecompiler::ShaderStage::Fragment ? HeldReason::NullFragment : stage == ShaderRecompiler::ShaderStage::Vertex ? HeldReason::NullVertex : HeldReason::NullOther);
|
||||
return held;
|
||||
}
|
||||
@@ -395,9 +397,12 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
|
||||
const auto& graphics = decode->state;
|
||||
auto& pixel = tail.pixel;
|
||||
pixel = decode->pixel;
|
||||
auto& programs = tail.programs;
|
||||
programs = decode->programs;
|
||||
for (const auto& patch : userPatches) programs[patch.program].userData[patch.index] = patch.value;
|
||||
const bool writesPrograms = graphics.stages.mesh || drawParameters.indirect || !userPatches.empty();
|
||||
auto& writablePrograms = tail.writablePrograms;
|
||||
if (writesPrograms) writablePrograms = decode->programs;
|
||||
for (const auto& patch : userPatches) writablePrograms[patch.program].userData[patch.index] = patch.value;
|
||||
tail.programs = writesPrograms ? &writablePrograms : &decode->programs;
|
||||
const auto& programs = *tail.programs;
|
||||
if (const auto skipped = SkippedShader(); skipped != 0 && std::any_of(programs.begin(), programs.end(), [skipped](const DrawProgram& program) { return program.binary.codeAddress == skipped; })) return drawn();
|
||||
const bool emptyIndirectSkip = EmptyIndirectSkip();
|
||||
auto& cpuIndirectPrograms = CpuIndirectPrograms();
|
||||
@@ -420,7 +425,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
|
||||
}
|
||||
const auto setMeshIndexBuffer = [&](const Pm4::DrawParameters& parameters) {
|
||||
if (!graphics.stages.mesh) return;
|
||||
auto& words = programs.front().userData;
|
||||
auto& words = writablePrograms.front().userData;
|
||||
require(programs.front().firstUserSgpr == 0 && words.size() >= ShaderRecompiler::MeshIndexBufferUserWord + 4, "mesh program lacks the hidden user words");
|
||||
const auto descriptor = Graphics::MeshIndexBufferDescriptor(parameters);
|
||||
std::copy(descriptor.begin(), descriptor.end(), words.begin() + ShaderRecompiler::MeshIndexBufferUserWord);
|
||||
@@ -497,7 +502,6 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
|
||||
auto& compiledStages = tail.compiledStages;
|
||||
compiledStages.reserve(programs.size());
|
||||
auto& stages = tail.stages;
|
||||
results.reserve(programs.size() + 2u);
|
||||
stages.reserve(programs.size());
|
||||
auto& pushCursorBytes = tail.pushCursorBytes;
|
||||
|
||||
@@ -538,6 +542,14 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
|
||||
if (drawHit || verifyHit) {
|
||||
++drawRelocationCounters.hits;
|
||||
++drawRelocationTrials[drawIdentity].hits;
|
||||
if (const auto alias = drawAliases.find(drawAlias); alias != drawAliases.end()) {
|
||||
const auto donor = drawCache.find(alias->second);
|
||||
if (donor != drawCache.end() && drawCacheHits - donor->second->touched > drawCacheEntries() / 8) {
|
||||
drawOrder.splice(drawOrder.begin(), drawOrder, donor->second->order);
|
||||
donor->second->touched = drawCacheHits;
|
||||
++drawEntryCounters.touches;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (profile && drawRelocationCounters.lookups >= 100000) reportDrawRelocation();
|
||||
}
|
||||
@@ -640,7 +652,8 @@ DrawVerdict Driver::finishDraw(DrawTail& tail, PerformanceTimer& timing) {
|
||||
auto& shapeKey = tail.shapeKey;
|
||||
const auto& graphics = decode->state;
|
||||
auto& pixel = tail.pixel;
|
||||
auto& programs = tail.programs;
|
||||
auto& writablePrograms = tail.writablePrograms;
|
||||
const auto& programs = *tail.programs;
|
||||
const std::vector<Role>& roles = decode->roles;
|
||||
auto& memory = tail.memory;
|
||||
auto& linked = tail.linked;
|
||||
@@ -682,7 +695,7 @@ DrawVerdict Driver::finishDraw(DrawTail& tail, PerformanceTimer& timing) {
|
||||
};
|
||||
const auto setMeshIndexBuffer = [&](const Pm4::DrawParameters& parameters) {
|
||||
if (!graphics.stages.mesh) return;
|
||||
auto& words = programs.front().userData;
|
||||
auto& words = writablePrograms.front().userData;
|
||||
require(programs.front().firstUserSgpr == 0 && words.size() >= ShaderRecompiler::MeshIndexBufferUserWord + 4, "mesh program lacks the hidden user words");
|
||||
const auto descriptor = Graphics::MeshIndexBufferDescriptor(parameters);
|
||||
std::copy(descriptor.begin(), descriptor.end(), words.begin() + ShaderRecompiler::MeshIndexBufferUserWord);
|
||||
@@ -853,6 +866,7 @@ DrawVerdict Driver::finishDraw(DrawTail& tail, PerformanceTimer& timing) {
|
||||
return;
|
||||
}
|
||||
require(stages.size() == 2, "rect-list requires vertex and fragment programs");
|
||||
results.reserve(programs.size() + 2u);
|
||||
rectIndex = results.size();
|
||||
results.push_back(std::move(control));
|
||||
results.push_back(std::move(rectangle.evaluation));
|
||||
@@ -878,6 +892,7 @@ DrawVerdict Driver::finishDraw(DrawTail& tail, PerformanceTimer& timing) {
|
||||
} else if (!graphics.rectList && !programs.empty() && programResults.back() != nullptr && programResults.back()->barycentricEmulation.active) {
|
||||
require(graphics.stages.path == Graphics::ShaderPath::Vertex && programs.size() == 2 && programResults[0] != nullptr && stages.size() == 2, "a pixel shader that reads barycentrics without VK_KHR_fragment_shader_barycentric needs a vertex shader before it");
|
||||
const auto geometryLimits = localDevice->GeometryLimits();
|
||||
results.reserve(programs.size() + 2u);
|
||||
if (geometryLimits || !localDevice->Target().tessellation) {
|
||||
results.push_back(ShaderRecompiler::BuildBarycentricGeometryShader(*programResults[0], *programResults[1], localDevice->Target(), geometryLimits));
|
||||
stages.insert(stages.begin() + 1, Graphics::CompiledShader{Stage::Geometry, &results.back(), 0});
|
||||
@@ -919,6 +934,7 @@ DrawVerdict Driver::finishDraw(DrawTail& tail, PerformanceTimer& timing) {
|
||||
if (drawParameters.indirect && indirectCpu) {
|
||||
|
||||
const auto indirect = *drawParameters.indirect;
|
||||
results.reserve(programs.size() + 2u);
|
||||
for (std::size_t i = 0; i < programs.size(); ++i) {
|
||||
if (programResults[i] == nullptr) continue;
|
||||
resultIndex[i] = results.size();
|
||||
@@ -949,7 +965,7 @@ DrawVerdict Driver::finishDraw(DrawTail& tail, PerformanceTimer& timing) {
|
||||
std::set<std::size_t> patched;
|
||||
const auto patch = [&](const std::vector<std::pair<std::size_t, std::size_t>>& words, std::uint32_t value) {
|
||||
for (const auto& [program, word] : words) {
|
||||
programs[program].userData[word] = value;
|
||||
writablePrograms[program].userData[word] = value;
|
||||
patched.insert(program);
|
||||
}
|
||||
};
|
||||
@@ -996,7 +1012,7 @@ DrawVerdict Driver::finishDraw(DrawTail& tail, PerformanceTimer& timing) {
|
||||
const auto commitQueue = submission.queue;
|
||||
const auto commitPacket = tail.packetOpcode;
|
||||
const auto epoch = DrawPipeline::EpochToken().load(std::memory_order_relaxed);
|
||||
auto commit = [this, localDevice = std::move(localDevice), decode = std::move(decode), direct, stages = std::move(stages), snapshots = std::move(snapshots), commitQueue, commitPacket, epoch, programs = std::move(programs), results = std::move(results), memory = std::move(memory), linked = std::move(linked), stageCaptures = std::move(stageCaptures), matched = std::move(matched), matchedRegions = std::move(matchedRegions), fresh = std::move(fresh), decodeReads = std::move(decodeReads), shaderMemory = std::move(shaderMemory), entry = std::move(entry), dataEntry = std::move(dataEntry), compiledStages = std::move(compiledStages), stageData = std::move(stageData)] {
|
||||
auto commit = [this, localDevice = std::move(localDevice), decode = std::move(decode), direct, stages = std::move(stages), snapshots = std::move(snapshots), commitQueue, commitPacket, epoch, writablePrograms = std::move(writablePrograms), results = std::move(results), memory = std::move(memory), linked = std::move(linked), stageCaptures = std::move(stageCaptures), matched = std::move(matched), matchedRegions = std::move(matchedRegions), fresh = std::move(fresh), decodeReads = std::move(decodeReads), shaderMemory = std::move(shaderMemory), entry = std::move(entry), dataEntry = std::move(dataEntry), compiledStages = std::move(compiledStages), stageData = std::move(stageData)] {
|
||||
DrawPipeline::FollowEpoch(epoch);
|
||||
GuestMemory::SetCurrentPacket(commitPacket, commitQueue);
|
||||
commitDraw(localDevice, commitQueue, decode->state, direct, stages, snapshots, nullptr, {}, 0);
|
||||
@@ -1042,7 +1058,7 @@ DrawVerdict Driver::finishDraw(DrawTail& tail, PerformanceTimer& timing) {
|
||||
const auto commitPacket = tail.packetOpcode;
|
||||
const auto epoch = DrawFinisher::OnFinisherThread() ? tail.epoch : DrawPipeline::EpochToken().load(std::memory_order_relaxed);
|
||||
auto reads = QueuedDrawReadRanges(drawParameters, stages);
|
||||
auto commit = [this, localDevice = std::move(localDevice), decode = std::move(decode), drawParameters, stages = std::move(stages), snapshots = std::move(snapshots), recipe = std::move(recipe), recipeStages = std::move(recipeStages), drawKey, commitQueue, commitPacket, epoch, programs = std::move(programs), results = std::move(results), memory = std::move(memory), linked = std::move(linked), stageCaptures = std::move(stageCaptures), matched = std::move(matched), matchedRegions = std::move(matchedRegions), fresh = std::move(fresh), decodeReads = std::move(decodeReads), shaderMemory = std::move(shaderMemory), entry = std::move(entry), dataEntry = std::move(dataEntry), compiledStages = std::move(compiledStages), stageData = std::move(stageData)]() mutable {
|
||||
auto commit = [this, localDevice = std::move(localDevice), decode = std::move(decode), drawParameters, stages = std::move(stages), snapshots = std::move(snapshots), recipe = std::move(recipe), recipeStages = std::move(recipeStages), drawKey, commitQueue, commitPacket, epoch, writablePrograms = std::move(writablePrograms), results = std::move(results), memory = std::move(memory), linked = std::move(linked), stageCaptures = std::move(stageCaptures), matched = std::move(matched), matchedRegions = std::move(matchedRegions), fresh = std::move(fresh), decodeReads = std::move(decodeReads), shaderMemory = std::move(shaderMemory), entry = std::move(entry), dataEntry = std::move(dataEntry), compiledStages = std::move(compiledStages), stageData = std::move(stageData)]() mutable {
|
||||
DrawPipeline::FollowEpoch(epoch);
|
||||
GuestMemory::SetCurrentPacket(commitPacket, commitQueue);
|
||||
commitDraw(localDevice, commitQueue, decode->state, drawParameters, stages, snapshots, recipe, recipeStages, drawKey);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/HandoffSpin.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/PerformanceTrace.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/WorkerSampler.hpp"
|
||||
#include "prx/libc/include/HostThreadLocal.hpp"
|
||||
#include <algorithm>
|
||||
@@ -364,7 +365,7 @@ void DrawPipeline::report(std::chrono::steady_clock::time_point now) {
|
||||
std::snprintf(reads, sizeof(reads), " %llu read ranges, %llu stores held, %llu decode drains", static_cast<unsigned long long>(heldReadRanges.load(std::memory_order_relaxed)), static_cast<unsigned long long>(heldReadHits.exchange(0, std::memory_order_relaxed)), static_cast<unsigned long long>(decodeDrainCount - reportedDecodeDrains));
|
||||
reportedDecodeDrains = decodeDrainCount;
|
||||
drainText += reads;
|
||||
std::fprintf(stderr, "[draw] pipelined (10 s, depth %zu): %llu commits at %.1f us each on the committer, %llu failed; %llu enqueued behind %.2f on average, waiting %.1f us per draw for room, %.2f us for the lock; %llu committer wakes;%s; drains:%s\n", Depth(), static_cast<unsigned long long>(commits), perCommit, static_cast<unsigned long long>(commitErrors), static_cast<unsigned long long>(enqueued), queued, perDraw, enqueued == 0 ? 0.0 : static_cast<double>(lockWaitNs) / 1e3 / static_cast<double>(enqueued), static_cast<unsigned long long>(wakes), reads, drainText.c_str());
|
||||
AgcDriver::ProfilePrint_nid_no_patch("[draw] pipelined (10 s, depth %zu): %llu commits at %.1f us each on the committer, %llu failed; %llu enqueued behind %.2f on average, waiting %.1f us per draw for room, %.2f us for the lock; %llu committer wakes;%s; drains:%s\n", Depth(), static_cast<unsigned long long>(commits), perCommit, static_cast<unsigned long long>(commitErrors), static_cast<unsigned long long>(enqueued), queued, perDraw, enqueued == 0 ? 0.0 : static_cast<double>(lockWaitNs) / 1e3 / static_cast<double>(enqueued), static_cast<unsigned long long>(wakes), reads, drainText.c_str());
|
||||
commits = commitNs = commitErrors = enqueued = fullWaitNs = depthSum = wakes = lockWaitNs = 0;
|
||||
drains = {};
|
||||
drainWaitNs = {};
|
||||
|
||||
@@ -251,7 +251,7 @@ bool Driver::sameDecode(const DrawDecode& a, const DrawDecode& b) {
|
||||
for (std::size_t i = 0; i < s.blends.size(); ++i) {
|
||||
if (!sameBlend(s.blends[i], t.blends[i])) return false;
|
||||
}
|
||||
if (s.hasColorTarget != t.hasColorTarget || s.rectList != t.rectList || s.renderExtent.width != t.renderExtent.width || s.renderExtent.height != t.renderExtent.height || s.topology != t.topology || s.negativeOneToOne != t.negativeOneToOne || s.depthClamp != t.depthClamp || s.conservativeRasterization != t.conservativeRasterization || s.perSampleShading != t.perSampleShading || s.cullMode != t.cullMode || s.polygonMode != t.polygonMode || s.frontFace != t.frontFace || !sameBlend(s.blend, t.blend) || s.blendConstants != t.blendConstants) return false;
|
||||
if (s.colorRop != t.colorRop || s.hasColorTarget != t.hasColorTarget || s.rectList != t.rectList || s.renderExtent.width != t.renderExtent.width || s.renderExtent.height != t.renderExtent.height || s.topology != t.topology || s.negativeOneToOne != t.negativeOneToOne || s.depthClamp != t.depthClamp || s.conservativeRasterization != t.conservativeRasterization || s.perSampleShading != t.perSampleShading || s.cullMode != t.cullMode || s.polygonMode != t.polygonMode || s.frontFace != t.frontFace || !sameBlend(s.blend, t.blend) || s.blendConstants != t.blendConstants) return false;
|
||||
if (std::memcmp(&s.viewport, &t.viewport, sizeof(VkViewport)) != 0 || std::memcmp(&s.scissor, &t.scissor, sizeof(VkRect2D)) != 0) return false;
|
||||
const auto& p = a.pixel;
|
||||
const auto& q = b.pixel;
|
||||
|
||||
@@ -173,15 +173,21 @@ void Driver::learnPendingRelocation() {
|
||||
}
|
||||
if (pending.alias == 0) return;
|
||||
if (drawAliases.size() >= MaxAliases) drawRelocationCounters.evicted += dropEveryOther(drawAliases);
|
||||
drawAliases[pending.alias] = inserted->second;
|
||||
drawAliases[pending.alias] = pending.key;
|
||||
}
|
||||
|
||||
void Driver::relocateDrawEntry(const QueueState& queue, std::uint64_t alias, std::shared_ptr<DrawEntry>& entry, std::shared_ptr<const DrawDecode>& decode, std::vector<DrawUserPatch>& patches) {
|
||||
auto& counters = drawRelocationCounters;
|
||||
++counters.lookups;
|
||||
const auto found = drawAliases.find(alias);
|
||||
if (found == drawAliases.end() || found->second->decode == nullptr) return;
|
||||
const auto donor = found->second;
|
||||
if (found == drawAliases.end()) return;
|
||||
const auto cached = drawCache.find(found->second);
|
||||
if (cached == drawCache.end()) {
|
||||
drawAliases.erase(found);
|
||||
return;
|
||||
}
|
||||
if (cached->second->decode == nullptr) return;
|
||||
const auto donor = cached->second;
|
||||
const auto& programs = donor->decode->programs;
|
||||
if (donor->stages.size() != programs.size()) return;
|
||||
struct Move {
|
||||
|
||||
@@ -422,6 +422,7 @@ struct VulkanDevice::State {
|
||||
bool depthBounds = false;
|
||||
bool depthBiasClamp = false;
|
||||
bool dualSrcBlend = false;
|
||||
bool logicOp = false;
|
||||
bool occlusionQueryPrecise = false;
|
||||
VkDeviceSize hostImportAlignment = 0;
|
||||
bool drawInputsInPlace = true;
|
||||
@@ -1775,6 +1776,8 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
|
||||
enabled.depthBiasClamp = available.depthBiasClamp;
|
||||
state->depthBiasClamp = enabled.depthBiasClamp == VK_TRUE;
|
||||
enabled.dualSrcBlend = available.dualSrcBlend;
|
||||
enabled.logicOp = available.logicOp;
|
||||
state->logicOp = enabled.logicOp == VK_TRUE;
|
||||
state->dualSrcBlend = enabled.dualSrcBlend == VK_TRUE;
|
||||
enabled.occlusionQueryPrecise = available.occlusionQueryPrecise;
|
||||
state->occlusionQueryPrecise = enabled.occlusionQueryPrecise == VK_TRUE;
|
||||
@@ -3971,6 +3974,7 @@ Graphics::Context VulkanDevice::buildContext() const {
|
||||
context.drawIndirectFirstInstance = state->drawIndirectFirstInstance;
|
||||
context.multiDrawIndirect = state->multiDrawIndirect;
|
||||
context.dualSrcBlend = state->dualSrcBlend;
|
||||
context.logicOp = state->logicOp;
|
||||
context.depthBounds = state->depthBounds;
|
||||
context.depthBiasClamp = state->depthBiasClamp;
|
||||
context.samplerFilterMinmax = state->samplerFilterMinmax;
|
||||
|
||||
@@ -213,6 +213,7 @@ struct Context {
|
||||
bool depthBounds = false;
|
||||
bool depthBiasClamp = false;
|
||||
bool dualSrcBlend = false;
|
||||
bool logicOp = false;
|
||||
bool samplerFilterMinmax = false;
|
||||
bool nonSeamlessCubeMap = false;
|
||||
VkSampleCountFlags mergedCoverageSampleCounts = 0;
|
||||
|
||||
@@ -107,7 +107,8 @@ public:
|
||||
static constexpr std::size_t DrawInputEntries = 16384;
|
||||
std::shared_ptr<Buffer> UnwrittenDrawSnapshot(std::uint64_t address, std::size_t bytes, const std::array<std::uint64_t, 3>& stamp, VkDeviceSize* offset = nullptr);
|
||||
void NoteUnwrittenDrawSnapshot(std::uint64_t address, std::size_t bytes, const std::array<std::uint64_t, 3>& stamp, std::shared_ptr<Buffer> buffer, VkDeviceSize offset = 0);
|
||||
std::shared_ptr<Buffer> ReusableDrawSnapshot(std::uint64_t address, std::size_t bytes, SnapshotUse use = SnapshotUse::Storage, std::uint32_t* derived = nullptr, std::uint64_t generation = 0);
|
||||
std::shared_ptr<Buffer> ReusableDrawSnapshot(std::uint64_t address, std::size_t bytes, SnapshotUse use = SnapshotUse::Storage, std::uint32_t* derived = nullptr, std::uint64_t generation = 0, VkDeviceSize* offset = nullptr);
|
||||
static constexpr std::size_t ContainingSnapshotProbes = 16;
|
||||
void KeepDrawSnapshot(std::uint64_t address, std::size_t bytes, std::uint64_t generation, std::uint64_t registryGeneration, std::shared_ptr<Buffer> buffer, SnapshotUse use = SnapshotUse::Storage, std::uint32_t derived = 0);
|
||||
static constexpr std::size_t DrawUploadChunk = std::size_t{1} << 20u;
|
||||
struct UploadSlice {
|
||||
@@ -888,6 +889,7 @@ private:
|
||||
std::map<DrawSnapshotKey, IndexRange> indexRanges;
|
||||
std::list<DrawSnapshotKey> indexRangeRecency;
|
||||
bool refreshDrawSnapshot(DrawSnapshot& entry, std::uint64_t address, std::size_t bytes);
|
||||
std::shared_ptr<Buffer> containingDrawSnapshot(std::uint64_t address, std::size_t bytes, VkDeviceSize* offset);
|
||||
mutable std::weak_ptr<const WriteRanges> publishedWriteRanges;
|
||||
mutable bool publishedWriteRangesValid = false;
|
||||
};
|
||||
|
||||
@@ -153,6 +153,7 @@ struct State {
|
||||
VkProvokingVertexModeEXT provokingVertexMode = VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT;
|
||||
VkPipelineColorBlendAttachmentState blend;
|
||||
std::array<float, 4> blendConstants;
|
||||
std::uint8_t colorRop = 0xccu;
|
||||
std::uint32_t samples = 1;
|
||||
std::array<std::uint8_t, 32> sampleLocations{};
|
||||
bool customSampleLocations = false;
|
||||
@@ -171,6 +172,9 @@ struct State {
|
||||
// distance vectors); a vertex program that exports pos1..pos3 depends on them.
|
||||
inline constexpr std::uint32_t PositionExportLayoutBits = 0xffffu | (1u << 21u) | (1u << 22u) | (1u << 23u);
|
||||
|
||||
inline constexpr std::uint8_t CopyRop = 0xccu;
|
||||
VkLogicOp LogicOpFromRop(std::uint8_t rop);
|
||||
|
||||
ShaderStages DecodeShaderStages(const QueueState& queue, bool vertexWorkgroup = false);
|
||||
void ApplyPointSize(const State& state, ShaderRecompiler::ShaderVertexStageInfo& vertex);
|
||||
State DecodeState(const QueueState& queue, bool vertexWorkgroup = false);
|
||||
|
||||
@@ -2105,6 +2105,12 @@ std::vector<std::pair<std::uint64_t, std::uint64_t>> FlushDepthPlanes(std::uint6
|
||||
const bool overlapped = !surface->syncing && plane < end && address < plane + planeBytes;
|
||||
if (overlapped && (surface->pendingClear & VK_IMAGE_ASPECT_DEPTH_BIT) != 0) surface->MarkAllStale(planeBytes);
|
||||
if (overlapped && (scope == DepthPlaneScope::Whole || anyUnit(surface->StaleUnitsIn(planeBytes, address, end)))) {
|
||||
if (GuestMemory::CommittedRanges(plane, static_cast<std::size_t>(planeBytes)).empty()) {
|
||||
static std::atomic<bool> reported{false};
|
||||
if (!reported.exchange(true)) std::fprintf(stderr, "[gpu] depth plane 0x%llx+0x%llx was unmapped with GPU results pending; they are dropped\n", static_cast<unsigned long long>(plane), static_cast<unsigned long long>(planeBytes));
|
||||
surface->ClearStale();
|
||||
continue;
|
||||
}
|
||||
surface->SyncFromMemory(resource);
|
||||
auto units = scope == DepthPlaneScope::Whole ? surface->staleUnits : surface->StaleUnitsIn(planeBytes, address, end);
|
||||
if (anyUnit(units)) {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp"
|
||||
#include "ThreadOwned.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libc/include/GuestAllocations.hpp"
|
||||
@@ -763,8 +764,8 @@ void reportDraw(const std::array<double, PhaseCount>& us, const ShaderResources:
|
||||
for (std::size_t i = 1; i < profile.recipeMisses.size() && room(); ++i) {
|
||||
n += std::snprintf(line + n, sizeof(line) - static_cast<std::size_t>(n), " %s %llu", DrawRecipeMissName(static_cast<DrawRecipeMiss>(i)), static_cast<unsigned long long>(profile.recipeMisses[i]));
|
||||
}
|
||||
std::fprintf(stderr, "%s\n", line);
|
||||
std::fprintf(stderr, "[rescache] draws: %llu hits, %llu misses, %llu invalidated, %llu uncacheable; validation memo %llu hits / %llu misses (which key words the misses differ in: the miss churn line)\n", static_cast<unsigned long long>(profile.cacheHits), static_cast<unsigned long long>(profile.cacheMisses), static_cast<unsigned long long>(profile.cacheInvalidated), static_cast<unsigned long long>(profile.uncacheable), static_cast<unsigned long long>(profile.validateHits), static_cast<unsigned long long>(profile.validateMisses));
|
||||
AgcDriver::ProfilePrint_nid_no_patch("%s\n", line);
|
||||
AgcDriver::ProfilePrint_nid_no_patch("[rescache] draws: %llu hits, %llu misses, %llu invalidated, %llu uncacheable; validation memo %llu hits / %llu misses (which key words the misses differ in: the miss churn line)\n", static_cast<unsigned long long>(profile.cacheHits), static_cast<unsigned long long>(profile.cacheMisses), static_cast<unsigned long long>(profile.cacheInvalidated), static_cast<unsigned long long>(profile.uncacheable), static_cast<unsigned long long>(profile.validateHits), static_cast<unsigned long long>(profile.validateMisses));
|
||||
profile.totalsUs.fill(0);
|
||||
profile.maxUs.fill(0);
|
||||
profile.maxDrawUs = profile.hookWaitUs = profile.maxHookWaitUs = profile.ownSyncUs = 0;
|
||||
|
||||
@@ -1365,7 +1365,7 @@ ImportRun joinedRun(const GuestAllocations::Lease& lease, std::uint64_t begin, s
|
||||
auto run = crossedRun(lease, begin, end);
|
||||
if (run.ranges.empty()) return run;
|
||||
if (std::all_of(run.ranges.begin(), run.ranges.end(), [window](const auto& range) { return range->bytes > window; })) return run;
|
||||
if (begin == run.base && end == run.base + run.bytes) return run;
|
||||
if (begin == run.base) return run;
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,34 @@
|
||||
|
||||
namespace AgcDriver::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
bool LogicOpFormat(VkFormat format) {
|
||||
switch (format) {
|
||||
case VK_FORMAT_R8_UNORM:
|
||||
case VK_FORMAT_R8G8_UNORM:
|
||||
case VK_FORMAT_R8G8B8A8_UNORM:
|
||||
case VK_FORMAT_B8G8R8A8_UNORM:
|
||||
case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
|
||||
case VK_FORMAT_A2R10G10B10_UNORM_PACK32:
|
||||
case VK_FORMAT_R16_UNORM:
|
||||
case VK_FORMAT_R16G16_UNORM:
|
||||
case VK_FORMAT_R16G16B16A16_UNORM:
|
||||
case VK_FORMAT_R8_UINT:
|
||||
case VK_FORMAT_R8G8_UINT:
|
||||
case VK_FORMAT_R8G8B8A8_UINT:
|
||||
case VK_FORMAT_R16_UINT:
|
||||
case VK_FORMAT_R16G16_UINT:
|
||||
case VK_FORMAT_R16G16B16A16_UINT:
|
||||
case VK_FORMAT_R32_UINT:
|
||||
case VK_FORMAT_R32G32_UINT:
|
||||
case VK_FORMAT_R32G32B32A32_UINT: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void LogPipelineStatistics_nid_no_patch(const Context& context, VkPipeline pipeline) {
|
||||
if (!context.pipelineExecutableInfo) return;
|
||||
const auto getProperties = context.Function<PFN_vkGetPipelineExecutablePropertiesKHR>("vkGetPipelineExecutablePropertiesKHR");
|
||||
@@ -363,6 +391,7 @@ bool libraryKeys(PipelineLibraryKeys& keys, const Context& context, const State&
|
||||
}
|
||||
}
|
||||
for (const auto& blend : state.blends) appendKey(keys.fragmentOutput, blend);
|
||||
appendKey(keys.fragmentOutput, state.colorRop);
|
||||
for (const auto value : state.blendConstants) appendKey(keys.fragmentOutput, value);
|
||||
appendKey(keys.renderPass, state.blends.size());
|
||||
for (const auto& color : state.colors) {
|
||||
@@ -429,6 +458,8 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput
|
||||
Require(!depthBias || state.depthBiasClamp == 0.0f || context.depthBiasClamp, "device does not support depth bias clamping");
|
||||
Require(!state.dualSourceBlend || (context.dualSrcBlend && state.blends.size() <= context.limits.maxFragmentDualSrcAttachments), "device does not support dual-source blending into this many attachments");
|
||||
Require(state.reversedBlend == 0 || (context.dualSrcBlend && state.blends.size() <= context.limits.maxFragmentDualSrcAttachments), "blending into a color target with a reversed component order through a second source color needs dual-source blending, which the device does not support");
|
||||
Require(LogicOpFromRop(state.colorRop) == VK_LOGIC_OP_COPY || context.logicOp, "device does not support color logic operations");
|
||||
Require(LogicOpFromRop(state.colorRop) == VK_LOGIC_OP_COPY || std::ranges::all_of(state.colors, [](const ColorTarget& color) { return LogicOpFormat(color.format); }), "color logic operations on a color target that is not a normalized or integer format are not implemented");
|
||||
Require(!state.negativeOneToOne || context.depthClipControl, "negative-one-to-one depth clipping requires VK_EXT_depth_clip_control with depthClipControl enabled");
|
||||
Require(!state.perSampleShading || context.sampleRateShading, "per-sample pixel shading requires the sampleRateShading device feature");
|
||||
Require(!state.mergedCoverage || (context.mergedCoverageSampleCounts & state.samples) != 0, "an overrasterized draw (a multisampled rasterizer into one-sample color targets) requires VK_NV_framebuffer_mixed_samples with merged coverage reduction");
|
||||
@@ -652,6 +683,10 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput
|
||||
secondChannels[index].colorBlendOp = state.blends[index].alphaBlendOp;
|
||||
}
|
||||
}
|
||||
if (const auto logicOp = LogicOpFromRop(state.colorRop); logicOp != VK_LOGIC_OP_COPY) {
|
||||
blend.logicOpEnable = VK_TRUE;
|
||||
blend.logicOp = logicOp;
|
||||
}
|
||||
blend.attachmentCount = static_cast<std::uint32_t>(firstChannels.size());
|
||||
blend.pAttachments = firstChannels.empty() ? nullptr : firstChannels.data();
|
||||
std::copy(state.blendConstants.begin(), state.blendConstants.end(), blend.blendConstants);
|
||||
@@ -995,6 +1030,7 @@ void pipelineKey(std::vector<std::byte>& key, const Context& context, const Stat
|
||||
append(key, state.conservativeRasterization);
|
||||
append(key, state.blends.size());
|
||||
for (const auto& blend : state.blends) append(key, blend);
|
||||
append(key, state.colorRop);
|
||||
for (const auto value : state.blendConstants) append(key, value);
|
||||
append(key, state.samples);
|
||||
append(key, state.perSampleShading);
|
||||
|
||||
@@ -47,7 +47,7 @@ static_assert(sizeof(ShaderRecompiler::TessellationConfiguration) == 40, "Tessel
|
||||
constexpr std::uint32_t FileMagic = 0x4c4a5041u;
|
||||
// Bumped when the recipe codec, CanonicalPipelineState or the store key change in a way the
|
||||
// layout guards above do not catch (a field renumbered or reinterpreted at the same size).
|
||||
constexpr std::uint32_t FileFormat = 9u;
|
||||
constexpr std::uint32_t FileFormat = 10u;
|
||||
constexpr std::uint32_t ModuleRecord = 1u;
|
||||
constexpr std::uint32_t GraphicsRecord = 2u;
|
||||
constexpr std::uint32_t ComputeRecord = 3u;
|
||||
@@ -206,6 +206,7 @@ void encodeState(Writer& writer, const State& state) {
|
||||
writer.Bool(state.rectList);
|
||||
writer.Bool(state.dualSourceBlend);
|
||||
writer.U32(state.reversedBlend);
|
||||
writer.U32(state.colorRop);
|
||||
writer.U32(static_cast<std::uint32_t>(state.topology));
|
||||
writer.Bool(state.primitiveRestart);
|
||||
writer.Bool(state.negativeOneToOne);
|
||||
@@ -273,6 +274,7 @@ void decodeState(Reader& reader, State& state) {
|
||||
state.rectList = reader.Bool();
|
||||
state.dualSourceBlend = reader.Bool();
|
||||
state.reversedBlend = reader.U32();
|
||||
state.colorRop = static_cast<std::uint8_t>(reader.U32());
|
||||
state.topology = static_cast<VkPrimitiveTopology>(reader.U32());
|
||||
state.primitiveRestart = reader.Bool();
|
||||
state.negativeOneToOne = reader.Bool();
|
||||
@@ -1511,6 +1513,7 @@ State CanonicalPipelineState(const State& state) {
|
||||
canonical.rectList = state.rectList;
|
||||
canonical.dualSourceBlend = state.dualSourceBlend;
|
||||
canonical.reversedBlend = state.reversedBlend;
|
||||
canonical.colorRop = state.colorRop;
|
||||
canonical.topology = state.topology;
|
||||
canonical.primitiveRestart = state.primitiveRestart;
|
||||
canonical.negativeOneToOne = state.negativeOneToOne;
|
||||
|
||||
@@ -2794,9 +2794,13 @@ void Recorder::NoteUnwrittenDrawSnapshot(std::uint64_t address, std::size_t byte
|
||||
unwrittenBytes += bytes;
|
||||
}
|
||||
|
||||
std::shared_ptr<Buffer> Recorder::ReusableDrawSnapshot(std::uint64_t address, std::size_t bytes, SnapshotUse use, std::uint32_t* derived, std::uint64_t generation) {
|
||||
std::shared_ptr<Buffer> Recorder::ReusableDrawSnapshot(std::uint64_t address, std::size_t bytes, SnapshotUse use, std::uint32_t* derived, std::uint64_t generation, VkDeviceSize* offset) {
|
||||
auto found = use == SnapshotUse::Vertex ? drawSnapshots.lower_bound({address, use, bytes}) : drawSnapshots.find({address, use, bytes});
|
||||
if (found == drawSnapshots.end() || std::get<0>(found->first) != address || std::get<1>(found->first) != use) return {};
|
||||
if (found == drawSnapshots.end() || std::get<0>(found->first) != address || std::get<1>(found->first) != use) {
|
||||
if (use != SnapshotUse::Storage || offset == nullptr) return {};
|
||||
return containingDrawSnapshot(address, bytes, offset);
|
||||
}
|
||||
if (offset != nullptr) *offset = 0;
|
||||
const bool sameRegistry = found->second.registryGeneration == GuestAllocations::GuestAllocationsGeneration_nid_postfix();
|
||||
if (!sameRegistry || !GuestMemory::UnchangedSince(address, bytes, found->second.generation)) {
|
||||
if (!sameRegistry || generation == 0 || use != SnapshotUse::Storage || !refreshDrawSnapshot(found->second, address, bytes)) {
|
||||
@@ -2811,6 +2815,23 @@ std::shared_ptr<Buffer> Recorder::ReusableDrawSnapshot(std::uint64_t address, st
|
||||
return found->second.buffer;
|
||||
}
|
||||
|
||||
std::shared_ptr<Buffer> Recorder::containingDrawSnapshot(std::uint64_t address, std::size_t bytes, VkDeviceSize* offset) {
|
||||
const VkDeviceSize alignment = std::max<VkDeviceSize>(context.limits.minStorageBufferOffsetAlignment, 4);
|
||||
const auto registry = GuestAllocations::GuestAllocationsGeneration_nid_postfix();
|
||||
auto entry = drawSnapshots.upper_bound({address, SnapshotUse::Storage, std::numeric_limits<std::size_t>::max()});
|
||||
for (std::size_t probe = 0; probe < ContainingSnapshotProbes && entry != drawSnapshots.begin(); ++probe) {
|
||||
--entry;
|
||||
const auto& [start, use, held] = entry->first;
|
||||
if (use != SnapshotUse::Storage || start + held < address + bytes || (address - start) % alignment != 0) continue;
|
||||
if (entry->second.registryGeneration != registry || !GuestMemory::UnchangedSince(address, bytes, entry->second.generation)) continue;
|
||||
auto& recency = drawSnapshotPools[SnapshotPool(use)].recency;
|
||||
recency.splice(recency.end(), recency, entry->second.recent);
|
||||
*offset = address - start;
|
||||
return entry->second.buffer;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool Recorder::refreshDrawSnapshot(DrawSnapshot& entry, std::uint64_t address, std::size_t bytes) {
|
||||
const auto held = entry.buffer->Bytes();
|
||||
if (held.size() != bytes || bytes == 0) return false;
|
||||
|
||||
@@ -5671,8 +5671,9 @@ std::shared_ptr<ShaderResources::DrawBindings> ShaderResources::PrepareDrawBindi
|
||||
if (verifySnapshots && buffer != nullptr) {
|
||||
static std::atomic<std::uint64_t> checked{0}, differing{0};
|
||||
const auto fast = buffer->Bytes().subspan(static_cast<std::size_t>(offset), bytes);
|
||||
const auto slow = recorder.ReusableDrawSnapshot(begin, bytes, Recorder::SnapshotUse::Storage, nullptr, GuestMemory::CollectWrites(begin, bytes));
|
||||
const bool same = slow == buffer || (slow != nullptr && slow->Bytes().size() == fast.size() && std::memcmp(slow->Bytes().data(), fast.data(), fast.size()) == 0) || (slow == nullptr && std::memcmp(fast.data(), reinterpret_cast<const void*>(begin), bytes) == 0);
|
||||
VkDeviceSize slowOffset = 0;
|
||||
const auto slow = recorder.ReusableDrawSnapshot(begin, bytes, Recorder::SnapshotUse::Storage, nullptr, GuestMemory::CollectWrites(begin, bytes), &slowOffset);
|
||||
const bool same = (slow == buffer && slowOffset == offset) || (slow != nullptr && slow->Bytes().size() >= slowOffset + fast.size() && std::memcmp(slow->Bytes().data() + slowOffset, fast.data(), fast.size()) == 0) || (slow == nullptr && std::memcmp(fast.data(), reinterpret_cast<const void*>(begin), bytes) == 0);
|
||||
if (!same) differing.fetch_add(1);
|
||||
if (checked.fetch_add(1) % 200000 == 199999) std::fprintf(stderr, "[draw-snapshots] verify: %llu fast answers checked, %llu differing%c", static_cast<unsigned long long>(checked.load()), static_cast<unsigned long long>(differing.load()), 10);
|
||||
}
|
||||
@@ -5685,7 +5686,7 @@ std::shared_ptr<ShaderResources::DrawBindings> ShaderResources::PrepareDrawBindi
|
||||
offset = slice.offset;
|
||||
} else {
|
||||
const auto generation = GuestMemory::CollectWrites(begin, bytes);
|
||||
buffer = recorder.ReusableDrawSnapshot(begin, bytes, Recorder::SnapshotUse::Storage, nullptr, generation);
|
||||
buffer = recorder.ReusableDrawSnapshot(begin, bytes, Recorder::SnapshotUse::Storage, nullptr, generation, &offset);
|
||||
if (buffer == nullptr) {
|
||||
buffer = std::make_shared<Buffer>(context, bytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
|
||||
std::memcpy(buffer->Bytes().data(), reinterpret_cast<const void*>(begin), bytes);
|
||||
|
||||
@@ -730,15 +730,19 @@ void rasterizeSampleZero(State& state) {
|
||||
state.sampleLocationGrid = sampleZero[8] != sampleZero[0] || sampleZero[16] != sampleZero[0] || sampleZero[24] != sampleZero[0];
|
||||
}
|
||||
|
||||
bool documentedRop(std::uint32_t rop) {
|
||||
return (rop >> 4u) == (rop & 0xfu) || ((rop >> 2u) & 0x33u) == (rop & 0x33u);
|
||||
}
|
||||
|
||||
bool colorControlSupported(std::uint32_t colorControl, bool hasColorTarget) {
|
||||
colorControl &= ~1u;
|
||||
return colorControl == 0xcc0010u || ((colorControl >> 4u) & 7u) == 0u || (!hasColorTarget && (colorControl & ~0x70u) == 0xcc0000u);
|
||||
return ((colorControl & ~0xff0000u) == 0x10u && documentedRop((colorControl >> 16u) & 0xffu)) || ((colorControl >> 4u) & 7u) == 0u || (!hasColorTarget && (colorControl & ~0x70u) == 0xcc0000u);
|
||||
}
|
||||
|
||||
std::string colorControlMessage(std::uint32_t colorControl) {
|
||||
static constexpr const char* modes[8] = {"disable", "normal", "eliminate fast clear", "resolve", "decompress", "FMASK decompress", "DCC decompress", "reserved"};
|
||||
char text[160];
|
||||
std::snprintf(text, sizeof(text), "AGC graphics: only normal color rendering with copy ROP is supported (CB_COLOR_CONTROL 0x%08x, mode %s)", colorControl, modes[(colorControl >> 4u) & 7u]);
|
||||
std::snprintf(text, sizeof(text), "AGC graphics: only normal color rendering with a ROP3 code of the source or brush form is supported (CB_COLOR_CONTROL 0x%08x, mode %s)", colorControl, modes[(colorControl >> 4u) & 7u]);
|
||||
return text;
|
||||
}
|
||||
|
||||
@@ -1112,6 +1116,21 @@ std::array<std::uint16_t, 4> QuadSampleMasks(const State& state, const SampleOrd
|
||||
return {mask, mask, mask, mask};
|
||||
}
|
||||
|
||||
VkLogicOp LogicOpFromRop(std::uint8_t rop) {
|
||||
if (!documentedRop(rop)) {
|
||||
char text[96];
|
||||
std::snprintf(text, sizeof(text), "AGC graphics: ROP3 code 0x%02x combines source and brush, which is not implemented", rop);
|
||||
throw std::runtime_error(text);
|
||||
}
|
||||
static constexpr std::array<VkLogicOp, 16> operations{
|
||||
VK_LOGIC_OP_CLEAR, VK_LOGIC_OP_NOR, VK_LOGIC_OP_AND_INVERTED, VK_LOGIC_OP_COPY_INVERTED,
|
||||
VK_LOGIC_OP_AND_REVERSE, VK_LOGIC_OP_INVERT, VK_LOGIC_OP_XOR, VK_LOGIC_OP_NAND,
|
||||
VK_LOGIC_OP_AND, VK_LOGIC_OP_EQUIVALENT, VK_LOGIC_OP_NO_OP, VK_LOGIC_OP_OR_INVERTED,
|
||||
VK_LOGIC_OP_COPY, VK_LOGIC_OP_OR_REVERSE, VK_LOGIC_OP_OR, VK_LOGIC_OP_SET};
|
||||
const std::uint32_t index = (rop & 3u) | ((rop >> 4u) & 4u) | ((rop >> 4u) & 8u);
|
||||
return operations[index];
|
||||
}
|
||||
|
||||
std::array<VkSampleLocationEXT, 32> SampleLocations(const State& state) {
|
||||
Require(state.samples == 1 || state.samples == 2 || state.samples == 4 || state.samples == 8, "invalid sample count");
|
||||
std::array<VkSampleLocationEXT, 32> locations{};
|
||||
@@ -1492,6 +1511,7 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) {
|
||||
|
||||
// CB_COLOR_CONTROL mode 0 disables color writes, which only matters when a target is written.
|
||||
if (const auto colorControl = read(cx, 0x202); !colorControlSupported(colorControl, result.hasColorTarget)) throw std::runtime_error(colorControlMessage(colorControl));
|
||||
if (const auto colorControl = read(cx, 0x202); ((colorControl >> 4u) & 7u) == 1u) result.colorRop = static_cast<std::uint8_t>(colorControl >> 16u);
|
||||
zero(cx, 0x1c4, zFormatSupported(read(cx, 0x1c4)) ? 0u : ~0u, "depth or sample-mask export");
|
||||
const auto exportFormat = result.hasColorTarget ? read(cx, 0x1c5) : 0u;
|
||||
APS5_LOG_OUT_DEBUG("Export format=%u", exportFormat);
|
||||
@@ -1708,6 +1728,9 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) {
|
||||
result.color = result.colors.front();
|
||||
result.blend = result.blends[result.colors.front().exportIndex];
|
||||
}
|
||||
if (LogicOpFromRop(result.colorRop) != VK_LOGIC_OP_COPY) {
|
||||
for (const auto& blend : result.blends) Require(blend.blendEnable == VK_FALSE, "a color logic operation together with blending is not implemented");
|
||||
}
|
||||
Require(result.reversedBlend == 0 || (exportSlots.size() == 1 && exportCount == 1), "blending into a color target with a reversed component order through a second source color needs that target to be the shader's only color export");
|
||||
if (result.dualSourceBlend) {
|
||||
Require(exportSlots.size() == 2 && exportSlots[0] == 0 && exportSlots[1] == 1 && exportCount == 1, "dual-source blending needs pixel exports to MRT slots 0 and 1 and color writes to slot 0 alone");
|
||||
|
||||
@@ -243,7 +243,12 @@ void FullQueueBlocksCaller() {
|
||||
constexpr int ChildLines = 50;
|
||||
|
||||
[[noreturn]] void EndChild(const std::string& mode, const char* output) {
|
||||
std::FILE* file = std::fopen(output, "wb");
|
||||
const auto path = std::filesystem::temp_directory_path() / output;
|
||||
#ifdef _WIN32
|
||||
std::FILE* file = _wfopen(path.c_str(), L"wb");
|
||||
#else
|
||||
std::FILE* file = std::fopen(path.c_str(), "wb");
|
||||
#endif
|
||||
Check(file != nullptr, "child output file");
|
||||
#ifdef _WIN32
|
||||
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
|
||||
@@ -288,12 +293,18 @@ std::string Quote(const std::string& text) {
|
||||
|
||||
void LinesSurviveEnd(const std::string& self, const std::string& mode) {
|
||||
const auto output = std::filesystem::temp_directory_path() / ("aps5-async-log-" + mode + "-" + std::to_string(Clock::now().time_since_epoch().count()) + ".txt");
|
||||
std::string command = Quote(self) + " child " + mode + " " + Quote(output.string());
|
||||
#ifdef _WIN32
|
||||
command = Quote(command);
|
||||
#endif
|
||||
(void)self;
|
||||
std::wstring executable(32768, L'\0');
|
||||
executable.resize(GetModuleFileNameW(nullptr, executable.data(), static_cast<DWORD>(executable.size())));
|
||||
const std::wstring command = L"\"\"" + executable + L"\" child " + std::filesystem::path(mode).wstring() + L" \"" + output.filename().wstring() + L"\"\"";
|
||||
const auto start = Clock::now();
|
||||
const int status = _wsystem(command.c_str());
|
||||
#else
|
||||
const std::string command = Quote(self) + " child " + mode + " " + Quote(output.filename().string());
|
||||
const auto start = Clock::now();
|
||||
const int status = std::system(command.c_str());
|
||||
#endif
|
||||
const auto lived = Clock::now() - start;
|
||||
std::error_code error;
|
||||
const bool started = std::filesystem::exists(output, error);
|
||||
|
||||
@@ -602,6 +602,9 @@ void importJoinTests(const Context& context) {
|
||||
Require(pieces != nullptr && pieces->base == heapBase && pieces->bytes == 2 * piece && piecesBuffer == pieces->buffer && piecesCopied == 0, "a buffer bound exactly over two pieces no larger than the import window was not imported as one");
|
||||
change(heap + 2 * piece, piece, true);
|
||||
static_cast<void>(bind(heapBase + 2 * piece - 16));
|
||||
const auto [prefixBuffer, prefixCopied] = bind(heapBase, 2 * piece + 16);
|
||||
const auto* prefix = HostImportFor(importing, heapBase, 2 * piece + 16);
|
||||
Require(prefix != nullptr && prefix->base == heapBase && prefix->bytes == 3 * piece && prefixBuffer == prefix->buffer && prefixCopied == 0, "a buffer bound from the first of three pieces into the last was not imported over the pieces it crosses");
|
||||
const auto* kept = HostImportFor(importing, first, 16);
|
||||
Require(kept != nullptr && kept->buffer == joinedBuffer && kept->bytes == 2 * large, "a registry change elsewhere re-imported the joined ranges");
|
||||
change(guest + 2 * large, large, true);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "GraphicsTests.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
|
||||
@@ -13,6 +14,14 @@
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -417,6 +426,62 @@ void TestRetiredSurfaceRelease(const Context& context) {
|
||||
ClearDepthSurfaces(context.device);
|
||||
}
|
||||
|
||||
void TestUnmappedStalePlaneFlush(const Context& context) {
|
||||
ClearDepthSurfaces(context.device);
|
||||
constexpr VkExtent2D extent{256, 256};
|
||||
constexpr std::uint64_t unmapped = 0xa0000000;
|
||||
DepthTarget target{unmapped, 0, extent, VK_FORMAT_D32_SFLOAT, 1.0f, 0};
|
||||
target.depthSwizzle = XorSwizzleMode(TextureTileMode::kZ64KBX);
|
||||
Require(DepthSurfaceView(context, target, true) != VK_NULL_HANDLE, "a mirrorable depth surface must render");
|
||||
Require(DepthPlanesStale(), "a written mirrorable depth surface must leave its plane stale");
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> stored;
|
||||
try {
|
||||
stored = FlushDepthPlanes(unmapped, DepthSliceBytes(extent, 4u));
|
||||
} catch (const std::exception& error) {
|
||||
Require(false, std::string("flushing a depth plane whose guest memory is not mapped must drop its results: ") + error.what());
|
||||
}
|
||||
Require(stored.empty(), "a depth plane without mapped guest memory must not be stored");
|
||||
Require(!DepthPlanesStale(), "a dropped depth plane must not stay stale");
|
||||
ClearDepthSurfaces(context.device);
|
||||
}
|
||||
|
||||
void TestReadOnlyStalePlaneFlush(const Context& context) {
|
||||
ClearDepthSurfaces(context.device);
|
||||
constexpr std::size_t bytes = 1024 * 1024;
|
||||
#ifdef _WIN32
|
||||
const auto release = [](void* pointer) { VirtualFree(pointer, 0, MEM_RELEASE); };
|
||||
std::unique_ptr<void, decltype(release)> mapping(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READONLY), release);
|
||||
Require(mapping != nullptr, "cannot map a read-only depth plane");
|
||||
#else
|
||||
const auto release = [](void* pointer) { munmap(pointer, bytes); };
|
||||
std::unique_ptr<void, decltype(release)> mapping(mmap(nullptr, bytes, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), release);
|
||||
Require(mapping.get() != MAP_FAILED, "cannot map a read-only depth plane");
|
||||
#endif
|
||||
const auto address = (reinterpret_cast<std::uintptr_t>(mapping.get()) + 65535u) & ~std::uintptr_t{65535u};
|
||||
constexpr VkExtent2D extent{256, 256};
|
||||
const auto planeBytes = DepthSliceBytes(extent, 4u);
|
||||
Require(!AgcDriver::GuestMemory::CommittedRanges(address, planeBytes).empty(), "a read-only depth plane must remain readable");
|
||||
Require(AgcDriver::GuestMemory::CommittedRanges(address, planeBytes, true).empty(), "a read-only depth plane must not be writable");
|
||||
auto storageContext = context;
|
||||
storageContext.formatProperties = [](VkPhysicalDevice, VkFormat, VkFormatProperties* properties) {
|
||||
*properties = {};
|
||||
properties->optimalTilingFeatures = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT;
|
||||
};
|
||||
DepthTarget target{address, 0, extent, VK_FORMAT_D32_SFLOAT, 1.0f, 0};
|
||||
target.depthSwizzle = XorSwizzleMode(TextureTileMode::kZ64KBX);
|
||||
Require(DepthSurfaceView(storageContext, target, true) != VK_NULL_HANDLE, "a read-only depth plane must render");
|
||||
Require(DepthPlanesStale(), "the read-only depth plane must have pending results");
|
||||
bool rejected = false;
|
||||
try {
|
||||
static_cast<void>(FlushDepthPlanes(address, planeBytes));
|
||||
} catch (const std::runtime_error& error) {
|
||||
rejected = std::string_view(error.what()).find("storage texture has no committed guest pages") != std::string_view::npos;
|
||||
}
|
||||
Require(rejected, "a mapped read-only depth plane must retain the unsupported-store rejection");
|
||||
Require(DepthPlanesStale(), "a mapped read-only depth plane must retain its pending results");
|
||||
ClearDepthSurfaces(context.device);
|
||||
}
|
||||
|
||||
void RunDepthSurfaceReuseTests() {
|
||||
static int deviceTag = 0;
|
||||
Context context{};
|
||||
@@ -432,6 +497,8 @@ void RunDepthSurfaceReuseTests() {
|
||||
TestMultisampleDepthOwnership(context);
|
||||
TestMultisampleStencilAliases(context);
|
||||
TestStencilUnormView(context);
|
||||
TestUnmappedStalePlaneFlush(context);
|
||||
TestReadOnlyStalePlaneFlush(context);
|
||||
constexpr std::uint64_t depth32 = 0x40000000;
|
||||
constexpr std::uint64_t depth16 = 0x50000000;
|
||||
constexpr std::uint64_t depthStencil = 0x60000000;
|
||||
|
||||
@@ -355,7 +355,11 @@ void testFailureLog() {
|
||||
std::fflush(stderr);
|
||||
const int saved = APS5_DUP(APS5_FILENO(stderr));
|
||||
check(saved >= 0, "stderr could not be saved");
|
||||
check(std::freopen(path.string().c_str(), "wb", stderr) != nullptr, "stderr could not be redirected");
|
||||
#ifdef _WIN32
|
||||
check(_wfreopen(path.wstring().c_str(), L"wb", stderr) != nullptr, "stderr could not be redirected");
|
||||
#else
|
||||
check(std::freopen(path.c_str(), "wb", stderr) != nullptr, "stderr could not be redirected");
|
||||
#endif
|
||||
std::string message;
|
||||
std::string error;
|
||||
try {
|
||||
|
||||
@@ -120,13 +120,15 @@ void DisableAfterFailure() {
|
||||
AgcDriver::FrameDumpOutput output;
|
||||
const std::array<std::byte, 16> pixels{};
|
||||
Check(output.Enabled_nid_no_patch(), "BMP output was disabled before any failure");
|
||||
const auto blocked = directory.string();
|
||||
const auto previous = std::filesystem::current_path();
|
||||
std::filesystem::current_path(directory.parent_path());
|
||||
const auto blocked = directory.filename().string();
|
||||
Check(!output.Write_nid_no_patch(blocked.c_str(), 2, 2, pixels), "BMP output accepted a directory as a file");
|
||||
Check(!output.Enabled_nid_no_patch(), "failed BMP output remained enabled");
|
||||
const auto skipped = directory / "must-not-exist.bmp";
|
||||
const auto path = skipped.string();
|
||||
const auto path = (directory.filename() / "must-not-exist.bmp").string();
|
||||
for (unsigned i = 0; i < 64; ++i) Check(!output.Write_nid_no_patch(path.c_str(), 2, 2, pixels), "disabled BMP output wrote another file");
|
||||
Check(!std::filesystem::exists(skipped), "disabled BMP output created a file");
|
||||
std::filesystem::current_path(previous);
|
||||
Check(!std::filesystem::exists(directory / "must-not-exist.bmp"), "disabled BMP output created a file");
|
||||
Check(std::filesystem::remove(directory), "cannot remove empty fixture directory");
|
||||
}
|
||||
|
||||
|
||||
@@ -1925,8 +1925,35 @@ void TuningFieldTests() {
|
||||
Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "DISABLE_DUAL_QUAD was rejected");
|
||||
Require(state.hasColorTarget && state.blend.colorWriteMask == baseline.blend.colorWriteMask, "DISABLE_DUAL_QUAD changed color output");
|
||||
queue.context[0x202] = 0xcc0013;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "copy ROP");
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "source or brush form");
|
||||
queue.context[0x202] = 0x5b0010;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "source or brush form");
|
||||
queue.context[0x202] = 0x5a0010;
|
||||
state = AgcDriver::Graphics::DecodeState(queue);
|
||||
Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "an XOR ROP was rejected");
|
||||
Require(state.hasColorTarget && state.colorRop == 0x5a && AgcDriver::Graphics::LogicOpFromRop(state.colorRop) == VK_LOGIC_OP_XOR, "an XOR ROP did not decode");
|
||||
queue.context[0x202] = 0xcc0010;
|
||||
Require(AgcDriver::Graphics::DecodeState(queue).colorRop == AgcDriver::Graphics::CopyRop, "the copy ROP did not decode as the copy operation");
|
||||
const std::array<std::pair<std::uint8_t, VkLogicOp>, 14> ropOperations{{
|
||||
{0x0f, VK_LOGIC_OP_COPY_INVERTED}, {0xa0, VK_LOGIC_OP_AND}, {0xf0, VK_LOGIC_OP_COPY}, {0xfa, VK_LOGIC_OP_OR},
|
||||
{0x00, VK_LOGIC_OP_CLEAR}, {0x11, VK_LOGIC_OP_NOR}, {0x33, VK_LOGIC_OP_COPY_INVERTED}, {0x55, VK_LOGIC_OP_INVERT}, {0x66, VK_LOGIC_OP_XOR},
|
||||
{0x88, VK_LOGIC_OP_AND}, {0xaa, VK_LOGIC_OP_NO_OP}, {0xcc, VK_LOGIC_OP_COPY}, {0xee, VK_LOGIC_OP_OR}, {0xff, VK_LOGIC_OP_SET}}};
|
||||
for (const auto& [rop, operation] : ropOperations) Require(AgcDriver::Graphics::LogicOpFromRop(rop) == operation, "a ROP3 code mapped to the wrong logic operation");
|
||||
std::uint32_t documentedRops = 0;
|
||||
for (std::uint32_t rop = 0; rop < 256u; ++rop) {
|
||||
try {
|
||||
static_cast<void>(AgcDriver::Graphics::LogicOpFromRop(static_cast<std::uint8_t>(rop)));
|
||||
++documentedRops;
|
||||
} catch (const std::runtime_error&) {
|
||||
}
|
||||
}
|
||||
Require(documentedRops == 28u, "a ROP3 code outside the source and brush forms was accepted, or one inside them was refused");
|
||||
auto blendedLogic = queue;
|
||||
blendedLogic.context[0x202] = 0x5a0010;
|
||||
blendedLogic.context[0x1e0] = 0x40000101u;
|
||||
for (std::uint32_t index = 0; index < 4u; ++index) blendedLogic.context[0x105 + index] = 0u;
|
||||
blendedLogic.context[0x31c] |= 0x8000u;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(blendedLogic); }, "logic operation together with blending");
|
||||
for (const auto groups : {1u, 2u, 15u}) {
|
||||
queue.context[0x2d5] = 0x2000u | (groups << 15u);
|
||||
state = AgcDriver::Graphics::DecodeState(queue);
|
||||
|
||||
@@ -105,6 +105,8 @@ public:
|
||||
Device() {
|
||||
#ifdef _WIN32
|
||||
library = SDL_LoadObject("vulkan-1.dll");
|
||||
#elif defined(__APPLE__)
|
||||
library = SDL_LoadObject("libvulkan.1.dylib");
|
||||
#else
|
||||
library = SDL_LoadObject("libvulkan.so.1");
|
||||
#endif
|
||||
@@ -116,6 +118,12 @@ public:
|
||||
application.apiVersion = VK_API_VERSION_1_1;
|
||||
VkInstanceCreateInfo info{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
|
||||
info.pApplicationInfo = &application;
|
||||
#ifdef __APPLE__
|
||||
const char* portability = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME;
|
||||
info.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
|
||||
info.enabledExtensionCount = 1;
|
||||
info.ppEnabledExtensionNames = &portability;
|
||||
#endif
|
||||
Check(function<PFN_vkCreateInstance>("vkCreateInstance")(&info, nullptr, &instance), "vkCreateInstance");
|
||||
std::uint32_t count = 0;
|
||||
const auto enumerate = function<PFN_vkEnumeratePhysicalDevices>("vkEnumeratePhysicalDevices");
|
||||
|
||||
@@ -39,6 +39,8 @@ public:
|
||||
Device() {
|
||||
#ifdef _WIN32
|
||||
library = SDL_LoadObject("vulkan-1.dll");
|
||||
#elif defined(__APPLE__)
|
||||
library = SDL_LoadObject("libvulkan.1.dylib");
|
||||
#else
|
||||
library = SDL_LoadObject("libvulkan.so.1");
|
||||
#endif
|
||||
@@ -50,6 +52,12 @@ public:
|
||||
application.apiVersion = VK_API_VERSION_1_1;
|
||||
VkInstanceCreateInfo info{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
|
||||
info.pApplicationInfo = &application;
|
||||
#ifdef __APPLE__
|
||||
const char* portability = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME;
|
||||
info.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
|
||||
info.enabledExtensionCount = 1;
|
||||
info.ppEnabledExtensionNames = &portability;
|
||||
#endif
|
||||
Check(function<PFN_vkCreateInstance>("vkCreateInstance")(&info, nullptr, &instance), "vkCreateInstance");
|
||||
std::uint32_t count = 0;
|
||||
const auto enumerate = function<PFN_vkEnumeratePhysicalDevices>("vkEnumeratePhysicalDevices");
|
||||
|
||||
@@ -3955,10 +3955,11 @@ void drawSnapshotTableSetTests(const Device& device, Recorder& recorder) {
|
||||
}
|
||||
constexpr std::uint32_t width = 4;
|
||||
constexpr std::uint32_t height = 4;
|
||||
alignas(256) static std::array<float, width * height> texels{};
|
||||
alignas(256) static std::array<float, width * height * 16u> texels{};
|
||||
texels.fill(1.0f);
|
||||
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(texels.data()));
|
||||
const std::array<std::uint32_t, 8> texture{static_cast<std::uint32_t>(address >> 8u), static_cast<std::uint32_t>((address >> 40u) & 0xffu) | (22u << 20u) | (((width - 1u) & 3u) << 30u), ((width - 1u) >> 2u) | ((height - 1u) << 14u), 0xfacu | (9u << 28u), 0u, 0u, 0u, 0u};
|
||||
Require(DescribeSurface(DecodeTextureResource(texture)).guestBytes <= sizeof(texels), "the draw snapshot table set test T# reads past its texels");
|
||||
constexpr auto bindingCount = static_cast<std::uint32_t>(ShaderRecompiler::RuntimeAbi::Binding::Count);
|
||||
const auto addTable = [&](ShaderRecompiler::RecompileResult& program, ShaderRecompiler::RuntimeAbi::Stage stage, std::uint32_t set, std::uint32_t elements) {
|
||||
ShaderRecompiler::DescriptorBinding map{};
|
||||
@@ -4552,6 +4553,46 @@ void drawSnapshotEvictionTests(const Device& device) {
|
||||
Require(cache.ReusableDrawSnapshot(address, 16) == nullptr && cache.ReusableDrawSnapshot(address, 16) == nullptr, "a snapshot outlived a CPU store");
|
||||
}
|
||||
|
||||
void containingSnapshotTests(const Device& device) {
|
||||
using namespace AgcDriver::GuestMemory;
|
||||
constexpr std::size_t bytes = 131072;
|
||||
void* block = WriteWatched() ? AllocateWatched(bytes, 65536) : nullptr;
|
||||
if (block == nullptr) {
|
||||
std::cout << "write watching unavailable: containing draw snapshots not tested\n";
|
||||
return;
|
||||
}
|
||||
struct Release {
|
||||
void* block;
|
||||
~Release() { ReleaseWatched(block, bytes); }
|
||||
} release{block};
|
||||
const auto address = reinterpret_cast<std::uint64_t>(block);
|
||||
const VkDeviceSize alignment = std::max<VkDeviceSize>(device.GetContext().limits.minStorageBufferOffsetAlignment, 4);
|
||||
Recorder cache(device.GetContext());
|
||||
const auto whole = std::make_shared<Buffer>(device.GetContext(), bytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
|
||||
const auto exact = std::make_shared<Buffer>(device.GetContext(), 4096, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
|
||||
const auto registry = GuestAllocations::GuestAllocationsGeneration_nid_postfix();
|
||||
const auto generation = CollectWrites(address, bytes);
|
||||
Require(generation != 0, "the watched block has no generation");
|
||||
cache.KeepDrawSnapshot(address, bytes, generation, registry, whole);
|
||||
const auto inner = address + 65536 + 16 * alignment;
|
||||
VkDeviceSize offset = 1;
|
||||
Require(cache.ReusableDrawSnapshot(inner, 4096) == nullptr, "a lookup without an offset took a containing snapshot");
|
||||
Require(cache.ReusableDrawSnapshot(inner, 4096, Recorder::SnapshotUse::Storage, nullptr, 0, &offset) == whole && offset == inner - address, "an unchanged range inside a kept snapshot was not taken from it at its offset");
|
||||
Require(cache.ReusableDrawSnapshot(inner, address + bytes - inner + 4, Recorder::SnapshotUse::Storage, nullptr, 0, &offset) == nullptr, "a range past the end of a kept snapshot was taken from it");
|
||||
Require(cache.ReusableDrawSnapshot(inner + alignment / 2, 4096, Recorder::SnapshotUse::Storage, nullptr, 0, &offset) == nullptr, "a misaligned range was taken from a kept snapshot");
|
||||
Require(cache.ReusableDrawSnapshot(inner, 4096, Recorder::SnapshotUse::Vertex, nullptr, 0, &offset) == nullptr, "a vertex lookup took a storage snapshot");
|
||||
cache.KeepDrawSnapshot(inner, 4096, generation, registry, exact);
|
||||
offset = 1;
|
||||
Require(cache.ReusableDrawSnapshot(inner, 4096, Recorder::SnapshotUse::Storage, nullptr, 0, &offset) == exact && offset == 0, "an exact snapshot lost to a containing one");
|
||||
std::memset(block, 0x5a, 16);
|
||||
CollectWrites(address, 16);
|
||||
Require(cache.ReusableDrawSnapshot(inner + 4096, 4096, Recorder::SnapshotUse::Storage, nullptr, 0, &offset) == whole && offset == inner + 4096 - address, "a store outside the range stopped it from being taken from a containing snapshot");
|
||||
Require(cache.ReusableDrawSnapshot(address, 4096, Recorder::SnapshotUse::Storage, nullptr, 0, &offset) == nullptr, "a range over a CPU store was taken from a containing snapshot");
|
||||
std::memset(reinterpret_cast<void*>(inner + 4096), 0x5a, 16);
|
||||
CollectWrites(inner + 4096, 16);
|
||||
Require(cache.ReusableDrawSnapshot(inner + 4096, 4096, Recorder::SnapshotUse::Storage, nullptr, 0, &offset) == nullptr, "a containing snapshot outlived a CPU store into the range");
|
||||
}
|
||||
|
||||
void unwrittenSnapshotTests(const Device& device) {
|
||||
Recorder cache(device.GetContext());
|
||||
const auto buffer = std::make_shared<Buffer>(device.GetContext(), 16, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
|
||||
@@ -9867,7 +9908,7 @@ int pipelineCacheFileTests(const Device& device) {
|
||||
} removeDirectory{directory};
|
||||
#ifdef _WIN32
|
||||
_putenv_s("ANYPS5_NO_SHADER_CACHE", "0");
|
||||
_putenv_s("ANYPS5_SHADER_CACHE_DIR", directory.string().c_str());
|
||||
_wputenv_s(L"ANYPS5_SHADER_CACHE_DIR", directory.wstring().c_str());
|
||||
#else
|
||||
setenv("ANYPS5_NO_SHADER_CACHE", "0", 1);
|
||||
setenv("ANYPS5_SHADER_CACHE_DIR", directory.string().c_str(), 1);
|
||||
@@ -11183,6 +11224,7 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--draw-snapshot-cap-only") {
|
||||
drawSnapshotCapTests(device, recorder);
|
||||
containingSnapshotTests(device);
|
||||
unwrittenSnapshotTests(device);
|
||||
drawSnapshotUploadTests(device, recorder);
|
||||
std::cout << "Draw snapshot cap tests passed\n";
|
||||
@@ -11235,6 +11277,7 @@ int main(int argc, char** argv) {
|
||||
imageTableFirstLayerRevalidateTests(device, recorder);
|
||||
drawSnapshotCapTests(device, recorder);
|
||||
drawSnapshotEvictionTests(device);
|
||||
containingSnapshotTests(device);
|
||||
unwrittenSnapshotTests(device);
|
||||
drawSnapshotUploadTests(device, recorder);
|
||||
drawInputReuseTests(device, recorder);
|
||||
|
||||
@@ -62,11 +62,19 @@ void setEnvironment(const char* name, const std::string& value) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void setPathEnvironment(const char* name, const std::filesystem::path& value) {
|
||||
#ifdef _WIN32
|
||||
_wputenv_s(std::filesystem::path(name).wstring().c_str(), value.wstring().c_str());
|
||||
#else
|
||||
setenv(name, value.c_str(), 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
void verifyHostStandardError() {
|
||||
const auto directory = std::filesystem::temp_directory_path() / ("aps5-shader-host-stderr-" + std::to_string(std::random_device{}()));
|
||||
setEnvironment("ANYPS5_NO_SHADER_CACHE", "0");
|
||||
setEnvironment("ANYPS5_SHADER_CACHE_DIR", directory.string());
|
||||
setPathEnvironment("ANYPS5_SHADER_CACHE_DIR", directory);
|
||||
int capture[2], guest[2];
|
||||
require(::_pipe(capture, 65536, _O_BINARY) == 0, "cannot create host capture");
|
||||
require(::_pipe(guest, 65536, _O_BINARY) == 0, "cannot create guest capture");
|
||||
@@ -1074,7 +1082,7 @@ void verifyLongHostPaths() {
|
||||
const auto extended = [](const auto& path) { return std::filesystem::path(L"\\\\?\\" + path.native()); };
|
||||
std::filesystem::create_directories(extended(directory));
|
||||
setEnvironment("ANYPS5_NO_SHADER_CACHE", "0");
|
||||
setEnvironment("ANYPS5_SHADER_CACHE_DIR", directory.string());
|
||||
setPathEnvironment("ANYPS5_SHADER_CACHE_DIR", directory);
|
||||
std::vector<std::filesystem::path> files;
|
||||
std::filesystem::path entries;
|
||||
struct Cleanup {
|
||||
@@ -2530,7 +2538,7 @@ int main(int argc, char** argv) {
|
||||
std::filesystem::remove_all(directory);
|
||||
verifyDefaultDirectory(argv[0]);
|
||||
setEnvironment("ANYPS5_NO_SHADER_CACHE", "0");
|
||||
setEnvironment("ANYPS5_SHADER_CACHE_DIR", directory.string());
|
||||
setPathEnvironment("ANYPS5_SHADER_CACHE_DIR", directory);
|
||||
verifyAtomicReplacement(directory);
|
||||
verifyResultRoundTrip();
|
||||
verifyEntryRoundTrip();
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "Recompiler.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
#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;
|
||||
alignas(256) std::array<std::byte, Width * Height * 4> Pixels{};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 6> VertexCode{
|
||||
0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 11> PixelCode{
|
||||
0x7e0802ff, 0x3e4ccccd, 0x7e0a02ff, 0x3ecccccd, 0x7e0c02ff, 0x3f19999a, 0x7e0e02ff, 0x3f4ccccd,
|
||||
0xf800180f, 0x07060504, 0xbf810000,
|
||||
};
|
||||
|
||||
constexpr std::array<std::uint8_t, 4> Source{0x33, 0x66, 0x99, 0xcc};
|
||||
|
||||
constexpr std::array<std::array<float, 4>, 3> Vertices{{
|
||||
{-1.0f, -1.0f, 0.5f, 1.0f}, {3.0f, -1.0f, 0.5f, 1.0f}, {-1.0f, 3.0f, 0.5f, 1.0f}
|
||||
}};
|
||||
alignas(256) std::array<std::array<float, 4>, 3> VertexStorage = Vertices;
|
||||
std::array<std::byte, Width * Height * 4> Before{};
|
||||
|
||||
|
||||
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};
|
||||
}
|
||||
|
||||
void Draw(AgcDriver::VulkanDevice& device, std::uint8_t rop) {
|
||||
for (std::size_t index = 0; index < Pixels.size(); ++index) Before[index] = Pixels[index] = static_cast<std::byte>((index * 37u + 11u) & 0xffu);
|
||||
const auto target = device.Target();
|
||||
|
||||
std::vector<std::uint32_t> vertexUserData(4, 0u);
|
||||
const auto vertexBuffer = BufferDescriptor(VertexStorage.data(), 16u, static_cast<std::uint32_t>(VertexStorage.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;
|
||||
const auto vertexResult = ShaderRecompiler::Recompile(vertex);
|
||||
const auto vertexPush = static_cast<std::uint32_t>(vertexResult.pushConstants.size());
|
||||
|
||||
ShaderRecompiler::ShaderPixelStageInfo pixel{};
|
||||
pixel.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionX) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionY);
|
||||
pixel.posX = true;
|
||||
pixel.posY = true;
|
||||
pixel.targetOutputMode[0] = 9;
|
||||
pixel.targetExportMapping.fill(0xe4u);
|
||||
const 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, pixel, std::nullopt, pixelMemory},
|
||||
target,
|
||||
PixelPushLayout(vertexPush, target)
|
||||
};
|
||||
fragment.useCache = false;
|
||||
const auto pixelResult = ShaderRecompiler::Recompile(fragment);
|
||||
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{
|
||||
{ShaderStage::Vertex, &vertexResult, 0},
|
||||
{ShaderStage::Fragment, &pixelResult, PixelPushOffset(vertexPush, target)}
|
||||
}};
|
||||
|
||||
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.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.colorRop = rop;
|
||||
state.scissor = {{0, 0}, {Width, Height}};
|
||||
state.cullMode = VK_CULL_MODE_NONE;
|
||||
state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
||||
state.blend.colorWriteMask = 15;
|
||||
state.blends = {state.blend};
|
||||
state.blendConstants = {};
|
||||
const AgcDriver::Pm4::DrawParameters draw{0, static_cast<std::uint32_t>(VertexStorage.size()), 0, 1, 0, false};
|
||||
device.Draw(state, draw, shaders);
|
||||
device.WaitIdle();
|
||||
}
|
||||
|
||||
std::uint8_t Combine(std::uint8_t rop, std::uint8_t destination, std::uint8_t source) {
|
||||
switch (rop) {
|
||||
case 0xcc: return source;
|
||||
case 0x5a: return static_cast<std::uint8_t>(destination ^ source);
|
||||
case 0x88: return static_cast<std::uint8_t>(destination & source);
|
||||
case 0xee: return static_cast<std::uint8_t>(destination | source);
|
||||
default: throw std::runtime_error("the test has no expectation for this ROP");
|
||||
}
|
||||
}
|
||||
|
||||
void Check(std::uint8_t rop, std::string_view what) {
|
||||
for (std::uint32_t y = 0; y < Height; ++y) {
|
||||
for (std::uint32_t x = 0; x < Width; ++x) {
|
||||
for (std::uint32_t channel = 0; channel < 4; ++channel) {
|
||||
const auto index = (y * Width + x) * 4u + channel;
|
||||
const auto expected = Combine(rop, static_cast<std::uint8_t>(Before[index]), Source[channel]);
|
||||
const auto actual = static_cast<std::uint8_t>(Pixels[index]);
|
||||
Require(actual == expected, std::string(what) + ": pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") channel " + std::to_string(channel) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
for (const std::uint8_t rop : {0xcc, 0x5a, 0x88, 0xee, 0xcc}) {
|
||||
try {
|
||||
Draw(*device, rop);
|
||||
} catch (const std::exception& error) {
|
||||
Require(std::string_view(error.what()).find("device does not support color logic operations") != std::string_view::npos, std::string("unexpected rejection: ") + error.what());
|
||||
std::puts("skipped, the device has no logicOp feature");
|
||||
return VulkanTestSkipped;
|
||||
}
|
||||
char what[48];
|
||||
std::snprintf(what, sizeof(what), "ROP3 0x%02x into an 8_8_8_8 unorm target", rop);
|
||||
Check(rop, what);
|
||||
}
|
||||
std::puts("color logic operation tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,8 @@ public:
|
||||
Device() {
|
||||
#ifdef _WIN32
|
||||
library = SDL_LoadObject("vulkan-1.dll");
|
||||
#elif defined(__APPLE__)
|
||||
library = SDL_LoadObject("libvulkan.1.dylib");
|
||||
#else
|
||||
library = SDL_LoadObject("libvulkan.so.1");
|
||||
#endif
|
||||
@@ -87,6 +89,12 @@ public:
|
||||
application.apiVersion = VK_API_VERSION_1_1;
|
||||
VkInstanceCreateInfo info{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
|
||||
info.pApplicationInfo = &application;
|
||||
#ifdef __APPLE__
|
||||
const char* portability = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME;
|
||||
info.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
|
||||
info.enabledExtensionCount = 1;
|
||||
info.ppEnabledExtensionNames = &portability;
|
||||
#endif
|
||||
Check(function<PFN_vkCreateInstance>("vkCreateInstance")(&info, nullptr, &instance), "vkCreateInstance");
|
||||
std::uint32_t count = 0;
|
||||
const auto enumerate = function<PFN_vkEnumeratePhysicalDevices>("vkEnumeratePhysicalDevices");
|
||||
|
||||
@@ -38,6 +38,8 @@ public:
|
||||
Device() {
|
||||
#ifdef _WIN32
|
||||
library = SDL_LoadObject("vulkan-1.dll");
|
||||
#elif defined(__APPLE__)
|
||||
library = SDL_LoadObject("libvulkan.1.dylib");
|
||||
#else
|
||||
library = SDL_LoadObject("libvulkan.so.1");
|
||||
#endif
|
||||
@@ -49,6 +51,12 @@ public:
|
||||
application.apiVersion = VK_API_VERSION_1_1;
|
||||
VkInstanceCreateInfo info{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
|
||||
info.pApplicationInfo = &application;
|
||||
#ifdef __APPLE__
|
||||
const char* portability = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME;
|
||||
info.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
|
||||
info.enabledExtensionCount = 1;
|
||||
info.ppEnabledExtensionNames = &portability;
|
||||
#endif
|
||||
Check(function<PFN_vkCreateInstance>("vkCreateInstance")(&info, nullptr, &instance), "vkCreateInstance");
|
||||
std::uint32_t count = 0;
|
||||
const auto enumerate = function<PFN_vkEnumeratePhysicalDevices>("vkEnumeratePhysicalDevices");
|
||||
|
||||
@@ -37,6 +37,8 @@ public:
|
||||
Device() {
|
||||
#ifdef _WIN32
|
||||
library = SDL_LoadObject("vulkan-1.dll");
|
||||
#elif defined(__APPLE__)
|
||||
library = SDL_LoadObject("libvulkan.1.dylib");
|
||||
#else
|
||||
library = SDL_LoadObject("libvulkan.so.1");
|
||||
#endif
|
||||
@@ -48,6 +50,12 @@ public:
|
||||
application.apiVersion = VK_API_VERSION_1_1;
|
||||
VkInstanceCreateInfo info{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
|
||||
info.pApplicationInfo = &application;
|
||||
#ifdef __APPLE__
|
||||
const char* portability = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME;
|
||||
info.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
|
||||
info.enabledExtensionCount = 1;
|
||||
info.ppEnabledExtensionNames = &portability;
|
||||
#endif
|
||||
Check(function<PFN_vkCreateInstance>("vkCreateInstance")(&info, nullptr, &instance), "vkCreateInstance");
|
||||
std::uint32_t count = 0;
|
||||
const auto enumerate = function<PFN_vkEnumeratePhysicalDevices>("vkEnumeratePhysicalDevices");
|
||||
|
||||
@@ -135,6 +135,69 @@ void MovedDataHitsTheCache(bool libraries) {
|
||||
Require(again.lookups == 10 && again.hits == 5, mode + ": a draw at an address seen before did not reuse the cached draw (" + std::to_string(again.hits) + " hits)");
|
||||
}
|
||||
|
||||
void EvictedEntriesServeNoRelocation(bool libraries) {
|
||||
Registered(VertexHeader, VertexCode.data(), sizeof(VertexCode), 2);
|
||||
Registered(PixelHeader, PixelCode.data(), sizeof(PixelCode), 1);
|
||||
Registered(SecondVertexHeader, SecondVertexCode.data(), sizeof(SecondVertexCode), 2);
|
||||
const auto mode = std::string(libraries ? "with" : "without") + " graphics pipeline libraries";
|
||||
const auto draw = [&](const float* source, const std::uint32_t* vertexCode) {
|
||||
auto commands = DrawFrom({source}, vertexCode);
|
||||
Packet packet{commands.data(), static_cast<std::uint32_t>(commands.size()), 0, {}};
|
||||
Require(sceAgcDriverSubmitDcb(&packet) == 0, "the draw submission failed");
|
||||
AgcDriverWaitIdle_nid_postfix();
|
||||
return AgcDriver::DriverDetail::Driver::Get().DrawCacheCounters();
|
||||
};
|
||||
static std::array<std::array<float, 4>, 6> same{};
|
||||
for (auto& entry : same) entry = {1.0f, 0.0f, 0.0f, 0.0f};
|
||||
alignas(16) static std::array<std::array<float, 4>, 3> others{};
|
||||
for (std::size_t i = 0; i < others.size(); ++i) others[i] = {0.25f + 0.03125f * static_cast<float>(i), 0.0f, 0.0f, 0.0f};
|
||||
AgcDriver::DriverDetail::DrawEntryCounters counters{};
|
||||
for (std::size_t i = 0; i < 4; ++i) counters = draw(same[i].data(), VertexCode.data());
|
||||
Require(counters.lookups == 4 && counters.hits == 0 && counters.inserts == 2, mode + ": the draws that teach the moved pointer and its contents made " + std::to_string(counters.hits) + " hits and " + std::to_string(counters.inserts) + " inserts, expected 0 and 2");
|
||||
const auto relocated = draw(same[4].data(), VertexCode.data());
|
||||
Require(relocated.hits == 1, mode + ": a draw whose data moved after the teaching draws did not reuse the cached draw (" + std::to_string(relocated.hits) + " hits)");
|
||||
auto evicted = relocated;
|
||||
for (const auto& entry : others) {
|
||||
draw(entry.data(), SecondVertexCode.data());
|
||||
evicted = draw(entry.data(), SecondVertexCode.data());
|
||||
}
|
||||
Require(evicted.evictions - relocated.evictions >= 2 && evicted.inserts - relocated.inserts >= 2, mode + ": draws of another vertex shader did not evict the two taught entries from the draw cache (" + std::to_string(evicted.inserts - relocated.inserts) + " inserts, " + std::to_string(evicted.evictions - relocated.evictions) + " evictions; run with APS5_DRAW_CACHE_ENTRIES=2)");
|
||||
const auto after = draw(same[5].data(), VertexCode.data());
|
||||
Require(after.lookups == evicted.lookups + 1 && after.hits == evicted.hits, mode + ": a draw whose data moved reused a draw the cache had evicted, through its relocation alias (" + std::to_string(after.hits - evicted.hits) + " hits)");
|
||||
}
|
||||
|
||||
void RelocatingDrawsKeepTheirDonor(bool libraries) {
|
||||
Registered(VertexHeader, VertexCode.data(), sizeof(VertexCode), 2);
|
||||
Registered(PixelHeader, PixelCode.data(), sizeof(PixelCode), 1);
|
||||
Registered(SecondVertexHeader, SecondVertexCode.data(), sizeof(SecondVertexCode), 2);
|
||||
const auto mode = std::string(libraries ? "with" : "without") + " graphics pipeline libraries";
|
||||
const auto draw = [&](const float* source, const std::uint32_t* vertexCode) {
|
||||
auto commands = DrawFrom({source}, vertexCode);
|
||||
Packet packet{commands.data(), static_cast<std::uint32_t>(commands.size()), 0, {}};
|
||||
Require(sceAgcDriverSubmitDcb(&packet) == 0, "the draw submission failed");
|
||||
AgcDriverWaitIdle_nid_postfix();
|
||||
return AgcDriver::DriverDetail::Driver::Get().DrawCacheCounters();
|
||||
};
|
||||
constexpr std::size_t Rounds = 3;
|
||||
alignas(16) static std::array<std::array<float, 4>, Rounds> others{};
|
||||
for (std::size_t i = 0; i < others.size(); ++i) others[i] = {0.25f + 0.03125f * static_cast<float>(i), 0.0f, 0.0f, 0.0f};
|
||||
static std::array<std::array<float, 4>, 5 + Rounds> same{};
|
||||
for (auto& entry : same) entry = {1.0f, 0.0f, 0.0f, 0.0f};
|
||||
AgcDriver::DriverDetail::DrawEntryCounters counters{};
|
||||
for (std::size_t i = 0; i < 4; ++i) counters = draw(same[i].data(), VertexCode.data());
|
||||
Require(counters.lookups == 4 && counters.hits == 0 && counters.inserts == 2, mode + ": the draws that teach the moved pointer and its contents made " + std::to_string(counters.hits) + " hits and " + std::to_string(counters.inserts) + " inserts, expected 0 and 2");
|
||||
const auto start = draw(same[4].data(), VertexCode.data());
|
||||
Require(start.hits == 1, mode + ": a draw whose data moved after the teaching draws did not reuse the cached draw (" + std::to_string(start.hits) + " hits)");
|
||||
auto relocated = start;
|
||||
for (std::size_t i = 0; i < others.size(); ++i) {
|
||||
draw(others[i].data(), SecondVertexCode.data());
|
||||
const auto inserted = draw(others[i].data(), SecondVertexCode.data());
|
||||
relocated = draw(same[5 + i].data(), VertexCode.data());
|
||||
Require(relocated.hits == inserted.hits + 1, mode + ": a draw whose data moved again lost the cached draw it reused before each of " + std::to_string(i + 1) + " rounds of another vertex shader's draws (" + std::to_string(inserted.inserts - start.inserts) + " inserts, " + std::to_string(inserted.evictions - start.evictions) + " evictions)");
|
||||
}
|
||||
Require(relocated.evictions - start.evictions >= 2, mode + ": draws of another vertex shader evicted " + std::to_string(relocated.evictions - start.evictions) + " entries, expected at least 2 (run with APS5_DRAW_CACHE_ENTRIES=2)");
|
||||
}
|
||||
|
||||
void SplitRunsNameTheirSharedWords() {
|
||||
alignas(16) static std::array<std::uint32_t, 4> data{1u, 2u, 3u, 4u};
|
||||
auto table = std::make_shared<ShaderRecompiler::SrtGuestWords>();
|
||||
@@ -228,9 +291,11 @@ void MovedForwardedWordReachesStageDataHit(bool libraries) {
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
SplitRunsNameTheirSharedWords();
|
||||
const bool evicted = argc == 2 && std::string(argv[1]) == "--evicted";
|
||||
const bool kept = argc == 2 && std::string(argv[1]) == "--kept";
|
||||
if (!evicted && !kept) SplitRunsNameTheirSharedWords();
|
||||
bool libraries = false;
|
||||
{
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
@@ -238,6 +303,18 @@ int main() {
|
||||
libraries = device->GraphicsPipelineLibraries();
|
||||
}
|
||||
if (!libraries && std::getenv("APS5_NO_GPL") == nullptr) return VulkanTestSkipped;
|
||||
if (evicted) {
|
||||
EvictedEntriesServeNoRelocation(libraries);
|
||||
AgcDriverShutdown_nid_postfix();
|
||||
std::cout << "An evicted draw served no relocation " << (libraries ? "with" : "without") << " graphics pipeline libraries\n";
|
||||
return 0;
|
||||
}
|
||||
if (kept) {
|
||||
RelocatingDrawsKeepTheirDonor(libraries);
|
||||
AgcDriverShutdown_nid_postfix();
|
||||
std::cout << "A draw relocated from a cached draw kept it cached " << (libraries ? "with" : "without") << " graphics pipeline libraries\n";
|
||||
return 0;
|
||||
}
|
||||
MovedDataHitsTheCache(libraries);
|
||||
UnrelocatedAliasesStayOut(libraries);
|
||||
MovedForwardedWordReachesStageDataHit(libraries);
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "Recompiler.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::uint32_t Threads = 96;
|
||||
constexpr std::uint32_t Inputs = 4;
|
||||
constexpr std::uint32_t Results = 16;
|
||||
constexpr std::uint32_t Instructions = 7;
|
||||
constexpr std::uint32_t FirstMultiply = 3;
|
||||
constexpr std::uint32_t FirstConstantFactor = 5;
|
||||
alignas(256) std::array<std::uint32_t, Threads * Inputs> Input{};
|
||||
alignas(256) std::array<std::uint32_t, Threads * Results> Output{};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 20> Code{
|
||||
0x34020084u, 0x34040086u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0x06140b04u, 0x08160b04u, 0x0a180b04u,
|
||||
0x101a0b04u, 0x0e1c0b04u, 0x101e08ffu, 0x3fc00000u, 0xd5070010u, 0x0001ff04u, 0x3fc00000u, 0xe0781000u,
|
||||
0x80010a02u, 0xe07c1010u, 0x80010e02u, 0xbf810000u,
|
||||
};
|
||||
|
||||
constexpr const char* Names[Instructions] = {"v_add_f32", "v_sub_f32", "v_subrev_f32", "v_mul_f32", "v_mul_legacy_f32", "v_mul_f32 1.5", "v_mul_legacy_f32 1.5"};
|
||||
|
||||
template <std::uint32_t Count>
|
||||
struct Vector {
|
||||
std::uint32_t operands[2];
|
||||
std::uint32_t outputsFlushed[Count];
|
||||
std::uint32_t inputsFlushed[Count];
|
||||
};
|
||||
|
||||
constexpr std::array<Vector<FirstMultiply + 2>, 40> ArithmeticVectors{{
|
||||
{{0x00000000u, 0x00000001u}, {0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
{{0x00000001u, 0x00800002u}, {0x00800003u, 0x80800001u, 0x00800001u, 0x00000000u, 0x00000000u}, {0x00800002u, 0x80800002u, 0x00800002u, 0x00000000u, 0x00000000u}},
|
||||
{{0x00000001u, 0x81000001u}, {0x81000000u, 0x01000002u, 0x81000002u, 0x80000000u, 0x80000000u}, {0x81000001u, 0x01000001u, 0x81000001u, 0x80000000u, 0x00000000u}},
|
||||
{{0x00000002u, 0x3f800000u}, {0x3f800000u, 0xbf800000u, 0x3f800000u, 0x00000000u, 0x00000000u}, {0x3f800000u, 0xbf800000u, 0x3f800000u, 0x00000000u, 0x00000000u}},
|
||||
{{0x00000002u, 0xff7fffffu}, {0xff7fffffu, 0x7f7fffffu, 0xff7fffffu, 0xb57fffffu, 0xb57fffffu}, {0xff7fffffu, 0x7f7fffffu, 0xff7fffffu, 0x80000000u, 0x00000000u}},
|
||||
{{0x00000003u, 0x80000001u}, {0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u}},
|
||||
{{0x00400000u, 0x00000002u}, {0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
{{0x00400000u, 0x807fffffu}, {0x80000000u, 0x00bfffffu, 0x80bfffffu, 0x80000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u}},
|
||||
{{0x007fffffu, 0x00800000u}, {0x00ffffffu, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00800000u, 0x80800000u, 0x00800000u, 0x00000000u, 0x00000000u}},
|
||||
{{0x007fffffu, 0x80800002u}, {0x80000000u, 0x01000000u, 0x81000000u, 0x80000000u, 0x80000000u}, {0x80800002u, 0x00800002u, 0x80800002u, 0x80000000u, 0x00000000u}},
|
||||
{{0x00800000u, 0x80000001u}, {0x00000000u, 0x00800001u, 0x80800001u, 0x80000000u, 0x80000000u}, {0x00800000u, 0x00800000u, 0x80800000u, 0x80000000u, 0x00000000u}},
|
||||
{{0x00800001u, 0x33800000u}, {0x33800000u, 0xb3800000u, 0x33800000u, 0x00000000u, 0x00000000u}, {0x33800000u, 0xb3800000u, 0x33800000u, 0x00000001u, 0x00000001u}},
|
||||
{{0x00800002u, 0x00800000u}, {0x01000001u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u}, {0x01000001u, 0x00000002u, 0x80000002u, 0x00000000u, 0x00000000u}},
|
||||
{{0x00ffffffu, 0x00000002u}, {0x01000000u, 0x00fffffdu, 0x80fffffdu, 0x00000000u, 0x00000000u}, {0x00ffffffu, 0x00ffffffu, 0x80ffffffu, 0x00000000u, 0x00000000u}},
|
||||
{{0x00ffffffu, 0x80800002u}, {0x00000000u, 0x01400000u, 0x81400000u, 0x80000000u, 0x80000000u}, {0x007ffffdu, 0x01400000u, 0x81400000u, 0x80000000u, 0x80000000u}},
|
||||
{{0x01000000u, 0x80000003u}, {0x00fffffdu, 0x01000002u, 0x81000002u, 0x80000000u, 0x80000000u}, {0x01000000u, 0x01000000u, 0x81000000u, 0x80000000u, 0x00000000u}},
|
||||
{{0x01000001u, 0x80000001u}, {0x01000000u, 0x01000002u, 0x81000002u, 0x80000000u, 0x80000000u}, {0x01000001u, 0x01000001u, 0x81000001u, 0x80000000u, 0x00000000u}},
|
||||
{{0x3f800000u, 0x807fffffu}, {0x3f800000u, 0x3f800000u, 0xbf800000u, 0x80000000u, 0x80000000u}, {0x3f800000u, 0x3f800000u, 0xbf800000u, 0x80000000u, 0x00000000u}},
|
||||
{{0x34000000u, 0x00800001u}, {0x34000000u, 0x34000000u, 0xb4000000u, 0x00000000u, 0x00000000u}, {0x34000000u, 0x34000000u, 0xb4000000u, 0x00000001u, 0x00000001u}},
|
||||
{{0x7f7fffffu, 0x00000003u}, {0x7f7fffffu, 0x7f7fffffu, 0xff7fffffu, 0x35bfffffu, 0x35bfffffu}, {0x7f7fffffu, 0x7f7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u}},
|
||||
{{0x80000000u, 0x00000001u}, {0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u}, {0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u}},
|
||||
{{0x80000001u, 0x00800002u}, {0x00800001u, 0x80800003u, 0x00800003u, 0x80000000u, 0x80000000u}, {0x00800002u, 0x80800002u, 0x00800002u, 0x80000000u, 0x00000000u}},
|
||||
{{0x80000001u, 0x80ffffffu}, {0x81000000u, 0x00fffffeu, 0x80fffffeu, 0x00000000u, 0x00000000u}, {0x80ffffffu, 0x00ffffffu, 0x80ffffffu, 0x00000000u, 0x00000000u}},
|
||||
{{0x80000002u, 0x3f800000u}, {0x3f800000u, 0xbf800000u, 0x3f800000u, 0x80000000u, 0x80000000u}, {0x3f800000u, 0xbf800000u, 0x3f800000u, 0x80000000u, 0x00000000u}},
|
||||
{{0x80000002u, 0xff7fffffu}, {0xff7fffffu, 0x7f7fffffu, 0xff7fffffu, 0x357fffffu, 0x357fffffu}, {0xff7fffffu, 0x7f7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u}},
|
||||
{{0x80000003u, 0x7f800000u}, {0x7f800000u, 0xff800000u, 0x7f800000u, 0xff800000u, 0xff800000u}, {0x7f800000u, 0xff800000u, 0x7f800000u, 0xffc00000u, 0x00000000u}},
|
||||
{{0x80400000u, 0x00000002u}, {0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u}, {0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u}},
|
||||
{{0x80400000u, 0x80000003u}, {0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
{{0x807fffffu, 0x00800000u}, {0x00000000u, 0x80ffffffu, 0x00ffffffu, 0x80000000u, 0x80000000u}, {0x00800000u, 0x80800000u, 0x00800000u, 0x80000000u, 0x00000000u}},
|
||||
{{0x807fffffu, 0x80800001u}, {0x81000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u}, {0x80800001u, 0x00800001u, 0x80800001u, 0x00000000u, 0x00000000u}},
|
||||
{{0x80800000u, 0x80000001u}, {0x80800001u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80800000u, 0x80800000u, 0x00800000u, 0x00000000u, 0x00000000u}},
|
||||
{{0x80800001u, 0x33800000u}, {0x33800000u, 0xb3800000u, 0x33800000u, 0x80000000u, 0x80000000u}, {0x33800000u, 0xb3800000u, 0x33800000u, 0x80000001u, 0x80000001u}},
|
||||
{{0x80800002u, 0x00800000u}, {0x80000000u, 0x81000001u, 0x01000001u, 0x80000000u, 0x80000000u}, {0x80000002u, 0x81000001u, 0x01000001u, 0x80000000u, 0x80000000u}},
|
||||
{{0x80ffffffu, 0x00000002u}, {0x80fffffdu, 0x81000000u, 0x01000000u, 0x80000000u, 0x80000000u}, {0x80ffffffu, 0x80ffffffu, 0x00ffffffu, 0x80000000u, 0x00000000u}},
|
||||
{{0x80ffffffu, 0x80800002u}, {0x81400000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x81400000u, 0x807ffffdu, 0x007ffffdu, 0x00000000u, 0x00000000u}},
|
||||
{{0x81000000u, 0x80000003u}, {0x81000002u, 0x80fffffdu, 0x00fffffdu, 0x00000000u, 0x00000000u}, {0x81000000u, 0x81000000u, 0x01000000u, 0x00000000u, 0x00000000u}},
|
||||
{{0x81000001u, 0x80000001u}, {0x81000002u, 0x81000000u, 0x01000000u, 0x00000000u, 0x00000000u}, {0x81000001u, 0x81000001u, 0x01000001u, 0x00000000u, 0x00000000u}},
|
||||
{{0xbf800000u, 0x807fffffu}, {0xbf800000u, 0xbf800000u, 0x3f800000u, 0x00000000u, 0x00000000u}, {0xbf800000u, 0xbf800000u, 0x3f800000u, 0x00000000u, 0x00000000u}},
|
||||
{{0xb4000000u, 0x00000003u}, {0xb4000000u, 0xb4000000u, 0x34000000u, 0x80000000u, 0x80000000u}, {0xb4000000u, 0xb4000000u, 0x34000000u, 0x80000000u, 0x00000000u}},
|
||||
{{0xff7fffffu, 0x00000003u}, {0xff7fffffu, 0xff7fffffu, 0x7f7fffffu, 0xb5bfffffu, 0xb5bfffffu}, {0xff7fffffu, 0xff7fffffu, 0x7f7fffffu, 0x80000000u, 0x00000000u}},
|
||||
}};
|
||||
|
||||
constexpr std::array<Vector<2>, 24> TinyProductVectors{{
|
||||
{{0x3f7ffffeu, 0x00800001u}, {0x00800000u, 0x00800000u}, {0x00800000u, 0x00800000u}},
|
||||
{{0x3f7ffffeu, 0x80800001u}, {0x80800000u, 0x80800000u}, {0x80800000u, 0x80800000u}},
|
||||
{{0xbf7ffffeu, 0x00800001u}, {0x80800000u, 0x80800000u}, {0x80800000u, 0x80800000u}},
|
||||
{{0xbf7ffffeu, 0x80800001u}, {0x00800000u, 0x00800000u}, {0x00800000u, 0x00800000u}},
|
||||
{{0x3f800001u, 0x007fffffu}, {0x00800000u, 0x00800000u}, {0x00000000u, 0x00000000u}},
|
||||
{{0x3f800001u, 0x807fffffu}, {0x80800000u, 0x80800000u}, {0x80000000u, 0x00000000u}},
|
||||
{{0xbf800001u, 0x007fffffu}, {0x80800000u, 0x80800000u}, {0x80000000u, 0x00000000u}},
|
||||
{{0xbf800001u, 0x807fffffu}, {0x00800000u, 0x00800000u}, {0x00000000u, 0x00000000u}},
|
||||
{{0x3f800002u, 0x007ffffeu}, {0x00800000u, 0x00800000u}, {0x00000000u, 0x00000000u}},
|
||||
{{0x3f800002u, 0x807ffffeu}, {0x80800000u, 0x80800000u}, {0x80000000u, 0x00000000u}},
|
||||
{{0xbf800002u, 0x007ffffeu}, {0x80800000u, 0x80800000u}, {0x80000000u, 0x00000000u}},
|
||||
{{0xbf800002u, 0x807ffffeu}, {0x00800000u, 0x00800000u}, {0x00000000u, 0x00000000u}},
|
||||
{{0x3f7ffffeu, 0x00800000u}, {0x00000000u, 0x00000000u}, {0x007fffffu, 0x007fffffu}},
|
||||
{{0xbf7fffffu, 0x00400000u}, {0x80000000u, 0x80000000u}, {0x80000000u, 0x00000000u}},
|
||||
{{0x3f800001u, 0x80000001u}, {0x80000000u, 0x80000000u}, {0x80000000u, 0x00000000u}},
|
||||
{{0xbfffffffu, 0x00400000u}, {0x80000000u, 0x80000000u}, {0x80000000u, 0x00000000u}},
|
||||
{{0x3f400000u, 0x00800000u}, {0x00000000u, 0x00000000u}, {0x00600000u, 0x00600000u}},
|
||||
{{0x40000000u, 0x807fffffu}, {0x80fffffeu, 0x80fffffeu}, {0x80000000u, 0x00000000u}},
|
||||
{{0xcb000000u, 0x007ffffeu}, {0x8bfffffcu, 0x8bfffffcu}, {0x80000000u, 0x00000000u}},
|
||||
{{0xcb7fffffu, 0x807ffffeu}, {0x0c7ffffbu, 0x0c7ffffbu}, {0x00000000u, 0x00000000u}},
|
||||
{{0x5f800000u, 0x00400000u}, {0x20000000u, 0x20000000u}, {0x00000000u, 0x00000000u}},
|
||||
{{0x7f000000u, 0x80400000u}, {0xbf800000u, 0xbf800000u}, {0x80000000u, 0x00000000u}},
|
||||
{{0xb3800000u, 0x00ffffffu}, {0x80000000u, 0x80000000u}, {0x80000001u, 0x80000001u}},
|
||||
{{0xb4000000u, 0x80800000u}, {0x00000000u, 0x00000000u}, {0x00000001u, 0x00000001u}},
|
||||
}};
|
||||
|
||||
constexpr std::array<Vector<2>, 32> ConstantFactorVectors{{
|
||||
{{0x00000001u, 0x00000001u}, {0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u}},
|
||||
{{0x00000002u, 0x00000001u}, {0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u}},
|
||||
{{0x00000003u, 0x00000001u}, {0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u}},
|
||||
{{0x00155555u, 0x00000001u}, {0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u}},
|
||||
{{0x003fffffu, 0x00000001u}, {0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u}},
|
||||
{{0x00400000u, 0x00000001u}, {0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u}},
|
||||
{{0x00400001u, 0x00000001u}, {0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u}},
|
||||
{{0x00555555u, 0x00000001u}, {0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u}},
|
||||
{{0x007ffffeu, 0x00000001u}, {0x00bffffdu, 0x00bffffdu}, {0x00000000u, 0x00000000u}},
|
||||
{{0x007fffffu, 0x00000001u}, {0x00bffffeu, 0x00bffffeu}, {0x00000000u, 0x00000000u}},
|
||||
{{0x00800000u, 0x00000001u}, {0x00c00000u, 0x00c00000u}, {0x00c00000u, 0x00c00000u}},
|
||||
{{0x00800001u, 0x00000001u}, {0x00c00002u, 0x00c00002u}, {0x00c00002u, 0x00c00002u}},
|
||||
{{0x00aaaaabu, 0x00000001u}, {0x01000000u, 0x01000000u}, {0x01000000u, 0x01000000u}},
|
||||
{{0x00ffffffu, 0x00000001u}, {0x013fffffu, 0x013fffffu}, {0x013fffffu, 0x013fffffu}},
|
||||
{{0x01000000u, 0x00000001u}, {0x01400000u, 0x01400000u}, {0x01400000u, 0x01400000u}},
|
||||
{{0x3f800000u, 0x00000001u}, {0x3fc00000u, 0x3fc00000u}, {0x3fc00000u, 0x3fc00000u}},
|
||||
{{0x00000000u, 0x00000001u}, {0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u}},
|
||||
{{0x80000001u, 0x00000001u}, {0x80000000u, 0x80000000u}, {0x80000000u, 0x00000000u}},
|
||||
{{0x80000002u, 0x00000001u}, {0x80000000u, 0x80000000u}, {0x80000000u, 0x00000000u}},
|
||||
{{0x80000003u, 0x00000001u}, {0x80000000u, 0x80000000u}, {0x80000000u, 0x00000000u}},
|
||||
{{0x80155555u, 0x00000001u}, {0x80000000u, 0x80000000u}, {0x80000000u, 0x00000000u}},
|
||||
{{0x803fffffu, 0x00000001u}, {0x80000000u, 0x80000000u}, {0x80000000u, 0x00000000u}},
|
||||
{{0x80400000u, 0x00000001u}, {0x80000000u, 0x80000000u}, {0x80000000u, 0x00000000u}},
|
||||
{{0x80400001u, 0x00000001u}, {0x80000000u, 0x80000000u}, {0x80000000u, 0x00000000u}},
|
||||
{{0x80555555u, 0x00000001u}, {0x80000000u, 0x80000000u}, {0x80000000u, 0x00000000u}},
|
||||
{{0x807ffffeu, 0x00000001u}, {0x80bffffdu, 0x80bffffdu}, {0x80000000u, 0x00000000u}},
|
||||
{{0x807fffffu, 0x00000001u}, {0x80bffffeu, 0x80bffffeu}, {0x80000000u, 0x00000000u}},
|
||||
{{0x80800000u, 0x00000001u}, {0x80c00000u, 0x80c00000u}, {0x80c00000u, 0x80c00000u}},
|
||||
{{0x80800001u, 0x00000001u}, {0x80c00002u, 0x80c00002u}, {0x80c00002u, 0x80c00002u}},
|
||||
{{0x80aaaaabu, 0x00000001u}, {0x81000000u, 0x81000000u}, {0x81000000u, 0x81000000u}},
|
||||
{{0x80ffffffu, 0x00000001u}, {0x813fffffu, 0x813fffffu}, {0x813fffffu, 0x813fffffu}},
|
||||
{{0x81000000u, 0x00000001u}, {0x81400000u, 0x81400000u}, {0x81400000u, 0x81400000u}},
|
||||
}};
|
||||
|
||||
constexpr std::uint32_t FirstTinyProductLane = static_cast<std::uint32_t>(ArithmeticVectors.size());
|
||||
constexpr std::uint32_t FirstConstantFactorLane = FirstTinyProductLane + static_cast<std::uint32_t>(TinyProductVectors.size());
|
||||
static_assert(FirstConstantFactorLane + ConstantFactorVectors.size() <= Threads);
|
||||
|
||||
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::string Hex(std::uint32_t value) {
|
||||
char text[16];
|
||||
std::snprintf(text, sizeof(text), "0x%08x", value);
|
||||
return text;
|
||||
}
|
||||
|
||||
template <std::uint32_t Count, std::size_t Size>
|
||||
void Fill(const std::array<Vector<Count>, Size>& vectors, std::uint32_t firstLane) {
|
||||
for (std::uint32_t row = 0; row < Size; ++row) {
|
||||
Input[(firstLane + row) * Inputs] = vectors[row].operands[0];
|
||||
Input[(firstLane + row) * Inputs + 1u] = vectors[row].operands[1];
|
||||
}
|
||||
}
|
||||
|
||||
bool Run(AgcDriver::VulkanDevice& device, ShaderRecompiler::ShaderFloatMode floatMode) {
|
||||
Input.fill(0u);
|
||||
Fill(ArithmeticVectors, 0u);
|
||||
Fill(TinyProductVectors, FirstTinyProductLane);
|
||||
Fill(ConstantFactorVectors, FirstConstantFactorLane);
|
||||
Output.fill(0xdeadbeefu);
|
||||
std::vector<std::uint32_t> userData(8, 0u);
|
||||
const auto input = BufferDescriptor(Input.data(), static_cast<std::uint32_t>(Input.size() * 4u));
|
||||
const auto output = BufferDescriptor(Output.data(), static_cast<std::uint32_t>(Output.size() * 4u));
|
||||
std::copy(input.begin(), input.end(), userData.begin());
|
||||
std::copy(output.begin(), output.end(), userData.begin() + 4);
|
||||
const std::span<const std::uint32_t> code(Code);
|
||||
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}, 0, {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;
|
||||
request.context.floatMode = floatMode;
|
||||
std::optional<decltype(ShaderRecompiler::Recompile(request))> result;
|
||||
try {
|
||||
result.emplace(ShaderRecompiler::Recompile(request));
|
||||
} catch (const std::exception& error) {
|
||||
const std::string message = error.what();
|
||||
const bool keepsDenormals = request.target.float32DenormPreserve && !request.target.float32FlushToZero;
|
||||
Require(!keepsDenormals && message.find("needs DenormPreserve 32") != std::string::npos, "f32 add and multiply FLOAT_MODE " + Hex(floatMode.floatMode) + ": " + message);
|
||||
return false;
|
||||
}
|
||||
device.Dispatch(*result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
|
||||
device.WaitIdle();
|
||||
return true;
|
||||
}
|
||||
|
||||
template <std::uint32_t Count, std::size_t Size>
|
||||
void Check(const std::array<Vector<Count>, Size>& vectors, std::uint32_t firstLane, std::uint32_t firstInstruction, std::uint32_t denormals, bool ieee) {
|
||||
for (std::uint32_t row = 0; row < Size; ++row) {
|
||||
const auto& vector = vectors[row];
|
||||
for (std::uint32_t index = 0; index < Count; ++index) {
|
||||
const std::uint32_t instruction = firstInstruction + index;
|
||||
const auto actual = Output[(firstLane + row) * Results + instruction];
|
||||
const auto expected = denormals == 1u ? vector.outputsFlushed[index] : vector.inputsFlushed[index];
|
||||
Require(actual == expected, std::string(Names[instruction]) + " f32 denormal mode " + std::to_string(denormals) + (ieee ? ", IEEE=1" : ", IEEE=0") + " of " +
|
||||
Hex(vector.operands[0]) + ", " + Hex(vector.operands[1]) + " is " + Hex(actual) + ", expected " + Hex(expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
std::uint32_t checked = 0u;
|
||||
for (const std::uint32_t denormals : {1u, 2u}) {
|
||||
for (const bool ieee : {false, true}) {
|
||||
if (!Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u | denormals << 4u, true, ieee, false})) continue;
|
||||
++checked;
|
||||
Check(ArithmeticVectors, 0u, 0u, denormals, ieee);
|
||||
Check(TinyProductVectors, FirstTinyProductLane, FirstMultiply, denormals, ieee);
|
||||
Check(ConstantFactorVectors, FirstConstantFactorLane, FirstConstantFactor, denormals, ieee);
|
||||
}
|
||||
}
|
||||
std::printf("f32 add and multiply denormal mode 1 and 2 tests passed (%u of 4 modes run; the others need DenormPreserve 32)\n", checked);
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "Recompiler.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::uint32_t Threads = 64;
|
||||
constexpr std::uint32_t Inputs = 4;
|
||||
constexpr std::uint32_t ConstantForms = 4;
|
||||
alignas(256) std::array<std::uint32_t, Threads * Inputs> Input{};
|
||||
alignas(256) std::array<std::uint32_t, Threads * ConstantForms> Output{};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 11> MullitCode{
|
||||
0x34020084u, 0x34040082u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0x7e140280u, 0xd550000au, 0x041a0b04u,
|
||||
0xe0701000u, 0x80010a02u, 0xbf810000u,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 17> ConstantMullitCode{
|
||||
0x34020084u, 0x34040084u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0xd550000au, 0x041a08f2u, 0xd550000bu,
|
||||
0x0419e904u, 0xd550000cu, 0x041a0a81u, 0xd550000du, 0x041a08ffu, 0x3fc00000u, 0xe0781000u, 0x80010a02u,
|
||||
0xbf810000u,
|
||||
};
|
||||
|
||||
constexpr const char* ConstantNames[ConstantForms] = {"v_mullit_f32 1.0, v4, v6", "v_mullit_f32 v4, 2.0, v6", "v_mullit_f32 0x1, v5, v6", "v_mullit_f32 0x3fc00000, v4, v6"};
|
||||
|
||||
struct Vector {
|
||||
std::uint32_t lhs;
|
||||
std::uint32_t rhs;
|
||||
std::uint32_t limit;
|
||||
std::uint32_t expected[8];
|
||||
};
|
||||
|
||||
constexpr Vector Vectors[] = {
|
||||
{0x00000000u, 0x00000000u, 0x00000001u, {0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u, 0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u}},
|
||||
{0x00000000u, 0x00800000u, 0x00000001u, {0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u, 0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u}},
|
||||
{0x00000001u, 0x80000001u, 0x00800000u, {0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u}},
|
||||
{0x00000001u, 0x3f800000u, 0x00000001u, {0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u, 0xff7fffffu, 0xff7fffffu, 0x00000001u, 0x00000001u}},
|
||||
{0x00000001u, 0x3f800000u, 0x00800000u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000001u}},
|
||||
{0x00000001u, 0xbf800000u, 0x00800000u, {0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x80000001u, 0x80000001u}},
|
||||
{0x00000001u, 0x7f7fffffu, 0x00800000u, {0x00000000u, 0x00000000u, 0x34ffffffu, 0x34ffffffu, 0x00000000u, 0x00000000u, 0x34ffffffu, 0x34ffffffu}},
|
||||
{0x00800000u, 0x00000000u, 0x00000001u, {0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u, 0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u}},
|
||||
{0x00800000u, 0x80000001u, 0x00800000u, {0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u}},
|
||||
{0x00800000u, 0x00800000u, 0x00000001u, {0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u, 0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u}},
|
||||
{0x00800000u, 0x3f000000u, 0x00000001u, {0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u, 0xff7fffffu, 0xff7fffffu, 0x00400000u, 0x00400000u}},
|
||||
{0x00800000u, 0x3f000000u, 0x00800000u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00400000u, 0x00400000u, 0x00400000u, 0x00400000u}},
|
||||
{0x3f000000u, 0x007fffffu, 0x00000001u, {0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u, 0xff7fffffu, 0xff7fffffu, 0x00400000u, 0x00400000u}},
|
||||
{0x3f000000u, 0x007fffffu, 0x00800000u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00400000u, 0x00400000u}},
|
||||
{0x3f000000u, 0x807fffffu, 0x00800000u, {0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x80400000u, 0x80400000u}},
|
||||
{0x7f800001u, 0x00000001u, 0x00000001u, {0xff7fffffu, 0xff7fffffu, 0x7f800001u, 0x7fc00001u, 0xff7fffffu, 0xff7fffffu, 0x7f800001u, 0x7fc00001u}},
|
||||
{0x7f800001u, 0x00000001u, 0x00800000u, {0x00000000u, 0x00000000u, 0x7f800001u, 0x7fc00001u, 0x00000000u, 0x00000000u, 0x7f800001u, 0x7fc00001u}},
|
||||
{0x7f800001u, 0x00800000u, 0x00000001u, {0xff7fffffu, 0xff7fffffu, 0x7f800001u, 0x7fc00001u, 0xff7fffffu, 0xff7fffffu, 0x7f800001u, 0x7fc00001u}},
|
||||
{0x7f800001u, 0x00800000u, 0x00800000u, {0x7f800001u, 0x7fc00001u, 0x7f800001u, 0x7fc00001u, 0x7f800001u, 0x7fc00001u, 0x7f800001u, 0x7fc00001u}},
|
||||
{0x3f800000u, 0x40000000u, 0x3f800000u, {0x40000000u, 0x40000000u, 0x40000000u, 0x40000000u, 0x40000000u, 0x40000000u, 0x40000000u, 0x40000000u}},
|
||||
{0x3f800000u, 0xff7fffffu, 0x3f800000u, {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu}},
|
||||
{0x3f800000u, 0x3f800000u, 0x80000000u, {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu}},
|
||||
{0x7f7fffffu, 0x7f7fffffu, 0x3f800000u, {0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u}},
|
||||
};
|
||||
|
||||
struct ConstantVector {
|
||||
std::uint32_t inputs[3];
|
||||
std::uint32_t expected[4][ConstantForms];
|
||||
};
|
||||
|
||||
constexpr ConstantVector ConstantVectors[] = {
|
||||
{{0x00000001u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000001u, 0x00000002u, 0x00000000u, 0x00000002u}}},
|
||||
{{0x00000001u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000001u, 0x00000002u, 0x00000000u, 0x00000002u}}},
|
||||
{{0x00000002u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u}}},
|
||||
{{0x00000002u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u}}},
|
||||
{{0x00000003u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000003u, 0x00000006u, 0x00000000u, 0x00000004u}}},
|
||||
{{0x00000003u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000003u, 0x00000006u, 0x00000000u, 0x00000004u}}},
|
||||
{{0x00155555u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00155555u, 0x002aaaaau, 0x00000000u, 0x00200000u}}},
|
||||
{{0x00155555u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00155555u, 0x002aaaaau, 0x00000000u, 0x00200000u}}},
|
||||
{{0x003fffffu, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x003fffffu, 0x007ffffeu, 0x00000000u, 0x005ffffeu}}},
|
||||
{{0x003fffffu, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x003fffffu, 0x007ffffeu, 0x00000000u, 0x005ffffeu}}},
|
||||
{{0x00400000u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00800000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00400000u, 0x00800000u, 0x00000000u, 0x00600000u}}},
|
||||
{{0x00400000u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00800000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00400000u, 0x00800000u, 0x00000000u, 0x00600000u}}},
|
||||
{{0x00400001u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00800002u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00400001u, 0x00800002u, 0x00000000u, 0x00600002u}}},
|
||||
{{0x00400001u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00800002u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00400001u, 0x00800002u, 0x00000000u, 0x00600002u}}},
|
||||
{{0x00555555u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00aaaaaau, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00555555u, 0x00aaaaaau, 0x00000000u, 0x00800000u}}},
|
||||
{{0x00555555u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00aaaaaau, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00555555u, 0x00aaaaaau, 0x00000000u, 0x00800000u}}},
|
||||
{{0x007ffffeu, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00fffffcu, 0x00000000u, 0x00bffffdu}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x007ffffeu, 0x00fffffcu, 0x00000000u, 0x00bffffdu}}},
|
||||
{{0x007ffffeu, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00fffffcu, 0x00000000u, 0x00bffffdu}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x007ffffeu, 0x00fffffcu, 0x00000000u, 0x00bffffdu}}},
|
||||
{{0x007fffffu, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00fffffeu, 0x00000000u, 0x00bffffeu}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x007fffffu, 0x00fffffeu, 0x00000000u, 0x00bffffeu}}},
|
||||
{{0x007fffffu, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x00000000u, 0x00fffffeu, 0x00000000u, 0x00bffffeu}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x007fffffu, 0x00fffffeu, 0x00000000u, 0x00bffffeu}}},
|
||||
{{0x80000001u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000001u, 0x80000002u, 0x00000000u, 0x80000002u}}},
|
||||
{{0x80000001u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000001u, 0x80000002u, 0x00000000u, 0x80000002u}}},
|
||||
{{0x80000002u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000002u, 0x80000004u, 0x00000000u, 0x80000003u}}},
|
||||
{{0x80000002u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000002u, 0x80000004u, 0x00000000u, 0x80000003u}}},
|
||||
{{0x80000003u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000003u, 0x80000006u, 0x00000000u, 0x80000004u}}},
|
||||
{{0x80000003u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000003u, 0x80000006u, 0x00000000u, 0x80000004u}}},
|
||||
{{0x80155555u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80155555u, 0x802aaaaau, 0x00000000u, 0x80200000u}}},
|
||||
{{0x80155555u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80155555u, 0x802aaaaau, 0x00000000u, 0x80200000u}}},
|
||||
{{0x803fffffu, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x803fffffu, 0x807ffffeu, 0x00000000u, 0x805ffffeu}}},
|
||||
{{0x803fffffu, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x803fffffu, 0x807ffffeu, 0x00000000u, 0x805ffffeu}}},
|
||||
{{0x80400000u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80800000u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80400000u, 0x80800000u, 0x00000000u, 0x80600000u}}},
|
||||
{{0x80400000u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80800000u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80400000u, 0x80800000u, 0x00000000u, 0x80600000u}}},
|
||||
{{0x80400001u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80800002u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80400001u, 0x80800002u, 0x00000000u, 0x80600002u}}},
|
||||
{{0x80400001u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80800002u, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80400001u, 0x80800002u, 0x00000000u, 0x80600002u}}},
|
||||
{{0x80555555u, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80aaaaaau, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80555555u, 0x80aaaaaau, 0x00000000u, 0x80800000u}}},
|
||||
{{0x80555555u, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80aaaaaau, 0x00000000u, 0x80000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80555555u, 0x80aaaaaau, 0x00000000u, 0x80800000u}}},
|
||||
{{0x807ffffeu, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80fffffcu, 0x00000000u, 0x80bffffdu}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x807ffffeu, 0x80fffffcu, 0x00000000u, 0x80bffffdu}}},
|
||||
{{0x807ffffeu, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80fffffcu, 0x00000000u, 0x80bffffdu}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x807ffffeu, 0x80fffffcu, 0x00000000u, 0x80bffffdu}}},
|
||||
{{0x807fffffu, 0x00000001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80fffffeu, 0x00000000u, 0x80bffffeu}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x807fffffu, 0x80fffffeu, 0x00000000u, 0x80bffffeu}}},
|
||||
{{0x807fffffu, 0x00800001u, 0x3f800000u}, {{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x80000000u, 0x80fffffeu, 0x00000000u, 0x80bffffeu}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, {0x807fffffu, 0x80fffffeu, 0x00000000u, 0x80bffffeu}}},
|
||||
};
|
||||
|
||||
static_assert(std::size(Vectors) <= Threads && std::size(ConstantVectors) <= Threads);
|
||||
|
||||
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::string Hex(std::uint32_t value) {
|
||||
char text[16];
|
||||
std::snprintf(text, sizeof(text), "0x%08x", value);
|
||||
return text;
|
||||
}
|
||||
|
||||
bool KeepsDenormals(const AgcDriver::VulkanDevice& device) {
|
||||
const auto target = device.Target();
|
||||
return target.float32DenormPreserve && !target.float32FlushToZero;
|
||||
}
|
||||
|
||||
bool Run(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> code, std::uint32_t denorm32, bool ieee) {
|
||||
Output.fill(0xdeadbeefu);
|
||||
std::vector<std::uint32_t> userData(8, 0u);
|
||||
const auto input = BufferDescriptor(Input.data(), static_cast<std::uint32_t>(Input.size() * 4u));
|
||||
const auto output = BufferDescriptor(Output.data(), static_cast<std::uint32_t>(Output.size() * 4u));
|
||||
std::copy(input.begin(), input.end(), userData.begin());
|
||||
std::copy(output.begin(), output.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;
|
||||
request.context.floatMode = ShaderRecompiler::ShaderFloatMode{(denorm32 << 4u) | (3u << 6u), true, ieee, false};
|
||||
std::optional<decltype(ShaderRecompiler::Recompile(request))> result;
|
||||
try {
|
||||
result.emplace(ShaderRecompiler::Recompile(request));
|
||||
} catch (const std::exception& error) {
|
||||
const std::string message = error.what();
|
||||
Require(!KeepsDenormals(device) && (denorm32 == 1u || denorm32 == 2u) && message.find("needs DenormPreserve 32") != std::string::npos,
|
||||
"v_mullit_f32 denorm32 " + std::to_string(denorm32) + ": " + message);
|
||||
return false;
|
||||
}
|
||||
device.Dispatch(*result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
|
||||
device.WaitIdle();
|
||||
return true;
|
||||
}
|
||||
|
||||
void CheckRegisters(std::uint32_t denorm32, bool ieee) {
|
||||
for (std::uint32_t row = 0; row < std::size(Vectors); ++row) {
|
||||
const auto& vector = Vectors[row];
|
||||
const auto actual = Output[row];
|
||||
const auto expected = vector.expected[denorm32 * 2u + (ieee ? 1u : 0u)];
|
||||
Require(actual == expected, "v_mullit_f32 denorm32 " + std::to_string(denorm32) + (ieee ? " ieee" : " no ieee") + " inputs " + Hex(vector.lhs) + " " + Hex(vector.rhs) + " " +
|
||||
Hex(vector.limit) + " is " + Hex(actual) + ", expected " + Hex(expected));
|
||||
}
|
||||
}
|
||||
|
||||
void CheckConstants(std::uint32_t denorm32, bool ieee) {
|
||||
for (std::uint32_t row = 0; row < std::size(ConstantVectors); ++row) {
|
||||
const auto& vector = ConstantVectors[row];
|
||||
for (std::uint32_t form = 0; form < ConstantForms; ++form) {
|
||||
const auto actual = Output[row * ConstantForms + form];
|
||||
const auto expected = vector.expected[denorm32][form];
|
||||
Require(actual == expected, std::string(ConstantNames[form]) + " denorm32 " + std::to_string(denorm32) + (ieee ? " ieee" : " no ieee") + " inputs " + Hex(vector.inputs[0]) + " " +
|
||||
Hex(vector.inputs[1]) + " " + Hex(vector.inputs[2]) + " is " + Hex(actual) + ", expected " + Hex(expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
const bool keeps = KeepsDenormals(*device);
|
||||
std::uint32_t checked = 0u;
|
||||
for (std::uint32_t denorm32 = 0u; denorm32 < 4u; ++denorm32) {
|
||||
if (denorm32 == 3u && !keeps) continue;
|
||||
for (const bool ieee : {false, true}) {
|
||||
Input.fill(0u);
|
||||
for (std::uint32_t row = 0; row < std::size(Vectors); ++row) {
|
||||
Input[row * Inputs] = Vectors[row].lhs;
|
||||
Input[row * Inputs + 1] = Vectors[row].rhs;
|
||||
Input[row * Inputs + 2] = Vectors[row].limit;
|
||||
}
|
||||
if (Run(*device, MullitCode, denorm32, ieee)) {
|
||||
CheckRegisters(denorm32, ieee);
|
||||
++checked;
|
||||
}
|
||||
Input.fill(0u);
|
||||
for (std::uint32_t row = 0; row < std::size(ConstantVectors); ++row) std::copy(std::begin(ConstantVectors[row].inputs), std::end(ConstantVectors[row].inputs), Input.begin() + row * Inputs);
|
||||
if (Run(*device, ConstantMullitCode, denorm32, ieee)) {
|
||||
CheckConstants(denorm32, ieee);
|
||||
++checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::printf("v_mullit_f32 denormal mode tests passed (%u of 16 runs checked; denormal modes 1 and 2 need DenormPreserve 32, mode 3 is checked only where the device keeps f32 denormals)\n", checked);
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,8 @@ public:
|
||||
Device() {
|
||||
#ifdef _WIN32
|
||||
library = SDL_LoadObject("vulkan-1.dll");
|
||||
#elif defined(__APPLE__)
|
||||
library = SDL_LoadObject("libvulkan.1.dylib");
|
||||
#else
|
||||
library = SDL_LoadObject("libvulkan.so.1");
|
||||
#endif
|
||||
@@ -66,6 +68,12 @@ public:
|
||||
app.apiVersion = VK_API_VERSION_1_1;
|
||||
VkInstanceCreateInfo info{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
|
||||
info.pApplicationInfo = &app;
|
||||
#ifdef __APPLE__
|
||||
const char* portability = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME;
|
||||
info.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
|
||||
info.enabledExtensionCount = 1;
|
||||
info.ppEnabledExtensionNames = &portability;
|
||||
#endif
|
||||
Check(function<PFN_vkCreateInstance>("vkCreateInstance")(&info, nullptr, &instance), "vkCreateInstance");
|
||||
std::uint32_t count = 0u;
|
||||
const auto enumerate = function<PFN_vkEnumeratePhysicalDevices>("vkEnumeratePhysicalDevices");
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
#if defined(ANYPS5_ENABLE_SPIRV_TOOLS) && ANYPS5_ENABLE_SPIRV_TOOLS
|
||||
#include "SpirvBackend/SpirvOptimizer.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include "SceTypes.hpp"
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using GuestFiberEntry = void (APS5_VABI*)(std::uint64_t, std::uint64_t);
|
||||
|
||||
extern "C" {
|
||||
std::int32_t APS5_VABI _sceFiberInitializeImpl_nid_postfix(FiberObject*, const char*, GuestFiberEntry, std::uint64_t, void*, std::uint64_t, const void*, std::uint32_t);
|
||||
std::int32_t APS5_VABI sceFiberFinalize(FiberObject*);
|
||||
std::int32_t APS5_VABI sceFiberRun_nid_postfix(FiberObject*, std::uint64_t, std::uint64_t*);
|
||||
std::int32_t APS5_VABI sceFiberReturnToThread(std::uint64_t, std::uint64_t*);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::size_t ContextBytes = 64 * 1024;
|
||||
constexpr std::size_t FiberStackLimit = 8 * 1024;
|
||||
constexpr std::uint32_t VulkanVersion = 0x00401000u;
|
||||
constexpr std::uint32_t SpirvVersion = 0x00010300u;
|
||||
constexpr std::uint64_t Fill = 0x5aa5c33c5aa5c33cull;
|
||||
|
||||
alignas(16) std::array<std::uint64_t, ContextBytes / sizeof(std::uint64_t)> FiberContext;
|
||||
struct alignas(8) FiberStorage {
|
||||
unsigned char bytes[0x100];
|
||||
};
|
||||
FiberStorage FiberObjectStorage;
|
||||
|
||||
constexpr std::array<std::uint32_t, 410> Module{
|
||||
0x07230203u, 0x00010300u, 0x0008000bu, 0x00000044u, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu,
|
||||
0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u,
|
||||
0x0006000fu, 0x00000005u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000bu, 0x00060010u, 0x00000004u,
|
||||
0x00000011u, 0x00000040u, 0x00000001u, 0x00000001u, 0x00030003u, 0x00000002u, 0x000001c2u, 0x00040005u,
|
||||
0x00000004u, 0x6e69616du, 0x00000000u, 0x00040005u, 0x00000008u, 0x65646e69u, 0x00000078u, 0x00080005u,
|
||||
0x0000000bu, 0x475f6c67u, 0x61626f6cu, 0x766e496cu, 0x7461636fu, 0x496e6f69u, 0x00000044u, 0x00030005u,
|
||||
0x00000010u, 0x006d7573u, 0x00040005u, 0x00000011u, 0x74617473u, 0x00000065u, 0x00040005u, 0x00000013u,
|
||||
0x61746144u, 0x00000000u, 0x00050006u, 0x00000013u, 0x00000000u, 0x756c6176u, 0x00007365u, 0x00030005u,
|
||||
0x00000015u, 0x00000000u, 0x00030005u, 0x0000001cu, 0x00000069u, 0x00040047u, 0x0000000bu, 0x0000000bu,
|
||||
0x0000001cu, 0x00040047u, 0x00000012u, 0x00000006u, 0x00000004u, 0x00030047u, 0x00000013u, 0x00000002u,
|
||||
0x00050048u, 0x00000013u, 0x00000000u, 0x00000023u, 0x00000000u, 0x00040047u, 0x00000015u, 0x00000021u,
|
||||
0x00000000u, 0x00040047u, 0x00000015u, 0x00000022u, 0x00000000u, 0x00040047u, 0x00000043u, 0x0000000bu,
|
||||
0x00000019u, 0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, 0x00040015u, 0x00000006u,
|
||||
0x00000020u, 0x00000000u, 0x00040020u, 0x00000007u, 0x00000007u, 0x00000006u, 0x00040017u, 0x00000009u,
|
||||
0x00000006u, 0x00000003u, 0x00040020u, 0x0000000au, 0x00000001u, 0x00000009u, 0x0004003bu, 0x0000000au,
|
||||
0x0000000bu, 0x00000001u, 0x0004002bu, 0x00000006u, 0x0000000cu, 0x00000000u, 0x00040020u, 0x0000000du,
|
||||
0x00000001u, 0x00000006u, 0x0003001du, 0x00000012u, 0x00000006u, 0x0003001eu, 0x00000013u, 0x00000012u,
|
||||
0x00040020u, 0x00000014u, 0x0000000cu, 0x00000013u, 0x0004003bu, 0x00000014u, 0x00000015u, 0x0000000cu,
|
||||
0x00040015u, 0x00000016u, 0x00000020u, 0x00000001u, 0x0004002bu, 0x00000016u, 0x00000017u, 0x00000000u,
|
||||
0x00040020u, 0x00000019u, 0x0000000cu, 0x00000006u, 0x0004002bu, 0x00000006u, 0x00000023u, 0x00000010u,
|
||||
0x00020014u, 0x00000024u, 0x0004002bu, 0x00000006u, 0x00000027u, 0x00000001u, 0x0004002bu, 0x00000006u,
|
||||
0x00000038u, 0x0019660du, 0x0004002bu, 0x00000006u, 0x0000003au, 0x3c6ef35fu, 0x0004002bu, 0x00000016u,
|
||||
0x0000003du, 0x00000001u, 0x0004002bu, 0x00000006u, 0x00000042u, 0x00000040u, 0x0006002cu, 0x00000009u,
|
||||
0x00000043u, 0x00000042u, 0x00000027u, 0x00000027u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u,
|
||||
0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003bu, 0x00000007u, 0x00000008u, 0x00000007u, 0x0004003bu,
|
||||
0x00000007u, 0x00000010u, 0x00000007u, 0x0004003bu, 0x00000007u, 0x00000011u, 0x00000007u, 0x0004003bu,
|
||||
0x00000007u, 0x0000001cu, 0x00000007u, 0x00050041u, 0x0000000du, 0x0000000eu, 0x0000000bu, 0x0000000cu,
|
||||
0x0004003du, 0x00000006u, 0x0000000fu, 0x0000000eu, 0x0003003eu, 0x00000008u, 0x0000000fu, 0x0003003eu,
|
||||
0x00000010u, 0x0000000cu, 0x0004003du, 0x00000006u, 0x00000018u, 0x00000008u, 0x00060041u, 0x00000019u,
|
||||
0x0000001au, 0x00000015u, 0x00000017u, 0x00000018u, 0x0004003du, 0x00000006u, 0x0000001bu, 0x0000001au,
|
||||
0x0003003eu, 0x00000011u, 0x0000001bu, 0x0003003eu, 0x0000001cu, 0x0000000cu, 0x000200f9u, 0x0000001du,
|
||||
0x000200f8u, 0x0000001du, 0x000400f6u, 0x0000001fu, 0x00000020u, 0x00000000u, 0x000200f9u, 0x00000021u,
|
||||
0x000200f8u, 0x00000021u, 0x0004003du, 0x00000006u, 0x00000022u, 0x0000001cu, 0x000500b0u, 0x00000024u,
|
||||
0x00000025u, 0x00000022u, 0x00000023u, 0x000400fau, 0x00000025u, 0x0000001eu, 0x0000001fu, 0x000200f8u,
|
||||
0x0000001eu, 0x0004003du, 0x00000006u, 0x00000026u, 0x00000011u, 0x000500c7u, 0x00000006u, 0x00000028u,
|
||||
0x00000026u, 0x00000027u, 0x000500abu, 0x00000024u, 0x00000029u, 0x00000028u, 0x0000000cu, 0x000300f7u,
|
||||
0x0000002bu, 0x00000000u, 0x000400fau, 0x00000029u, 0x0000002au, 0x00000031u, 0x000200f8u, 0x0000002au,
|
||||
0x0004003du, 0x00000006u, 0x0000002cu, 0x00000011u, 0x0004003du, 0x00000006u, 0x0000002du, 0x0000001cu,
|
||||
0x00050084u, 0x00000006u, 0x0000002eu, 0x0000002cu, 0x0000002du, 0x0004003du, 0x00000006u, 0x0000002fu,
|
||||
0x00000010u, 0x00050080u, 0x00000006u, 0x00000030u, 0x0000002fu, 0x0000002eu, 0x0003003eu, 0x00000010u,
|
||||
0x00000030u, 0x000200f9u, 0x0000002bu, 0x000200f8u, 0x00000031u, 0x0004003du, 0x00000006u, 0x00000032u,
|
||||
0x00000011u, 0x0004003du, 0x00000006u, 0x00000033u, 0x0000001cu, 0x00050080u, 0x00000006u, 0x00000034u,
|
||||
0x00000032u, 0x00000033u, 0x0004003du, 0x00000006u, 0x00000035u, 0x00000010u, 0x000500c6u, 0x00000006u,
|
||||
0x00000036u, 0x00000035u, 0x00000034u, 0x0003003eu, 0x00000010u, 0x00000036u, 0x000200f9u, 0x0000002bu,
|
||||
0x000200f8u, 0x0000002bu, 0x0004003du, 0x00000006u, 0x00000037u, 0x00000011u, 0x00050084u, 0x00000006u,
|
||||
0x00000039u, 0x00000037u, 0x00000038u, 0x00050080u, 0x00000006u, 0x0000003bu, 0x00000039u, 0x0000003au,
|
||||
0x0003003eu, 0x00000011u, 0x0000003bu, 0x000200f9u, 0x00000020u, 0x000200f8u, 0x00000020u, 0x0004003du,
|
||||
0x00000006u, 0x0000003cu, 0x0000001cu, 0x00050080u, 0x00000006u, 0x0000003eu, 0x0000003cu, 0x0000003du,
|
||||
0x0003003eu, 0x0000001cu, 0x0000003eu, 0x000200f9u, 0x0000001du, 0x000200f8u, 0x0000001fu, 0x0004003du,
|
||||
0x00000006u, 0x0000003fu, 0x00000008u, 0x0004003du, 0x00000006u, 0x00000040u, 0x00000010u, 0x00060041u,
|
||||
0x00000019u, 0x00000041u, 0x00000015u, 0x00000017u, 0x0000003fu, 0x0003003eu, 0x00000041u, 0x00000040u,
|
||||
0x000100fdu, 0x00010038u
|
||||
};
|
||||
|
||||
std::vector<std::uint32_t> Optimized;
|
||||
std::exception_ptr Failure;
|
||||
|
||||
void Require(bool condition, const std::string& message) {
|
||||
if (!condition) throw std::runtime_error(message);
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> Optimize() {
|
||||
return ShaderRecompiler::ValidateAndOptimizeSpirv(Module, VulkanVersion, SpirvVersion);
|
||||
}
|
||||
|
||||
void APS5_VABI OptimizeOnFiber(std::uint64_t, std::uint64_t) {
|
||||
try {
|
||||
Optimized = Optimize();
|
||||
} catch (...) {
|
||||
Failure = std::current_exception();
|
||||
}
|
||||
sceFiberReturnToThread(0, nullptr);
|
||||
}
|
||||
|
||||
std::size_t FiberStackUsed() {
|
||||
std::size_t untouched = 0;
|
||||
while (untouched < FiberContext.size() && FiberContext[untouched] == Fill) ++untouched;
|
||||
return (FiberContext.size() - untouched) * sizeof(std::uint64_t);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
FiberContext.fill(Fill);
|
||||
auto* fiber = reinterpret_cast<FiberObject*>(&FiberObjectStorage);
|
||||
Require(_sceFiberInitializeImpl_nid_postfix(fiber, "optimize", OptimizeOnFiber, 0, FiberContext.data(), ContextBytes, nullptr, 0) == 0, "SPIR-V optimization on a fiber: the fiber was not initialized");
|
||||
const auto before = ShaderRecompiler::OptimizerHostThreads();
|
||||
Require(sceFiberRun_nid_postfix(fiber, 0, nullptr) == 0, "SPIR-V optimization on a fiber: the fiber did not run");
|
||||
const auto onFiber = ShaderRecompiler::OptimizerHostThreads() - before;
|
||||
Require(sceFiberFinalize(fiber) == 0, "SPIR-V optimization on a fiber: the fiber was not finalized");
|
||||
if (Failure) std::rethrow_exception(Failure);
|
||||
const auto used = FiberStackUsed();
|
||||
const auto beforeThread = ShaderRecompiler::OptimizerHostThreads();
|
||||
const auto onThreadResult = Optimize();
|
||||
const auto onThread = ShaderRecompiler::OptimizerHostThreads() - beforeThread;
|
||||
Require(Optimized == onThreadResult, "SPIR-V optimization on a fiber returned another module than on the thread");
|
||||
Require(used <= FiberStackLimit, "SPIR-V optimization on a fiber running on its context used " + std::to_string(used) + " bytes of it, more than " + std::to_string(FiberStackLimit) + ": the optimizer ran on it");
|
||||
Require(onFiber == 1, "SPIR-V optimization on a fiber running on its context started " + std::to_string(onFiber) + " host threads instead of one");
|
||||
Require(onThread == 0, "SPIR-V optimization on an ordinary thread started " + std::to_string(onThread) + " host threads instead of optimizing on its own stack");
|
||||
std::cout << "SPIR-V optimization on a fiber used " << used << " bytes of its context\n";
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
#else
|
||||
int main() {
|
||||
return 77;
|
||||
}
|
||||
#endif
|
||||
@@ -55,6 +55,8 @@ public:
|
||||
Device() {
|
||||
#ifdef _WIN32
|
||||
library = SDL_LoadObject("vulkan-1.dll");
|
||||
#elif defined(__APPLE__)
|
||||
library = SDL_LoadObject("libvulkan.1.dylib");
|
||||
#else
|
||||
library = SDL_LoadObject("libvulkan.so.1");
|
||||
#endif
|
||||
@@ -66,6 +68,12 @@ public:
|
||||
application.apiVersion = VK_API_VERSION_1_1;
|
||||
VkInstanceCreateInfo info{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
|
||||
info.pApplicationInfo = &application;
|
||||
#ifdef __APPLE__
|
||||
const char* portability = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME;
|
||||
info.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
|
||||
info.enabledExtensionCount = 1;
|
||||
info.ppEnabledExtensionNames = &portability;
|
||||
#endif
|
||||
Check(function<PFN_vkCreateInstance>("vkCreateInstance")(&info, nullptr, &instance), "vkCreateInstance");
|
||||
std::uint32_t count = 0;
|
||||
const auto enumerate = function<PFN_vkEnumeratePhysicalDevices>("vkEnumeratePhysicalDevices");
|
||||
|
||||
@@ -144,6 +144,8 @@ public:
|
||||
Device() {
|
||||
#ifdef _WIN32
|
||||
library = SDL_LoadObject("vulkan-1.dll");
|
||||
#elif defined(__APPLE__)
|
||||
library = SDL_LoadObject("libvulkan.1.dylib");
|
||||
#else
|
||||
library = SDL_LoadObject("libvulkan.so.1");
|
||||
#endif
|
||||
@@ -155,6 +157,12 @@ public:
|
||||
application.apiVersion = VK_API_VERSION_1_1;
|
||||
VkInstanceCreateInfo info{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
|
||||
info.pApplicationInfo = &application;
|
||||
#ifdef __APPLE__
|
||||
const char* portability = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME;
|
||||
info.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
|
||||
info.enabledExtensionCount = 1;
|
||||
info.ppEnabledExtensionNames = &portability;
|
||||
#endif
|
||||
Check(function<PFN_vkCreateInstance>("vkCreateInstance")(&info, nullptr, &instance), "vkCreateInstance");
|
||||
std::uint32_t count = 0;
|
||||
const auto enumerate = function<PFN_vkEnumeratePhysicalDevices>("vkEnumeratePhysicalDevices");
|
||||
|
||||
@@ -41,6 +41,8 @@ public:
|
||||
Device() {
|
||||
#ifdef _WIN32
|
||||
library = SDL_LoadObject("vulkan-1.dll");
|
||||
#elif defined(__APPLE__)
|
||||
library = SDL_LoadObject("libvulkan.1.dylib");
|
||||
#else
|
||||
library = SDL_LoadObject("libvulkan.so.1");
|
||||
#endif
|
||||
@@ -52,6 +54,12 @@ public:
|
||||
application.apiVersion = VK_API_VERSION_1_1;
|
||||
VkInstanceCreateInfo info{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
|
||||
info.pApplicationInfo = &application;
|
||||
#ifdef __APPLE__
|
||||
const char* portability = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME;
|
||||
info.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
|
||||
info.enabledExtensionCount = 1;
|
||||
info.ppEnabledExtensionNames = &portability;
|
||||
#endif
|
||||
Check(function<PFN_vkCreateInstance>("vkCreateInstance")(&info, nullptr, &instance), "vkCreateInstance");
|
||||
std::uint32_t count = 0;
|
||||
const auto enumerate = function<PFN_vkEnumeratePhysicalDevices>("vkEnumeratePhysicalDevices");
|
||||
|
||||
@@ -3,6 +3,19 @@ target_include_directories(libSceLibcInternal PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
set_source_files_properties(UserContext.cpp PROPERTIES COMPILE_OPTIONS -fno-omit-frame-pointer)
|
||||
target_link_libraries(libSceLibcInternal PRIVATE ${CMAKE_DL_LIBS})
|
||||
target_sources(libSceLibcInternal PRIVATE Directory.cpp)
|
||||
target_sources(libSceLibcInternal PRIVATE InitOnce.cpp)
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(guest_init_once_tests ${LIBS_INCLUDE_DIR}/tests/GuestInitOnce.cpp)
|
||||
target_include_directories(guest_init_once_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_init_once_tests PRIVATE libSceLibcInternal libkernel libc)
|
||||
configure_windows_unwind(guest_init_once_tests)
|
||||
add_test(NAME guest_init_once COMMAND guest_init_once_tests)
|
||||
set_tests_properties(guest_init_once PROPERTIES TIMEOUT 20)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_init_once PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceLibcInternal>")
|
||||
endif()
|
||||
endif()
|
||||
target_sources(libSceLibcInternal PRIVATE Err.cpp)
|
||||
configure_windows_unwind(libSceLibcInternal)
|
||||
target_sources(libSceLibcInternal PRIVATE FilesystemStatistics.cpp PathStatistics.cpp)
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <atomic>
|
||||
#include <climits>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
|
||||
extern "C" int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name);
|
||||
extern "C" int APS5_VABI sceKernelSyncOnAddressWake(void* address, std::int32_t count);
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::uintptr_t OnceIdle = 0;
|
||||
constexpr std::uintptr_t OnceRunning = 1;
|
||||
constexpr std::uintptr_t OnceDone = 2;
|
||||
constexpr unsigned long InitFailed = 4;
|
||||
|
||||
std::atomic<std::uintptr_t>& OnceState(void** once) {
|
||||
return *reinterpret_cast<std::atomic<std::uintptr_t>*>(once);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI __std_init_once_begin_initialize_clr_nid_postfix(void** once, unsigned long flags, int* pending, void** context) {
|
||||
(void)flags;
|
||||
(void)context;
|
||||
auto& state = OnceState(once);
|
||||
for (;;) {
|
||||
std::uintptr_t current = state.load(std::memory_order_acquire);
|
||||
if (current == OnceIdle) {
|
||||
if (state.compare_exchange_weak(current, OnceRunning, std::memory_order_acq_rel)) {
|
||||
*pending = 1;
|
||||
return 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (current == OnceDone) {
|
||||
*pending = 0;
|
||||
return 1;
|
||||
}
|
||||
sceKernelSyncOnAddressWait(reinterpret_cast<std::uint32_t*>(once), static_cast<std::uint32_t>(OnceRunning), nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
int APS5_VABI __std_init_once_complete_clr_nid_postfix(void** once, unsigned long flags, void* context) {
|
||||
(void)context;
|
||||
OnceState(once).store(flags == InitFailed ? OnceIdle : OnceDone, std::memory_order_release);
|
||||
sceKernelSyncOnAddressWake(once, INT_MAX);
|
||||
return 1;
|
||||
}
|
||||
|
||||
[[noreturn]] void APS5_VABI __std_init_once_link_alternate_names_and_abort_nid_postfix() {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -82,10 +82,16 @@ if(BUILD_TESTING)
|
||||
target_link_libraries(guest_sce_net_tests PRIVATE libSceNet libSceNetCtl libkernel libc)
|
||||
configure_windows_unwind(guest_sce_net_tests)
|
||||
add_test(NAME guest_sce_net COMMAND guest_sce_net_tests)
|
||||
add_test_executable(guest_net_thread_tests ${LIBS_INCLUDE_DIR}/tests/GuestNetThread.cpp)
|
||||
target_include_directories(guest_net_thread_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_net_thread_tests PRIVATE libSceNet libkernel libc)
|
||||
configure_windows_unwind(guest_net_thread_tests)
|
||||
add_test(NAME guest_net_thread COMMAND guest_net_thread_tests)
|
||||
set_tests_properties(guest_net_thread PROPERTIES TIMEOUT 20)
|
||||
set_tests_properties(guest_sce_net PROPERTIES TIMEOUT 20 SKIP_RETURN_CODE 77)
|
||||
if(WIN32)
|
||||
target_link_libraries(guest_sce_net_tests PRIVATE ws2_32)
|
||||
set_tests_properties(guest_sce_net PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
set_tests_properties(guest_sce_net guest_net_thread PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceNet>")
|
||||
endif()
|
||||
add_test_executable(net_epoll_lifetime_tests ${LIBS_INCLUDE_DIR}/tests/NetEpollLifetime.cpp)
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
#include "prx/libkernel/Socket/include/SocketPoll.hpp"
|
||||
#include "prx/libkernel/Socket/include/SocketRuntime.hpp"
|
||||
#include "prx/libkernel/Time/include/Time.hpp"
|
||||
#include "prx/libkernel/Pthread/include/ThreadLifecycle.hpp"
|
||||
#include "src/ResolverTestHooks.hpp"
|
||||
|
||||
// Guest socket descriptors retain PS5 semantics while their transport is backed by host sockets.
|
||||
@@ -97,7 +98,12 @@ struct NetDataTraffic;
|
||||
struct NetEmulationParameter;
|
||||
|
||||
struct NetResolverConnection;
|
||||
struct NetThreadParameter;
|
||||
struct NetThreadParameter {
|
||||
void (APS5_VABI *entry)(void*);
|
||||
void* arg;
|
||||
std::uint64_t reserved;
|
||||
std::uint32_t flags;
|
||||
};
|
||||
struct NetIpv4InterfaceAddress;
|
||||
struct NetStatisticsInfoInternal;
|
||||
struct NetInitializeParameter;
|
||||
@@ -501,6 +507,15 @@ int fail(int err) {
|
||||
return NET_ERROR_BASE | err;
|
||||
}
|
||||
|
||||
extern "C" int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name);
|
||||
extern "C" int APS5_VABI scePthreadJoin(Pthread thread, void** retval);
|
||||
|
||||
void* APS5_VABI RunNetThread(void* param) {
|
||||
const auto* thread = static_cast<const NetThreadParameter*>(param);
|
||||
thread->entry(thread->arg);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int set_native_timeout(NativeSocket native, int optname, std::int64_t microseconds) {
|
||||
const int native_option = optname == NET_SO_RCVTIMEO ? SO_RCVTIMEO : SO_SNDTIMEO;
|
||||
#ifdef _WIN32
|
||||
@@ -1656,7 +1671,9 @@ int APS5_VABI sceNetGetpeername(int s, void* addr, uint32_t* addrlen) {
|
||||
|
||||
constexpr int NET_SOCKINFO_F_SELF = 0x1;
|
||||
constexpr int NET_SOCKINFO_F_NONBLOCK = 0x200000;
|
||||
constexpr int NET_SOCKINFO_F_IPV6 = 0x400000;
|
||||
constexpr int NET_SOCKINFO_TIME_WAIT = 0x40;
|
||||
constexpr int NET_SOCKINFO6_IPV46 = 0x1000;
|
||||
|
||||
struct NetSockInfo {
|
||||
char name[32];
|
||||
@@ -1693,6 +1710,44 @@ struct NetSockInfo {
|
||||
static_assert(sizeof(NetSockInfo) == 0xA0 && offsetof(NetSockInfo, s) == 0x24 && offsetof(NetSockInfo, local_adr) == 0x34 &&
|
||||
offsetof(NetSockInfo, local_port) == 0x3C && offsetof(NetSockInfo, recv_buffer_size) == 0x64);
|
||||
|
||||
struct NetSockInfo6 {
|
||||
char name[32];
|
||||
std::int32_t pid;
|
||||
std::int32_t s;
|
||||
std::int8_t socket_type;
|
||||
std::int8_t policy;
|
||||
std::int8_t priority;
|
||||
std::int8_t reserved8;
|
||||
std::int32_t recv_queue_length;
|
||||
std::int32_t send_queue_length;
|
||||
std::uint32_t local_adr;
|
||||
std::uint32_t remote_adr;
|
||||
std::uint16_t local_port;
|
||||
std::uint16_t remote_port;
|
||||
std::uint16_t local_vport;
|
||||
std::uint16_t remote_vport;
|
||||
std::int32_t state;
|
||||
std::int32_t flags;
|
||||
std::int32_t tx_bps;
|
||||
std::int32_t rx_bps;
|
||||
std::int32_t max_tx_bps;
|
||||
std::int32_t max_rx_bps;
|
||||
std::int32_t tx_vbps;
|
||||
std::int32_t rx_vbps;
|
||||
std::int32_t recv_buffer_size;
|
||||
std::int32_t send_buffer_size;
|
||||
std::uint8_t local_adr6[16];
|
||||
std::uint8_t remote_adr6[16];
|
||||
std::int32_t tx_drops;
|
||||
std::int32_t rx_drops;
|
||||
std::int32_t tx_wait;
|
||||
std::int32_t reserved[2];
|
||||
};
|
||||
static_assert(sizeof(NetSockInfo6) == sizeof(NetSockInfo) && offsetof(NetSockInfo6, s) == offsetof(NetSockInfo, s) &&
|
||||
offsetof(NetSockInfo6, local_port) == offsetof(NetSockInfo, local_port) && offsetof(NetSockInfo6, flags) == offsetof(NetSockInfo, flags) &&
|
||||
offsetof(NetSockInfo6, recv_buffer_size) == offsetof(NetSockInfo, recv_buffer_size) && offsetof(NetSockInfo6, local_adr6) == 0x6C &&
|
||||
offsetof(NetSockInfo6, remote_adr6) == 0x7C && offsetof(NetSockInfo6, tx_drops) == offsetof(NetSockInfo, tx_drops));
|
||||
|
||||
static int sock_buffer_size(const Sock& socket, bool send, std::int32_t& value) {
|
||||
value = send ? socket.snd_buffer : socket.rcv_buffer;
|
||||
if (value != 0) return 0;
|
||||
@@ -1766,6 +1821,86 @@ int APS5_VABI sceNetGetSockInfo(int s, void* info, int n, int flags) {
|
||||
return written;
|
||||
}
|
||||
|
||||
static int fill_sock_info6(int s, NetSockInfo6& record, bool includeIpv4) {
|
||||
Sock socket;
|
||||
{
|
||||
std::lock_guard lk(g_mutex);
|
||||
const auto it = g_socks.find(s);
|
||||
if (it == g_socks.end()) return NET_EBADF;
|
||||
socket = it->second;
|
||||
}
|
||||
const bool ipv6 = socket.family == NET_AF_INET6;
|
||||
if (!ipv6 && !(includeIpv4 && socket.family == NET_AF_INET)) return NET_EAFNOSUPPORT;
|
||||
sockaddr_storage local{};
|
||||
NativeLength length = sizeof(local);
|
||||
if (::getsockname(socket.native->value, reinterpret_cast<sockaddr*>(&local), &length) != 0) {
|
||||
#ifdef _WIN32
|
||||
if (WSAGetLastError() != WSAEINVAL) return native_error();
|
||||
#else
|
||||
return native_error();
|
||||
#endif
|
||||
}
|
||||
sockaddr_storage remote{};
|
||||
length = sizeof(remote);
|
||||
if (::getpeername(socket.native->value, reinterpret_cast<sockaddr*>(&remote), &length) != 0) {
|
||||
const int error = native_error();
|
||||
if (error != NET_ENOTCONN) return error;
|
||||
}
|
||||
record = {};
|
||||
if (const int error = sock_buffer_size(socket, false, record.recv_buffer_size)) return error;
|
||||
if (const int error = sock_buffer_size(socket, true, record.send_buffer_size)) return error;
|
||||
record.s = s;
|
||||
record.socket_type = static_cast<std::int8_t>(socket.type);
|
||||
record.flags = NET_SOCKINFO_F_SELF | (socket.native->nonblock ? NET_SOCKINFO_F_NONBLOCK : 0);
|
||||
if (ipv6) {
|
||||
const auto& local6 = reinterpret_cast<const sockaddr_in6&>(local);
|
||||
const auto& remote6 = reinterpret_cast<const sockaddr_in6&>(remote);
|
||||
std::memcpy(record.local_adr6, &local6.sin6_addr, sizeof(record.local_adr6));
|
||||
std::memcpy(record.remote_adr6, &remote6.sin6_addr, sizeof(record.remote_adr6));
|
||||
record.local_port = local6.sin6_port;
|
||||
record.remote_port = remote6.sin6_port;
|
||||
record.flags |= NET_SOCKINFO_F_IPV6;
|
||||
} else {
|
||||
const auto& local4 = reinterpret_cast<const sockaddr_in&>(local);
|
||||
const auto& remote4 = reinterpret_cast<const sockaddr_in&>(remote);
|
||||
record.local_adr = local4.sin_addr.s_addr;
|
||||
record.remote_adr = remote4.sin_addr.s_addr;
|
||||
record.local_port = local4.sin_port;
|
||||
record.remote_port = remote4.sin_port;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNetGetSockInfo6(std::int32_t s, NetSockInfo6* info, std::uint32_t capacity, std::uint32_t flags) {
|
||||
if ((flags & ~(NET_SOCKINFO_TIME_WAIT | NET_SOCKINFO6_IPV46)) != 0) return fail(NET_EINVAL);
|
||||
const bool includeIpv4 = (flags & NET_SOCKINFO6_IPV46) != 0;
|
||||
std::vector<int> listed;
|
||||
if (s < 0) {
|
||||
std::lock_guard lk(g_mutex);
|
||||
for (const auto& [descriptor, socket] : g_socks) {
|
||||
if (socket.family == NET_AF_INET6 || (includeIpv4 && socket.family == NET_AF_INET)) listed.push_back(descriptor);
|
||||
}
|
||||
}
|
||||
if (!info) return s < 0 ? static_cast<int>(listed.size()) : fail(NET_EINVAL);
|
||||
if (static_cast<std::int32_t>(capacity) < 1) return fail(NET_EINVAL);
|
||||
NetSockInfo6 record;
|
||||
if (s >= 0) {
|
||||
if (const int error = fill_sock_info6(s, record, includeIpv4)) return fail(error);
|
||||
*info = record;
|
||||
return 1;
|
||||
}
|
||||
int written = 0;
|
||||
for (const int descriptor : listed) {
|
||||
if (written == static_cast<std::int32_t>(capacity)) break;
|
||||
const int error = fill_sock_info6(descriptor, record, includeIpv4);
|
||||
if (error == NET_EBADF || error == NET_EAFNOSUPPORT) continue;
|
||||
if (error != 0) return fail(error);
|
||||
info[written] = record;
|
||||
++written;
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNetEpollCreate(const char* name, int flags) {
|
||||
(void)name;
|
||||
(void)flags;
|
||||
@@ -2330,11 +2465,6 @@ int APS5_VABI sceNetResolverConnectDestroy(NetResolverConnection* connection) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNetGetSockInfo6(std::int32_t socket, NetSockInfo6* info, std::uint32_t capacity, std::uint32_t flags) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNetSetDns6Info(const NetDns6Info* info, std::uint32_t flags) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
@@ -2600,18 +2730,20 @@ int APS5_VABI sceNetSysctl(const int* name, std::uint32_t nameLength, void* oldV
|
||||
}
|
||||
|
||||
int APS5_VABI sceNetThreadCreate(Pthread* thread, const NetThreadParameter* parameter, const char* name) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
if (!parameter) return fail(NET_EINVAL);
|
||||
Pthread created = nullptr;
|
||||
const int result = scePthreadCreate(&created, nullptr, RunNetThread, const_cast<NetThreadParameter*>(parameter), name != nullptr ? name : "");
|
||||
if (result != 0) return result;
|
||||
if (thread) *thread = created;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNetThreadExit(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
void APS5_VABI sceNetThreadExit() {
|
||||
scePthreadExit(nullptr);
|
||||
}
|
||||
|
||||
int APS5_VABI sceNetThreadJoin(void* thread) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return scePthreadJoin(static_cast<Pthread>(thread), nullptr);
|
||||
}
|
||||
|
||||
int APS5_VABI sceNetUsleep(int microseconds) {
|
||||
@@ -3167,14 +3299,12 @@ int APS5_VABI sceNetEmulationDebugSettingsGet(NetEmulationParameter* parameter,
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNetEmulationSet(const NetEmulationParameter* parameter, std::uint32_t flags) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
int APS5_VABI sceNetEmulationSet(const NetEmulationParameter*, std::uint32_t) {
|
||||
return fail(NET_EOPNOTSUPP);
|
||||
}
|
||||
|
||||
int APS5_VABI sceNetEmulationGet(NetEmulationParameter* parameter, std::uint32_t flags) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
int APS5_VABI sceNetEmulationGet(NetEmulationParameter*, std::uint32_t) {
|
||||
return fail(NET_EOPNOTSUPP);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -99,6 +99,7 @@ static std::atomic_bool g_initialized{true};
|
||||
static std::mutex g_callbackLock;
|
||||
static CallbackSlot g_callbacks[MAX_CALLBACKS] = {};
|
||||
static CallbackSlot g_callbacksV6[MAX_CALLBACKS] = {};
|
||||
static CallbackSlot g_callbacksNpToolkit[MAX_CALLBACKS] = {};
|
||||
|
||||
static int RegisterCallback(CallbackSlot (&callbacks)[MAX_CALLBACKS], NetCtlCallback func, void* arg, int* cid) {
|
||||
if (!g_initialized.load()) return SCE_NET_CTL_ERROR_NOT_INITIALIZED;
|
||||
@@ -123,9 +124,9 @@ static int UnregisterCallback(CallbackSlot (&callbacks)[MAX_CALLBACKS], int cid)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool HasIpv4Callback() {
|
||||
static bool HasCallback(const CallbackSlot (&callbacks)[MAX_CALLBACKS]) {
|
||||
std::lock_guard lock(g_callbackLock);
|
||||
for (const auto& slot : g_callbacks) {
|
||||
for (const auto& slot : callbacks) {
|
||||
if (slot.func) return true;
|
||||
}
|
||||
return false;
|
||||
@@ -512,17 +513,15 @@ static void HostTraffic(std::uint64_t& sent, std::uint64_t& received) {
|
||||
#endif
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI sceNetCtlCheckCallback(void) {
|
||||
static int CheckCallbacks(CallbackSlot (&callbacks)[MAX_CALLBACKS]) {
|
||||
if (!g_initialized.load()) return SCE_NET_CTL_ERROR_NOT_INITIALIZED;
|
||||
if (!HasIpv4Callback()) return 0;
|
||||
if (!HasCallback(callbacks)) return 0;
|
||||
const int event = HostHasAddress(AF_UNSPEC) ? NET_CTL_EVENT_IPOBTAINED : NET_CTL_EVENT_DISCONNECTED;
|
||||
CallbackSlot due[MAX_CALLBACKS];
|
||||
int count = 0;
|
||||
{
|
||||
std::lock_guard lock(g_callbackLock);
|
||||
for (auto& slot : g_callbacks) {
|
||||
for (auto& slot : callbacks) {
|
||||
if (!slot.func || slot.lastEvent == event) continue;
|
||||
slot.lastEvent = event;
|
||||
due[count++] = slot;
|
||||
@@ -532,6 +531,12 @@ int APS5_VABI sceNetCtlCheckCallback(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI sceNetCtlCheckCallback(void) {
|
||||
return CheckCallbacks(g_callbacks);
|
||||
}
|
||||
|
||||
int APS5_VABI sceNetCtlGetIfStat(NetCtlIfStat* stat) {
|
||||
if (!g_initialized.load()) return SCE_NET_CTL_ERROR_NOT_INITIALIZED;
|
||||
if (!stat) return SCE_NET_CTL_ERROR_INVALID_ADDR;
|
||||
@@ -637,8 +642,7 @@ int APS5_VABI sceNetCtlUnregisterCallbackV6(int cid) {
|
||||
}
|
||||
|
||||
int APS5_VABI sceNetCtlRegisterCallbackForNpToolkit(NetCtlCallback callback, void* argument, int* callbackId) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return RegisterCallback(g_callbacksNpToolkit, callback, argument, callbackId);
|
||||
}
|
||||
|
||||
int APS5_VABI sceNetCtlRegisterCallbackIpcInt(NetCtlCallback callback, void* argument, int* callbackId) {
|
||||
@@ -652,8 +656,7 @@ int APS5_VABI sceNetCtlRegisterCallbackForLibIpcInt(NetCtlCallback callback, voi
|
||||
}
|
||||
|
||||
int APS5_VABI sceNetCtlUnregisterCallbackForNpToolkit(int callbackId) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return UnregisterCallback(g_callbacksNpToolkit, callbackId);
|
||||
}
|
||||
|
||||
int APS5_VABI sceNetCtlUnregisterCallbackIpcInt(int callbackId) {
|
||||
@@ -667,8 +670,7 @@ int APS5_VABI sceNetCtlUnregisterCallbackForLibIpcInt(int callbackId) {
|
||||
}
|
||||
|
||||
int APS5_VABI sceNetCtlCheckCallbackForNpToolkit(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return CheckCallbacks(g_callbacksNpToolkit);
|
||||
}
|
||||
|
||||
int APS5_VABI sceNetCtlCheckCallbackForLibIpcInt(void) {
|
||||
|
||||
@@ -113,7 +113,8 @@ constexpr int PAD_ERROR_INVALID_USER_ID = static_cast<int>(0x809b0001);
|
||||
constexpr int PAD_ERROR_INVALID_PORT_INDEX = static_cast<int>(0x809b0003);
|
||||
constexpr int PAD_USER_ID_INVALID = -1;
|
||||
|
||||
bool g_opened = false;
|
||||
std::array<bool, PAD_MAX_CONTROLLERS> g_opened{false};
|
||||
std::array<bool, PAD_MAX_CONTROLLERS> g_everOpened{false};
|
||||
bool g_remoteOpened = false;
|
||||
std::array<int, PAD_SPECIAL_HANDLE_COUNT> g_specialUsers{};
|
||||
std::atomic_bool g_initialized{false};
|
||||
@@ -130,8 +131,14 @@ int SpecialHandleOf(int userId) {
|
||||
return PAD_ERROR_DEVICE_NO_HANDLE;
|
||||
}
|
||||
|
||||
int PortOf(int handle) {
|
||||
const int port = handle - PAD_HANDLE_BASE;
|
||||
if (port < 0 || port >= PAD_MAX_CONTROLLERS || !g_everOpened[port]) return -1;
|
||||
return port;
|
||||
}
|
||||
|
||||
bool KnownHandle(int handle) {
|
||||
return handle == PAD_HANDLE || SpecialHandle(handle);
|
||||
return PortOf(handle) >= 0 || SpecialHandle(handle);
|
||||
}
|
||||
|
||||
PadData SpecialData() {
|
||||
@@ -150,9 +157,19 @@ bool RemoteHandle(int handle) {
|
||||
return g_remoteOpened && handle == PAD_REMOTE_HANDLE;
|
||||
}
|
||||
|
||||
int PortForUser(int userId, int type, int index) {
|
||||
if (index != 0) return -1;
|
||||
if (type == PAD_PORT_TYPE_STANDARD) {
|
||||
const int port = userId - 0x10000000;
|
||||
if (port >= 0 && port < PAD_MAX_CONTROLLERS) return port;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool ValidPort(int userId, int type, int index) {
|
||||
const bool personalPort = type == PAD_PORT_TYPE_STANDARD || type == PAD_PORT_TYPE_SPECIAL;
|
||||
return index == 0 && (personalPort || SystemRemote(userId, type));
|
||||
if (index != 0) return false;
|
||||
if (type == PAD_PORT_TYPE_SPECIAL || SystemRemote(userId, type)) return true;
|
||||
return PortForUser(userId, type, index) >= 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -184,10 +201,9 @@ int APS5_VABI scePadClose_nid_postfix(int handle) {
|
||||
g_specialUsers[handle - PAD_SPECIAL_HANDLE] = 0;
|
||||
return PAD_OK;
|
||||
}
|
||||
if (handle != PAD_HANDLE) {
|
||||
return PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
g_opened = false;
|
||||
const int port = PortOf(handle);
|
||||
if (port < 0) return PAD_ERROR_INVALID_HANDLE;
|
||||
g_opened[port] = false;
|
||||
return PAD_OK;
|
||||
}
|
||||
|
||||
@@ -249,22 +265,41 @@ int APS5_VABI scePadGetHandle(int user_id, int type, int index) {
|
||||
if (!ValidPort(user_id, type, index)) return PAD_ERROR_DEVICE_NO_HANDLE;
|
||||
if (type == PAD_PORT_TYPE_SPECIAL) return SpecialHandleOf(user_id);
|
||||
if (SystemRemote(user_id, type)) return g_remoteOpened ? PAD_REMOTE_HANDLE : PAD_ERROR_DEVICE_NO_HANDLE;
|
||||
if (!g_opened) return PAD_ERROR_DEVICE_NO_HANDLE;
|
||||
return PAD_HANDLE;
|
||||
const int port = PortForUser(user_id, type, index);
|
||||
if (port < 0 || !g_opened[port]) return PAD_ERROR_DEVICE_NO_HANDLE;
|
||||
return PAD_HANDLE_BASE + port;
|
||||
}
|
||||
|
||||
int APS5_VABI scePadGetInfo_nid_postfix(PadInfo* info) {
|
||||
if (info == nullptr) return PAD_ERROR_INVALID_ARG;
|
||||
std::memset(info, 0, sizeof(*info));
|
||||
info->maxConnectCount = 4;
|
||||
info->connectedCount[0] = 1;
|
||||
info->maxConnectCount = PAD_MAX_CONTROLLERS;
|
||||
std::uint8_t count = 0;
|
||||
for (int port = 0; port < PAD_MAX_CONTROLLERS; ++port) {
|
||||
if (Pad::IsConnected(port)) {
|
||||
PadInfo::PadTypeInfo& slot = info->padTypeInfo[count];
|
||||
slot.connectPort = static_cast<std::uint8_t>(port);
|
||||
slot.status = 1;
|
||||
slot.deviceType = PAD_DEVICE_TYPE_DUAL_SENSE;
|
||||
slot.connectType = PAD_CONNECT_TYPE_BLUETOOTH;
|
||||
std::uint8_t mac[6];
|
||||
std::memcpy(mac, PAD_BD_ADDRESS, sizeof(mac));
|
||||
mac[5] = static_cast<std::uint8_t>(mac[5] + port);
|
||||
std::memcpy(slot.bluetoothMacAddress, mac, sizeof(slot.bluetoothMacAddress));
|
||||
++count;
|
||||
}
|
||||
}
|
||||
if (count == 0) {
|
||||
PadInfo::PadTypeInfo& slot = info->padTypeInfo[0];
|
||||
slot.connectPort = 0;
|
||||
slot.status = 1;
|
||||
slot.deviceType = PAD_DEVICE_TYPE_DUAL_SENSE;
|
||||
slot.connectType = PAD_CONNECT_TYPE_BLUETOOTH;
|
||||
std::memcpy(slot.bluetoothMacAddress, PAD_BD_ADDRESS, sizeof(slot.bluetoothMacAddress));
|
||||
count = 1;
|
||||
}
|
||||
info->connectedCount[0] = count;
|
||||
info->connectedCount[1] = 0;
|
||||
PadInfo::PadTypeInfo& slot = info->padTypeInfo[0];
|
||||
slot.connectPort = 0;
|
||||
slot.status = 1;
|
||||
slot.deviceType = PAD_DEVICE_TYPE_DUAL_SENSE;
|
||||
slot.connectType = PAD_CONNECT_TYPE_BLUETOOTH;
|
||||
std::memcpy(slot.bluetoothMacAddress, PAD_BD_ADDRESS, sizeof(slot.bluetoothMacAddress));
|
||||
return PAD_OK;
|
||||
}
|
||||
|
||||
@@ -312,7 +347,8 @@ int APS5_VABI scePadInit_nid_postfix(void) {
|
||||
int APS5_VABI scePadOpen_nid_postfix(int userId, int type, int index, const void* param) {
|
||||
(void)param;
|
||||
const bool unknownType = type == 3 || type == 4;
|
||||
if (!unknownType && !ValidPort(userId, type, 0)) return PAD_ERROR_INVALID_ARG;
|
||||
const bool knownType = type == PAD_PORT_TYPE_STANDARD || type == PAD_PORT_TYPE_SPECIAL || SystemRemote(userId, type);
|
||||
if (!unknownType && !knownType) return PAD_ERROR_INVALID_ARG;
|
||||
if (userId == PAD_USER_ID_INVALID) return PAD_ERROR_INVALID_USER_ID;
|
||||
if (unknownType) NotImplemented_nid_no_patch(__func__);
|
||||
if (index != 0) return PAD_ERROR_INVALID_PORT_INDEX;
|
||||
@@ -330,8 +366,13 @@ int APS5_VABI scePadOpen_nid_postfix(int userId, int type, int index, const void
|
||||
g_remoteOpened = true;
|
||||
return PAD_REMOTE_HANDLE;
|
||||
}
|
||||
g_opened = true;
|
||||
return PAD_HANDLE;
|
||||
const int port = PortForUser(userId, type, index);
|
||||
if (port < 0) {
|
||||
return PAD_ERROR_INVALID_ARG;
|
||||
}
|
||||
g_opened[port] = true;
|
||||
g_everOpened[port] = true;
|
||||
return PAD_HANDLE_BASE + port;
|
||||
}
|
||||
|
||||
int APS5_VABI scePadOpenExt(int userId, int type, int index, const void* param) {
|
||||
@@ -361,14 +402,14 @@ int APS5_VABI scePadRead_nid_postfix(int handle, PadData* data, int num) {
|
||||
data[0] = SpecialData();
|
||||
return 1;
|
||||
}
|
||||
return Pad::Read(data, num);
|
||||
return Pad::Read(data, num, PortOf(handle));
|
||||
}
|
||||
|
||||
int APS5_VABI scePadReadState(int handle, PadData* data) {
|
||||
if (!KnownHandle(handle) && !RemoteHandle(handle)) return PAD_ERROR_INVALID_HANDLE;
|
||||
if (data == nullptr) return PAD_ERROR_INVALID_ARG;
|
||||
|
||||
*data = RemoteHandle(handle) ? PadData{} : SpecialHandle(handle) ? SpecialData() : Pad::ReadState();
|
||||
*data = RemoteHandle(handle) ? PadData{} : SpecialHandle(handle) ? SpecialData() : Pad::ReadState(PortOf(handle));
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -376,21 +417,21 @@ int APS5_VABI scePadReadState(int handle, PadData* data) {
|
||||
int APS5_VABI scePadResetLightBar(int handle) {
|
||||
if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE;
|
||||
if (SpecialHandle(handle)) return PAD_OK;
|
||||
Pad::SetLightBar(false, 0, 0, 0);
|
||||
Pad::SetLightBar(PortOf(handle), false, 0, 0, 0);
|
||||
return PAD_OK;
|
||||
}
|
||||
|
||||
int APS5_VABI scePadResetOrientation(int handle) {
|
||||
if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE;
|
||||
if (SpecialHandle(handle)) return PAD_OK;
|
||||
Pad::ResetOrientation();
|
||||
Pad::ResetOrientation(PortOf(handle));
|
||||
return PAD_OK;
|
||||
}
|
||||
|
||||
int APS5_VABI scePadSetAngularVelocityDeadbandState(int handle, bool enable) {
|
||||
if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE;
|
||||
if (SpecialHandle(handle)) return PAD_OK;
|
||||
Pad::SetAngularVelocityDeadband(enable);
|
||||
Pad::SetAngularVelocityDeadband(PortOf(handle), enable);
|
||||
return PAD_OK;
|
||||
}
|
||||
|
||||
@@ -398,21 +439,21 @@ int APS5_VABI scePadSetLightBar(int handle, const PadLightBarParam* param) {
|
||||
if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE;
|
||||
if (param == nullptr) return PAD_ERROR_INVALID_ARG;
|
||||
if (SpecialHandle(handle)) return PAD_OK;
|
||||
Pad::SetLightBar(true, param->r, param->g, param->b);
|
||||
Pad::SetLightBar(PortOf(handle), true, param->r, param->g, param->b);
|
||||
return PAD_OK;
|
||||
}
|
||||
|
||||
int APS5_VABI scePadSetMotionSensorState(int handle, bool enable) {
|
||||
if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE;
|
||||
if (SpecialHandle(handle)) return PAD_OK;
|
||||
Pad::SetMotionEnabled(enable);
|
||||
Pad::SetMotionEnabled(PortOf(handle), enable);
|
||||
return PAD_OK;
|
||||
}
|
||||
|
||||
int APS5_VABI scePadSetTiltCorrectionState(int handle, bool enabled) {
|
||||
if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE;
|
||||
if (SpecialHandle(handle)) return PAD_OK;
|
||||
Pad::SetTiltCorrection(enabled);
|
||||
Pad::SetTiltCorrection(PortOf(handle), enabled);
|
||||
return PAD_OK;
|
||||
}
|
||||
|
||||
@@ -430,8 +471,9 @@ int APS5_VABI scePadSetTriggerEffect(int handle, const void* param) {
|
||||
if (mode[0] > 6 || mode[1] > 6) {
|
||||
return PAD_ERROR_INVALID_ARG;
|
||||
}
|
||||
const int port = PortOf(handle);
|
||||
for (int trigger = 0; trigger < 2; ++trigger) {
|
||||
if ((mask & (1u << trigger)) != 0) Pad::SetTriggerCommand(trigger, bytes + 8 + 56 * trigger);
|
||||
if ((mask & (1u << trigger)) != 0) Pad::SetTriggerCommand(port, trigger, bytes + 8 + 56 * trigger);
|
||||
}
|
||||
return PAD_OK;
|
||||
}
|
||||
@@ -440,7 +482,7 @@ int APS5_VABI scePadSetVibration(int handle, const PadVibrationParam* param) {
|
||||
if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE;
|
||||
if (param == nullptr) return PAD_ERROR_INVALID_ARG;
|
||||
if (SpecialHandle(handle)) return PAD_OK;
|
||||
Pad::SetVibration(param->large_motor, param->small_motor);
|
||||
Pad::SetVibration(PortOf(handle), param->large_motor, param->small_motor);
|
||||
return PAD_OK;
|
||||
}
|
||||
|
||||
@@ -450,7 +492,7 @@ int APS5_VABI scePadSetVibrationMode(int handle, int mode) {
|
||||
if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE;
|
||||
if (mode > 3 && mode != 5) return PAD_ERROR_INVALID_ARG;
|
||||
if (SpecialHandle(handle)) return PAD_OK;
|
||||
Pad::SetVibrationMode(mode);
|
||||
Pad::SetVibrationMode(PortOf(handle), mode);
|
||||
return PAD_OK;
|
||||
}
|
||||
|
||||
@@ -670,7 +712,7 @@ int APS5_VABI scePadGetVersionInfo(std::int32_t handle, PadVersionInfo* info) {
|
||||
int APS5_VABI scePadGetControllerType(std::int32_t handle, std::int32_t* type) {
|
||||
if (!Pad::IsInitialized()) return PAD_ERROR_NOT_INITIALIZED;
|
||||
if (type == nullptr) return PAD_ERROR_INVALID_ARG;
|
||||
if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE;
|
||||
if (PortOf(handle) < 0) return PAD_ERROR_INVALID_HANDLE;
|
||||
*type = PAD_CONTROLLER_TYPE_DUAL_SENSE;
|
||||
return PAD_OK;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ constexpr int PAD_PORT_TYPE_STANDARD = 0;
|
||||
constexpr int PAD_PORT_TYPE_SPECIAL = 2;
|
||||
constexpr int PAD_PORT_TYPE_REMOTE = 16;
|
||||
constexpr int PAD_USER_ID_SYSTEM = 0xff;
|
||||
constexpr int PAD_MAX_CONTROLLERS = 4;
|
||||
constexpr int PAD_HANDLE_BASE = 1;
|
||||
constexpr int PAD_HANDLE = 1;
|
||||
constexpr int PAD_REMOTE_HANDLE = 0x10;
|
||||
constexpr int PAD_SPECIAL_HANDLE = 0x20;
|
||||
|
||||
@@ -56,23 +56,36 @@ struct PadOutputState {
|
||||
namespace Pad {
|
||||
void Initialize();
|
||||
bool IsInitialized();
|
||||
PadData ReadState();
|
||||
int Read(PadData* data, int num);
|
||||
PadData ReadState(int port = 0);
|
||||
int Read(PadData* data, int num, int port = 0);
|
||||
void SetVibration(int port, std::uint8_t large, std::uint8_t small);
|
||||
void SetVibration(std::uint8_t large, std::uint8_t small);
|
||||
void SetVibrationMode(int port, int mode);
|
||||
void SetVibrationMode(int mode);
|
||||
void SetLightBar(int port, bool valid, std::uint8_t r, std::uint8_t g, std::uint8_t b);
|
||||
void SetLightBar(bool valid, std::uint8_t r, std::uint8_t g, std::uint8_t b);
|
||||
void SetTriggerCommand(int port, int trigger, const std::uint8_t* command);
|
||||
void SetTriggerCommand(int trigger, const std::uint8_t* command);
|
||||
void ResetOrientation();
|
||||
void ResetOrientation(int port = 0);
|
||||
void SetMotionEnabled(int port, bool enabled);
|
||||
void SetMotionEnabled(bool enabled);
|
||||
void SetTiltCorrection(int port, bool enabled);
|
||||
void SetTiltCorrection(bool enabled);
|
||||
void SetAngularVelocityDeadband(int port, bool enabled);
|
||||
void SetAngularVelocityDeadband(bool enabled);
|
||||
void SetConnected(int port, bool connected);
|
||||
bool IsConnected(int port);
|
||||
}
|
||||
|
||||
extern "C" void PadPublishInput_nid_postfix(const PadInputState& input);
|
||||
extern "C" void PadPublishInputForPort_nid_postfix(int port, const PadInputState& input);
|
||||
extern "C" void PadReportInputFailure_nid_postfix(std::exception_ptr error);
|
||||
extern "C" bool PadFetchOutput_nid_postfix(std::uint32_t* seenSequence, PadOutputState* out);
|
||||
extern "C" bool PadFetchOutputForPort_nid_postfix(int port, std::uint32_t* seenSequence, PadOutputState* out);
|
||||
extern "C" void PadSubmitAudioHaptics_nid_postfix(std::uint8_t large, std::uint8_t small);
|
||||
extern "C" void PadQueueLegacyAudioHaptics_nid_postfix(int source, std::uint64_t begin, std::uint64_t end, std::uint8_t large, std::uint8_t small);
|
||||
extern "C" void PadClearLegacyAudioHaptics_nid_postfix(int source);
|
||||
extern "C" void PadSubmitAudioHapticsForPort_nid_postfix(int port, std::uint8_t large, std::uint8_t small);
|
||||
extern "C" void PadSetPortConnected_nid_postfix(int port, bool connected);
|
||||
|
||||
#endif
|
||||
@@ -13,18 +13,8 @@
|
||||
#include <stdexcept>
|
||||
|
||||
namespace {
|
||||
std::mutex stateMutex;
|
||||
PadInputState state;
|
||||
std::uint32_t pressedSinceRead = 0;
|
||||
std::uint8_t pressedAnalogL2 = 0;
|
||||
std::uint8_t pressedAnalogR2 = 0;
|
||||
std::uint64_t pressedAt = 0;
|
||||
struct Quat { float w = 1.0f, x = 0.0f, y = 0.0f, z = 0.0f; };
|
||||
constexpr std::uint64_t kLatchExpiry = 100000;
|
||||
PadOutputState output;
|
||||
std::chrono::steady_clock::time_point audioHapticEnd;
|
||||
std::array<std::uint8_t, 2> audioHapticRumble{};
|
||||
PadAudioHapticQueue legacyHaptics;
|
||||
std::uint64_t timestamp = 0;
|
||||
std::uint64_t injectedTimestamp = 0;
|
||||
|
||||
struct QueuedInput {
|
||||
@@ -32,29 +22,48 @@ namespace {
|
||||
std::uint64_t timestamp = 0;
|
||||
};
|
||||
constexpr std::size_t kQueueCapacity = 64;
|
||||
std::deque<QueuedInput> queue;
|
||||
|
||||
struct PadSlot {
|
||||
PadInputState state;
|
||||
PadOutputState output;
|
||||
std::deque<QueuedInput> queue;
|
||||
Quat orientation;
|
||||
std::uint64_t lastFuseTime = 0;
|
||||
float biasIntegral[3] = {0.0f, 0.0f, 0.0f};
|
||||
bool tiltCorrection = true;
|
||||
bool angularVelocityDeadband = false;
|
||||
std::uint8_t nextTouchId = 0;
|
||||
bool prevTouchActive[2] = {false, false};
|
||||
std::uint8_t touchIds[2] = {0, 0};
|
||||
std::chrono::steady_clock::time_point audioHapticEnd{};
|
||||
std::array<std::uint8_t, 2> audioHapticRumble{};
|
||||
PadAudioHapticQueue legacyHaptics;
|
||||
std::uint64_t timestamp = 0;
|
||||
std::uint32_t pressedSinceRead = 0;
|
||||
std::uint8_t pressedAnalogL2 = 0;
|
||||
std::uint8_t pressedAnalogR2 = 0;
|
||||
std::uint64_t pressedAt = 0;
|
||||
bool connected = false;
|
||||
};
|
||||
|
||||
std::mutex stateMutex;
|
||||
std::array<PadSlot, PAD_MAX_CONTROLLERS> slots = [] {
|
||||
std::array<PadSlot, PAD_MAX_CONTROLLERS> initialSlots{};
|
||||
initialSlots[0].connected = true;
|
||||
return initialSlots;
|
||||
}();
|
||||
std::exception_ptr failure;
|
||||
bool initialized = false;
|
||||
|
||||
struct Quat { float w = 1.0f, x = 0.0f, y = 0.0f, z = 0.0f; };
|
||||
Quat orientation;
|
||||
std::uint64_t lastFuseTime = 0;
|
||||
float biasIntegral[3] = {0.0f, 0.0f, 0.0f};
|
||||
bool tiltCorrection = true;
|
||||
bool angularVelocityDeadband = false;
|
||||
constexpr float kAngularVelocityDeadband = 0.017453292f;
|
||||
std::uint8_t nextTouchId = 0;
|
||||
bool prevTouchActive[2] = {false, false};
|
||||
std::uint8_t touchIds[2] = {0, 0};
|
||||
|
||||
bool RefreshAudioHaptics() {
|
||||
if (std::chrono::steady_clock::now() >= audioHapticEnd) audioHapticRumble = {};
|
||||
const auto legacy = legacyHaptics.Read(sceKernelGetProcessTime());
|
||||
const auto large = std::max(audioHapticRumble[0], legacy[0]);
|
||||
const auto small = std::max(audioHapticRumble[1], legacy[1]);
|
||||
if (output.audioVibrationLarge == large && output.audioVibrationSmall == small) return false;
|
||||
output.audioVibrationLarge = large;
|
||||
output.audioVibrationSmall = small;
|
||||
bool RefreshAudioHaptics(PadSlot& slot) {
|
||||
if (std::chrono::steady_clock::now() >= slot.audioHapticEnd) slot.audioHapticRumble = {};
|
||||
const auto legacy = slot.legacyHaptics.Read(sceKernelGetProcessTime());
|
||||
const auto large = std::max(slot.audioHapticRumble[0], legacy[0]);
|
||||
const auto small = std::max(slot.audioHapticRumble[1], legacy[1]);
|
||||
if (slot.output.audioVibrationLarge == large && slot.output.audioVibrationSmall == small) return false;
|
||||
slot.output.audioVibrationLarge = large;
|
||||
slot.output.audioVibrationSmall = small;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -64,33 +73,33 @@ namespace {
|
||||
q.w /= n; q.x /= n; q.y /= n; q.z /= n;
|
||||
}
|
||||
|
||||
void fuse(float dt, const std::array<float, 3>& accel, const std::array<float, 3>& gyro) {
|
||||
void fuse(PadSlot& slot, float dt, const std::array<float, 3>& accel, const std::array<float, 3>& gyro) {
|
||||
float gx = gyro[0], gy = gyro[1], gz = gyro[2];
|
||||
const float an = std::sqrt(accel[0] * accel[0] + accel[1] * accel[1] + accel[2] * accel[2]);
|
||||
if (tiltCorrection && an > 1.0f) {
|
||||
if (slot.tiltCorrection && an > 1.0f) {
|
||||
const float ax = accel[0] / an, ay = accel[1] / an, az = accel[2] / an;
|
||||
const float vx = 2.0f * (orientation.x * orientation.y + orientation.w * orientation.z);
|
||||
const float vy = orientation.w * orientation.w - orientation.x * orientation.x + orientation.y * orientation.y - orientation.z * orientation.z;
|
||||
const float vz = 2.0f * (orientation.y * orientation.z - orientation.w * orientation.x);
|
||||
const float vx = 2.0f * (slot.orientation.x * slot.orientation.y + slot.orientation.w * slot.orientation.z);
|
||||
const float vy = slot.orientation.w * slot.orientation.w - slot.orientation.x * slot.orientation.x + slot.orientation.y * slot.orientation.y - slot.orientation.z * slot.orientation.z;
|
||||
const float vz = 2.0f * (slot.orientation.y * slot.orientation.z - slot.orientation.w * slot.orientation.x);
|
||||
const float ex = ay * vz - az * vy;
|
||||
const float ey = az * vx - ax * vz;
|
||||
const float ez = ax * vy - ay * vx;
|
||||
constexpr float kp = 1.5f;
|
||||
constexpr float ki = 0.02f;
|
||||
biasIntegral[0] += ki * ex * dt;
|
||||
biasIntegral[1] += ki * ey * dt;
|
||||
biasIntegral[2] += ki * ez * dt;
|
||||
gx += kp * ex + biasIntegral[0];
|
||||
gy += kp * ey + biasIntegral[1];
|
||||
gz += kp * ez + biasIntegral[2];
|
||||
slot.biasIntegral[0] += ki * ex * dt;
|
||||
slot.biasIntegral[1] += ki * ey * dt;
|
||||
slot.biasIntegral[2] += ki * ez * dt;
|
||||
gx += kp * ex + slot.biasIntegral[0];
|
||||
gy += kp * ey + slot.biasIntegral[1];
|
||||
gz += kp * ez + slot.biasIntegral[2];
|
||||
}
|
||||
const float hx = 0.5f * gx * dt, hy = 0.5f * gy * dt, hz = 0.5f * gz * dt;
|
||||
const Quat q = orientation;
|
||||
orientation.w = q.w - q.x * hx - q.y * hy - q.z * hz;
|
||||
orientation.x = q.x + q.w * hx + q.y * hz - q.z * hy;
|
||||
orientation.y = q.y + q.w * hy - q.x * hz + q.z * hx;
|
||||
orientation.z = q.z + q.w * hz + q.x * hy - q.y * hx;
|
||||
normalize(orientation);
|
||||
const Quat q = slot.orientation;
|
||||
slot.orientation.w = q.w - q.x * hx - q.y * hy - q.z * hz;
|
||||
slot.orientation.x = q.x + q.w * hx + q.y * hz - q.z * hy;
|
||||
slot.orientation.y = q.y + q.w * hy - q.x * hz + q.z * hx;
|
||||
slot.orientation.z = q.z + q.w * hz + q.x * hy - q.y * hx;
|
||||
normalize(slot.orientation);
|
||||
}
|
||||
|
||||
void encodeZones(std::uint8_t* dst, std::uint8_t mode, const std::uint8_t* strengths, int count, std::uint8_t frequency) {
|
||||
@@ -125,10 +134,13 @@ void Pad::Initialize() {
|
||||
std::lock_guard lock(stateMutex);
|
||||
if (failure) std::rethrow_exception(failure);
|
||||
if (!initialized) {
|
||||
timestamp = sceKernelGetProcessTime();
|
||||
lastFuseTime = timestamp;
|
||||
queue.clear();
|
||||
pressedSinceRead = 0;
|
||||
const std::uint64_t now = sceKernelGetProcessTime();
|
||||
for (std::size_t i = 0; i < slots.size(); ++i) {
|
||||
slots[i].timestamp = now;
|
||||
slots[i].lastFuseTime = now;
|
||||
slots[i].queue.clear();
|
||||
slots[i].pressedSinceRead = 0;
|
||||
}
|
||||
}
|
||||
initialized = true;
|
||||
}
|
||||
@@ -139,7 +151,7 @@ bool Pad::IsInitialized() {
|
||||
}
|
||||
|
||||
namespace {
|
||||
PadData BuildData(const PadInputState& state, std::uint64_t changeTime, std::uint64_t now, const Pad::InputCommandFile::Input& injected) {
|
||||
PadData BuildData(PadSlot& slot, const PadInputState& state, std::uint64_t changeTime, std::uint64_t now, const Pad::InputCommandFile::Input& injected, int port) {
|
||||
PadData data{};
|
||||
data.buttons = state.buttons | injected.buttons;
|
||||
const auto& sticks = injected.hasSticks ? injected.sticks : state.sticks;
|
||||
@@ -153,28 +165,28 @@ PadData BuildData(const PadInputState& state, std::uint64_t changeTime, std::uin
|
||||
data.analog_buttons_l2 = trigger(state.analogButtonsL2, 0x100);
|
||||
data.analog_buttons_r2 = trigger(state.analogButtonsR2, 0x200);
|
||||
|
||||
const bool live = state.hasMotion && output.motionEnabled;
|
||||
const bool live = state.hasMotion && slot.output.motionEnabled;
|
||||
const std::array<float, 3> rest{0.0f, 9.80665f, 0.0f};
|
||||
const std::array<float, 3>& accel = live ? state.accel : rest;
|
||||
std::array<float, 3> gyro = live ? state.gyro : std::array<float, 3>{0.0f, 0.0f, 0.0f};
|
||||
if (angularVelocityDeadband) {
|
||||
if (slot.angularVelocityDeadband) {
|
||||
for (float& axis : gyro) {
|
||||
if (axis < kAngularVelocityDeadband && axis > -kAngularVelocityDeadband) axis = 0.0f;
|
||||
}
|
||||
}
|
||||
if (live) {
|
||||
float dt = lastFuseTime != 0 && now > lastFuseTime ? static_cast<float>(now - lastFuseTime) * 1e-6f : 0.0f;
|
||||
float dt = slot.lastFuseTime != 0 && now > slot.lastFuseTime ? static_cast<float>(now - slot.lastFuseTime) * 1e-6f : 0.0f;
|
||||
dt = std::min(dt, 0.1f);
|
||||
fuse(dt, accel, gyro);
|
||||
fuse(slot, dt, accel, gyro);
|
||||
} else {
|
||||
orientation = Quat{};
|
||||
biasIntegral[0] = biasIntegral[1] = biasIntegral[2] = 0.0f;
|
||||
slot.orientation = Quat{};
|
||||
slot.biasIntegral[0] = slot.biasIntegral[1] = slot.biasIntegral[2] = 0.0f;
|
||||
}
|
||||
lastFuseTime = now;
|
||||
data.orientation_x = orientation.x;
|
||||
data.orientation_y = orientation.y;
|
||||
data.orientation_z = orientation.z;
|
||||
data.orientation_w = orientation.w;
|
||||
slot.lastFuseTime = now;
|
||||
data.orientation_x = slot.orientation.x;
|
||||
data.orientation_y = slot.orientation.y;
|
||||
data.orientation_z = slot.orientation.z;
|
||||
data.orientation_w = slot.orientation.w;
|
||||
constexpr float kGravity = 9.80665f;
|
||||
data.acceleration_x = accel[0] / kGravity;
|
||||
data.acceleration_y = accel[1] / kGravity;
|
||||
@@ -183,20 +195,24 @@ PadData BuildData(const PadInputState& state, std::uint64_t changeTime, std::uin
|
||||
data.angular_velocity_y = gyro[1];
|
||||
data.angular_velocity_z = gyro[2];
|
||||
|
||||
data.connected = true;
|
||||
data.connected_count = 1;
|
||||
std::uint8_t connectedCount = 0;
|
||||
for (const auto& s : slots) {
|
||||
if (s.connected) ++connectedCount;
|
||||
}
|
||||
data.connected = slot.connected;
|
||||
data.connected_count = connectedCount > 0 ? connectedCount : 1;
|
||||
data.timestamp = changeTime;
|
||||
|
||||
std::uint8_t touchNum = 0;
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
const PadTouchPoint& tp = state.touch[i];
|
||||
if (!tp.active) { prevTouchActive[i] = false; continue; }
|
||||
if (!prevTouchActive[i]) touchIds[i] = static_cast<std::uint8_t>(nextTouchId++ & 0x7F);
|
||||
prevTouchActive[i] = true;
|
||||
if (!tp.active) { slot.prevTouchActive[i] = false; continue; }
|
||||
if (!slot.prevTouchActive[i]) slot.touchIds[i] = static_cast<std::uint8_t>(slot.nextTouchId++ & 0x7F);
|
||||
slot.prevTouchActive[i] = true;
|
||||
if (touchNum == 0) {
|
||||
data.touch_data_touch0_x = tp.x; data.touch_data_touch0_y = tp.y; data.touch_data_touch0_id = touchIds[i];
|
||||
data.touch_data_touch0_x = tp.x; data.touch_data_touch0_y = tp.y; data.touch_data_touch0_id = slot.touchIds[i];
|
||||
} else {
|
||||
data.touch_data_touch1_x = tp.x; data.touch_data_touch1_y = tp.y; data.touch_data_touch1_id = touchIds[i];
|
||||
data.touch_data_touch1_x = tp.x; data.touch_data_touch1_y = tp.y; data.touch_data_touch1_id = slot.touchIds[i];
|
||||
}
|
||||
++touchNum;
|
||||
}
|
||||
@@ -224,67 +240,85 @@ Pad::InputCommandFile::Input InjectedInput(std::uint64_t now) {
|
||||
const auto injected = commands.Poll(now);
|
||||
if (injected != lastInjected) {
|
||||
lastInjected = injected;
|
||||
timestamp = now;
|
||||
slots[0].timestamp = now;
|
||||
injectedTimestamp = now;
|
||||
}
|
||||
return injected;
|
||||
}
|
||||
}
|
||||
|
||||
PadData Pad::ReadState() {
|
||||
PadData Pad::ReadState(int port) {
|
||||
if (port < 0 || port >= PAD_MAX_CONTROLLERS) throw std::invalid_argument("Pad: invalid port");
|
||||
std::lock_guard lock(stateMutex);
|
||||
if (failure) std::rethrow_exception(failure);
|
||||
if (!initialized) throw std::runtime_error("Pad: read before initialization");
|
||||
const std::uint64_t now = sceKernelGetProcessTime();
|
||||
const auto injected = InjectedInput(now);
|
||||
PadData data = BuildData(state, timestamp, now, injected);
|
||||
if (pressedSinceRead != 0 && now - pressedAt <= kLatchExpiry) {
|
||||
data.buttons |= pressedSinceRead;
|
||||
if (pressedSinceRead & 0x100u) data.analog_buttons_l2 = std::max(data.analog_buttons_l2, pressedAnalogL2);
|
||||
if (pressedSinceRead & 0x200u) data.analog_buttons_r2 = std::max(data.analog_buttons_r2, pressedAnalogR2);
|
||||
const auto injected = port == 0 ? InjectedInput(now) : Pad::InputCommandFile::Input{};
|
||||
auto& slot = slots[port];
|
||||
PadData data = BuildData(slot, slot.state, slot.timestamp, now, injected, port);
|
||||
if (slot.pressedSinceRead != 0 && now - slot.pressedAt <= kLatchExpiry) {
|
||||
data.buttons |= slot.pressedSinceRead;
|
||||
if (slot.pressedSinceRead & 0x100u) data.analog_buttons_l2 = std::max(data.analog_buttons_l2, slot.pressedAnalogL2);
|
||||
if (slot.pressedSinceRead & 0x200u) data.analog_buttons_r2 = std::max(data.analog_buttons_r2, slot.pressedAnalogR2);
|
||||
}
|
||||
pressedSinceRead = 0;
|
||||
slot.pressedSinceRead = 0;
|
||||
return data;
|
||||
}
|
||||
|
||||
int Pad::Read(PadData* data, int num) {
|
||||
int Pad::Read(PadData* data, int num, int port) {
|
||||
if (data == nullptr || num <= 0) throw std::invalid_argument("Pad: invalid read buffer");
|
||||
if (port < 0 || port >= PAD_MAX_CONTROLLERS) throw std::invalid_argument("Pad: invalid port");
|
||||
std::lock_guard lock(stateMutex);
|
||||
if (failure) std::rethrow_exception(failure);
|
||||
if (!initialized) throw std::runtime_error("Pad: read before initialization");
|
||||
pressedSinceRead = 0;
|
||||
auto& slot = slots[port];
|
||||
slot.pressedSinceRead = 0;
|
||||
const std::uint64_t now = sceKernelGetProcessTime();
|
||||
const auto injected = InjectedInput(now);
|
||||
if (queue.empty()) {
|
||||
data[0] = BuildData(state, timestamp, now, injected);
|
||||
const auto injected = port == 0 ? InjectedInput(now) : Pad::InputCommandFile::Input{};
|
||||
if (slot.queue.empty()) {
|
||||
data[0] = BuildData(slot, slot.state, slot.timestamp, now, injected, port);
|
||||
return 1;
|
||||
}
|
||||
int count = 0;
|
||||
while (count < num && !queue.empty()) {
|
||||
const QueuedInput& entry = queue.front();
|
||||
data[count++] = BuildData(entry.input, std::max(entry.timestamp, injectedTimestamp), now, injected);
|
||||
queue.pop_front();
|
||||
while (count < num && !slot.queue.empty()) {
|
||||
const QueuedInput& entry = slot.queue.front();
|
||||
data[count++] = BuildData(slot, entry.input, port == 0 ? std::max(entry.timestamp, injectedTimestamp) : entry.timestamp, now, injected, port);
|
||||
slot.queue.pop_front();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void Pad::SetVibration(std::uint8_t large, std::uint8_t small) {
|
||||
void Pad::SetVibration(int port, std::uint8_t large, std::uint8_t small) {
|
||||
if (port < 0 || port >= PAD_MAX_CONTROLLERS) return;
|
||||
std::lock_guard lock(stateMutex);
|
||||
auto& output = slots[port].output;
|
||||
if (output.vibrationLarge == large && output.vibrationSmall == small) return;
|
||||
output.vibrationLarge = large;
|
||||
output.vibrationSmall = small;
|
||||
++output.sequence;
|
||||
}
|
||||
|
||||
void Pad::SetVibrationMode(int mode) {
|
||||
void Pad::SetVibration(std::uint8_t large, std::uint8_t small) {
|
||||
SetVibration(0, large, small);
|
||||
}
|
||||
|
||||
void Pad::SetVibrationMode(int port, int mode) {
|
||||
if (port < 0 || port >= PAD_MAX_CONTROLLERS) return;
|
||||
std::lock_guard lock(stateMutex);
|
||||
auto& output = slots[port].output;
|
||||
if (output.vibrationMode == mode) return;
|
||||
output.vibrationMode = mode;
|
||||
++output.sequence;
|
||||
}
|
||||
|
||||
void Pad::SetLightBar(bool valid, std::uint8_t r, std::uint8_t g, std::uint8_t b) {
|
||||
void Pad::SetVibrationMode(int mode) {
|
||||
SetVibrationMode(0, mode);
|
||||
}
|
||||
|
||||
void Pad::SetLightBar(int port, bool valid, std::uint8_t r, std::uint8_t g, std::uint8_t b) {
|
||||
if (port < 0 || port >= PAD_MAX_CONTROLLERS) return;
|
||||
std::lock_guard lock(stateMutex);
|
||||
auto& output = slots[port].output;
|
||||
output.lightBarValid = valid;
|
||||
output.lightBar[0] = r;
|
||||
output.lightBar[1] = g;
|
||||
@@ -292,8 +326,12 @@ void Pad::SetLightBar(bool valid, std::uint8_t r, std::uint8_t g, std::uint8_t b
|
||||
++output.sequence;
|
||||
}
|
||||
|
||||
void Pad::SetTriggerCommand(int trigger, const std::uint8_t* command) {
|
||||
if (trigger < 0 || trigger > 1 || command == nullptr) return;
|
||||
void Pad::SetLightBar(bool valid, std::uint8_t r, std::uint8_t g, std::uint8_t b) {
|
||||
SetLightBar(0, valid, r, g, b);
|
||||
}
|
||||
|
||||
void Pad::SetTriggerCommand(int port, int trigger, const std::uint8_t* command) {
|
||||
if (port < 0 || port >= PAD_MAX_CONTROLLERS || trigger < 0 || trigger > 1 || command == nullptr) return;
|
||||
std::uint32_t mode = 0;
|
||||
std::memcpy(&mode, command, 4);
|
||||
const std::uint8_t* p = command + 8;
|
||||
@@ -350,55 +388,95 @@ void Pad::SetTriggerCommand(int trigger, const std::uint8_t* command) {
|
||||
req.valid = req.effect[0] != 0x05;
|
||||
if (!req.valid) req.fallback = 0;
|
||||
std::lock_guard lock(stateMutex);
|
||||
auto& output = slots[port].output;
|
||||
output.trigger[trigger] = req;
|
||||
output.triggerTouched = true;
|
||||
++output.sequence;
|
||||
}
|
||||
|
||||
void Pad::ResetOrientation() {
|
||||
void Pad::SetTriggerCommand(int trigger, const std::uint8_t* command) {
|
||||
SetTriggerCommand(0, trigger, command);
|
||||
}
|
||||
|
||||
void Pad::ResetOrientation(int port) {
|
||||
if (port < 0 || port >= PAD_MAX_CONTROLLERS) return;
|
||||
std::lock_guard lock(stateMutex);
|
||||
orientation = Quat{};
|
||||
biasIntegral[0] = biasIntegral[1] = biasIntegral[2] = 0.0f;
|
||||
slots[port].orientation = Quat{};
|
||||
slots[port].biasIntegral[0] = slots[port].biasIntegral[1] = slots[port].biasIntegral[2] = 0.0f;
|
||||
}
|
||||
|
||||
void Pad::SetTiltCorrection(int port, bool enabled) {
|
||||
if (port < 0 || port >= PAD_MAX_CONTROLLERS) return;
|
||||
std::lock_guard lock(stateMutex);
|
||||
slots[port].tiltCorrection = enabled;
|
||||
slots[port].biasIntegral[0] = slots[port].biasIntegral[1] = slots[port].biasIntegral[2] = 0.0f;
|
||||
}
|
||||
|
||||
void Pad::SetTiltCorrection(bool enabled) {
|
||||
SetTiltCorrection(0, enabled);
|
||||
}
|
||||
|
||||
void Pad::SetAngularVelocityDeadband(int port, bool enabled) {
|
||||
if (port < 0 || port >= PAD_MAX_CONTROLLERS) return;
|
||||
std::lock_guard lock(stateMutex);
|
||||
tiltCorrection = enabled;
|
||||
biasIntegral[0] = biasIntegral[1] = biasIntegral[2] = 0.0f;
|
||||
slots[port].angularVelocityDeadband = enabled;
|
||||
}
|
||||
|
||||
void Pad::SetAngularVelocityDeadband(bool enabled) {
|
||||
std::lock_guard lock(stateMutex);
|
||||
angularVelocityDeadband = enabled;
|
||||
SetAngularVelocityDeadband(0, enabled);
|
||||
}
|
||||
|
||||
void Pad::SetMotionEnabled(bool enabled) {
|
||||
void Pad::SetMotionEnabled(int port, bool enabled) {
|
||||
if (port < 0 || port >= PAD_MAX_CONTROLLERS) return;
|
||||
std::lock_guard lock(stateMutex);
|
||||
auto& output = slots[port].output;
|
||||
if (output.motionEnabled == enabled) return;
|
||||
output.motionEnabled = enabled;
|
||||
++output.sequence;
|
||||
}
|
||||
|
||||
extern "C" void PadPublishInput_nid_postfix(const PadInputState& input) {
|
||||
void Pad::SetMotionEnabled(bool enabled) {
|
||||
SetMotionEnabled(0, enabled);
|
||||
}
|
||||
|
||||
void Pad::SetConnected(int port, bool connected) {
|
||||
if (port < 0 || port >= PAD_MAX_CONTROLLERS) return;
|
||||
std::lock_guard lock(stateMutex);
|
||||
slots[port].connected = connected;
|
||||
}
|
||||
|
||||
bool Pad::IsConnected(int port) {
|
||||
if (port < 0 || port >= PAD_MAX_CONTROLLERS) return false;
|
||||
std::lock_guard lock(stateMutex);
|
||||
return slots[port].connected;
|
||||
}
|
||||
|
||||
extern "C" void PadPublishInputForPort_nid_postfix(int port, const PadInputState& input) {
|
||||
if (port < 0 || port >= PAD_MAX_CONTROLLERS) return;
|
||||
std::lock_guard lock(stateMutex);
|
||||
if (failure) std::rethrow_exception(failure);
|
||||
if (state.buttons == input.buttons && state.sticks == input.sticks &&
|
||||
state.analogButtonsL2 == input.analogButtonsL2 && state.analogButtonsR2 == input.analogButtonsR2 &&
|
||||
state.touchLeft == input.touchLeft && state.touchRight == input.touchRight &&
|
||||
state.hasMotion == input.hasMotion && state.accel == input.accel && state.gyro == input.gyro &&
|
||||
state.touch == input.touch && state.deviceKind == input.deviceKind) return;
|
||||
const std::uint32_t pressed = input.buttons & ~state.buttons;
|
||||
timestamp = sceKernelGetProcessTime();
|
||||
auto& slot = slots[port];
|
||||
if (slot.state.buttons == input.buttons && slot.state.sticks == input.sticks &&
|
||||
slot.state.analogButtonsL2 == input.analogButtonsL2 && slot.state.analogButtonsR2 == input.analogButtonsR2 &&
|
||||
slot.state.touchLeft == input.touchLeft && slot.state.touchRight == input.touchRight &&
|
||||
slot.state.hasMotion == input.hasMotion && slot.state.accel == input.accel && slot.state.gyro == input.gyro &&
|
||||
slot.state.touch == input.touch && slot.state.deviceKind == input.deviceKind) return;
|
||||
const std::uint32_t pressed = input.buttons & ~slot.state.buttons;
|
||||
slot.timestamp = sceKernelGetProcessTime();
|
||||
if (pressed != 0) {
|
||||
if (pressedSinceRead != 0 && timestamp - pressedAt > kLatchExpiry) pressedSinceRead = 0;
|
||||
pressedSinceRead |= pressed;
|
||||
pressedAt = timestamp;
|
||||
if (pressed & 0x100u) pressedAnalogL2 = input.analogButtonsL2;
|
||||
if (pressed & 0x200u) pressedAnalogR2 = input.analogButtonsR2;
|
||||
if (slot.pressedSinceRead != 0 && slot.timestamp - slot.pressedAt > kLatchExpiry) slot.pressedSinceRead = 0;
|
||||
slot.pressedSinceRead |= pressed;
|
||||
slot.pressedAt = slot.timestamp;
|
||||
if (pressed & 0x100u) slot.pressedAnalogL2 = input.analogButtonsL2;
|
||||
if (pressed & 0x200u) slot.pressedAnalogR2 = input.analogButtonsR2;
|
||||
}
|
||||
state = input;
|
||||
if (queue.size() == kQueueCapacity) queue.pop_front();
|
||||
queue.push_back({input, timestamp});
|
||||
slot.state = input;
|
||||
if (slot.queue.size() == kQueueCapacity) slot.queue.pop_front();
|
||||
slot.queue.push_back({input, slot.timestamp});
|
||||
}
|
||||
|
||||
extern "C" void PadPublishInput_nid_postfix(const PadInputState& input) {
|
||||
PadPublishInputForPort_nid_postfix(0, input);
|
||||
}
|
||||
|
||||
extern "C" void PadReportInputFailure_nid_postfix(std::exception_ptr error) {
|
||||
@@ -407,35 +485,51 @@ extern "C" void PadReportInputFailure_nid_postfix(std::exception_ptr error) {
|
||||
if (!failure) failure = error;
|
||||
}
|
||||
|
||||
extern "C" bool PadFetchOutput_nid_postfix(std::uint32_t* seenSequence, PadOutputState* out) {
|
||||
extern "C" bool PadFetchOutputForPort_nid_postfix(int port, std::uint32_t* seenSequence, PadOutputState* out) {
|
||||
if (port < 0 || port >= PAD_MAX_CONTROLLERS) return false;
|
||||
std::lock_guard lock(stateMutex);
|
||||
if (RefreshAudioHaptics()) ++output.sequence;
|
||||
if (seenSequence == nullptr || out == nullptr || *seenSequence == output.sequence) return false;
|
||||
*seenSequence = output.sequence;
|
||||
*out = output;
|
||||
auto& slot = slots[port];
|
||||
if (RefreshAudioHaptics(slot)) ++slot.output.sequence;
|
||||
if (seenSequence == nullptr || out == nullptr || *seenSequence == slot.output.sequence) return false;
|
||||
*seenSequence = slot.output.sequence;
|
||||
*out = slot.output;
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" void PadSubmitAudioHaptics_nid_postfix(std::uint8_t large, std::uint8_t small) {
|
||||
extern "C" bool PadFetchOutput_nid_postfix(std::uint32_t* seenSequence, PadOutputState* out) {
|
||||
return PadFetchOutputForPort_nid_postfix(0, seenSequence, out);
|
||||
}
|
||||
|
||||
extern "C" void PadSubmitAudioHapticsForPort_nid_postfix(int port, std::uint8_t large, std::uint8_t small) {
|
||||
if (port < 0 || port >= PAD_MAX_CONTROLLERS) return;
|
||||
std::lock_guard lock(stateMutex);
|
||||
if (large == 0 && small == 0) return;
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (now >= audioHapticEnd) audioHapticRumble = {};
|
||||
audioHapticRumble[0] = std::max(audioHapticRumble[0], large);
|
||||
audioHapticRumble[1] = std::max(audioHapticRumble[1], small);
|
||||
audioHapticEnd = now + std::chrono::milliseconds(80);
|
||||
static_cast<void>(RefreshAudioHaptics());
|
||||
++output.sequence;
|
||||
auto& slot = slots[port];
|
||||
if (now >= slot.audioHapticEnd) slot.audioHapticRumble = {};
|
||||
slot.audioHapticRumble[0] = std::max(slot.audioHapticRumble[0], large);
|
||||
slot.audioHapticRumble[1] = std::max(slot.audioHapticRumble[1], small);
|
||||
slot.audioHapticEnd = now + std::chrono::milliseconds(80);
|
||||
static_cast<void>(RefreshAudioHaptics(slot));
|
||||
++slot.output.sequence;
|
||||
}
|
||||
|
||||
extern "C" void PadQueueLegacyAudioHaptics_nid_postfix(int source, std::uint64_t begin, std::uint64_t end, std::uint8_t large, std::uint8_t small) {
|
||||
std::lock_guard lock(stateMutex);
|
||||
legacyHaptics.Queue(source, begin, end, large, small);
|
||||
if (RefreshAudioHaptics()) ++output.sequence;
|
||||
slots[0].legacyHaptics.Queue(source, begin, end, large, small);
|
||||
if (RefreshAudioHaptics(slots[0])) ++slots[0].output.sequence;
|
||||
}
|
||||
|
||||
extern "C" void PadClearLegacyAudioHaptics_nid_postfix(int source) {
|
||||
std::lock_guard lock(stateMutex);
|
||||
legacyHaptics.Clear(source);
|
||||
if (RefreshAudioHaptics()) ++output.sequence;
|
||||
slots[0].legacyHaptics.Clear(source);
|
||||
if (RefreshAudioHaptics(slots[0])) ++slots[0].output.sequence;
|
||||
}
|
||||
|
||||
extern "C" void PadSubmitAudioHaptics_nid_postfix(std::uint8_t large, std::uint8_t small) {
|
||||
PadSubmitAudioHapticsForPort_nid_postfix(0, large, small);
|
||||
}
|
||||
|
||||
extern "C" void PadSetPortConnected_nid_postfix(int port, bool connected) {
|
||||
Pad::SetConnected(port, connected);
|
||||
}
|
||||
@@ -3,14 +3,35 @@
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include <atomic>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include "prx/libSceUserService/UserService.hpp"
|
||||
|
||||
static std::atomic<bool> initialized{false};
|
||||
static std::atomic<bool> loginReported{false};
|
||||
namespace {
|
||||
|
||||
static int ReportAccessibilitySetting(int user_id, int32_t* setting, int32_t value) {
|
||||
if (user_id != USER_SERVICE_INITIAL_USER_ID) {
|
||||
std::atomic<bool> initialized{false};
|
||||
std::mutex userMutex;
|
||||
|
||||
struct UserEvent {
|
||||
std::uint32_t eventType;
|
||||
int userId;
|
||||
};
|
||||
|
||||
std::deque<UserEvent> eventQueue;
|
||||
std::array<bool, USER_SERVICE_MAX_USERS> userConnected{true, false, false, false};
|
||||
|
||||
int UserIndex(int userId) {
|
||||
if (userId >= USER_SERVICE_INITIAL_USER_ID && userId < USER_SERVICE_INITIAL_USER_ID + USER_SERVICE_MAX_USERS) {
|
||||
return userId - USER_SERVICE_INITIAL_USER_ID;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ReportAccessibilitySetting(int user_id, int32_t* setting, int32_t value) {
|
||||
const int index = UserIndex(user_id);
|
||||
if (index < 0) {
|
||||
return USER_SERVICE_ERROR_NOT_LOGGED_IN;
|
||||
}
|
||||
if (setting == nullptr) {
|
||||
@@ -20,6 +41,17 @@ static int ReportAccessibilitySetting(int user_id, int32_t* setting, int32_t val
|
||||
return USER_SERVICE_OK;
|
||||
}
|
||||
|
||||
void PopulateInitialLoginEvents() {
|
||||
std::lock_guard lock(userMutex);
|
||||
eventQueue.clear();
|
||||
constexpr std::uint32_t EventTypeLogin = 0;
|
||||
eventQueue.push_back({EventTypeLogin, USER_SERVICE_INITIAL_USER_ID});
|
||||
for (int i = 1; i < USER_SERVICE_MAX_USERS; ++i) {
|
||||
if (userConnected[i]) {
|
||||
eventQueue.push_back({EventTypeLogin, USER_SERVICE_INITIAL_USER_ID + i});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" int APS5_VABI sceSystemServiceParamGetInt(int param_id, int* value);
|
||||
|
||||
@@ -47,15 +79,43 @@ static int ReportKeyboardSetting(int user_id, int32_t* type, int32_t (*value)())
|
||||
if (type == nullptr) {
|
||||
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (user_id != USER_SERVICE_INITIAL_USER_ID) {
|
||||
if (UserIndex(user_id) < 0) {
|
||||
return USER_SERVICE_ERROR_UNKNOWN_USER;
|
||||
}
|
||||
*type = value();
|
||||
return USER_SERVICE_OK;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
void UserServiceSetUserConnected_nid_postfix(int userIndex, bool connected) {
|
||||
if (userIndex <= 0 || userIndex >= USER_SERVICE_MAX_USERS) return;
|
||||
std::lock_guard lock(userMutex);
|
||||
if (userConnected[userIndex] != connected) {
|
||||
userConnected[userIndex] = connected;
|
||||
if (initialized.load()) {
|
||||
constexpr std::uint32_t EventTypeLogin = 0;
|
||||
constexpr std::uint32_t EventTypeLogout = 1;
|
||||
const std::uint32_t eventType = connected ? EventTypeLogin : EventTypeLogout;
|
||||
const int userId = USER_SERVICE_INITIAL_USER_ID + userIndex;
|
||||
bool collapsed = false;
|
||||
for (auto it = eventQueue.begin(); it != eventQueue.end(); ++it) {
|
||||
if (it->userId == userId) {
|
||||
if (it->eventType != eventType) {
|
||||
eventQueue.erase(it);
|
||||
}
|
||||
collapsed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!collapsed) {
|
||||
eventQueue.push_back({eventType, userId});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int APS5_VABI sceUserServiceGetAccessibilityChatTranscription(int user_id, int32_t* chat_transcription) {
|
||||
return ReportAccessibilitySetting(user_id, chat_transcription, 0);
|
||||
}
|
||||
@@ -91,7 +151,8 @@ int APS5_VABI sceUserServiceGetAccessibilityZoomFollowFocus(int user_id, int32_t
|
||||
}
|
||||
|
||||
int APS5_VABI sceUserServiceGetAgeLevel(int user_id, uint32_t* age_level) {
|
||||
if (user_id != USER_SERVICE_INITIAL_USER_ID) {
|
||||
const int index = UserIndex(user_id);
|
||||
if (index < 0) {
|
||||
return USER_SERVICE_ERROR_NOT_LOGGED_IN;
|
||||
}
|
||||
if (age_level == nullptr) {
|
||||
@@ -110,12 +171,14 @@ int APS5_VABI sceUserServiceGetEvent(SceUserServiceEvent* event) {
|
||||
if (event == nullptr) {
|
||||
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (loginReported.exchange(true)) {
|
||||
std::lock_guard lock(userMutex);
|
||||
if (eventQueue.empty()) {
|
||||
return USER_SERVICE_ERROR_NO_EVENT;
|
||||
}
|
||||
constexpr std::uint32_t EventTypeLogin = 0;
|
||||
event->event_type = EventTypeLogin;
|
||||
event->user_id = USER_SERVICE_INITIAL_USER_ID;
|
||||
const auto item = eventQueue.front();
|
||||
eventQueue.pop_front();
|
||||
event->event_type = item.eventType;
|
||||
event->user_id = item.userId;
|
||||
return USER_SERVICE_OK;
|
||||
}
|
||||
|
||||
@@ -123,7 +186,8 @@ int APS5_VABI sceUserServiceGetGamePresets(int user_id, UserServiceGamePresets*
|
||||
// System-wide game presets (difficulty, view inversion, subtitles, audio language) of the user's
|
||||
// profile. A fresh console has none set: every field is 0, "not specified", and the title falls
|
||||
// back to its own defaults. this_size is the caller's.
|
||||
if (presets == nullptr || user_id != USER_SERVICE_INITIAL_USER_ID) {
|
||||
const int index = UserIndex(user_id);
|
||||
if (presets == nullptr || index < 0) {
|
||||
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
presets->difficulty = 0;
|
||||
@@ -152,10 +216,14 @@ int APS5_VABI sceUserServiceGetLoginUserIdList(UserServiceLoginUserIdList* user_
|
||||
if (user_id_list == nullptr) {
|
||||
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
user_id_list->user_id[0] = USER_SERVICE_INITIAL_USER_ID;
|
||||
user_id_list->user_id[1] = USER_SERVICE_USER_ID_INVALID;
|
||||
user_id_list->user_id[2] = USER_SERVICE_USER_ID_INVALID;
|
||||
user_id_list->user_id[3] = USER_SERVICE_USER_ID_INVALID;
|
||||
std::lock_guard lock(userMutex);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (i < USER_SERVICE_MAX_USERS && userConnected[i]) {
|
||||
user_id_list->user_id[i] = USER_SERVICE_INITIAL_USER_ID + i;
|
||||
} else {
|
||||
user_id_list->user_id[i] = USER_SERVICE_USER_ID_INVALID;
|
||||
}
|
||||
}
|
||||
return USER_SERVICE_OK;
|
||||
}
|
||||
|
||||
@@ -163,22 +231,24 @@ int APS5_VABI sceUserServiceGetUserName(int user_id, char* name, size_t size) {
|
||||
if (name == nullptr) {
|
||||
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (user_id != USER_SERVICE_INITIAL_USER_ID) {
|
||||
const int index = UserIndex(user_id);
|
||||
if (index < 0) {
|
||||
return USER_SERVICE_ERROR_UNKNOWN_USER;
|
||||
}
|
||||
if (size < USER_SERVICE_MAX_USER_NAME_LENGTH + 1) {
|
||||
std::memset(name, 0, size);
|
||||
return USER_SERVICE_ERROR_BUFFER_TOO_SHORT;
|
||||
}
|
||||
std::strncpy(name, USER_SERVICE_INITIAL_USER_NAME, size);
|
||||
std::strncpy(name, USER_SERVICE_USER_NAMES[index], size);
|
||||
return USER_SERVICE_OK;
|
||||
}
|
||||
|
||||
int APS5_VABI sceUserServiceGetUserNumber(int user_id, int32_t* number) {
|
||||
if (number == nullptr || user_id != USER_SERVICE_INITIAL_USER_ID) {
|
||||
const int index = UserIndex(user_id);
|
||||
if (number == nullptr || index < 0) {
|
||||
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
*number = 1;
|
||||
*number = index + 1;
|
||||
return USER_SERVICE_OK;
|
||||
}
|
||||
|
||||
@@ -190,7 +260,7 @@ static int MarkInitialized() {
|
||||
if (initialized.exchange(true)) {
|
||||
return USER_SERVICE_ERROR_ALREADY_INITIALIZED;
|
||||
}
|
||||
loginReported.store(false);
|
||||
PopulateInitialLoginEvents();
|
||||
return USER_SERVICE_OK;
|
||||
}
|
||||
|
||||
@@ -214,6 +284,8 @@ int APS5_VABI sceUserServiceTerminate(void) {
|
||||
if (!initialized.exchange(false)) {
|
||||
return USER_SERVICE_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
std::lock_guard lock(userMutex);
|
||||
eventQueue.clear();
|
||||
return USER_SERVICE_OK;
|
||||
}
|
||||
|
||||
@@ -237,18 +309,23 @@ int APS5_VABI sceUserServiceGetRegisteredUserIdList(UserServiceRegisteredUserIdL
|
||||
if (user_id_list == nullptr) {
|
||||
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
user_id_list->user_id[0] = USER_SERVICE_INITIAL_USER_ID;
|
||||
for (int i = 1; i < 16; ++i) {
|
||||
user_id_list->user_id[i] = USER_SERVICE_USER_ID_INVALID;
|
||||
std::lock_guard lock(userMutex);
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
if (i < USER_SERVICE_MAX_USERS && userConnected[i]) {
|
||||
user_id_list->user_id[i] = USER_SERVICE_INITIAL_USER_ID + i;
|
||||
} else {
|
||||
user_id_list->user_id[i] = USER_SERVICE_USER_ID_INVALID;
|
||||
}
|
||||
}
|
||||
return USER_SERVICE_OK;
|
||||
}
|
||||
|
||||
int APS5_VABI sceUserServiceGetUserColor(int user_id, int* color) {
|
||||
if (color == nullptr || user_id != USER_SERVICE_INITIAL_USER_ID) {
|
||||
const int index = UserIndex(user_id);
|
||||
if (color == nullptr || index < 0) {
|
||||
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
*color = 0;
|
||||
*color = index;
|
||||
return USER_SERVICE_OK;
|
||||
}
|
||||
|
||||
@@ -265,7 +342,8 @@ int APS5_VABI sceUserServiceGetTraditionalChineseInputType(int user_id, int32_t*
|
||||
}
|
||||
|
||||
int APS5_VABI sceUserServiceGetNpAccountId(int user_id, uint64_t* account_id) {
|
||||
if (account_id == nullptr || user_id != USER_SERVICE_INITIAL_USER_ID) {
|
||||
const int index = UserIndex(user_id);
|
||||
if (account_id == nullptr || index < 0) {
|
||||
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
*account_id = 0;
|
||||
|
||||
@@ -24,5 +24,11 @@ constexpr int USER_SERVICE_ERROR_BUFFER_TOO_SHORT = -2137653238;
|
||||
constexpr int USER_SERVICE_ERROR_UNKNOWN_USER = static_cast<int>(0x80960105u);
|
||||
constexpr std::size_t USER_SERVICE_MAX_USER_NAME_LENGTH = 16;
|
||||
constexpr const char* USER_SERVICE_INITIAL_USER_NAME = "Player";
|
||||
constexpr int USER_SERVICE_MAX_USERS = 4;
|
||||
constexpr const char* USER_SERVICE_USER_NAMES[USER_SERVICE_MAX_USERS] = {
|
||||
"Player", "Player2", "Player3", "Player4"
|
||||
};
|
||||
|
||||
extern "C" void UserServiceSetUserConnected_nid_postfix(int userIndex, bool connected);
|
||||
|
||||
#endif
|
||||
@@ -23,7 +23,7 @@ target_include_directories(libSceVideoOut PRIVATE
|
||||
${CMAKE_BINARY_DIR}/core/libs/prx/libSceAgcDriver/generated
|
||||
)
|
||||
add_dependencies(libSceVideoOut agc_trace_revision)
|
||||
target_link_libraries(libSceVideoOut PRIVATE libSceAgcDriver libScePad libSceMouse libSceKeyboard libSceIme libc libkernel decoder_png SDL2-static)
|
||||
target_link_libraries(libSceVideoOut PRIVATE libSceAgcDriver libScePad libSceUserService libSceMouse libSceKeyboard libSceIme libc libkernel decoder_png SDL2-static)
|
||||
if(WIN32)
|
||||
target_link_libraries(libSceVideoOut PRIVATE comctl32 dxgi)
|
||||
endif()
|
||||
@@ -145,8 +145,14 @@ endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(pad_sdl_tests tests/PadSdl.cpp)
|
||||
target_link_libraries(pad_sdl_tests PRIVATE SDL2-static)
|
||||
target_include_directories(pad_sdl_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(pad_sdl_tests PRIVATE libSceVideoOut libScePad libSceUserService libc SDL2-static)
|
||||
configure_windows_unwind(pad_sdl_tests)
|
||||
add_test(NAME pad_sdl COMMAND pad_sdl_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(pad_sdl PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceVideoOut>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libScePad>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceUserService>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test_executable(pad_input_mapping_tests tests/InputMapping.cpp src/InputMapping.cpp)
|
||||
target_include_directories(pad_input_mapping_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/3rdparty/SDL2/include)
|
||||
target_link_libraries(pad_input_mapping_tests PRIVATE SDL2-static libc)
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
#include "SDL_gamecontroller.h"
|
||||
#include "prx/libScePad/include/KeyboardMouse.hpp"
|
||||
#include "prx/libScePad/include/PadState.hpp"
|
||||
#include "prx/libScePad/include/Pad.hpp"
|
||||
#include "prx/libSceVideoOut/include/PadOutputPacing.hpp"
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include "prx/libSceVideoOut/include/Telemetry.hpp"
|
||||
|
||||
@@ -18,28 +20,37 @@ public:
|
||||
void HandleEvent(const SDL_Event& event, DisplayWindow& window);
|
||||
void Update(const DisplayWindow& window);
|
||||
bool MouseModeEnabled() const { return keyboardMouse.MouseMode(); }
|
||||
bool SlotOccupied(int slot) const {
|
||||
return slot >= 0 && slot < PAD_MAX_CONTROLLERS && slots[slot].controller != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
struct ControllerSlot {
|
||||
SDL_GameController* controller = nullptr;
|
||||
SDL_JoystickID instanceId = -1;
|
||||
PadInputState state{};
|
||||
std::uint32_t outputSequence = 0;
|
||||
PadOutputState outputState{};
|
||||
bool outputPending = false;
|
||||
std::chrono::steady_clock::time_point nextRumbleRefresh{};
|
||||
LightBarPacer lightBar;
|
||||
TriggerEffectCache triggerEffect;
|
||||
};
|
||||
|
||||
explicit PadInput(Pad::InputConfiguration configuration);
|
||||
void publish(std::uint64_t frame);
|
||||
void publish(std::uint64_t frame, int slot);
|
||||
void setMouseMode(bool enabled);
|
||||
void openFirstAvailableController();
|
||||
void openAvailableControllers();
|
||||
void openController(int deviceIndex);
|
||||
void closeController();
|
||||
PadInputState sampleController() const;
|
||||
void closeController(int slot);
|
||||
PadInputState sampleController(int slot) const;
|
||||
void applyOutput();
|
||||
void enableSensors();
|
||||
void enableSensors(int slot);
|
||||
|
||||
Pad::KeyboardMouse keyboardMouse;
|
||||
SDL_GameController* controller = nullptr;
|
||||
PadInputState controllerState{};
|
||||
std::uint32_t outputSequence = 0;
|
||||
PadOutputState outputState{};
|
||||
bool outputPending = false;
|
||||
std::array<ControllerSlot, PAD_MAX_CONTROLLERS> slots{};
|
||||
VideoOutTelemetry::PadChangeFilter telemetryFilter;
|
||||
std::chrono::steady_clock::time_point nextRumbleRefresh{};
|
||||
LightBarPacer lightBar;
|
||||
TriggerEffectCache triggerEffect;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -14,7 +14,9 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/Presentation.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
|
||||
#include "prx/libScePad/include/PadState.hpp"
|
||||
#include "prx/libScePad/include/Pad.hpp"
|
||||
#include "prx/libScePad/include/PadInputTypes.hpp"
|
||||
#include "prx/libSceUserService/UserService.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libSceIme/include/ImeInput.hpp"
|
||||
|
||||
@@ -22,15 +24,16 @@ PadInput::PadInput() : PadInput(Pad::LoadInputMapping()) {
|
||||
}
|
||||
|
||||
PadInput::PadInput(Pad::InputConfiguration configuration) : keyboardMouse(std::move(configuration)) {
|
||||
openFirstAvailableController();
|
||||
openAvailableControllers();
|
||||
}
|
||||
|
||||
PadInput::~PadInput() {
|
||||
closeController();
|
||||
for (int i = 0; i < PAD_MAX_CONTROLLERS; ++i) {
|
||||
closeController(i);
|
||||
}
|
||||
}
|
||||
|
||||
void PadInput::openFirstAvailableController() {
|
||||
if (controller != nullptr) return;
|
||||
void PadInput::openAvailableControllers() {
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
|
||||
if ((SDL_WasInit(SDL_INIT_GAMECONTROLLER) & SDL_INIT_GAMECONTROLLER) == 0) {
|
||||
@@ -42,82 +45,122 @@ void PadInput::openFirstAvailableController() {
|
||||
for (int deviceIndex = 0; deviceIndex < SDL_NumJoysticks(); ++deviceIndex) {
|
||||
if (!SDL_IsGameController(deviceIndex)) continue;
|
||||
openController(deviceIndex);
|
||||
if (controller != nullptr) return;
|
||||
}
|
||||
}
|
||||
|
||||
void PadInput::openController(int deviceIndex) {
|
||||
if (controller != nullptr || !SDL_IsGameController(deviceIndex)) return;
|
||||
controller = SDL_GameControllerOpen(deviceIndex);
|
||||
if (controller == nullptr) {
|
||||
if (!SDL_IsGameController(deviceIndex)) return;
|
||||
const auto instanceId = SDL_JoystickGetDeviceInstanceID(deviceIndex);
|
||||
for (int i = 0; i < PAD_MAX_CONTROLLERS; ++i) {
|
||||
if (slots[i].controller != nullptr && slots[i].instanceId == instanceId) return;
|
||||
}
|
||||
int slot = -1;
|
||||
for (int i = 0; i < PAD_MAX_CONTROLLERS; ++i) {
|
||||
if (slots[i].controller == nullptr) {
|
||||
slot = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (slot == -1) return;
|
||||
SDL_GameController* gc = SDL_GameControllerOpen(deviceIndex);
|
||||
if (gc == nullptr) {
|
||||
APS5_LOG_ERR("Pad: could not open game controller %d: %s", deviceIndex, SDL_GetError());
|
||||
return;
|
||||
}
|
||||
const char* name = SDL_GameControllerName(controller);
|
||||
APS5_LOG_OUT("Pad: connected game controller: %s (type %d, sensors accel=%d gyro=%d, touchpads=%d, led=%d, trigger rumble=%d)",
|
||||
name != nullptr ? name : "unknown", static_cast<int>(SDL_GameControllerGetType(controller)),
|
||||
SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) == SDL_TRUE, SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) == SDL_TRUE,
|
||||
SDL_GameControllerGetNumTouchpads(controller), SDL_GameControllerHasLED(controller) == SDL_TRUE, SDL_GameControllerHasRumbleTriggers(controller) == SDL_TRUE);
|
||||
enableSensors();
|
||||
outputPending = true;
|
||||
lightBar.Reset();
|
||||
triggerEffect.Reset();
|
||||
slots[slot].controller = gc;
|
||||
slots[slot].instanceId = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(gc));
|
||||
slots[slot].state = {};
|
||||
slots[slot].outputSequence = 0;
|
||||
slots[slot].outputState = {};
|
||||
slots[slot].outputPending = true;
|
||||
slots[slot].nextRumbleRefresh = {};
|
||||
slots[slot].lightBar.Reset();
|
||||
slots[slot].triggerEffect.Reset();
|
||||
const char* name = SDL_GameControllerName(gc);
|
||||
APS5_LOG_OUT("Pad: connected game controller (slot %d): %s (type %d, sensors accel=%d gyro=%d, touchpads=%d, led=%d, trigger rumble=%d)",
|
||||
slot, name != nullptr ? name : "unknown", static_cast<int>(SDL_GameControllerGetType(gc)),
|
||||
SDL_GameControllerHasSensor(gc, SDL_SENSOR_ACCEL) == SDL_TRUE, SDL_GameControllerHasSensor(gc, SDL_SENSOR_GYRO) == SDL_TRUE,
|
||||
SDL_GameControllerGetNumTouchpads(gc), SDL_GameControllerHasLED(gc) == SDL_TRUE, SDL_GameControllerHasRumbleTriggers(gc) == SDL_TRUE);
|
||||
enableSensors(slot);
|
||||
PadSetPortConnected_nid_postfix(slot, true);
|
||||
UserServiceSetUserConnected_nid_postfix(slot, true);
|
||||
}
|
||||
|
||||
void PadInput::enableSensors() {
|
||||
void PadInput::enableSensors(int slot) {
|
||||
if (slot < 0 || slot >= PAD_MAX_CONTROLLERS) return;
|
||||
SDL_GameController* controller = slots[slot].controller;
|
||||
if (controller == nullptr) return;
|
||||
const SDL_bool wanted = outputState.motionEnabled ? SDL_TRUE : SDL_FALSE;
|
||||
const SDL_bool wanted = slots[slot].outputState.motionEnabled ? SDL_TRUE : SDL_FALSE;
|
||||
if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) == SDL_TRUE) SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, wanted);
|
||||
if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) == SDL_TRUE) SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, wanted);
|
||||
}
|
||||
|
||||
void PadInput::closeController() {
|
||||
if (controller == nullptr) return;
|
||||
SDL_GameControllerClose(controller);
|
||||
controller = nullptr;
|
||||
controllerState = {};
|
||||
void PadInput::closeController(int slot) {
|
||||
if (slot < 0 || slot >= PAD_MAX_CONTROLLERS) return;
|
||||
if (slots[slot].controller == nullptr) return;
|
||||
SDL_GameControllerClose(slots[slot].controller);
|
||||
slots[slot].controller = nullptr;
|
||||
slots[slot].instanceId = -1;
|
||||
slots[slot].state = {};
|
||||
slots[slot].outputState = {};
|
||||
slots[slot].outputPending = false;
|
||||
if (slot > 0) {
|
||||
PadSetPortConnected_nid_postfix(slot, false);
|
||||
UserServiceSetUserConnected_nid_postfix(slot, false);
|
||||
}
|
||||
}
|
||||
|
||||
void PadInput::applyOutput() {
|
||||
PadOutputState fetched;
|
||||
if (PadFetchOutput_nid_postfix(&outputSequence, &fetched)) {
|
||||
const bool motionChanged = fetched.motionEnabled != outputState.motionEnabled;
|
||||
outputState = fetched;
|
||||
outputPending = true;
|
||||
if (motionChanged) enableSensors();
|
||||
}
|
||||
if (controller == nullptr) return;
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const bool isPs5 = SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_PS5;
|
||||
const auto large = std::max(outputState.vibrationLarge, outputState.audioVibrationLarge);
|
||||
const auto small = std::max(outputState.vibrationSmall, outputState.audioVibrationSmall);
|
||||
const bool rumbling = large != 0 || small != 0;
|
||||
const bool triggerRumble = outputState.trigger[0].fallback != 0 || outputState.trigger[1].fallback != 0;
|
||||
if (SDL_GameControllerHasLED(controller) == SDL_TRUE) {
|
||||
const LightBarPacer::Color color = outputState.lightBarValid
|
||||
? LightBarPacer::Color{outputState.lightBar[0], outputState.lightBar[1], outputState.lightBar[2]}
|
||||
: LightBarPacer::Color{0, 64, 255};
|
||||
if (lightBar.Due(color, now)) lightBar.Result(color, now, SDL_GameControllerSetLED(controller, color[0], color[1], color[2]) == 0);
|
||||
}
|
||||
if (!outputPending) {
|
||||
if ((rumbling || (triggerRumble && !isPs5)) && now >= nextRumbleRefresh) outputPending = true;
|
||||
else return;
|
||||
}
|
||||
outputPending = false;
|
||||
nextRumbleRefresh = now + std::chrono::milliseconds(700);
|
||||
constexpr Uint32 rumbleMs = 2000;
|
||||
SDL_GameControllerRumble(controller, static_cast<Uint16>(large * 257), static_cast<Uint16>(small * 257), rumbling ? rumbleMs : 0);
|
||||
if (outputState.triggerTouched) {
|
||||
if (isPs5) {
|
||||
TriggerEffectCache::Effect effect{};
|
||||
effect[0] = 0x04 | 0x08;
|
||||
std::memcpy(effect.data() + 10, outputState.trigger[1].effect, 11);
|
||||
std::memcpy(effect.data() + 21, outputState.trigger[0].effect, 11);
|
||||
if (triggerEffect.NeedsSend(effect)) {
|
||||
triggerEffect.Result(effect, SDL_GameControllerSendEffect(controller, effect.data(), static_cast<int>(effect.size())) == 0);
|
||||
for (int slot = 0; slot < PAD_MAX_CONTROLLERS; ++slot) {
|
||||
PadOutputState fetched;
|
||||
if (PadFetchOutputForPort_nid_postfix(slot, &slots[slot].outputSequence, &fetched)) {
|
||||
const bool motionChanged = fetched.motionEnabled != slots[slot].outputState.motionEnabled;
|
||||
slots[slot].outputState = fetched;
|
||||
slots[slot].outputPending = true;
|
||||
if (motionChanged) enableSensors(slot);
|
||||
}
|
||||
SDL_GameController* controller = slots[slot].controller;
|
||||
if (controller == nullptr) continue;
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const bool isPs5 = SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_PS5;
|
||||
const auto large = std::max(slots[slot].outputState.vibrationLarge, slots[slot].outputState.audioVibrationLarge);
|
||||
const auto small = std::max(slots[slot].outputState.vibrationSmall, slots[slot].outputState.audioVibrationSmall);
|
||||
const bool rumbling = large != 0 || small != 0;
|
||||
const bool triggerRumble = slots[slot].outputState.trigger[0].fallback != 0 || slots[slot].outputState.trigger[1].fallback != 0;
|
||||
if (SDL_GameControllerHasLED(controller) == SDL_TRUE) {
|
||||
struct PlayerLedColor { Uint8 r, g, b; };
|
||||
static constexpr PlayerLedColor defaultColors[4] = {
|
||||
{0, 64, 255},
|
||||
{255, 0, 0},
|
||||
{0, 255, 0},
|
||||
{255, 0, 128}
|
||||
};
|
||||
const auto& def = defaultColors[slot % 4];
|
||||
const LightBarPacer::Color color = slots[slot].outputState.lightBarValid
|
||||
? LightBarPacer::Color{slots[slot].outputState.lightBar[0], slots[slot].outputState.lightBar[1], slots[slot].outputState.lightBar[2]}
|
||||
: LightBarPacer::Color{def.r, def.g, def.b};
|
||||
if (slots[slot].lightBar.Due(color, now)) slots[slot].lightBar.Result(color, now, SDL_GameControllerSetLED(controller, color[0], color[1], color[2]) == 0);
|
||||
}
|
||||
if (!slots[slot].outputPending) {
|
||||
if ((rumbling || (triggerRumble && !isPs5)) && now >= slots[slot].nextRumbleRefresh) slots[slot].outputPending = true;
|
||||
else continue;
|
||||
}
|
||||
slots[slot].outputPending = false;
|
||||
slots[slot].nextRumbleRefresh = now + std::chrono::milliseconds(700);
|
||||
constexpr Uint32 rumbleMs = 2000;
|
||||
SDL_GameControllerRumble(controller, static_cast<Uint16>(large * 257), static_cast<Uint16>(small * 257), rumbling ? rumbleMs : 0);
|
||||
if (slots[slot].outputState.triggerTouched) {
|
||||
if (isPs5) {
|
||||
TriggerEffectCache::Effect effect{};
|
||||
effect[0] = 0x04 | 0x08;
|
||||
std::memcpy(effect.data() + 10, slots[slot].outputState.trigger[1].effect, 11);
|
||||
std::memcpy(effect.data() + 21, slots[slot].outputState.trigger[0].effect, 11);
|
||||
if (slots[slot].triggerEffect.NeedsSend(effect)) {
|
||||
slots[slot].triggerEffect.Result(effect, SDL_GameControllerSendEffect(controller, effect.data(), static_cast<int>(effect.size())) == 0);
|
||||
}
|
||||
} else if (SDL_GameControllerHasRumbleTriggers(controller) == SDL_TRUE) {
|
||||
SDL_GameControllerRumbleTriggers(controller, static_cast<Uint16>(slots[slot].outputState.trigger[0].fallback * 257), static_cast<Uint16>(slots[slot].outputState.trigger[1].fallback * 257), triggerRumble ? rumbleMs : 0);
|
||||
}
|
||||
} else if (SDL_GameControllerHasRumbleTriggers(controller) == SDL_TRUE) {
|
||||
SDL_GameControllerRumbleTriggers(controller, static_cast<Uint16>(outputState.trigger[0].fallback * 257), static_cast<Uint16>(outputState.trigger[1].fallback * 257), triggerRumble ? rumbleMs : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,10 +172,12 @@ void PadInput::setMouseMode(bool enabled) {
|
||||
keyboardMouse.SetMouseMode(enabled);
|
||||
}
|
||||
|
||||
PadInputState PadInput::sampleController() const {
|
||||
PadInputState PadInput::sampleController(int slot) const {
|
||||
PadInputState result;
|
||||
if (slot < 0 || slot >= PAD_MAX_CONTROLLERS) return result;
|
||||
SDL_GameController* controller = slots[slot].controller;
|
||||
if (controller == nullptr) return result;
|
||||
const auto readButton = [this](SDL_GameControllerButton button) {
|
||||
const auto readButton = [controller](SDL_GameControllerButton button) {
|
||||
return SDL_GameControllerGetButton(controller, button) != 0;
|
||||
};
|
||||
const auto addButton = [&result, &readButton](SDL_GameControllerButton source, Pad::PadButton button) {
|
||||
@@ -156,7 +201,7 @@ PadInputState PadInput::sampleController() const {
|
||||
addButton(SDL_CONTROLLER_BUTTON_DPAD_LEFT, Pad::PadButton::Left);
|
||||
addButton(SDL_CONTROLLER_BUTTON_TOUCHPAD, Pad::PadButton::TouchPad);
|
||||
|
||||
const auto triggerValue = [this](SDL_GameControllerAxis axis) {
|
||||
const auto triggerValue = [controller](SDL_GameControllerAxis axis) {
|
||||
const auto value = std::clamp<int>(SDL_GameControllerGetAxis(controller, axis), 0, 32767);
|
||||
return static_cast<std::uint8_t>((value * 255 + 16383) / 32767);
|
||||
};
|
||||
@@ -165,7 +210,7 @@ PadInputState PadInput::sampleController() const {
|
||||
if (result.analogButtonsL2 != 0) result.buttons |= static_cast<std::uint32_t>(Pad::PadButton::L2);
|
||||
if (result.analogButtonsR2 != 0) result.buttons |= static_cast<std::uint32_t>(Pad::PadButton::R2);
|
||||
|
||||
const auto stick = [this](SDL_GameControllerAxis xAxis, SDL_GameControllerAxis yAxis, int deadzonePercent) {
|
||||
const auto stick = [this, controller](SDL_GameControllerAxis xAxis, SDL_GameControllerAxis yAxis, int deadzonePercent) {
|
||||
return Pad::StickWithDeadzone(SDL_GameControllerGetAxis(controller, xAxis), SDL_GameControllerGetAxis(controller, yAxis), deadzonePercent);
|
||||
};
|
||||
const auto left = stick(SDL_CONTROLLER_AXIS_LEFTX, SDL_CONTROLLER_AXIS_LEFTY, keyboardMouse.Configuration().settings.leftStickDeadzonePercent);
|
||||
@@ -207,23 +252,34 @@ void PadInput::HandleEvent(const SDL_Event& event, DisplayWindow& window) {
|
||||
openController(event.cdevice.which);
|
||||
return;
|
||||
}
|
||||
if (event.type == SDL_CONTROLLERDEVICEREMOVED && controller != nullptr) {
|
||||
const auto instanceId = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller));
|
||||
if (instanceId == event.cdevice.which) {
|
||||
closeController();
|
||||
openFirstAvailableController();
|
||||
publish(window.FrameNumber());
|
||||
if (event.type == SDL_CONTROLLERDEVICEREMOVED) {
|
||||
for (int slot = 0; slot < PAD_MAX_CONTROLLERS; ++slot) {
|
||||
if (slots[slot].controller != nullptr && slots[slot].instanceId == event.cdevice.which) {
|
||||
closeController(slot);
|
||||
publish(window.FrameNumber(), slot);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (event.type == SDL_CONTROLLERBUTTONDOWN || event.type == SDL_CONTROLLERBUTTONUP ||
|
||||
event.type == SDL_CONTROLLERAXISMOTION || event.type == SDL_CONTROLLERDEVICEREMAPPED) {
|
||||
publish(window.FrameNumber());
|
||||
SDL_JoystickID which = -1;
|
||||
if (event.type == SDL_CONTROLLERBUTTONDOWN || event.type == SDL_CONTROLLERBUTTONUP) which = event.cbutton.which;
|
||||
else if (event.type == SDL_CONTROLLERAXISMOTION) which = event.caxis.which;
|
||||
else if (event.type == SDL_CONTROLLERDEVICEREMAPPED) which = event.cdevice.which;
|
||||
for (int slot = 0; slot < PAD_MAX_CONTROLLERS; ++slot) {
|
||||
if (slots[slot].controller != nullptr && slots[slot].instanceId == which) {
|
||||
slots[slot].state = sampleController(slot);
|
||||
publish(window.FrameNumber(), slot);
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (event.type == SDL_WINDOWEVENT) {
|
||||
if (keyboardMouse.HandleEvent(event, std::chrono::steady_clock::now()).releaseMouse) setMouseMode(false);
|
||||
publish(window.FrameNumber());
|
||||
publish(window.FrameNumber(), 0);
|
||||
return;
|
||||
}
|
||||
// APS5_NO_PAD_INPUT=1 keeps a measurement run from reacting to keys or mouse buttons that reach
|
||||
@@ -239,25 +295,54 @@ void PadInput::HandleEvent(const SDL_Event& event, DisplayWindow& window) {
|
||||
if (requests.togglePerformanceOverlay && window.Handle() != nullptr && event.key.windowID == SDL_GetWindowID(window.Handle())) AgcDriverTogglePerformanceOverlay_nid_postfix();
|
||||
if (requests.screenshot && window.Handle() != nullptr && event.key.windowID == SDL_GetWindowID(window.Handle())) AgcDriverRequestScreenshot_nid_postfix();
|
||||
if (requests.toggleMouse) setMouseMode(!keyboardMouse.MouseMode());
|
||||
if (event.type != SDL_MOUSEMOTION) publish(window.FrameNumber());
|
||||
if (event.type != SDL_MOUSEMOTION) publish(window.FrameNumber(), 0);
|
||||
}
|
||||
|
||||
void PadInput::Update(const DisplayWindow& window) {
|
||||
if (controller != nullptr) SDL_GameControllerUpdate();
|
||||
bool anyController = false;
|
||||
for (int slot = 0; slot < PAD_MAX_CONTROLLERS; ++slot) {
|
||||
if (slots[slot].controller != nullptr) {
|
||||
anyController = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (anyController) SDL_GameControllerUpdate();
|
||||
applyOutput();
|
||||
if (controller != nullptr) controllerState = sampleController();
|
||||
for (int slot = 0; slot < PAD_MAX_CONTROLLERS; ++slot) {
|
||||
if (slots[slot].controller != nullptr) {
|
||||
slots[slot].state = sampleController(slot);
|
||||
}
|
||||
}
|
||||
if (keyboardMouse.MouseMode() && SDL_GetKeyboardFocus() == nullptr) {
|
||||
keyboardMouse.Release();
|
||||
setMouseMode(false);
|
||||
publish(window.FrameNumber());
|
||||
publish(window.FrameNumber(), 0);
|
||||
return;
|
||||
}
|
||||
const bool changed = keyboardMouse.Advance(std::chrono::steady_clock::now());
|
||||
if (controller != nullptr || keyboardMouse.MouseMode() || changed) publish(window.FrameNumber());
|
||||
if (slots[0].controller != nullptr || keyboardMouse.MouseMode() || changed) {
|
||||
publish(window.FrameNumber(), 0);
|
||||
}
|
||||
for (int slot = 1; slot < PAD_MAX_CONTROLLERS; ++slot) {
|
||||
if (slots[slot].controller != nullptr) {
|
||||
publish(window.FrameNumber(), slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PadInput::publish(std::uint64_t frame) {
|
||||
const auto state = keyboardMouse.Compose(controllerState);
|
||||
PadPublishInput_nid_postfix(state);
|
||||
if (auto* telemetry = VideoOutTelemetry::Instance(); telemetry != nullptr) VideoOutTelemetry::EmitPadIfChanged(*telemetry, telemetryFilter, frame, state);
|
||||
for (int slot = 0; slot < PAD_MAX_CONTROLLERS; ++slot) {
|
||||
publish(frame, slot);
|
||||
}
|
||||
}
|
||||
|
||||
void PadInput::publish(std::uint64_t frame, int slot) {
|
||||
if (slot < 0 || slot >= PAD_MAX_CONTROLLERS) return;
|
||||
if (slot == 0) {
|
||||
const auto state = keyboardMouse.Compose(slots[0].state);
|
||||
PadPublishInput_nid_postfix(state);
|
||||
if (auto* telemetry = VideoOutTelemetry::Instance(); telemetry != nullptr) VideoOutTelemetry::EmitPadIfChanged(*telemetry, telemetryFilter, frame, state);
|
||||
} else {
|
||||
PadPublishInputForPort_nid_postfix(slot, slots[slot].state);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,99 @@
|
||||
#include "SDL_config.h"
|
||||
|
||||
#include "SDL.h"
|
||||
#include "SDL_joystick.h"
|
||||
#include "SDL_gamecontroller.h"
|
||||
#include "prx/libSceVideoOut/include/PadInput.hpp"
|
||||
#include "prx/libSceVideoOut/include/DisplayWindow.hpp"
|
||||
#include "prx/libScePad/include/PadState.hpp"
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#undef main
|
||||
|
||||
namespace {
|
||||
|
||||
static void Require(bool condition) {
|
||||
if (!condition) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
#ifdef SDL_JOYSTICK_HIDAPI
|
||||
std::puts("pad_sdl: SDL drives controllers through HIDAPI");
|
||||
return 0;
|
||||
#else
|
||||
std::fputs("pad_sdl: SDL is built without HIDAPI, so a DualSense gets no light bar, touchpad or trigger effects\n", stderr);
|
||||
return 1;
|
||||
#endif
|
||||
|
||||
Require(SDL_Init(SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK) == 0);
|
||||
|
||||
SDL_VirtualJoystickDesc desc{};
|
||||
desc.version = SDL_VIRTUAL_JOYSTICK_DESC_VERSION;
|
||||
desc.type = SDL_JOYSTICK_TYPE_GAMECONTROLLER;
|
||||
desc.naxes = 6;
|
||||
desc.nbuttons = 16;
|
||||
desc.name = "Virtual Controller 0";
|
||||
const int dev0 = SDL_JoystickAttachVirtualEx(&desc);
|
||||
Require(dev0 >= 0);
|
||||
Require(SDL_IsGameController(dev0));
|
||||
|
||||
DisplayWindow window;
|
||||
PadInput padInput;
|
||||
Require(padInput.SlotOccupied(0));
|
||||
Require(!padInput.SlotOccupied(1));
|
||||
|
||||
SDL_Event add0{};
|
||||
add0.type = SDL_CONTROLLERDEVICEADDED;
|
||||
add0.cdevice.which = dev0;
|
||||
padInput.HandleEvent(add0, window);
|
||||
Require(padInput.SlotOccupied(0));
|
||||
Require(!padInput.SlotOccupied(1));
|
||||
|
||||
desc.name = "Virtual Controller 1";
|
||||
const int dev1 = SDL_JoystickAttachVirtualEx(&desc);
|
||||
Require(dev1 >= 0);
|
||||
Require(SDL_IsGameController(dev1));
|
||||
|
||||
SDL_Event add1{};
|
||||
add1.type = SDL_CONTROLLERDEVICEADDED;
|
||||
add1.cdevice.which = dev1;
|
||||
padInput.HandleEvent(add1, window);
|
||||
Require(padInput.SlotOccupied(0));
|
||||
Require(padInput.SlotOccupied(1));
|
||||
Require(!padInput.SlotOccupied(2));
|
||||
Require(Pad::IsConnected(1));
|
||||
|
||||
const SDL_JoystickID id0 = SDL_JoystickGetDeviceInstanceID(dev0);
|
||||
SDL_Event rem0{};
|
||||
rem0.type = SDL_CONTROLLERDEVICEREMOVED;
|
||||
rem0.cdevice.which = id0;
|
||||
padInput.HandleEvent(rem0, window);
|
||||
Require(!padInput.SlotOccupied(0));
|
||||
Require(padInput.SlotOccupied(1));
|
||||
Require(Pad::IsConnected(1));
|
||||
|
||||
padInput.HandleEvent(add0, window);
|
||||
Require(padInput.SlotOccupied(0));
|
||||
Require(padInput.SlotOccupied(1));
|
||||
|
||||
const SDL_JoystickID id1 = SDL_JoystickGetDeviceInstanceID(dev1);
|
||||
SDL_Event rem1{};
|
||||
rem1.type = SDL_CONTROLLERDEVICEREMOVED;
|
||||
rem1.cdevice.which = id1;
|
||||
padInput.HandleEvent(rem1, window);
|
||||
Require(padInput.SlotOccupied(0));
|
||||
Require(!padInput.SlotOccupied(1));
|
||||
Require(!Pad::IsConnected(1));
|
||||
|
||||
padInput.HandleEvent(rem0, window);
|
||||
Require(!padInput.SlotOccupied(0));
|
||||
Require(!padInput.SlotOccupied(1));
|
||||
|
||||
Require(SDL_JoystickDetachVirtual(dev1) == 0);
|
||||
Require(SDL_JoystickDetachVirtual(dev0) == 0);
|
||||
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -410,6 +410,15 @@ if(BUILD_TESTING)
|
||||
set_tests_properties(guest_libc-random-seeding PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test_executable(guest_random_device_seed_tests ${LIBS_INCLUDE_DIR}/tests/GuestRandomDeviceSeed.cpp src/Random.cpp)
|
||||
target_include_directories(guest_random_device_seed_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_random_device_seed_tests PRIVATE libc)
|
||||
configure_windows_unwind(guest_random_device_seed_tests)
|
||||
add_test(NAME guest_random_device_seed COMMAND guest_random_device_seed_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_random_device_seed PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test_executable(guest_integer_builtins_tests ${LIBS_INCLUDE_DIR}/tests/GuestIntegerBuiltins.cpp)
|
||||
target_include_directories(guest_integer_builtins_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_integer_builtins_tests PRIVATE libc)
|
||||
|
||||
@@ -90,6 +90,9 @@ struct PthreadSyncForwards_nid_no_patch {
|
||||
};
|
||||
extern "C" void RegisterPthreadSyncForwards_nid_no_patch(const PthreadSyncForwards_nid_no_patch* forwards);
|
||||
|
||||
using SigactionFn_nid_no_patch = int (APS5_VABI*)(int, const void*, void*);
|
||||
extern "C" void RegisterSigaction_nid_no_patch(SigactionFn_nid_no_patch fn, std::uint32_t* interruptSet);
|
||||
|
||||
#define APS5_INVALID_ARG_EX throw std::invalid_argument(std::string(__func__) + ": invalid argument")
|
||||
|
||||
#define APS5_DUMMY_FUN \
|
||||
|
||||
@@ -58,6 +58,40 @@ struct CollateVtable {
|
||||
std::int64_t (APS5_VABI *hash)(const CollateFacet* self, const char* first, const char* last);
|
||||
};
|
||||
|
||||
struct CodecvtVtable {
|
||||
FacetVtable facet;
|
||||
bool (APS5_VABI *alwaysNoconv)(const Facet* self);
|
||||
int (APS5_VABI *maxLength)(const Facet* self);
|
||||
int (APS5_VABI *encoding)(const Facet* self);
|
||||
int (APS5_VABI *in)(Facet* self, std::uint64_t* state, const char* first1, const char* last1, const char** next1, char* first3, char* last3, char** next3);
|
||||
int (APS5_VABI *out)(Facet* self, std::uint64_t* state, const char* first1, const char* last1, const char** next1, char* first3, char* last3, char** next3);
|
||||
int (APS5_VABI *unshift)(Facet* self, std::uint64_t* state, char* first3, char* last3, char** next3);
|
||||
std::size_t (APS5_VABI *length)(Facet* self, std::uint64_t* state, const char* first1, const char* last1, std::size_t count);
|
||||
};
|
||||
|
||||
struct NumpunctFacet {
|
||||
Facet base;
|
||||
const char* grouping;
|
||||
char decimalPoint;
|
||||
char thousandsSep;
|
||||
const char* falsename;
|
||||
const char* truename;
|
||||
};
|
||||
|
||||
struct NumpunctVtable {
|
||||
FacetVtable facet;
|
||||
char (APS5_VABI *decimalPoint)(const NumpunctFacet* self);
|
||||
char (APS5_VABI *thousandsSep)(const NumpunctFacet* self);
|
||||
String* (APS5_VABI *grouping)(String* result, const NumpunctFacet* self);
|
||||
String* (APS5_VABI *falsename)(String* result, const NumpunctFacet* self);
|
||||
String* (APS5_VABI *truename)(String* result, const NumpunctFacet* self);
|
||||
};
|
||||
|
||||
struct UnsupportedMethodsVtable {
|
||||
FacetVtable facet;
|
||||
void (APS5_VABI *methods[8])();
|
||||
};
|
||||
|
||||
struct IosBase {
|
||||
const void* vtable;
|
||||
std::int64_t standardStream;
|
||||
@@ -89,6 +123,16 @@ static_assert(offsetof(CollateFacet, collation) == 0x10);
|
||||
static_assert(sizeof(CollateFacet) == 0x20);
|
||||
static_assert(offsetof(CollateVtable, compare) == 0x20);
|
||||
static_assert(offsetof(CollateVtable, hash) == 0x30);
|
||||
static_assert(offsetof(CodecvtVtable, alwaysNoconv) == 0x20);
|
||||
static_assert(offsetof(CodecvtVtable, in) == 0x38);
|
||||
static_assert(offsetof(CodecvtVtable, length) == 0x50);
|
||||
static_assert(sizeof(NumpunctFacet) == 0x30);
|
||||
static_assert(offsetof(NumpunctFacet, grouping) == 0x10);
|
||||
static_assert(offsetof(NumpunctFacet, decimalPoint) == 0x18);
|
||||
static_assert(offsetof(NumpunctFacet, falsename) == 0x20);
|
||||
static_assert(offsetof(NumpunctFacet, truename) == 0x28);
|
||||
static_assert(offsetof(NumpunctVtable, decimalPoint) == 0x20);
|
||||
static_assert(offsetof(NumpunctVtable, truename) == 0x40);
|
||||
static_assert(alignof(LocinfoStorage) == 8);
|
||||
|
||||
}
|
||||
|
||||
@@ -279,8 +279,11 @@ inline int FormatWindows(char* buffer, size_t size, const char* format, const vo
|
||||
}
|
||||
if (const size_t position = ArgumentPosition(format)) args.Seek(position);
|
||||
std::string spec = "%";
|
||||
for (; *format && std::strchr("-+ #0'", *format); ++format)
|
||||
bool unsupportedUnknownFlag = false;
|
||||
for (; *format && std::strchr("-+ #0'", *format); ++format) {
|
||||
if (*format != '\'') spec += *format;
|
||||
if (*format != '-') unsupportedUnknownFlag = true;
|
||||
}
|
||||
if (*format == '*') {
|
||||
++format;
|
||||
const int width = star();
|
||||
@@ -316,7 +319,7 @@ inline int FormatWindows(char* buffer, size_t size, const char* format, const vo
|
||||
if ((length == "h" && *format == 'h') || (length == "l" && *format == 'l'))
|
||||
length += *format++;
|
||||
}
|
||||
const char conversion = *format;
|
||||
char conversion = *format;
|
||||
if (!conversion) throw std::invalid_argument("Incomplete format conversion");
|
||||
++format;
|
||||
const bool integerLength = length.empty() || length == "h" || length == "hh" ||
|
||||
@@ -404,6 +407,11 @@ inline int FormatWindows(char* buffer, size_t size, const char* format, const vo
|
||||
else if (length == "h") *static_cast<short*>(pointer) = static_cast<short>(count);
|
||||
else if (length.empty()) *static_cast<int*>(pointer) = count;
|
||||
else *static_cast<long long*>(pointer) = count;
|
||||
} else if (!std::strchr("diouxXaAeEfFgGcsSpnmCS", conversion)) {
|
||||
if (secure || unsupportedUnknownFlag || spec.find_first_not_of("%-0123456789") != std::string::npos)
|
||||
throw std::invalid_argument("Unsupported format conversion '" + spec + length + conversion + "' in \"" + std::string(formatStart).substr(0, 160) + "\"");
|
||||
const std::string text = conversion == '%' ? "%" : std::string("%") + conversion;
|
||||
output.Value(spec + 's', text.c_str());
|
||||
} else {
|
||||
throw std::invalid_argument("Unsupported format conversion '" + spec + length + conversion + "' in \"" + std::string(formatStart).substr(0, 160) + "\"");
|
||||
}
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
extern "C" {
|
||||
int* APS5_VABI __error_nid_postfix();
|
||||
double APS5_VABI strtod_nid_postfix(const char* str, char** endptr);
|
||||
float APS5_VABI strtof_nid_postfix(const char* str, char** endptr);
|
||||
long double APS5_VABI strtold_nid_postfix(const char* str, char** endptr);
|
||||
long long APS5_VABI strtoll_nid_postfix(const char* str, char** endptr, int base);
|
||||
unsigned long long APS5_VABI strtoull_nid_postfix(const char* str, char** endptr, int base);
|
||||
double APS5_VABI sin_nid_postfix(double x);
|
||||
double APS5_VABI cos_nid_postfix(double x);
|
||||
float APS5_VABI sinf_nid_postfix(float x);
|
||||
@@ -28,6 +31,7 @@ float APS5_VABI log2f_nid_postfix(float x);
|
||||
namespace {
|
||||
|
||||
constexpr long long ExponentLimit = 1000000000;
|
||||
constexpr int InvalidArgument = 22;
|
||||
constexpr int RangeError = 34;
|
||||
|
||||
bool Hexadecimal(const char* text) {
|
||||
@@ -36,39 +40,115 @@ bool Hexadecimal(const char* text) {
|
||||
return text[0] == '0' && (text[1] == 'x' || text[1] == 'X');
|
||||
}
|
||||
|
||||
template<typename TFloat>
|
||||
TFloat ScaledFloat(const char* text, char** end, std::int64_t pten, int* error, TFloat (APS5_VABI *parse)(const char*, char**), const char* function) {
|
||||
int& guestErrno = *__error_nid_postfix();
|
||||
const int saved = guestErrno;
|
||||
if (error != nullptr) *error = 0;
|
||||
guestErrno = 0;
|
||||
char* stop = nullptr;
|
||||
TFloat value = parse(text, &stop);
|
||||
if (end != nullptr) *end = stop;
|
||||
const bool overflowed = std::isinf(value) && guestErrno == RangeError;
|
||||
if (pten != 0 && stop != text && (std::isfinite(value) || overflowed)) {
|
||||
if (Hexadecimal(text)) NotImplemented_nid_no_patch(function);
|
||||
std::string number(text, static_cast<const char*>(stop));
|
||||
long long exponent = 0;
|
||||
const auto marker = number.find_first_of("eE");
|
||||
if (marker != std::string::npos) {
|
||||
exponent = std::strtoll(number.c_str() + marker + 1, nullptr, 10);
|
||||
number.resize(marker);
|
||||
}
|
||||
exponent = std::clamp(exponent, -ExponentLimit, ExponentLimit) + std::clamp<std::int64_t>(pten, -ExponentLimit, ExponentLimit);
|
||||
number += 'e';
|
||||
number += std::to_string(exponent);
|
||||
guestErrno = 0;
|
||||
value = parse(number.c_str(), nullptr);
|
||||
}
|
||||
if (guestErrno == RangeError && (value == 0 || std::isinf(value))) {
|
||||
if (error != nullptr) *error = 1;
|
||||
} else if (guestErrno == 0 || guestErrno == RangeError) {
|
||||
guestErrno = saved;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
bool InvalidBase(const char* text, char** end, int base, int* error) {
|
||||
if (base >= 0 && base != 1 && base <= 36) return false;
|
||||
if (end != nullptr) *end = const_cast<char*>(text);
|
||||
if (error != nullptr) *error = InvalidArgument;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename TInteger>
|
||||
TInteger ParsedInteger(const char* text, char** end, int base, int* error, TInteger (APS5_VABI *parse)(const char*, char**, int)) {
|
||||
if (error != nullptr) *error = 0;
|
||||
if (InvalidBase(text, end, base, error)) return 0;
|
||||
int& guestErrno = *__error_nid_postfix();
|
||||
const int saved = guestErrno;
|
||||
guestErrno = 0;
|
||||
const TInteger value = parse(text, end, base);
|
||||
if (error != nullptr) *error = guestErrno;
|
||||
if (guestErrno == 0) guestErrno = saved;
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
double APS5_VABI _Stodx_nid_postfix(const char* text, char** end, std::int64_t pten, int* error) {
|
||||
return ScaledFloat<double>(text, end, pten, error, strtod_nid_postfix, "_Stodx (hexadecimal text with a power-of-ten scale)");
|
||||
}
|
||||
|
||||
float APS5_VABI _Stofx_nid_postfix(const char* text, char** end, std::int64_t pten, int* error) {
|
||||
return ScaledFloat<float>(text, end, pten, error, strtof_nid_postfix, "_Stofx (hexadecimal text with a power-of-ten scale)");
|
||||
}
|
||||
|
||||
long double APS5_VABI _Stoldx_nid_postfix(const char* text, char** end, std::int64_t pten, int* error) {
|
||||
return ScaledFloat<long double>(text, end, pten, error, strtold_nid_postfix, "_Stoldx (hexadecimal text with a power-of-ten scale)");
|
||||
}
|
||||
|
||||
double APS5_VABI _Stod_nid_postfix(const char* text, char** end, std::int64_t pten) {
|
||||
return _Stodx_nid_postfix(text, end, pten, nullptr);
|
||||
}
|
||||
|
||||
float APS5_VABI _Stof_nid_postfix(const char* text, char** end, std::int64_t pten) {
|
||||
return _Stofx_nid_postfix(text, end, pten, nullptr);
|
||||
}
|
||||
|
||||
long double APS5_VABI _Stold_nid_postfix(const char* text, char** end, std::int64_t pten) {
|
||||
return _Stoldx_nid_postfix(text, end, pten, nullptr);
|
||||
}
|
||||
|
||||
long long APS5_VABI _Stollx_nid_postfix(const char* text, char** end, int base, int* error) {
|
||||
return ParsedInteger<long long>(text, end, base, error, strtoll_nid_postfix);
|
||||
}
|
||||
|
||||
unsigned long long APS5_VABI _Stoullx_nid_postfix(const char* text, char** end, int base, int* error) {
|
||||
return ParsedInteger<unsigned long long>(text, end, base, error, strtoull_nid_postfix);
|
||||
}
|
||||
|
||||
std::uint64_t APS5_VABI _Stoulx_nid_postfix(const char* text, char** end, int base, int* error) {
|
||||
return _Stoullx_nid_postfix(text, end, base, error);
|
||||
}
|
||||
|
||||
std::int64_t APS5_VABI _Stolx_nid_postfix(const char* text, char** end, int base, int* error) {
|
||||
if (error != nullptr) *error = 0;
|
||||
if (InvalidBase(text, end, base, error)) return 0;
|
||||
int& guestErrno = *__error_nid_postfix();
|
||||
const int saved = guestErrno;
|
||||
guestErrno = 0;
|
||||
char* stop = nullptr;
|
||||
const double value = strtod_nid_postfix(text, &stop);
|
||||
if (end != nullptr) *end = stop;
|
||||
const bool overflowed = std::isinf(value) && guestErrno == RangeError;
|
||||
if (pten == 0 || stop == text || !(std::isfinite(value) || overflowed)) {
|
||||
if (guestErrno == 0) guestErrno = saved;
|
||||
return value;
|
||||
}
|
||||
if (Hexadecimal(text)) NotImplemented_nid_no_patch("_Stod (hexadecimal text with a power-of-ten scale)");
|
||||
std::string number(text, static_cast<const char*>(stop));
|
||||
long long exponent = 0;
|
||||
const auto marker = number.find_first_of("eE");
|
||||
if (marker != std::string::npos) {
|
||||
exponent = std::strtoll(number.c_str() + marker + 1, nullptr, 10);
|
||||
number.resize(marker);
|
||||
}
|
||||
exponent = std::clamp(exponent, -ExponentLimit, ExponentLimit) + std::clamp<std::int64_t>(pten, -ExponentLimit, ExponentLimit);
|
||||
number += 'e';
|
||||
number += std::to_string(exponent);
|
||||
guestErrno = saved;
|
||||
return strtod_nid_postfix(number.c_str(), nullptr);
|
||||
const long long value = strtoll_nid_postfix(text, end, base);
|
||||
if (error != nullptr) *error = guestErrno;
|
||||
guestErrno = 0;
|
||||
static_cast<void>(strtoull_nid_postfix(text, nullptr, base));
|
||||
if (guestErrno == 0) guestErrno = saved;
|
||||
return value;
|
||||
}
|
||||
|
||||
long long APS5_VABI _Stoll_nid_postfix(const char* text, char** end, int base) {
|
||||
return strtoll_nid_postfix(text, end, base);
|
||||
return _Stollx_nid_postfix(text, end, base, nullptr);
|
||||
}
|
||||
|
||||
double APS5_VABI _Sin_nid_postfix(double x, unsigned int quadrant) {
|
||||
|
||||
@@ -220,6 +220,83 @@ std::int64_t APS5_VABI WideCollateHash(const GuestLocale::CollateFacet*, const c
|
||||
|
||||
const GuestLocale::CollateVtable g_wideCollateVtable{{DestroyCollate, DeleteCollate, RetainFacet, ReleaseFacet}, WideCollateCompare, WideCollateTransform, WideCollateHash};
|
||||
|
||||
bool APS5_VABI CodecvtAlwaysNoconv(const GuestLocale::Facet*) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int APS5_VABI CodecvtConstantOne(const GuestLocale::Facet*) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int APS5_VABI CodecvtNoConversion(GuestLocale::Facet*, std::uint64_t*, const char* first1, const char*, const char** next1, char* first3, char*, char** next3) {
|
||||
*next1 = first1;
|
||||
*next3 = first3;
|
||||
return 3;
|
||||
}
|
||||
|
||||
int APS5_VABI CodecvtUnshift(GuestLocale::Facet*, std::uint64_t*, char* first3, char*, char** next3) {
|
||||
*next3 = first3;
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::size_t APS5_VABI CodecvtLength(GuestLocale::Facet*, std::uint64_t*, const char* first1, const char* last1, std::size_t count) {
|
||||
return std::min(static_cast<std::size_t>(last1 - first1), count);
|
||||
}
|
||||
|
||||
const GuestLocale::CodecvtVtable g_codecvtVtable{{DestroyCollate, DeleteCollate, RetainFacet, ReleaseFacet}, CodecvtAlwaysNoconv, CodecvtConstantOne, CodecvtConstantOne, CodecvtNoConversion, CodecvtNoConversion, CodecvtUnshift, CodecvtLength};
|
||||
|
||||
GuestLocale::String* APS5_VABI AssignCString(GuestLocale::String* result, const char* source) {
|
||||
if (result == nullptr || source == nullptr) throw std::invalid_argument("numpunct string: null argument");
|
||||
const std::size_t length = std::strlen(source);
|
||||
char* data = result->buffer;
|
||||
std::size_t capacity = GuestStringInlineCapacity;
|
||||
if (length > GuestStringInlineCapacity) {
|
||||
capacity = length | GuestStringCapacityMask;
|
||||
data = static_cast<char*>(AllocateStringStorage(capacity + 1));
|
||||
}
|
||||
if (length != 0) std::memcpy(data, source, length);
|
||||
data[length] = 0;
|
||||
if (data != result->buffer) result->pointer = data;
|
||||
result->size = length;
|
||||
result->capacity = capacity;
|
||||
return result;
|
||||
}
|
||||
|
||||
char APS5_VABI NumpunctDecimalPoint(const GuestLocale::NumpunctFacet* self) {
|
||||
return self->decimalPoint;
|
||||
}
|
||||
|
||||
char APS5_VABI NumpunctThousandsSep(const GuestLocale::NumpunctFacet* self) {
|
||||
return self->thousandsSep;
|
||||
}
|
||||
|
||||
GuestLocale::String* APS5_VABI NumpunctGrouping(GuestLocale::String* result, const GuestLocale::NumpunctFacet* self) {
|
||||
return AssignCString(result, self->grouping);
|
||||
}
|
||||
|
||||
GuestLocale::String* APS5_VABI NumpunctFalsename(GuestLocale::String* result, const GuestLocale::NumpunctFacet* self) {
|
||||
return AssignCString(result, self->falsename);
|
||||
}
|
||||
|
||||
GuestLocale::String* APS5_VABI NumpunctTruename(GuestLocale::String* result, const GuestLocale::NumpunctFacet* self) {
|
||||
return AssignCString(result, self->truename);
|
||||
}
|
||||
|
||||
const GuestLocale::NumpunctVtable g_numpunctVtable{{DestroyCollate, DeleteCollate, RetainFacet, ReleaseFacet}, NumpunctDecimalPoint, NumpunctThousandsSep, NumpunctGrouping, NumpunctFalsename, NumpunctTruename};
|
||||
|
||||
void APS5_VABI NumPutMethodUnsupported() {
|
||||
NotImplemented_nid_no_patch("num_put<char>::do_put");
|
||||
}
|
||||
|
||||
void APS5_VABI TimePutMethodUnsupported() {
|
||||
NotImplemented_nid_no_patch("time_put<char>::do_put");
|
||||
}
|
||||
|
||||
const GuestLocale::UnsupportedMethodsVtable g_numPutVtable{{DestroyCollate, DeleteCollate, RetainFacet, ReleaseFacet}, {NumPutMethodUnsupported, NumPutMethodUnsupported, NumPutMethodUnsupported, NumPutMethodUnsupported, NumPutMethodUnsupported, NumPutMethodUnsupported, NumPutMethodUnsupported, NumPutMethodUnsupported}};
|
||||
const GuestLocale::UnsupportedMethodsVtable g_timePutVtable{{DestroyCollate, DeleteCollate, RetainFacet, ReleaseFacet}, {TimePutMethodUnsupported, TimePutMethodUnsupported, TimePutMethodUnsupported, TimePutMethodUnsupported, TimePutMethodUnsupported, TimePutMethodUnsupported, TimePutMethodUnsupported, TimePutMethodUnsupported}};
|
||||
|
||||
constexpr std::size_t GuestTimePutFacetSize = 0xb8;
|
||||
|
||||
constexpr std::array<short, 257> MakeClassificationTable() {
|
||||
std::array<short, 257> table{};
|
||||
for (unsigned int value = 0; value < 256; ++value) {
|
||||
@@ -306,6 +383,8 @@ std::uintptr_t _ZTVSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid
|
||||
std::uint64_t _ZNSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 0;
|
||||
std::uint64_t _ZNSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE2idE_nid_postfix = 0;
|
||||
std::uintptr_t _ZTVSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE_nid_postfix[14] {};
|
||||
std::uint64_t _ZNSt8numpunctIcE2idE_nid_postfix = 0;
|
||||
std::uint64_t _ZNSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 0;
|
||||
|
||||
std::streamoff _ZSt7_BADOFF_nid_postfix = -1;
|
||||
std::fpos_t _ZSt4_Fpz_nid_postfix {};
|
||||
@@ -405,6 +484,43 @@ std::size_t APS5_VABI _ZNSt7collateIcE7_GetcatEPPKNSt6locale5facetEPKS1__nid_pos
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::size_t APS5_VABI _ZNSt7codecvtIcc9_MbstatetE7_GetcatEPPKNSt6locale5facetEPKS2__nid_postfix(GuestLocale::Facet** facet, const GuestLocale::Implementation* const*) {
|
||||
if (facet != nullptr && *facet == nullptr) {
|
||||
auto* object = static_cast<GuestLocale::Facet*>(_Znwm_nid_postfix(sizeof(GuestLocale::Facet)));
|
||||
*object = {&g_codecvtVtable.facet, 0, 0};
|
||||
*facet = object;
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
||||
std::size_t APS5_VABI _ZNSt8numpunctIcE7_GetcatEPPKNSt6locale5facetEPKS1__nid_postfix(GuestLocale::Facet** facet, const GuestLocale::Implementation* const*) {
|
||||
if (facet != nullptr && *facet == nullptr) {
|
||||
auto* punct = static_cast<GuestLocale::NumpunctFacet*>(_Znwm_nid_postfix(sizeof(GuestLocale::NumpunctFacet)));
|
||||
*punct = {{&g_numpunctVtable.facet, 0, 0}, "", '.', ',', "false", "true"};
|
||||
*facet = &punct->base;
|
||||
}
|
||||
return 4;
|
||||
}
|
||||
|
||||
std::size_t APS5_VABI _ZNSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE7_GetcatEPPKNSt6locale5facetEPKS5__nid_postfix(GuestLocale::Facet** facet, const GuestLocale::Implementation* const*) {
|
||||
if (facet != nullptr && *facet == nullptr) {
|
||||
auto* object = static_cast<GuestLocale::Facet*>(_Znwm_nid_postfix(sizeof(GuestLocale::Facet)));
|
||||
*object = {&g_numPutVtable.facet, 0, 0};
|
||||
*facet = object;
|
||||
}
|
||||
return 4;
|
||||
}
|
||||
|
||||
std::size_t APS5_VABI _ZNSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE7_GetcatEPPKNSt6locale5facetEPKS5__nid_postfix(GuestLocale::Facet** facet, const GuestLocale::Implementation* const*) {
|
||||
if (facet != nullptr && *facet == nullptr) {
|
||||
auto* object = static_cast<GuestLocale::Facet*>(_Znwm_nid_postfix(GuestTimePutFacetSize));
|
||||
std::memset(object, 0, GuestTimePutFacetSize);
|
||||
object->vtable = &g_timePutVtable.facet;
|
||||
*facet = object;
|
||||
}
|
||||
return 5;
|
||||
}
|
||||
|
||||
void APS5_VABI _ZNSt8_LocinfoC1EPKc_nid_postfix(GuestLocale::LocinfoStorage* self, const char* localeName) {
|
||||
if (self == nullptr || localeName == nullptr || std::strcmp(localeName, "C") != 0) throw std::invalid_argument("_Locinfo: only the C locale is supported");
|
||||
new (self) GuestLocale::LocinfoStorage{};
|
||||
@@ -472,9 +588,11 @@ std::uint32_t APS5_VABI _Towctrans_nid_postfix(std::uint32_t c, std::uint64_t de
|
||||
std::uint64_t _ZNSt7codecvtIwc9_MbstatetE2idE_nid_postfix = 0;
|
||||
alignas(16) unsigned char _ZSt4cout_nid_postfix[0x400] {};
|
||||
alignas(16) unsigned char _ZSt4cerr_nid_postfix[0x400] {};
|
||||
alignas(16) unsigned char _ZSt4clog_nid_postfix[0x400] {};
|
||||
alignas(16) unsigned char _ZSt3cin_nid_postfix[0x400] {};
|
||||
alignas(16) unsigned char _ZSt5wcout_nid_postfix[0x400] {};
|
||||
alignas(16) unsigned char _ZSt5wcerr_nid_postfix[0x400] {};
|
||||
alignas(16) unsigned char _ZSt5wclog_nid_postfix[0x400] {};
|
||||
alignas(16) unsigned char _ZSt4wcin_nid_postfix[0x400] {};
|
||||
|
||||
// Facet vectors are read by inlined guest code, so they come from the guest application heap.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <csignal>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
@@ -281,3 +282,42 @@ int APS5_VABI daemon_nid_postfix(int noChdir, int noClose) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
constexpr int SaRestart = 0x2;
|
||||
std::atomic<SigactionFn_nid_no_patch> registeredSigaction{nullptr};
|
||||
std::atomic<std::uint32_t*> registeredInterruptSet{nullptr};
|
||||
|
||||
struct InterruptSigaction {
|
||||
std::uintptr_t handler;
|
||||
int flags;
|
||||
std::uint32_t mask[4];
|
||||
};
|
||||
static_assert(sizeof(InterruptSigaction) == 32 && offsetof(InterruptSigaction, flags) == 8 && offsetof(InterruptSigaction, mask) == 12);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
void RegisterSigaction_nid_no_patch(SigactionFn_nid_no_patch fn, std::uint32_t* interruptSet) {
|
||||
registeredInterruptSet.store(interruptSet, std::memory_order_release);
|
||||
registeredSigaction.store(fn, std::memory_order_release);
|
||||
}
|
||||
|
||||
int APS5_VABI siginterrupt_nid_postfix(int guest, int flag) {
|
||||
const auto sigaction = registeredSigaction.load(std::memory_order_acquire);
|
||||
auto* const interruptSet = registeredInterruptSet.load(std::memory_order_acquire);
|
||||
if (sigaction == nullptr || interruptSet == nullptr) throw std::runtime_error("siginterrupt: libkernel has not registered sigaction");
|
||||
InterruptSigaction action{};
|
||||
if (sigaction(guest, nullptr, &action) != 0) return -1;
|
||||
const auto index = static_cast<std::uint32_t>(guest - 1);
|
||||
if (flag) {
|
||||
interruptSet[index >> 5] |= 1u << (index & 31);
|
||||
action.flags &= ~SaRestart;
|
||||
} else {
|
||||
interruptSet[index >> 5] &= ~(1u << (index & 31));
|
||||
action.flags |= SaRestart;
|
||||
}
|
||||
return sigaction(guest, &action, nullptr);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include "prx/libc/include/HostEntropy.hpp"
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
@@ -181,6 +182,16 @@ void APS5_VABI srandom_nid_postfix(std::uint64_t seed) {
|
||||
SeedLocked(static_cast<std::uint32_t>(seed));
|
||||
}
|
||||
|
||||
void APS5_VABI srandomdev_nid_postfix() {
|
||||
std::lock_guard lock(g_randomLock);
|
||||
const auto words = g_random.type == LinearCongruentialType ? 1 : g_random.degree;
|
||||
HostEntropy_nid_no_patch(g_random.table + sizeof(std::uint32_t), static_cast<std::size_t>(words) * sizeof(std::uint32_t));
|
||||
if (g_random.type != LinearCongruentialType) {
|
||||
g_random.front = g_random.separation;
|
||||
g_random.rear = 0;
|
||||
}
|
||||
}
|
||||
|
||||
char* APS5_VABI initstate_nid_postfix(std::uint64_t seed, char* state, std::int64_t size) {
|
||||
if (size < MinimumStateBytes[0]) return nullptr;
|
||||
if (state == nullptr) throw std::invalid_argument("initstate: null state");
|
||||
|
||||
@@ -1210,6 +1210,16 @@ if(BUILD_TESTING)
|
||||
set_tests_properties(guest_sched_priority PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
|
||||
endif()
|
||||
add_test_executable(guest_rtprio_thread_tests ${LIBS_INCLUDE_DIR}/tests/GuestRtprioThread.cpp)
|
||||
target_include_directories(guest_rtprio_thread_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_rtprio_thread_tests PRIVATE libkernel libc)
|
||||
configure_windows_unwind(guest_rtprio_thread_tests)
|
||||
add_test(NAME guest_rtprio_thread COMMAND guest_rtprio_thread_tests)
|
||||
set_tests_properties(guest_rtprio_thread PROPERTIES TIMEOUT 20)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_rtprio_thread PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
|
||||
endif()
|
||||
add_test_executable(guest_operation_mode_tests ${LIBS_INCLUDE_DIR}/tests/GuestOperationMode.cpp)
|
||||
target_include_directories(guest_operation_mode_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_operation_mode_tests PRIVATE libkernel libc)
|
||||
@@ -1336,6 +1346,19 @@ if(BUILD_TESTING)
|
||||
set_tests_properties(guest_named_shm_concurrency PROPERTIES TIMEOUT 20)
|
||||
endif()
|
||||
if(WIN32)
|
||||
add_test_executable(guest_named_shm_windows_tests ${LIBS_INCLUDE_DIR}/tests/GuestNamedShmWindows.cpp)
|
||||
target_include_directories(guest_named_shm_windows_tests PRIVATE ${LIBS_INCLUDE_DIR} ${LIBS_INCLUDE_DIR}/nid/include)
|
||||
target_link_libraries(guest_named_shm_windows_tests PRIVATE libkernel libc advapi32)
|
||||
configure_windows_unwind(guest_named_shm_windows_tests)
|
||||
add_test(NAME guest_named_shm_windows COMMAND guest_named_shm_windows_tests)
|
||||
set_tests_properties(guest_named_shm_windows PROPERTIES TIMEOUT 20 ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
|
||||
add_test(NAME guest_named_shm_windows_short_temp COMMAND guest_named_shm_windows_tests --short-temp)
|
||||
set_tests_properties(guest_named_shm_windows_short_temp PROPERTIES TIMEOUT 20 SKIP_RETURN_CODE 77 ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
|
||||
add_test(NAME guest_named_shm_windows_long_short_temp COMMAND guest_named_shm_windows_tests --long-short-temp)
|
||||
set_tests_properties(guest_named_shm_windows_long_short_temp PROPERTIES TIMEOUT 20 SKIP_RETURN_CODE 77 ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
|
||||
set_tests_properties(guest_shm_open PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
|
||||
endif()
|
||||
|
||||
@@ -12,6 +12,64 @@
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
|
||||
extern "C" int APS5_VABI sceKernelFstat(int d, FileStat* sb);
|
||||
|
||||
static constexpr std::uint64_t FileRepollNs = 1000000;
|
||||
|
||||
struct UnreadFile {
|
||||
bool measured = false;
|
||||
std::int64_t bytes = 0;
|
||||
};
|
||||
|
||||
static UnreadFile UnreadFileBytes(int file) {
|
||||
FileStat status{};
|
||||
if (sceKernelFstat(file, &status) != 0) {
|
||||
return {};
|
||||
}
|
||||
const std::int64_t offset = sceKernelLseek(file, 0, 1);
|
||||
if (offset < 0) {
|
||||
return {};
|
||||
}
|
||||
return {true, status.st_size - offset};
|
||||
}
|
||||
|
||||
static bool RegularFile(int descriptor) {
|
||||
if (File::IsHostStream(descriptor)) {
|
||||
return false;
|
||||
}
|
||||
FileStat status{};
|
||||
return sceKernelFstat(descriptor, &status) == 0 && (status.st_mode & 0170000) == 0100000;
|
||||
}
|
||||
|
||||
static KernelEqueueEvent FileEvent(int descriptor, int16_t filter, uint16_t flags, void* udata) {
|
||||
KernelEqueueEvent event{};
|
||||
event.event.ident = static_cast<uintptr_t>(descriptor);
|
||||
event.event.filter = filter;
|
||||
event.event.flags = flags;
|
||||
event.event.udata = udata;
|
||||
event.filter.repoll = true;
|
||||
if (filter == EVFILT_WRITE) {
|
||||
event.filter.pollFunc = [](KernelEqueueEvent* e) {
|
||||
e->event.data = 0;
|
||||
return true;
|
||||
};
|
||||
} else {
|
||||
event.filter.owner = std::make_shared<UnreadFile>();
|
||||
event.filter.pollFunc = [](KernelEqueueEvent* e) {
|
||||
const auto* unread = static_cast<const UnreadFile*>(e->filter.owner.get());
|
||||
if (!unread->measured || unread->bytes == 0) {
|
||||
return false;
|
||||
}
|
||||
e->event.data = static_cast<intptr_t>(unread->bytes);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
event.filter.resetFunc = [](KernelEqueueEvent* e) {
|
||||
e->triggered = false;
|
||||
};
|
||||
return event;
|
||||
}
|
||||
|
||||
static std::unordered_map<KernelEqueue, KernelEqueueRef> g_equeues;
|
||||
static std::mutex g_equeueMutex;
|
||||
static uint64_t g_nextEqueue = 1;
|
||||
@@ -21,6 +79,7 @@ struct KernelEqueuePrivate::WaitSet {
|
||||
std::vector<std::shared_ptr<void>> held;
|
||||
std::vector<std::shared_ptr<KernelEqueuePrivate>> queues;
|
||||
uint64_t nearestTimerNs = std::numeric_limits<uint64_t>::max();
|
||||
bool repoll = false;
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
@@ -98,6 +157,28 @@ bool KernelEqueuePrivate::PollEvents() {
|
||||
return polled;
|
||||
}
|
||||
|
||||
void KernelEqueuePrivate::MeasureFiles(std::unique_lock<std::mutex>& lock) {
|
||||
std::vector<std::pair<int, std::shared_ptr<void>>> files;
|
||||
for (const auto& e : m_events) {
|
||||
if (e.enabled && e.filter.repoll && e.event.filter == EVFILT_READ) {
|
||||
files.emplace_back(static_cast<int>(e.event.ident), e.filter.owner);
|
||||
}
|
||||
}
|
||||
if (files.empty()) {
|
||||
return;
|
||||
}
|
||||
lock.unlock();
|
||||
std::vector<UnreadFile> measured;
|
||||
measured.reserve(files.size());
|
||||
for (const auto& [file, owner] : files) {
|
||||
measured.push_back(UnreadFileBytes(file));
|
||||
}
|
||||
lock.lock();
|
||||
for (std::size_t i = 0; i < files.size(); ++i) {
|
||||
*static_cast<UnreadFile*>(files[i].second.get()) = measured[i];
|
||||
}
|
||||
}
|
||||
|
||||
void KernelEqueuePrivate::WakePollers() {
|
||||
for (const auto waker : m_pollers) {
|
||||
GuestSockets::Wake(waker);
|
||||
@@ -117,6 +198,8 @@ void KernelEqueuePrivate::GatherInterests(WaitSet& wait, std::uintptr_t waker, b
|
||||
}
|
||||
if (e.filter.watched) {
|
||||
GatherWatched(wait, e.filter.watched, waker);
|
||||
} else if (e.filter.repoll) {
|
||||
wait.repoll = true;
|
||||
} else if (e.filter.waitDescriptor >= 0) {
|
||||
wait.interests.push_back({e.filter.waitDescriptor, false});
|
||||
wait.held.push_back(e.filter.owner);
|
||||
@@ -151,6 +234,12 @@ void KernelEqueuePrivate::WaitForDescriptors(std::unique_lock<std::mutex>& lock,
|
||||
const auto waker = GuestSockets::CurrentWaker();
|
||||
WaitSet wait;
|
||||
GatherInterests(wait, waker, false);
|
||||
if (wait.repoll) {
|
||||
const std::uint64_t soon = TimedWait::NowNanos() + FileRepollNs;
|
||||
if (deadlineNanos == 0 || deadlineNanos > soon) {
|
||||
deadlineNanos = soon;
|
||||
}
|
||||
}
|
||||
m_pollers.push_back(waker);
|
||||
if (!wait.queues.empty()) {
|
||||
PollEvents();
|
||||
@@ -279,6 +368,10 @@ int KernelEqueuePrivate::GetTriggeredEvents(KernelEvent* ev, int num) {
|
||||
if (m_closed) {
|
||||
return SCE_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
MeasureFiles(lock);
|
||||
if (m_closed) {
|
||||
return SCE_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
TriggerExpiredTimers(MonotonicNs());
|
||||
PollEvents();
|
||||
return CollectTriggered(ev, num);
|
||||
@@ -293,6 +386,10 @@ std::size_t KernelEqueuePrivate::CountTriggeredEvents() {
|
||||
if (m_closed) {
|
||||
return 0;
|
||||
}
|
||||
MeasureFiles(lock);
|
||||
if (m_closed) {
|
||||
return 0;
|
||||
}
|
||||
TriggerExpiredTimers(MonotonicNs());
|
||||
PollEvents();
|
||||
return static_cast<std::size_t>(std::count_if(m_events.begin(), m_events.end(), [](const KernelEqueueEvent& event) { return event.enabled && event.triggered; }));
|
||||
@@ -319,6 +416,10 @@ int KernelEqueuePrivate::WaitForEventsUntil(KernelEvent* ev, int num, std::uint6
|
||||
return SCE_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
for (;;) {
|
||||
MeasureFiles(lock);
|
||||
if (m_closed) {
|
||||
return SCE_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
TriggerExpiredTimers(MonotonicNs());
|
||||
const bool polling = PollEvents();
|
||||
const int ret = CollectTriggered(ev, num);
|
||||
@@ -720,15 +821,19 @@ static int AddDescriptorEvent(KernelEqueue eq, int fd, std::size_t size, void* u
|
||||
std::int64_t available = 0;
|
||||
bool eof = false;
|
||||
const bool isPipe = !isSocket && filter == EVFILT_READ && !File::IsHostStream(fd) && GuestSockets::PipeReadState(fd, &available, &eof);
|
||||
if (!isSocket && !isPipe && DescriptorIsOpen_nid_no_patch(fd)) {
|
||||
NotImplemented_nid_no_patch(filter == EVFILT_READ ? "sceKernelAddReadEvent: descriptor that is neither a socket nor the read end of a pipe" : "sceKernelAddWriteEvent: non-socket descriptor");
|
||||
const bool isFile = !isSocket && !isPipe && RegularFile(fd);
|
||||
if (!isSocket && !isPipe && !isFile && DescriptorIsOpen_nid_no_patch(fd)) {
|
||||
NotImplemented_nid_no_patch(filter == EVFILT_READ ? "sceKernelAddReadEvent: descriptor that is neither a socket, the read end of a pipe nor a regular file" : "sceKernelAddWriteEvent: descriptor that is neither a socket nor a regular file");
|
||||
}
|
||||
if (size > 1 && !isPipe) {
|
||||
NotImplemented_nid_no_patch(filter == EVFILT_READ ? "sceKernelAddReadEvent: low watermark" : "sceKernelAddWriteEvent: low watermark");
|
||||
}
|
||||
if (!isSocket && !isPipe) {
|
||||
if (!isSocket && !isPipe && !isFile) {
|
||||
return SCE_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
if (isFile) {
|
||||
return EqueueAddEvent_nid_postfix(eq, FileEvent(fd, filter, EV_ADD, udata));
|
||||
}
|
||||
return EqueueAddEvent_nid_postfix(eq, EqueueDescriptorEvent(fd, filter, EV_ADD, udata));
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ struct KernelFilter {
|
||||
EqueueDeleteFunc deleteEventFunc = nullptr;
|
||||
EqueuePollFunc pollFunc = nullptr;
|
||||
int waitDescriptor = -1;
|
||||
bool repoll = false;
|
||||
std::shared_ptr<KernelEqueuePrivate> watched;
|
||||
};
|
||||
|
||||
@@ -104,6 +105,7 @@ private:
|
||||
int CollectTriggered(KernelEvent* ev, int num);
|
||||
int CollectOncePerWait(KernelEvent* ev, int num);
|
||||
bool PollEvents();
|
||||
void MeasureFiles(std::unique_lock<std::mutex>& lock);
|
||||
void WakePollers();
|
||||
void WaitForDescriptors(std::unique_lock<std::mutex>& lock, std::uint64_t deadlineNanos);
|
||||
void GatherInterests(WaitSet& wait, std::uintptr_t waker, bool nested);
|
||||
|
||||
@@ -14,6 +14,15 @@ std::int64_t WriteSharedMemory_nid_no_patch(int descriptor, const void* buffer,
|
||||
std::int64_t PwriteSharedMemory_nid_no_patch(int descriptor, const void* buffer, std::size_t size, std::int64_t offset);
|
||||
std::int64_t SeekSharedMemory_nid_no_patch(int descriptor, std::int64_t offset, int whence);
|
||||
int TruncateSharedMemory_nid_no_patch(int descriptor, std::int64_t length);
|
||||
#ifdef _WIN32
|
||||
int SetSharedMemoryMode_nid_no_patch(int descriptor, int mode);
|
||||
int SharedMemoryDescriptorWritable_nid_no_patch(int descriptor);
|
||||
void TrackSharedMemoryDescriptor_nid_no_patch(int descriptor, bool writable);
|
||||
void ForgetSharedMemoryDescriptor_nid_no_patch(int descriptor);
|
||||
void DuplicateSharedMemoryDescriptor_nid_no_patch(int source, int destination);
|
||||
int CloseSharedMemoryDescriptor_nid_no_patch(int descriptor);
|
||||
int ReplaceSharedMemoryDescriptor_nid_no_patch(int source, int destination);
|
||||
#endif
|
||||
#ifndef _WIN32
|
||||
std::int64_t WritevSharedMemory_nid_no_patch(int descriptor, const struct iovec* vectors, int count);
|
||||
std::int64_t PwritevSharedMemory_nid_no_patch(int descriptor, const struct iovec* vectors, int count, std::int64_t offset);
|
||||
|
||||
@@ -87,6 +87,8 @@ static int NativeWrite(int fd, const void* buf, std::size_t n) {
|
||||
extern "C" _invalid_parameter_handler _set_thread_local_invalid_parameter_handler(_invalid_parameter_handler);
|
||||
static void IgnoreInvalidParameter(const wchar_t*, const wchar_t*, const wchar_t*, unsigned int, std::uintptr_t) {}
|
||||
static int NativeClose(int fd) {
|
||||
const int shared = File::CloseSharedMemoryDescriptor_nid_no_patch(fd);
|
||||
if (shared != -2) return shared;
|
||||
const auto previous = _set_thread_local_invalid_parameter_handler(IgnoreInvalidParameter);
|
||||
const int result = ::_close(fd);
|
||||
_set_thread_local_invalid_parameter_handler(previous);
|
||||
@@ -296,10 +298,10 @@ int OpenInDirectory_nid_no_patch(int directory, const char* path, int flags, std
|
||||
int APS5_VABI sceKernelClose(int d) {
|
||||
if (File::IsHostStream(d)) return SCE_KERNEL_ERROR_EPERM;
|
||||
if (GuestSockets::IsOpen(d)) return GuestSockets::Close(d) == 0 ? 0 : SceKernelError(*__error_nid_postfix());
|
||||
EqueueDescriptorClosed(d);
|
||||
#ifdef _WIN32
|
||||
const auto positioned = File::LockPositionedTransfer(d);
|
||||
#endif
|
||||
EqueueDescriptorClosed(d);
|
||||
File::ForgetRandomDevice(d);
|
||||
File::ForgetDescriptor(d);
|
||||
ForgetJitSharedMemory(d);
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <fcntl.h>
|
||||
@@ -22,6 +24,11 @@
|
||||
#include <sys/stat.h>
|
||||
#include <filesystem>
|
||||
#include <random>
|
||||
#include <windows.h>
|
||||
#include <aclapi.h>
|
||||
#include <sddl.h>
|
||||
#include "nid/Sha1.hpp"
|
||||
extern "C" _invalid_parameter_handler _set_thread_local_invalid_parameter_handler(_invalid_parameter_handler);
|
||||
#else
|
||||
#include "nid/Sha1.hpp"
|
||||
#include <fcntl.h>
|
||||
@@ -37,6 +44,32 @@ extern "C" int* APS5_VABI __error_nid_postfix();
|
||||
|
||||
namespace {
|
||||
|
||||
#ifdef _WIN32
|
||||
std::mutex sharedDescriptorAccessMutex;
|
||||
std::unordered_map<int, bool> sharedDescriptorAccess;
|
||||
|
||||
const std::wstring& WindowsNamedRoot() {
|
||||
static const std::wstring root = [] {
|
||||
const auto directory = std::filesystem::temp_directory_path();
|
||||
std::vector<wchar_t> pathBuffer(32768);
|
||||
const HANDLE handle = ::CreateFileW(directory.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
||||
if (handle == INVALID_HANDLE_VALUE)
|
||||
throw std::system_error(static_cast<int>(::GetLastError()), std::system_category(), "opening the named shared-memory temporary root");
|
||||
const DWORD length = ::GetFinalPathNameByHandleW(handle, pathBuffer.data(), static_cast<DWORD>(pathBuffer.size()),
|
||||
FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
|
||||
const DWORD error = length == 0 ? ::GetLastError() : length >= pathBuffer.size() ? ERROR_FILENAME_EXCED_RANGE : ERROR_SUCCESS;
|
||||
::CloseHandle(handle);
|
||||
if (error != ERROR_SUCCESS)
|
||||
throw std::system_error(static_cast<int>(error), std::system_category(), "resolving the named shared-memory temporary root");
|
||||
return (std::filesystem::path(std::wstring(pathBuffer.data(), length)) / L"AnyPS5" / L"shm").wstring();
|
||||
}();
|
||||
return root;
|
||||
}
|
||||
|
||||
void IgnoreDescriptorParameter(const wchar_t*, const wchar_t*, const wchar_t*, unsigned int, std::uintptr_t) {}
|
||||
#endif
|
||||
|
||||
bool IsHostSharedMemoryDescriptor(int descriptor) {
|
||||
#ifdef __linux__
|
||||
char descriptorPath[64];
|
||||
@@ -47,6 +80,44 @@ bool IsHostSharedMemoryDescriptor(int descriptor) {
|
||||
objectPath[length] = '\0';
|
||||
return std::string_view(objectPath).starts_with("/dev/shm/anyps5-shm-") ||
|
||||
std::string_view(objectPath).starts_with("/memfd:anyps5 shm_open");
|
||||
#elif defined(_WIN32)
|
||||
{
|
||||
std::lock_guard lock(sharedDescriptorAccessMutex);
|
||||
if (!sharedDescriptorAccess.contains(descriptor)) return false;
|
||||
}
|
||||
const auto previous = _set_thread_local_invalid_parameter_handler(IgnoreDescriptorParameter);
|
||||
const auto handleValue = ::_get_osfhandle(descriptor);
|
||||
_set_thread_local_invalid_parameter_handler(previous);
|
||||
if (handleValue == -1) return false;
|
||||
const auto handle = reinterpret_cast<HANDLE>(handleValue);
|
||||
std::vector<wchar_t> pathBuffer(32768);
|
||||
const DWORD length = ::GetFinalPathNameByHandleW(handle, pathBuffer.data(), static_cast<DWORD>(pathBuffer.size()), FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
|
||||
if (length == 0 || length >= pathBuffer.size()) return false;
|
||||
std::wstring objectPath(pathBuffer.data(), length);
|
||||
std::wstring_view root;
|
||||
try {
|
||||
root = WindowsNamedRoot();
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
if (objectPath.size() <= root.size() || objectPath[root.size()] != L'\\') return false;
|
||||
if (::CompareStringOrdinal(objectPath.data(), static_cast<int>(root.size()), root.data(), static_cast<int>(root.size()), TRUE) != CSTR_EQUAL) return false;
|
||||
const std::wstring_view relative(objectPath.data() + root.size() + 1, objectPath.size() - root.size() - 1);
|
||||
if (relative.find_first_of(L"\\/") != std::wstring_view::npos) return false;
|
||||
const auto validHex = [](wchar_t character) {
|
||||
return (character >= L'0' && character <= L'9') || (character >= L'a' && character <= L'f');
|
||||
};
|
||||
if (relative.size() < 44 || !std::all_of(relative.begin(), relative.begin() + 40, validHex)) return false;
|
||||
if (relative.substr(40) == L".dat") return true;
|
||||
constexpr std::wstring_view marker = L"-unlinked-";
|
||||
if (!relative.substr(40).starts_with(marker) || !relative.ends_with(L".dat")) return false;
|
||||
const auto suffix = relative.substr(40 + marker.size(), relative.size() - 40 - marker.size() - 4);
|
||||
const auto separator = suffix.find(L'-');
|
||||
if (separator == std::wstring_view::npos || separator == 0 || separator + 1 == suffix.size()) return false;
|
||||
const auto decimal = [](std::wstring_view value) {
|
||||
return std::all_of(value.begin(), value.end(), [](wchar_t character) { return character >= L'0' && character <= L'9'; });
|
||||
};
|
||||
return decimal(suffix.substr(0, separator)) && decimal(suffix.substr(separator + 1));
|
||||
#else
|
||||
(void)descriptor;
|
||||
return false;
|
||||
@@ -107,7 +178,6 @@ int GuestError(int error) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
int NamedGuestError(int error) {
|
||||
switch (error) {
|
||||
case ENOENT:
|
||||
@@ -133,14 +203,17 @@ int NamedGuestError(int error) {
|
||||
}
|
||||
}
|
||||
|
||||
std::string NamedHostPath(const char* path, int& error) {
|
||||
std::string NamedHostName(const char* path, int& error) {
|
||||
if (path == nullptr) { error = EFAULT; return {}; }
|
||||
std::size_t length = 0;
|
||||
while (length < 1024 && path[length] != '\0') ++length;
|
||||
if (length == 1024) { error = ENAMETOOLONG; return {}; }
|
||||
if (length == 0 || path[0] != '/') { error = EINVAL; return {}; }
|
||||
const auto digest = Nid::Sha1(std::vector<std::uint8_t>(path, path + length));
|
||||
std::string hostName = "/anyps5-shm-";
|
||||
std::string hostName;
|
||||
#ifndef _WIN32
|
||||
hostName = "/anyps5-shm-";
|
||||
#endif
|
||||
for (const auto byte : digest) {
|
||||
hostName += "0123456789abcdef"[byte >> 4];
|
||||
hostName += "0123456789abcdef"[byte & 15];
|
||||
@@ -149,9 +222,384 @@ std::string NamedHostPath(const char* path, int& error) {
|
||||
return hostName;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
int WindowsNamedError(DWORD error) {
|
||||
switch (error) {
|
||||
case ERROR_FILE_NOT_FOUND:
|
||||
case ERROR_PATH_NOT_FOUND: return ENOENT;
|
||||
case ERROR_ACCESS_DENIED:
|
||||
case ERROR_SHARING_VIOLATION: return EACCES;
|
||||
case ERROR_FILE_EXISTS:
|
||||
case ERROR_ALREADY_EXISTS: return EEXIST;
|
||||
case ERROR_INVALID_NAME: return EINVAL;
|
||||
case ERROR_TOO_MANY_OPEN_FILES: return EMFILE;
|
||||
case ERROR_DISK_FULL: return ENOSPC;
|
||||
case ERROR_NOT_ENOUGH_MEMORY:
|
||||
case ERROR_OUTOFMEMORY: return ENOMEM;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path WindowsNamedPath(const std::string& name) {
|
||||
return std::filesystem::path(WindowsNamedRoot()) / std::filesystem::path(name + ".dat");
|
||||
}
|
||||
|
||||
void EnsurePrivateWindowsDirectory(const std::filesystem::path& path, bool objectDirectory = false) {
|
||||
const std::wstring ownerRights = objectDirectory ? L"0x001701ff" : L"FA";
|
||||
const std::wstring policy = L"D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;" + ownerRights + L";;;OW)";
|
||||
PSECURITY_DESCRIPTOR descriptor = nullptr;
|
||||
if (!::ConvertStringSecurityDescriptorToSecurityDescriptorW(
|
||||
policy.c_str(), SDDL_REVISION_1, &descriptor, nullptr))
|
||||
throw std::system_error(static_cast<int>(::GetLastError()), std::system_category(), "creating the named shared-memory access policy");
|
||||
PACL dacl = nullptr;
|
||||
BOOL present = FALSE;
|
||||
BOOL defaulted = FALSE;
|
||||
if (!::GetSecurityDescriptorDacl(descriptor, &present, &dacl, &defaulted) || !present || dacl == nullptr) {
|
||||
const DWORD error = ::GetLastError();
|
||||
::LocalFree(descriptor);
|
||||
throw std::system_error(static_cast<int>(error == ERROR_SUCCESS ? ERROR_INVALID_SECURITY_DESCR : error),
|
||||
std::system_category(), "reading the named shared-memory access policy");
|
||||
}
|
||||
|
||||
SECURITY_ATTRIBUTES security{sizeof(SECURITY_ATTRIBUTES), descriptor, FALSE};
|
||||
if (!::CreateDirectoryW(path.c_str(), &security) && ::GetLastError() != ERROR_ALREADY_EXISTS) {
|
||||
const DWORD error = ::GetLastError();
|
||||
::LocalFree(descriptor);
|
||||
throw std::system_error(static_cast<int>(error), std::system_category(), "creating the named shared-memory directory");
|
||||
}
|
||||
const DWORD attributes = ::GetFileAttributesW(path.c_str());
|
||||
if (attributes == INVALID_FILE_ATTRIBUTES || (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0 ||
|
||||
(attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
|
||||
const DWORD error = attributes == INVALID_FILE_ATTRIBUTES ? ::GetLastError() : ERROR_ACCESS_DENIED;
|
||||
::LocalFree(descriptor);
|
||||
throw std::system_error(static_cast<int>(error), std::system_category(), "validating the named shared-memory directory");
|
||||
}
|
||||
const DWORD result = ::SetNamedSecurityInfoW(const_cast<LPWSTR>(path.c_str()), SE_FILE_OBJECT,
|
||||
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
|
||||
nullptr, nullptr, dacl, nullptr);
|
||||
::LocalFree(descriptor);
|
||||
if (result != ERROR_SUCCESS) throw std::system_error(static_cast<int>(result), std::system_category(), "securing the named shared-memory directory");
|
||||
}
|
||||
|
||||
DWORD SetWindowsNamedPermissions(HANDLE handle, int mode) {
|
||||
std::wstring policy = L"D:P(A;OICI;FA;;;SY)(A;;WD;;;OW)";
|
||||
if ((mode & 0400) != 0) policy += L"(A;;GRWD;;;OW)";
|
||||
if ((mode & 0200) != 0) policy += L"(A;;GWSDWD;;;OW)";
|
||||
PSECURITY_DESCRIPTOR descriptor = nullptr;
|
||||
if (!::ConvertStringSecurityDescriptorToSecurityDescriptorW(policy.c_str(), SDDL_REVISION_1, &descriptor, nullptr))
|
||||
return ::GetLastError();
|
||||
PACL dacl = nullptr;
|
||||
BOOL present = FALSE;
|
||||
BOOL defaulted = FALSE;
|
||||
if (!::GetSecurityDescriptorDacl(descriptor, &present, &dacl, &defaulted) || !present || dacl == nullptr) {
|
||||
const DWORD error = ::GetLastError();
|
||||
::LocalFree(descriptor);
|
||||
return error == ERROR_SUCCESS ? ERROR_INVALID_SECURITY_DESCR : error;
|
||||
}
|
||||
const DWORD result = ::SetSecurityInfo(handle, SE_FILE_OBJECT,
|
||||
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION, nullptr, nullptr, dacl, nullptr);
|
||||
::LocalFree(descriptor);
|
||||
return result;
|
||||
}
|
||||
|
||||
void CleanupUnlinkedWindowsObjects(const std::filesystem::path& objectPath, const std::string& name) {
|
||||
const auto pattern = objectPath.parent_path() / (std::wstring(name.begin(), name.end()) + L"-unlinked-*.dat");
|
||||
WIN32_FIND_DATAW data{};
|
||||
const HANDLE search = ::FindFirstFileW(pattern.c_str(), &data);
|
||||
if (search == INVALID_HANDLE_VALUE) return;
|
||||
do {
|
||||
if ((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
|
||||
const auto orphanPath = objectPath.parent_path() / data.cFileName;
|
||||
HANDLE orphan = ::CreateFileW(orphanPath.c_str(), DELETE, 0, nullptr, OPEN_EXISTING,
|
||||
FILE_FLAG_OPEN_REPARSE_POINT, nullptr);
|
||||
if (orphan != INVALID_HANDLE_VALUE) {
|
||||
FILE_DISPOSITION_INFO deletion{TRUE};
|
||||
::SetFileInformationByHandle(orphan, FileDispositionInfo, &deletion, sizeof(deletion));
|
||||
::CloseHandle(orphan);
|
||||
}
|
||||
}
|
||||
} while (::FindNextFileW(search, &data));
|
||||
::FindClose(search);
|
||||
}
|
||||
|
||||
void ReapUnlinkedWindowsObject(const std::filesystem::path& orphanPath) {
|
||||
HANDLE orphan = ::CreateFileW(orphanPath.c_str(), DELETE, 0, nullptr, OPEN_EXISTING,
|
||||
FILE_FLAG_OPEN_REPARSE_POINT, nullptr);
|
||||
if (orphan == INVALID_HANDLE_VALUE) return;
|
||||
FILE_DISPOSITION_INFO deletion{TRUE};
|
||||
::SetFileInformationByHandle(orphan, FileDispositionInfo, &deletion, sizeof(deletion));
|
||||
::CloseHandle(orphan);
|
||||
}
|
||||
|
||||
class WindowsNamedLock {
|
||||
public:
|
||||
explicit WindowsNamedLock(const std::string& name) {
|
||||
Initialize(WindowsNamedPath(name).wstring());
|
||||
}
|
||||
|
||||
explicit WindowsNamedLock(HANDLE object) {
|
||||
BY_HANDLE_FILE_INFORMATION status{};
|
||||
if (!::GetFileInformationByHandle(object, &status)) { error = ::GetLastError(); return; }
|
||||
Initialize(L"object-" + std::to_wstring(status.dwVolumeSerialNumber) + L"-" +
|
||||
std::to_wstring(status.nFileIndexHigh) + L"-" + std::to_wstring(status.nFileIndexLow));
|
||||
}
|
||||
|
||||
~WindowsNamedLock() {
|
||||
if (locked) ::ReleaseMutex(handle);
|
||||
if (handle != nullptr) ::CloseHandle(handle);
|
||||
}
|
||||
|
||||
DWORD Error() const { return error; }
|
||||
|
||||
private:
|
||||
void Initialize(const std::wstring& identity) {
|
||||
const auto bytes = std::vector<std::uint8_t>(reinterpret_cast<const std::uint8_t*>(identity.data()),
|
||||
reinterpret_cast<const std::uint8_t*>(identity.data() + identity.size()));
|
||||
const auto digest = Nid::Sha1(bytes);
|
||||
std::wstring lockName = L"Global\\AnyPS5NamedShm-";
|
||||
for (const auto byte : digest) {
|
||||
lockName += L"0123456789abcdef"[byte >> 4];
|
||||
lockName += L"0123456789abcdef"[byte & 15];
|
||||
}
|
||||
handle = ::CreateMutexW(nullptr, FALSE, lockName.c_str());
|
||||
if (handle == nullptr) { error = ::GetLastError(); return; }
|
||||
const DWORD result = ::WaitForSingleObject(handle, INFINITE);
|
||||
if (result == WAIT_OBJECT_0 || result == WAIT_ABANDONED) locked = true;
|
||||
else error = result == WAIT_FAILED ? ::GetLastError() : ERROR_GEN_FAILURE;
|
||||
}
|
||||
HANDLE handle = nullptr;
|
||||
DWORD error = ERROR_SUCCESS;
|
||||
bool locked = false;
|
||||
};
|
||||
|
||||
int OpenNamedFile(const char* path, int flags, int mode) {
|
||||
const int savedError = errno;
|
||||
int validationError = 0;
|
||||
const auto hostName = NamedHostName(path, validationError);
|
||||
if (validationError != 0) return Fail(NamedGuestError(validationError) ? NamedGuestError(validationError) : GuestInvalid);
|
||||
|
||||
WindowsNamedLock namespaceLock(hostName);
|
||||
if (namespaceLock.Error() != ERROR_SUCCESS) {
|
||||
if (const int guest = WindowsNamedError(namespaceLock.Error())) return Fail(NamedGuestError(guest));
|
||||
throw std::runtime_error("shm_open: locking the host named-object namespace failed, error=" + std::to_string(namespaceLock.Error()));
|
||||
}
|
||||
|
||||
std::filesystem::path objectPath;
|
||||
try {
|
||||
objectPath = WindowsNamedPath(hostName);
|
||||
EnsurePrivateWindowsDirectory(objectPath.parent_path().parent_path());
|
||||
EnsurePrivateWindowsDirectory(objectPath.parent_path(), true);
|
||||
CleanupUnlinkedWindowsObjects(objectPath, hostName);
|
||||
} catch (const std::system_error& error) {
|
||||
const auto nativeError = static_cast<DWORD>(error.code().value());
|
||||
if (const int guest = WindowsNamedError(nativeError)) return Fail(NamedGuestError(guest));
|
||||
throw std::runtime_error("shm_open: preparing the host named-object namespace failed: " + std::string(error.what()));
|
||||
}
|
||||
|
||||
const bool readWrite = (flags & SCE_KERNEL_O_ACCMODE) == SCE_KERNEL_O_RDWR;
|
||||
const DWORD access = GENERIC_READ | (readWrite ? GENERIC_WRITE : 0) | WRITE_DAC;
|
||||
const bool create = (flags & SCE_KERNEL_O_CREAT) != 0;
|
||||
const DWORD creationAccess = access | (create ? WRITE_DAC | DELETE : 0);
|
||||
::SetLastError(ERROR_SUCCESS);
|
||||
HANDLE handle = ::CreateFileW(objectPath.c_str(), creationAccess, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
nullptr, create ? CREATE_NEW : OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);
|
||||
DWORD createResult = ::GetLastError();
|
||||
bool created = handle != INVALID_HANDLE_VALUE && create;
|
||||
if (handle == INVALID_HANDLE_VALUE && create && createResult == ERROR_FILE_EXISTS && !(flags & SCE_KERNEL_O_EXCL)) {
|
||||
handle = ::CreateFileW(objectPath.c_str(), access, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);
|
||||
createResult = ::GetLastError();
|
||||
}
|
||||
if (handle == INVALID_HANDLE_VALUE) {
|
||||
const DWORD nativeError = createResult;
|
||||
if (const int guest = WindowsNamedError(nativeError)) return Fail(NamedGuestError(guest));
|
||||
throw std::runtime_error("shm_open: opening the host named object failed, error=" + std::to_string(nativeError));
|
||||
}
|
||||
|
||||
BY_HANDLE_FILE_INFORMATION status{};
|
||||
if (!::GetFileInformationByHandle(handle, &status) || (status.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
|
||||
const DWORD lastError = ::GetLastError();
|
||||
const DWORD nativeError = lastError == ERROR_SUCCESS ? ERROR_INVALID_NAME : lastError;
|
||||
if (created) {
|
||||
FILE_DISPOSITION_INFO deletion{TRUE};
|
||||
::SetFileInformationByHandle(handle, FileDispositionInfo, &deletion, sizeof(deletion));
|
||||
}
|
||||
::CloseHandle(handle);
|
||||
if (const int guest = WindowsNamedError(nativeError)) return Fail(NamedGuestError(guest));
|
||||
throw std::runtime_error("shm_open: refusing a reparse-point named object, error=" + std::to_string(nativeError));
|
||||
}
|
||||
|
||||
if (created) {
|
||||
const int permissions = (mode & 0777) & ~File::GetCreationMask_nid_no_patch();
|
||||
const DWORD nativeError = SetWindowsNamedPermissions(handle, permissions);
|
||||
if (nativeError != ERROR_SUCCESS) {
|
||||
FILE_DISPOSITION_INFO deletion{TRUE};
|
||||
::SetFileInformationByHandle(handle, FileDispositionInfo, &deletion, sizeof(deletion));
|
||||
::CloseHandle(handle);
|
||||
if (const int guest = WindowsNamedError(nativeError)) return Fail(NamedGuestError(guest));
|
||||
throw std::runtime_error("shm_open: setting guest creation permissions failed, error=" + std::to_string(nativeError));
|
||||
}
|
||||
}
|
||||
|
||||
if ((flags & SCE_KERNEL_O_TRUNC) && readWrite) {
|
||||
WindowsNamedLock objectLock(handle);
|
||||
if (objectLock.Error() != ERROR_SUCCESS) {
|
||||
const DWORD nativeError = objectLock.Error();
|
||||
::CloseHandle(handle);
|
||||
if (const int guest = WindowsNamedError(nativeError)) return Fail(NamedGuestError(guest));
|
||||
throw std::runtime_error("shm_open: locking the host named object for truncation failed, error=" + std::to_string(nativeError));
|
||||
}
|
||||
LARGE_INTEGER zero{};
|
||||
if (!::SetFilePointerEx(handle, zero, nullptr, FILE_BEGIN) || !::SetEndOfFile(handle)) {
|
||||
const DWORD nativeError = ::GetLastError();
|
||||
::CloseHandle(handle);
|
||||
if (const int guest = WindowsNamedError(nativeError)) return Fail(NamedGuestError(guest));
|
||||
throw std::runtime_error("shm_open: truncating the host named object failed, error=" + std::to_string(nativeError));
|
||||
}
|
||||
}
|
||||
|
||||
const int nativeFlags = _O_BINARY | (readWrite ? _O_RDWR : _O_RDONLY);
|
||||
const int descriptor = ::_open_osfhandle(reinterpret_cast<std::intptr_t>(handle), nativeFlags);
|
||||
if (descriptor >= 0) {
|
||||
File::TrackSharedMemoryDescriptor_nid_no_patch(descriptor, readWrite);
|
||||
errno = savedError;
|
||||
return descriptor;
|
||||
}
|
||||
const int nativeError = errno;
|
||||
::CloseHandle(handle);
|
||||
if (const int guest = GuestError(nativeError)) return Fail(guest);
|
||||
throw std::runtime_error("shm_open: converting the host named object to a descriptor failed, errno=" + std::to_string(nativeError));
|
||||
}
|
||||
|
||||
int UnlinkNamedFile(const char* path) {
|
||||
const int savedError = errno;
|
||||
int validationError = 0;
|
||||
const auto hostName = NamedHostName(path, validationError);
|
||||
if (validationError != 0) return Fail(NamedGuestError(validationError) ? NamedGuestError(validationError) : GuestInvalid);
|
||||
WindowsNamedLock namespaceLock(hostName);
|
||||
if (namespaceLock.Error() != ERROR_SUCCESS) {
|
||||
if (const int guest = WindowsNamedError(namespaceLock.Error())) return Fail(NamedGuestError(guest));
|
||||
throw std::runtime_error("shm_unlink: locking the host named-object namespace failed, error=" + std::to_string(namespaceLock.Error()));
|
||||
}
|
||||
|
||||
std::filesystem::path objectPath;
|
||||
try {
|
||||
objectPath = WindowsNamedPath(hostName);
|
||||
} catch (const std::filesystem::filesystem_error& error) {
|
||||
const auto nativeError = static_cast<DWORD>(error.code().value());
|
||||
if (const int guest = WindowsNamedError(nativeError)) return Fail(NamedGuestError(guest));
|
||||
throw std::runtime_error("shm_unlink: locating the host named object failed: " + std::string(error.what()));
|
||||
}
|
||||
|
||||
HANDLE objectHandle = ::CreateFileW(objectPath.c_str(), FILE_READ_ATTRIBUTES | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING,
|
||||
FILE_FLAG_OPEN_REPARSE_POINT, nullptr);
|
||||
if (objectHandle == INVALID_HANDLE_VALUE) {
|
||||
const DWORD nativeError = ::GetLastError();
|
||||
if (const int guest = WindowsNamedError(nativeError)) return Fail(NamedGuestError(guest));
|
||||
throw std::runtime_error("shm_unlink: opening the host named object failed, error=" + std::to_string(nativeError));
|
||||
}
|
||||
BY_HANDLE_FILE_INFORMATION status{};
|
||||
if (!::GetFileInformationByHandle(objectHandle, &status) ||
|
||||
(status.dwFileAttributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)) != 0) {
|
||||
const DWORD lastError = ::GetLastError();
|
||||
const DWORD nativeError = lastError == ERROR_SUCCESS ? ERROR_INVALID_NAME : lastError;
|
||||
::CloseHandle(objectHandle);
|
||||
if (const int guest = WindowsNamedError(nativeError)) return Fail(NamedGuestError(guest));
|
||||
throw std::runtime_error("shm_unlink: refusing an invalid host named object, error=" + std::to_string(nativeError));
|
||||
}
|
||||
|
||||
static std::atomic<unsigned> sequence{0};
|
||||
for (unsigned attempt = 0; attempt < 64; ++attempt) {
|
||||
const auto orphanPath = objectPath.parent_path() /
|
||||
(objectPath.stem().wstring() + L"-unlinked-" + std::to_wstring(::GetCurrentProcessId()) +
|
||||
L"-" + std::to_wstring(sequence.fetch_add(1, std::memory_order_relaxed)) + objectPath.extension().wstring());
|
||||
if (!::MoveFileExW(objectPath.c_str(), orphanPath.c_str(), MOVEFILE_WRITE_THROUGH)) {
|
||||
const DWORD nativeError = ::GetLastError();
|
||||
if (nativeError == ERROR_ALREADY_EXISTS || nativeError == ERROR_FILE_EXISTS) continue;
|
||||
::CloseHandle(objectHandle);
|
||||
if (const int guest = WindowsNamedError(nativeError)) return Fail(NamedGuestError(guest));
|
||||
throw std::runtime_error("shm_unlink: detaching the host named object failed, error=" + std::to_string(nativeError));
|
||||
}
|
||||
::CloseHandle(objectHandle);
|
||||
ReapUnlinkedWindowsObject(orphanPath);
|
||||
errno = savedError;
|
||||
return 0;
|
||||
}
|
||||
::CloseHandle(objectHandle);
|
||||
return Fail(NamedGuestError(EEXIST));
|
||||
}
|
||||
|
||||
int SetSharedMemoryModeInternal(int descriptor, int mode) {
|
||||
if (!IsHostSharedMemoryDescriptor(descriptor)) return -2;
|
||||
const auto rawHandle = ::_get_osfhandle(descriptor);
|
||||
if (rawHandle == -1) { errno = EBADF; return -1; }
|
||||
const auto handle = reinterpret_cast<HANDLE>(rawHandle);
|
||||
WindowsNamedLock objectLock(handle);
|
||||
if (objectLock.Error() != ERROR_SUCCESS) {
|
||||
const int error = WindowsNamedError(objectLock.Error());
|
||||
errno = error ? error : EIO;
|
||||
return -1;
|
||||
}
|
||||
const DWORD nativeError = SetWindowsNamedPermissions(handle, mode & 0777);
|
||||
if (nativeError == ERROR_SUCCESS) return 0;
|
||||
const int error = WindowsNamedError(nativeError);
|
||||
errno = error ? error : EACCES;
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::filesystem::path SharedMemoryDescriptorPath(int descriptor) {
|
||||
const auto rawHandle = ::_get_osfhandle(descriptor);
|
||||
std::filesystem::path finalPath;
|
||||
if (rawHandle != -1) {
|
||||
std::vector<wchar_t> path(32768);
|
||||
const DWORD length = ::GetFinalPathNameByHandleW(reinterpret_cast<HANDLE>(rawHandle), path.data(),
|
||||
static_cast<DWORD>(path.size()), FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
|
||||
if (length > 4 && length < path.size()) {
|
||||
std::wstring value(path.data(), length);
|
||||
finalPath = std::filesystem::path(std::move(value));
|
||||
}
|
||||
}
|
||||
return finalPath;
|
||||
}
|
||||
|
||||
void ReapSharedMemoryDescriptorPath(const std::filesystem::path& path) {
|
||||
if (!path.empty() && path.filename().wstring().find(L"-unlinked-") != std::wstring::npos)
|
||||
ReapUnlinkedWindowsObject(path);
|
||||
}
|
||||
|
||||
int CloseSharedMemoryDescriptorInternal(int descriptor) {
|
||||
if (!IsHostSharedMemoryDescriptor(descriptor)) return -2;
|
||||
const int savedError = errno;
|
||||
const auto finalPath = SharedMemoryDescriptorPath(descriptor);
|
||||
int result;
|
||||
{
|
||||
std::lock_guard lock(sharedDescriptorAccessMutex);
|
||||
result = ::_close(descriptor);
|
||||
if (result == 0) sharedDescriptorAccess.erase(descriptor);
|
||||
}
|
||||
if (result == 0) ReapSharedMemoryDescriptorPath(finalPath);
|
||||
if (result == 0) errno = savedError;
|
||||
return result;
|
||||
}
|
||||
|
||||
int ReplaceSharedMemoryDescriptorInternal(int source, int destination) {
|
||||
if (!IsHostSharedMemoryDescriptor(destination)) return -2;
|
||||
const int savedError = errno;
|
||||
const auto finalPath = SharedMemoryDescriptorPath(destination);
|
||||
const auto previous = _set_thread_local_invalid_parameter_handler(IgnoreDescriptorParameter);
|
||||
const int result = ::_dup2(source, destination);
|
||||
_set_thread_local_invalid_parameter_handler(previous);
|
||||
if (result == 0) {
|
||||
ReapSharedMemoryDescriptorPath(finalPath);
|
||||
errno = savedError;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#else
|
||||
int OpenNamedFile(const char* path, int flags, int mode) {
|
||||
int validationError = 0;
|
||||
const auto hostName = NamedHostPath(path, validationError);
|
||||
const auto hostName = NamedHostName(path, validationError);
|
||||
if (validationError != 0) return Fail(NamedGuestError(validationError) ? NamedGuestError(validationError) : GuestInvalid);
|
||||
HostShmLock namespaceLock;
|
||||
if (namespaceLock.Error() != 0) {
|
||||
@@ -222,6 +670,44 @@ bool IsSharedMemoryDescriptor_nid_no_patch(int descriptor) {
|
||||
return IsHostSharedMemoryDescriptor(descriptor);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
int SetSharedMemoryMode_nid_no_patch(int descriptor, int mode) {
|
||||
return SetSharedMemoryModeInternal(descriptor, mode);
|
||||
}
|
||||
|
||||
int CloseSharedMemoryDescriptor_nid_no_patch(int descriptor) {
|
||||
return CloseSharedMemoryDescriptorInternal(descriptor);
|
||||
}
|
||||
|
||||
int ReplaceSharedMemoryDescriptor_nid_no_patch(int source, int destination) {
|
||||
return ReplaceSharedMemoryDescriptorInternal(source, destination);
|
||||
}
|
||||
|
||||
int SharedMemoryDescriptorWritable_nid_no_patch(int descriptor) {
|
||||
if (!IsHostSharedMemoryDescriptor(descriptor)) return -2;
|
||||
std::lock_guard lock(sharedDescriptorAccessMutex);
|
||||
const auto found = sharedDescriptorAccess.find(descriptor);
|
||||
return found != sharedDescriptorAccess.end() && found->second ? 1 : 0;
|
||||
}
|
||||
|
||||
void TrackSharedMemoryDescriptor_nid_no_patch(int descriptor, bool writable) {
|
||||
std::lock_guard lock(sharedDescriptorAccessMutex);
|
||||
sharedDescriptorAccess[descriptor] = writable;
|
||||
}
|
||||
|
||||
void ForgetSharedMemoryDescriptor_nid_no_patch(int descriptor) {
|
||||
std::lock_guard lock(sharedDescriptorAccessMutex);
|
||||
sharedDescriptorAccess.erase(descriptor);
|
||||
}
|
||||
|
||||
void DuplicateSharedMemoryDescriptor_nid_no_patch(int source, int destination) {
|
||||
std::lock_guard lock(sharedDescriptorAccessMutex);
|
||||
sharedDescriptorAccess.erase(destination);
|
||||
const auto found = sharedDescriptorAccess.find(source);
|
||||
if (found != sharedDescriptorAccess.end()) sharedDescriptorAccess[destination] = found->second;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
class SharedMemoryLock {
|
||||
@@ -230,6 +716,18 @@ public:
|
||||
#ifdef __linux__
|
||||
struct stat originalStatus{};
|
||||
if (::fstat(value, &originalStatus) != 0) { shared = true; error = errno; return; }
|
||||
#elif defined(_WIN32)
|
||||
if (!IsHostSharedMemoryDescriptor(value)) return;
|
||||
const auto originalValue = ::_get_osfhandle(value);
|
||||
if (originalValue == -1) return;
|
||||
BY_HANDLE_FILE_INFORMATION originalStatus{};
|
||||
if (!::GetFileInformationByHandle(reinterpret_cast<HANDLE>(originalValue), &originalStatus)) {
|
||||
shared = true;
|
||||
error = EBADF;
|
||||
return;
|
||||
}
|
||||
windowsObjectLock = std::make_unique<WindowsNamedLock>(reinterpret_cast<HANDLE>(originalValue));
|
||||
if (windowsObjectLock->Error() != ERROR_SUCCESS) { shared = true; error = WindowsNamedError(windowsObjectLock->Error()); if (!error) error = EIO; return; }
|
||||
#endif
|
||||
if (!IsHostSharedMemoryDescriptor(value)) {
|
||||
#ifdef __linux__
|
||||
@@ -247,10 +745,20 @@ public:
|
||||
if (hostLock->Error() != 0) { error = hostLock->Error(); return; }
|
||||
descriptor = Duplicate(value);
|
||||
if (descriptor < 0) { error = errno; return; }
|
||||
#ifdef _WIN32
|
||||
DuplicateSharedMemoryDescriptor_nid_no_patch(value, descriptor);
|
||||
#endif
|
||||
if (!IsHostSharedMemoryDescriptor(descriptor)) { error = EBADF; return; }
|
||||
if (!Refresh()) { error = errno; return; }
|
||||
#ifdef __linux__
|
||||
if (status.st_dev != originalStatus.st_dev || status.st_ino != originalStatus.st_ino) error = EBADF;
|
||||
#elif defined(_WIN32)
|
||||
BY_HANDLE_FILE_INFORMATION duplicatedStatus{};
|
||||
const auto duplicatedValue = ::_get_osfhandle(descriptor);
|
||||
if (duplicatedValue == -1 || !::GetFileInformationByHandle(reinterpret_cast<HANDLE>(duplicatedValue), &duplicatedStatus) ||
|
||||
duplicatedStatus.dwVolumeSerialNumber != originalStatus.dwVolumeSerialNumber ||
|
||||
duplicatedStatus.nFileIndexHigh != originalStatus.nFileIndexHigh ||
|
||||
duplicatedStatus.nFileIndexLow != originalStatus.nFileIndexLow) error = EBADF;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -258,7 +766,7 @@ public:
|
||||
if (descriptor >= 0) {
|
||||
const int savedError = errno;
|
||||
#ifdef _WIN32
|
||||
::_close(descriptor);
|
||||
CloseSharedMemoryDescriptor_nid_no_patch(descriptor);
|
||||
#else
|
||||
::close(descriptor);
|
||||
#endif
|
||||
@@ -292,6 +800,9 @@ private:
|
||||
|
||||
int descriptor = -1;
|
||||
std::optional<HostShmLock> hostLock;
|
||||
#ifdef _WIN32
|
||||
std::unique_ptr<WindowsNamedLock> windowsObjectLock;
|
||||
#endif
|
||||
bool shared = false;
|
||||
int error = 0;
|
||||
};
|
||||
@@ -303,7 +814,9 @@ std::int64_t WriteSharedMemory_nid_no_patch(int descriptor, const void* buffer,
|
||||
if (guard.Error() != 0) { errno = guard.Error(); return -1; }
|
||||
if (!guard.Refresh()) return -1;
|
||||
const auto fd = guard.Descriptor();
|
||||
#ifndef _WIN32
|
||||
#ifdef _WIN32
|
||||
if (SharedMemoryDescriptorWritable_nid_no_patch(fd) == 0) { errno = EBADF; return -1; }
|
||||
#else
|
||||
const int access = ::fcntl(fd, F_GETFL);
|
||||
if (access < 0) return -1;
|
||||
if ((access & O_ACCMODE) == O_RDONLY) { errno = EBADF; return -1; }
|
||||
@@ -331,7 +844,9 @@ std::int64_t PwriteSharedMemory_nid_no_patch(int descriptor, const void* buffer,
|
||||
if (guard.Error() != 0) { errno = guard.Error(); return -1; }
|
||||
if (!guard.Refresh()) return -1;
|
||||
const auto fd = guard.Descriptor();
|
||||
#ifndef _WIN32
|
||||
#ifdef _WIN32
|
||||
if (SharedMemoryDescriptorWritable_nid_no_patch(fd) == 0) { errno = EBADF; return -1; }
|
||||
#else
|
||||
const int access = ::fcntl(fd, F_GETFL);
|
||||
if (access < 0) return -1;
|
||||
if ((access & O_ACCMODE) == O_RDONLY) { errno = EBADF; return -1; }
|
||||
@@ -341,7 +856,19 @@ std::int64_t PwriteSharedMemory_nid_no_patch(int descriptor, const void* buffer,
|
||||
if (offset >= status.st_size) return 0;
|
||||
const auto count = std::min<std::uint64_t>(size, static_cast<std::uint64_t>(status.st_size - offset));
|
||||
#ifdef _WIN32
|
||||
const auto result = NativePositioned_nid_no_patch(fd, const_cast<void*>(buffer), static_cast<std::size_t>(count), offset, true);
|
||||
const auto handle = reinterpret_cast<HANDLE>(::_get_osfhandle(fd));
|
||||
if (handle == INVALID_HANDLE_VALUE || ::GetFileType(handle) != FILE_TYPE_DISK) { errno = EBADF; return -1; }
|
||||
LARGE_INTEGER previous{};
|
||||
if (!::SetFilePointerEx(handle, LARGE_INTEGER{}, &previous, FILE_CURRENT)) { errno = EIO; return -1; }
|
||||
OVERLAPPED overlapped{};
|
||||
overlapped.Offset = static_cast<DWORD>(offset);
|
||||
overlapped.OffsetHigh = static_cast<DWORD>(static_cast<std::uint64_t>(offset) >> 32u);
|
||||
DWORD written = 0;
|
||||
const BOOL succeeded = ::WriteFile(handle, buffer, static_cast<DWORD>(std::min<std::uint64_t>(count, std::numeric_limits<DWORD>::max())), &written, &overlapped);
|
||||
const DWORD writeError = succeeded ? ERROR_SUCCESS : ::GetLastError();
|
||||
if (!::SetFilePointerEx(handle, previous, nullptr, FILE_BEGIN)) throw std::runtime_error("pwrite: restoring the host file position failed");
|
||||
if (!succeeded) { errno = writeError == ERROR_ACCESS_DENIED ? EBADF : EIO; return -1; }
|
||||
const auto result = written;
|
||||
#else
|
||||
const auto result = ::pwrite(fd, buffer, static_cast<std::size_t>(count), static_cast<off_t>(offset));
|
||||
#endif
|
||||
@@ -436,9 +963,19 @@ std::int64_t PwritevSharedMemory_nid_no_patch(int descriptor, const struct iovec
|
||||
#endif
|
||||
|
||||
int TruncateSharedMemory_nid_no_patch(int descriptor, std::int64_t length) {
|
||||
#ifdef _WIN32
|
||||
const auto positioned = LockPositionedTransfer(descriptor);
|
||||
#endif
|
||||
SharedMemoryLock guard(descriptor);
|
||||
if (!guard.IsShared()) return -2;
|
||||
if (guard.Error() != 0) { errno = guard.Error(); return -1; }
|
||||
if (guard.Error() != 0) {
|
||||
errno = guard.Error();
|
||||
#ifdef _WIN32
|
||||
return errno;
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
const auto fd = guard.Descriptor();
|
||||
#ifdef _WIN32
|
||||
const int result = static_cast<int>(::_chsize_s(fd, length));
|
||||
@@ -456,7 +993,7 @@ extern "C" int APS5_VABI shm_open_nid_postfix(const char* path, int flags, int m
|
||||
if ((flags & ~AcceptedFlags) != 0) return Fail(GuestInvalid);
|
||||
if (path != anonymousObject) {
|
||||
#ifdef _WIN32
|
||||
NotImplemented_nid_no_patch("shm_open of a named object");
|
||||
return OpenNamedFile(path, flags, mode);
|
||||
#else
|
||||
return OpenNamedFile(path, flags, mode);
|
||||
#endif
|
||||
@@ -471,12 +1008,10 @@ extern "C" int APS5_VABI shm_open_nid_postfix(const char* path, int flags, int m
|
||||
|
||||
extern "C" int APS5_VABI shm_unlink_nid_postfix(const char* path) {
|
||||
#ifdef _WIN32
|
||||
(void)path;
|
||||
NotImplemented_nid_no_patch("shm_unlink of a named object");
|
||||
return 0;
|
||||
return UnlinkNamedFile(path);
|
||||
#else
|
||||
int validationError = 0;
|
||||
const auto hostName = NamedHostPath(path, validationError);
|
||||
const auto hostName = NamedHostName(path, validationError);
|
||||
if (validationError != 0) return Fail(NamedGuestError(validationError) ? NamedGuestError(validationError) : GuestInvalid);
|
||||
HostShmLock namespaceLock;
|
||||
if (namespaceLock.Error() != 0) {
|
||||
|
||||
@@ -734,16 +734,18 @@ int APS5_VABI close_nid_postfix(int d) {
|
||||
return -1;
|
||||
}
|
||||
if (GuestSockets::IsOpen(d)) return GuestSockets::Close(d);
|
||||
EqueueDescriptorClosed(d);
|
||||
#ifdef _WIN32
|
||||
const auto positioned = File::LockPositionedTransfer(d);
|
||||
#endif
|
||||
EqueueDescriptorClosed(d);
|
||||
File::ForgetRandomDevice(d);
|
||||
File::ForgetDescriptor(d);
|
||||
ForgetJitSharedMemory(d);
|
||||
#ifdef _WIN32
|
||||
File::ForgetDirectoryDescriptor(d);
|
||||
File::ForgetFileLock(d);
|
||||
const int shared = File::CloseSharedMemoryDescriptor_nid_no_patch(d);
|
||||
if (shared != -2) return shared;
|
||||
return WithoutParameterHandler([d] { return ::_close(d); });
|
||||
#else
|
||||
return ::close(d);
|
||||
@@ -784,6 +786,7 @@ int APS5_VABI dup_nid_postfix(int d) {
|
||||
#ifdef _WIN32
|
||||
const int duplicate = WithoutParameterHandler([d] { return ::_dup(d); });
|
||||
if (duplicate >= 0) File::DuplicateDirectoryDescriptor(d, duplicate);
|
||||
if (duplicate >= 0) File::DuplicateSharedMemoryDescriptor_nid_no_patch(d, duplicate);
|
||||
#else
|
||||
const int duplicate = ::dup(d);
|
||||
#endif
|
||||
@@ -816,8 +819,10 @@ int APS5_VABI dup2_nid_postfix(int from, int to) {
|
||||
if (from == to) return to;
|
||||
if (to == 1) PreserveHostStandardOutput();
|
||||
if (to == 2) PreserveHostStandardError();
|
||||
auto positioned = File::LockPositionedTransfer(to);
|
||||
auto* const standard = HostStandardHandle_nid_no_patch(to);
|
||||
const int duplicated = WithoutParameterHandler([from, to] { return ::_dup2(from, to); });
|
||||
int duplicated = File::ReplaceSharedMemoryDescriptor_nid_no_patch(from, to);
|
||||
if (duplicated == -2) duplicated = WithoutParameterHandler([from, to] { return ::_dup2(from, to); });
|
||||
KeepStandardHandle_nid_no_patch(to, standard);
|
||||
if (duplicated != 0) return PosixFailure(SceErrorFromErrno(errno) & 0xffff);
|
||||
EqueueDescriptorClosed(to);
|
||||
@@ -826,7 +831,10 @@ int APS5_VABI dup2_nid_postfix(int from, int to) {
|
||||
File::DuplicateDirectoryDescriptor(from, to);
|
||||
File::ForgetFileLock(to);
|
||||
File::ForgetRandomDevice(to);
|
||||
File::ForgetSharedMemoryDescriptor_nid_no_patch(to);
|
||||
File::DuplicateSharedMemoryDescriptor_nid_no_patch(from, to);
|
||||
if (sourceIsRandom) File::RememberRandomDevice(to);
|
||||
positioned.unlock();
|
||||
File::ShareDescriptor(from, to);
|
||||
DuplicateJitSharedMemory(from, to);
|
||||
return to;
|
||||
@@ -1545,6 +1553,10 @@ int APS5_VABI sceKernelChmod_nid_postfix(const char* path, std::uint16_t mode) {
|
||||
int APS5_VABI sceKernelFchmod(int d, std::uint16_t mode) {
|
||||
if (GuestSockets::IsOpen(d)) return SceErrorFromErrno(GUEST_EINVAL);
|
||||
File::RefuseArchivedFile(d, "fchmod");
|
||||
#ifdef _WIN32
|
||||
const int sharedMemoryResult = File::SetSharedMemoryMode_nid_no_patch(d, mode);
|
||||
if (sharedMemoryResult != -2) return sharedMemoryResult == 0 ? 0 : SceErrorFromErrno(errno);
|
||||
#endif
|
||||
if (NativeFchmod(d, mode & 07777) != 0) return SceErrorFromErrno(errno);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -145,26 +145,28 @@ struct GuestRtPrio {
|
||||
|
||||
int APS5_VABI rtprio_thread_nid_postfix(int function, int lwpid, GuestRtPrio* priority) {
|
||||
constexpr int Lookup = 0, Set = 1;
|
||||
constexpr int GuestEsrch = 3, GuestEfault = 14, GuestEinval = 22;
|
||||
constexpr std::uint16_t Normal = 3;
|
||||
constexpr int GuestEsrch = 3, GuestEfault = 14;
|
||||
constexpr int GuestSchedFifo = 1, GuestSchedRr = 3;
|
||||
constexpr std::uint16_t Realtime = 2, Normal = 3, Fifo = 10;
|
||||
const auto fail = [](int error) {
|
||||
*__error_nid_postfix() = error;
|
||||
return -1;
|
||||
};
|
||||
if (function != Lookup && function != Set) return fail(GuestEinval);
|
||||
if (function != Lookup && function != Set) return fail(GuestEsrch);
|
||||
if (priority == nullptr) return fail(GuestEfault);
|
||||
auto* self = scePthreadSelf();
|
||||
if (lwpid != 0 && static_cast<std::int64_t>(lwpid) != static_cast<std::int64_t>(self->tid)) {
|
||||
if (!LiveGuestThreadExists(lwpid)) return fail(GuestEsrch);
|
||||
NotImplemented_nid_no_patch("rtprio_thread: a thread other than the calling one");
|
||||
}
|
||||
const int policy = self->schedPolicy.load(std::memory_order_relaxed);
|
||||
if (policy == 1 || policy == 3) NotImplemented_nid_no_patch("rtprio_thread: a guest thread's real-time class");
|
||||
int policy = self->schedPolicy.load(std::memory_order_relaxed);
|
||||
int schedPriority = self->priority.load(std::memory_order_relaxed);
|
||||
const bool other = lwpid != 0 && static_cast<std::int64_t>(lwpid) != static_cast<std::int64_t>(self->tid);
|
||||
if (other && !GetLiveGuestThreadSched_nid_no_patch(lwpid, policy, schedPriority)) return fail(GuestEsrch);
|
||||
const bool realTime = policy == GuestSchedFifo || policy == GuestSchedRr;
|
||||
if (function == Lookup) {
|
||||
priority->type = Normal;
|
||||
priority->prio = 0;
|
||||
priority->type = realTime ? (policy == GuestSchedFifo ? Fifo : Realtime) : Normal;
|
||||
priority->prio = realTime ? static_cast<std::uint16_t>(schedPriority) : 0;
|
||||
return 0;
|
||||
}
|
||||
if (other) NotImplemented_nid_no_patch("rtprio_thread: a thread other than the calling one");
|
||||
if (realTime) NotImplemented_nid_no_patch("rtprio_thread: a guest thread's real-time class");
|
||||
if (priority->type == Normal && priority->prio == 0) return 0;
|
||||
NotImplemented_nid_no_patch("rtprio_thread: a real-time priority change");
|
||||
}
|
||||
|
||||
@@ -181,6 +181,7 @@ bool GuestThreadIsLive(const PthreadPrivate* thread);
|
||||
std::unique_lock<std::mutex> LockLiveGuestThread(const PthreadPrivate* thread);
|
||||
bool LiveGuestThreadExists(std::int64_t tid);
|
||||
bool GetLiveGuestThreadAffinity_nid_no_patch(std::int64_t tid, KernelCpumask& mask);
|
||||
bool GetLiveGuestThreadSched_nid_no_patch(std::int64_t tid, int& policy, int& priority);
|
||||
bool SetLiveGuestThreadAffinity_nid_no_patch(std::int64_t tid, KernelCpumask mask);
|
||||
extern "C" std::size_t GetDefaultStackSize_nid_no_patch();
|
||||
extern "C" bool SetDefaultStackSize_nid_no_patch(std::size_t size);
|
||||
|
||||
@@ -14,6 +14,8 @@ static constexpr int SCE_KERNEL_ERROR_EDEADLK = 0x8002000B;
|
||||
static constexpr int SCE_KERNEL_ERROR_EBUSY = 0x80020010;
|
||||
static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = 0x8002003C;
|
||||
static constexpr int SCE_KERNEL_ERROR_EINVAL = 0x80020016;
|
||||
static constexpr int FIRST_STORED_RWLOCK_TYPE = 1;
|
||||
static constexpr int LAST_STORED_RWLOCK_TYPE = 2;
|
||||
|
||||
static PthreadRwlockPrivate* const DestroyedRwlock = reinterpret_cast<PthreadRwlockPrivate*>(std::uintptr_t{1});
|
||||
|
||||
@@ -174,7 +176,7 @@ int APS5_VABI scePthreadRwlockattrInit(PthreadRwlockattr* attr) {
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadRwlockattrSettype(PthreadRwlockattr* attr, int type) {
|
||||
if (!attr || !*attr) throw std::runtime_error("scePthreadRwlockattrSettype: null attr");
|
||||
if (!attr || !*attr || type < FIRST_STORED_RWLOCK_TYPE || type > LAST_STORED_RWLOCK_TYPE) return SCE_KERNEL_ERROR_EINVAL;
|
||||
(*attr)->type = type;
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
@@ -549,6 +549,18 @@ bool GetLiveGuestThreadAffinity_nid_no_patch(std::int64_t tid, KernelCpumask& ma
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetLiveGuestThreadSched_nid_no_patch(std::int64_t tid, int& policy, int& priority) {
|
||||
auto& registry = CpuClocks();
|
||||
std::lock_guard lock(registry.lock);
|
||||
for (const auto& [id, thread] : registry.threads) {
|
||||
if (thread->tid != tid || thread->_finished.load(std::memory_order_acquire)) continue;
|
||||
policy = thread->schedPolicy.load(std::memory_order_relaxed);
|
||||
priority = thread->priority.load(std::memory_order_relaxed);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SetLiveGuestThreadAffinity_nid_no_patch(std::int64_t tid, KernelCpumask mask) {
|
||||
auto& registry = CpuClocks();
|
||||
std::lock_guard lock(registry.lock);
|
||||
|
||||
@@ -17,13 +17,18 @@ struct SocketOptionState {
|
||||
int ipTypeOfService = 0;
|
||||
int multicastTimeToLive = 1;
|
||||
bool multicastLoop = true;
|
||||
int tcpMaximumSegment = 536;
|
||||
unsigned int tcpKeepIdle = 0;
|
||||
unsigned int tcpKeepInterval = 0;
|
||||
unsigned int tcpKeepCount = 0;
|
||||
int keptFlags = 0;
|
||||
};
|
||||
|
||||
bool NativeListening(std::uintptr_t native, bool& listening);
|
||||
int SetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int family, int type, int option, const void* value, std::uint32_t length);
|
||||
int GetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int family, int type, int option, void* value, std::uint32_t* length);
|
||||
int SetTcpLevelOption(std::uintptr_t native, bool tcp, int option, const void* value, std::uint32_t length);
|
||||
int GetTcpLevelOption(std::uintptr_t native, bool tcp, int option, void* value, std::uint32_t* length);
|
||||
int SetTcpLevelOption(std::uintptr_t native, SocketOptionState& state, bool tcp, int option, const void* value, std::uint32_t length);
|
||||
int GetTcpLevelOption(std::uintptr_t native, SocketOptionState& state, bool tcp, int option, void* value, std::uint32_t* length);
|
||||
int SetIpLevelOption(std::uintptr_t native, SocketOptionState& state, bool ipv4, int option, const void* value, std::uint32_t length);
|
||||
int GetIpLevelOption(SocketOptionState& state, bool ipv4, int option, void* value, std::uint32_t* length);
|
||||
int SetIpv6LevelOption(std::uintptr_t native, bool ipv6, int option, const void* value, std::uint32_t length);
|
||||
|
||||
@@ -40,7 +40,19 @@ constexpr int GuestNonBlocking = 0x1200;
|
||||
constexpr int GuestNonBlockingState = 0x100;
|
||||
constexpr int GuestUnix = 1;
|
||||
constexpr int GuestTcpNoDelay = 1;
|
||||
constexpr int GuestReusePort = 0x200;
|
||||
constexpr int KeptFlags = 0x1 | GuestReuseAddress | 0x40 | GuestReusePort | 0x400 | 0x2000 | 0x10000;
|
||||
constexpr int WriteOnlyFlags = 0x4000 | 0x8000;
|
||||
constexpr int GuestTcpNoDelayFlag = 4;
|
||||
constexpr int GuestTcpMaximumSegment = 2;
|
||||
constexpr int GuestTcpKeepIdle = 0x100;
|
||||
constexpr int GuestTcpKeepInterval = 0x200;
|
||||
constexpr int GuestTcpKeepCount = 0x400;
|
||||
constexpr int MinimumSegment = 216;
|
||||
constexpr unsigned int TicksPerSecond = 1000;
|
||||
constexpr unsigned int DefaultKeepIdle = 7200;
|
||||
constexpr unsigned int DefaultKeepInterval = 75;
|
||||
constexpr unsigned int DefaultKeepCount = 8;
|
||||
constexpr int GuestIpv6Only = 27;
|
||||
constexpr int GuestIpTypeOfService = 3;
|
||||
constexpr int GuestIpTimeToLive = 4;
|
||||
@@ -89,7 +101,6 @@ int HostError() { return GuestErrorFromHost(errno); }
|
||||
|
||||
int NativeBoolean(int option) {
|
||||
switch (option) {
|
||||
case GuestReuseAddress: return SO_REUSEADDR;
|
||||
case GuestKeepAlive: return SO_KEEPALIVE;
|
||||
case GuestDontRoute: return SO_DONTROUTE;
|
||||
case GuestBroadcast: return SO_BROADCAST;
|
||||
@@ -193,6 +204,25 @@ int SetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int fa
|
||||
if (::setsockopt(handle, SOL_SOCKET, NativeBoolean(option), reinterpret_cast<NativeOptionValue>(&enabled), sizeof(enabled))) return HostError();
|
||||
return 0;
|
||||
}
|
||||
if ((option & (KeptFlags | WriteOnlyFlags)) != 0 && (option & (option - 1)) == 0) {
|
||||
if (length < sizeof(int)) return ErrorInvalid;
|
||||
int number;
|
||||
std::memcpy(&number, value, sizeof(number));
|
||||
if ((option & KeptFlags) == 0) return 0;
|
||||
const int flags = number != 0 ? state.keptFlags | option : state.keptFlags & ~option;
|
||||
if (option == GuestReuseAddress || option == GuestReusePort) {
|
||||
#ifdef _WIN32
|
||||
const int reuseAddress = (flags & (GuestReuseAddress | GuestReusePort)) != 0 ? 1 : 0;
|
||||
#else
|
||||
const int reuseAddress = (flags & GuestReuseAddress) != 0 ? 1 : 0;
|
||||
const int reusePort = (flags & GuestReusePort) != 0 ? 1 : 0;
|
||||
if (::setsockopt(handle, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<NativeOptionValue>(&reusePort), sizeof(reusePort))) return HostError();
|
||||
#endif
|
||||
if (::setsockopt(handle, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<NativeOptionValue>(&reuseAddress), sizeof(reuseAddress))) return HostError();
|
||||
}
|
||||
state.keptFlags = flags;
|
||||
return 0;
|
||||
}
|
||||
return ErrorNoProtocolOption;
|
||||
}
|
||||
|
||||
@@ -235,6 +265,8 @@ int GetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int fa
|
||||
number = listening ? GuestAcceptConnection : 0;
|
||||
} else if (option == GuestType) {
|
||||
number = type;
|
||||
} else if ((option & KeptFlags) != 0 && (option & (option - 1)) == 0) {
|
||||
number = state.keptFlags & option;
|
||||
} else if (option == GuestNoSigpipe) {
|
||||
number = state.noSignal ? GuestNoSigpipe : 0;
|
||||
} else if (option == GuestNonBlocking) {
|
||||
@@ -258,19 +290,43 @@ int GetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int fa
|
||||
return Copy(value, length, &number, sizeof(number));
|
||||
}
|
||||
|
||||
int SetTcpLevelOption(std::uintptr_t native, bool tcp, int option, const void* value, std::uint32_t length) {
|
||||
int SetTcpLevelOption(std::uintptr_t native, SocketOptionState& state, bool tcp, int option, const void* value, std::uint32_t length) {
|
||||
if (!tcp) return ErrorInvalid;
|
||||
if (option != GuestTcpNoDelay) return ErrorNoProtocolOption;
|
||||
if (option != GuestTcpNoDelay && option != GuestTcpMaximumSegment && option != GuestTcpKeepIdle && option != GuestTcpKeepInterval &&
|
||||
option != GuestTcpKeepCount)
|
||||
return ErrorNoProtocolOption;
|
||||
if (length < sizeof(int)) return ErrorInvalid;
|
||||
int number;
|
||||
std::memcpy(&number, value, sizeof(number));
|
||||
const auto unsignedNumber = static_cast<unsigned int>(number);
|
||||
if (option == GuestTcpMaximumSegment) {
|
||||
if (number <= 0 || number > state.tcpMaximumSegment || number + 40 < MinimumSegment) return ErrorInvalid;
|
||||
state.tcpMaximumSegment = number;
|
||||
return 0;
|
||||
}
|
||||
if (option == GuestTcpKeepCount) {
|
||||
state.tcpKeepCount = unsignedNumber;
|
||||
return 0;
|
||||
}
|
||||
if (option == GuestTcpKeepIdle || option == GuestTcpKeepInterval) {
|
||||
if (unsignedNumber > UINT32_MAX / TicksPerSecond) return ErrorInvalid;
|
||||
(option == GuestTcpKeepIdle ? state.tcpKeepIdle : state.tcpKeepInterval) = unsignedNumber;
|
||||
return 0;
|
||||
}
|
||||
const int enabled = number != 0 ? 1 : 0;
|
||||
if (::setsockopt(static_cast<NativeHandle>(native), IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<NativeOptionValue>(&enabled), sizeof(enabled))) return HostError();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int GetTcpLevelOption(std::uintptr_t native, bool tcp, int option, void* value, std::uint32_t* length) {
|
||||
int GetTcpLevelOption(std::uintptr_t native, SocketOptionState& state, bool tcp, int option, void* value, std::uint32_t* length) {
|
||||
if (!tcp) return ErrorInvalid;
|
||||
if (option == GuestTcpMaximumSegment || option == GuestTcpKeepIdle || option == GuestTcpKeepInterval || option == GuestTcpKeepCount) {
|
||||
const unsigned int number = option == GuestTcpMaximumSegment ? static_cast<unsigned int>(state.tcpMaximumSegment)
|
||||
: option == GuestTcpKeepIdle ? (state.tcpKeepIdle != 0 ? state.tcpKeepIdle : DefaultKeepIdle)
|
||||
: option == GuestTcpKeepInterval ? (state.tcpKeepInterval != 0 ? state.tcpKeepInterval : DefaultKeepInterval)
|
||||
: (state.tcpKeepCount != 0 ? state.tcpKeepCount : DefaultKeepCount);
|
||||
return Copy(value, length, &number, sizeof(number));
|
||||
}
|
||||
if (option != GuestTcpNoDelay) return ErrorNoProtocolOption;
|
||||
int enabled = 0;
|
||||
socklen_t size = sizeof(enabled);
|
||||
|
||||
@@ -1149,7 +1149,7 @@ int APS5_VABI setsockopt_nid_postfix(int descriptor, int level, int option,
|
||||
std::lock_guard lock(socket->modeMutex);
|
||||
const auto native = static_cast<std::uintptr_t>(socket->value);
|
||||
const int error = level == 0 ? GuestSockets::SetIpLevelOption(native, socket->options, socket->family == 2, option, value, length)
|
||||
: level == 6 ? GuestSockets::SetTcpLevelOption(native, socket->family != GuestUnix && socket->type == 1, option, value, length)
|
||||
: level == 6 ? GuestSockets::SetTcpLevelOption(native, socket->options, socket->family != GuestUnix && socket->type == 1, option, value, length)
|
||||
: level == 41 ? GuestSockets::SetIpv6LevelOption(native, socket->family == 28, option, value, length)
|
||||
: GuestSockets::SetSocketLevelOption(native, socket->options, socket->family, socket->type, option, value, length);
|
||||
return error == 0 ? 0 : Fail(error);
|
||||
@@ -1164,7 +1164,7 @@ int APS5_VABI getsockopt_nid_postfix(int descriptor, int level, int option,
|
||||
std::lock_guard lock(socket->modeMutex);
|
||||
const auto native = static_cast<std::uintptr_t>(socket->value);
|
||||
const int error = level == 0 ? GuestSockets::GetIpLevelOption(socket->options, socket->family == 2, option, value, length)
|
||||
: level == 6 ? GuestSockets::GetTcpLevelOption(native, socket->family != GuestUnix && socket->type == 1, option, value, length)
|
||||
: level == 6 ? GuestSockets::GetTcpLevelOption(native, socket->options, socket->family != GuestUnix && socket->type == 1, option, value, length)
|
||||
: level == 41 ? GuestSockets::GetIpv6LevelOption(native, socket->family == 28, option, value, length)
|
||||
: GuestSockets::GetSocketLevelOption(native, socket->options, socket->family, socket->type, option, value, length);
|
||||
return error == 0 ? 0 : Fail(error);
|
||||
@@ -1299,6 +1299,7 @@ int APS5_VABI accept_nid_postfix(int descriptor, void* address, std::uint32_t* l
|
||||
std::lock_guard modeLock(listener->modeMutex);
|
||||
inheritNonblocking = listener->options.nonblocking;
|
||||
accepted->options.noSignal = listener->options.noSignal;
|
||||
accepted->options.keptFlags = listener->options.keptFlags;
|
||||
accepted->options.sendTimeout = listener->options.sendTimeout;
|
||||
accepted->options.receiveTimeout = listener->options.receiveTimeout;
|
||||
accepted->options.sendBuffer = listener->options.sendBuffer;
|
||||
@@ -1307,6 +1308,9 @@ int APS5_VABI accept_nid_postfix(int descriptor, void* address, std::uint32_t* l
|
||||
accepted->options.receiveLowWater = listener->options.receiveLowWater;
|
||||
accepted->options.ipTimeToLive = listener->options.ipTimeToLive;
|
||||
accepted->options.ipTypeOfService = listener->options.ipTypeOfService;
|
||||
accepted->options.tcpKeepIdle = listener->options.tcpKeepIdle;
|
||||
accepted->options.tcpKeepInterval = listener->options.tcpKeepInterval;
|
||||
accepted->options.tcpKeepCount = listener->options.tcpKeepCount;
|
||||
}
|
||||
if (inheritNonblocking && SetNonBlocking(*accepted)) return -1;
|
||||
const int acceptedDescriptor = Install(std::move(accepted));
|
||||
|
||||
@@ -58,6 +58,7 @@ extern "C" int APS5_VABI sceKernelVirtualQuery(const void* addr, int flags, Virt
|
||||
namespace {
|
||||
|
||||
constexpr int sceInvalidArgument = static_cast<int>(0x80020016u);
|
||||
constexpr int errnoOperationNotPermitted = 1;
|
||||
constexpr int errnoNoSuchProcess = 3;
|
||||
constexpr int errnoNoChild = 10;
|
||||
constexpr int errnoBadAddress = 14;
|
||||
@@ -344,6 +345,24 @@ int APS5_VABI getsid_nid_postfix(int pid) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int APS5_VABI setpgid_nid_postfix(int pid, int pgid) {
|
||||
if (pgid < 0) {
|
||||
*__error_nid_postfix() = errnoInvalidArgument;
|
||||
return -1;
|
||||
}
|
||||
const int self = getpid_nid_postfix();
|
||||
if (pid != 0 && pid != self) {
|
||||
*__error_nid_postfix() = errnoNoSuchProcess;
|
||||
return -1;
|
||||
}
|
||||
*__error_nid_postfix() = errnoOperationNotPermitted;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int APS5_VABI setpgrp_nid_postfix(int pid, int pgrp) {
|
||||
return setpgid_nid_postfix(pid, pgrp);
|
||||
}
|
||||
|
||||
int APS5_VABI getppid_nid_postfix(void) {
|
||||
#ifdef _WIN32
|
||||
const DWORD self = GetCurrentProcessId();
|
||||
|
||||
@@ -681,3 +681,12 @@ int APS5_VABI _is_signal_return_nid_postfix(std::uint64_t programCounter) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct RegisterSignalsOnLoad {
|
||||
RegisterSignalsOnLoad() {
|
||||
RegisterSigaction_nid_no_patch(reinterpret_cast<SigactionFn_nid_no_patch>(sigaction_nid_postfix), _sigintr_nid_postfix.bits);
|
||||
}
|
||||
};
|
||||
const RegisterSignalsOnLoad gRegisterSignals;
|
||||
}
|
||||
@@ -364,6 +364,24 @@ __attribute__((noinline)) static void APS5_VABI RunChecks() {
|
||||
}
|
||||
char narrow[8] = {};
|
||||
Require(sscanf_nid_postfix("ab%", "%7[^%ls]", narrow) == 1 && std::strcmp(narrow, "ab") == 0);
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
char freebsd[64];
|
||||
Require(snprintf_nid_postfix(freebsd, sizeof(freebsd), "%qd|%qu|%qx|%qo") == 15 &&
|
||||
std::strcmp(freebsd, "%qd|%qu|%qx|%qo") == 0);
|
||||
Require(snprintf_nid_postfix(freebsd, sizeof(freebsd), "%qd|%d", 7) == 5 && std::strcmp(freebsd, "%qd|7") == 0);
|
||||
Require(snprintf_nid_postfix(freebsd, sizeof(freebsd), "%D|%hD|%O|%U|%jD") == 14 &&
|
||||
std::strcmp(freebsd, "%D|%D|%O|%U|%D") == 0);
|
||||
Require(snprintf_nid_postfix(freebsd, sizeof(freebsd), "[%5%][%-5%][%y][%5y][%-3y][%hhz]") == 34 &&
|
||||
std::strcmp(freebsd, "[ %][% ][%y][ %y][%y ][%z]") == 0);
|
||||
for (const char* unsupported : {"%+y", "%#y", "%05y", "%.2y"}) {
|
||||
bool threw = false;
|
||||
try { snprintf_nid_postfix(freebsd, sizeof(freebsd), unsupported, 1); } catch (const std::invalid_argument&) { threw = true; }
|
||||
Require(threw);
|
||||
}
|
||||
bool incomplete = false;
|
||||
try { snprintf_nid_postfix(freebsd, sizeof(freebsd), "100%"); } catch (const std::invalid_argument&) { incomplete = true; }
|
||||
Require(incomplete);
|
||||
#endif
|
||||
long long count = -1;
|
||||
int smallCount = -1;
|
||||
|
||||
@@ -4,11 +4,21 @@
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <initializer_list>
|
||||
|
||||
extern "C" {
|
||||
int* APS5_VABI __error_nid_postfix();
|
||||
double APS5_VABI _Stod_nid_postfix(const char* text, char** end, std::int64_t pten);
|
||||
long long APS5_VABI _Stoll_nid_postfix(const char* text, char** end, int base);
|
||||
double APS5_VABI _Stodx_nid_postfix(const char* text, char** end, std::int64_t pten, int* error);
|
||||
float APS5_VABI _Stofx_nid_postfix(const char* text, char** end, std::int64_t pten, int* error);
|
||||
long double APS5_VABI _Stoldx_nid_postfix(const char* text, char** end, std::int64_t pten, int* error);
|
||||
float APS5_VABI _Stof_nid_postfix(const char* text, char** end, std::int64_t pten);
|
||||
long double APS5_VABI _Stold_nid_postfix(const char* text, char** end, std::int64_t pten);
|
||||
long long APS5_VABI _Stollx_nid_postfix(const char* text, char** end, int base, int* error);
|
||||
std::int64_t APS5_VABI _Stolx_nid_postfix(const char* text, char** end, int base, int* error);
|
||||
std::uint64_t APS5_VABI _Stoulx_nid_postfix(const char* text, char** end, int base, int* error);
|
||||
unsigned long long APS5_VABI _Stoullx_nid_postfix(const char* text, char** end, int base, int* error);
|
||||
double APS5_VABI _Sin_nid_postfix(double x, unsigned int quadrant);
|
||||
float APS5_VABI _FSin_nid_postfix(float x, unsigned int quadrant);
|
||||
long double APS5_VABI _LSin_nid_postfix(long double x, unsigned int quadrant);
|
||||
@@ -19,6 +29,7 @@ float APS5_VABI _FLog_nid_postfix(float x, int base);
|
||||
namespace {
|
||||
|
||||
constexpr int RangeError = 34;
|
||||
constexpr int InvalidArgument = 22;
|
||||
|
||||
void Require(bool condition, const char* message) {
|
||||
if (!condition) {
|
||||
@@ -71,6 +82,101 @@ void TestStoll() {
|
||||
Require(_Stoll_nid_postfix("9223372036854775808", &end, 10) == 9223372036854775807ll && *__error_nid_postfix() == RangeError, "_Stoll saturates");
|
||||
}
|
||||
|
||||
void TestFloatErrors() {
|
||||
char* end = nullptr;
|
||||
int error = 99;
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(_Stodx_nid_postfix("1.5", &end, 2, &error) == 150.0 && error == 0 && *__error_nid_postfix() == 0, "_Stodx in range");
|
||||
error = 99;
|
||||
Require(std::isinf(_Stodx_nid_postfix("1e400", &end, 0, &error)) && error == 1 && *__error_nid_postfix() == RangeError, "_Stodx overflow");
|
||||
error = 99;
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(_Stodx_nid_postfix("1e-400", &end, 2, &error) == 0.0 && error == 1 && *__error_nid_postfix() == RangeError, "_Stodx underflow");
|
||||
error = 99;
|
||||
*__error_nid_postfix() = 7;
|
||||
Require(_Stodx_nid_postfix("1e-400", &end, 300, &error) == 1e-100 && error == 0 && *__error_nid_postfix() == 7, "_Stodx rescued by its scale");
|
||||
error = 99;
|
||||
*__error_nid_postfix() = RangeError;
|
||||
Require(_Stodx_nid_postfix("2.5", &end, 1, &error) == 25.0 && error == 0 && *__error_nid_postfix() == RangeError, "_Stodx kept a preset errno");
|
||||
error = 99;
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(std::isinf(_Stofx_nid_postfix("1e39", &end, 0, &error)) && error == 1 && *__error_nid_postfix() == RangeError, "_Stofx overflow");
|
||||
error = 99;
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(_Stofx_nid_postfix("2.5e-1", &end, 3, &error) == 250.0f && error == 0 && *__error_nid_postfix() == 0, "_Stofx scaled");
|
||||
error = 99;
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(_Stofx_nid_postfix("1e39", &end, -1, &error) == 1e38f && error == 0 && *__error_nid_postfix() == 0, "_Stofx overflow scaled back into range");
|
||||
error = 99;
|
||||
Require(_Stodx_nid_postfix("1e400", &end, -100, &error) == 1e300 && error == 0 && *__error_nid_postfix() == 0, "_Stodx overflow scaled back into range");
|
||||
error = 99;
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(_Stoldx_nid_postfix("1e400", &end, 0, &error) == 1e400L && error == 0 && *__error_nid_postfix() == 0, "_Stoldx uses the extended range");
|
||||
error = 99;
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(_Stodx_nid_postfix("1e-310", &end, 0, &error) == 1e-310 && error == 0 && *__error_nid_postfix() == 0 && end[0] == '\0', "_Stodx subnormal");
|
||||
error = 99;
|
||||
Require(_Stodx_nid_postfix("1e-300", &end, -10, &error) == 1e-310 && error == 0 && *__error_nid_postfix() == 0, "_Stodx scaled into the subnormal range");
|
||||
error = 99;
|
||||
*__error_nid_postfix() = 7;
|
||||
Require(_Stodx_nid_postfix("1e-310", &end, 0, &error) == 1e-310 && error == 0 && *__error_nid_postfix() == 7, "_Stodx subnormal kept a preset errno");
|
||||
error = 99;
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(_Stodx_nid_postfix("2e-324", &end, 0, &error) == 0.0 && error == 1 && *__error_nid_postfix() == RangeError, "_Stodx below the smallest subnormal");
|
||||
error = 99;
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(_Stofx_nid_postfix("1e-45", &end, 0, &error) == 0x1p-149f && error == 0 && *__error_nid_postfix() == 0 && end[0] == '\0', "_Stofx inexact subnormal");
|
||||
error = 99;
|
||||
Require(_Stofx_nid_postfix("1e-35", &end, -10, &error) == 0x1p-149f && error == 0 && *__error_nid_postfix() == 0, "_Stofx scaled into the subnormal range");
|
||||
error = 99;
|
||||
*__error_nid_postfix() = 7;
|
||||
const float subnormal = _Stofx_nid_postfix("1e-40", &end, 0, &error);
|
||||
Require(subnormal > 0x1p-134f && subnormal < 0x1p-132f && error == 0 && *__error_nid_postfix() == 7, "_Stofx subnormal kept a preset errno");
|
||||
error = 99;
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(_Stofx_nid_postfix("1e-46", &end, 0, &error) == 0.0f && error == 1 && *__error_nid_postfix() == RangeError, "_Stofx underflow");
|
||||
error = 99;
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(_Stofx_nid_postfix("1.17549435e-38", &end, 0, &error) == 0x1p-126f && error == 0 && *__error_nid_postfix() == 0, "_Stofx smallest normal");
|
||||
error = 99;
|
||||
Require(_Stoldx_nid_postfix("1e-4940", &end, 0, &error) == 1e-4940L && error == 0 && *__error_nid_postfix() == 0, "_Stoldx subnormal");
|
||||
error = 99;
|
||||
Require(_Stoldx_nid_postfix("1e-4930", &end, -10, &error) == 1e-4940L && error == 0 && *__error_nid_postfix() == 0, "_Stoldx scaled into the subnormal range");
|
||||
error = 99;
|
||||
Require(_Stoldx_nid_postfix("1e-5000", &end, 0, &error) == 0.0L && error == 1 && *__error_nid_postfix() == RangeError, "_Stoldx underflow");
|
||||
Require(_Stof_nid_postfix("1.5", &end, 1) == 15.0f, "_Stof");
|
||||
Require(_Stold_nid_postfix("1.5", &end, -1) == 0.15L, "_Stold");
|
||||
error = 99;
|
||||
Require(std::isnan(_Stodx_nid_postfix("nan", &end, 2, &error)) && error == 0 && end[0] == '\0', "_Stodx NaN");
|
||||
}
|
||||
|
||||
void TestIntegerErrors() {
|
||||
char* end = nullptr;
|
||||
int error = 99;
|
||||
*__error_nid_postfix() = 5;
|
||||
Require(_Stollx_nid_postfix("123", &end, 0, &error) == 123 && error == 0 && *__error_nid_postfix() == 5, "_Stollx in range");
|
||||
Require(_Stollx_nid_postfix("9223372036854775808", &end, 10, &error) == 9223372036854775807ll && error == RangeError && *__error_nid_postfix() == RangeError, "_Stollx overflow");
|
||||
*__error_nid_postfix() = 5;
|
||||
const char* text = " 12";
|
||||
for (int base : {1, -1, 37}) {
|
||||
error = 99;
|
||||
Require(_Stollx_nid_postfix(text, &end, base, &error) == 0 && error == InvalidArgument && end == text && *__error_nid_postfix() == 5, "_Stollx invalid base");
|
||||
error = 99;
|
||||
Require(_Stolx_nid_postfix(text, &end, base, &error) == 0 && error == InvalidArgument && end == text && *__error_nid_postfix() == 5, "_Stolx invalid base");
|
||||
}
|
||||
Require(_Stoll_nid_postfix(text, &end, 1) == 0 && end == text && *__error_nid_postfix() == 5, "_Stoll invalid base");
|
||||
error = 99;
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(_Stolx_nid_postfix("9223372036854775808", &end, 0, &error) == 9223372036854775807ll && error == RangeError && *__error_nid_postfix() == 0, "_Stolx overflow inside the unsigned range");
|
||||
Require(_Stolx_nid_postfix("-18446744073709551615", &end, 10, &error) == (-9223372036854775807ll - 1) && error == RangeError && *__error_nid_postfix() == 0, "_Stolx negative overflow inside the unsigned range");
|
||||
Require(_Stolx_nid_postfix("18446744073709551616", &end, 10, &error) == 9223372036854775807ll && error == RangeError && *__error_nid_postfix() == RangeError, "_Stolx overflow past the unsigned range");
|
||||
error = 99;
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(_Stoulx_nid_postfix("-1", &end, 0, &error) == 18446744073709551615ull && error == 0, "_Stoulx wraps a negative value");
|
||||
Require(_Stoullx_nid_postfix("-18446744073709551615", &end, 10, &error) == 1 && error == 0 && *__error_nid_postfix() == 0, "_Stoullx wraps a negated maximum");
|
||||
Require(_Stoullx_nid_postfix("18446744073709551616", &end, 10, &error) == 18446744073709551615ull && error == RangeError && *__error_nid_postfix() == RangeError, "_Stoullx overflow");
|
||||
}
|
||||
|
||||
void TestDispatch() {
|
||||
Require(_Sin_nid_postfix(0.5, 0) == std::sin(0.5) && _Sin_nid_postfix(0.5, 1) == std::cos(0.5) && _Sin_nid_postfix(0.5, 7) == std::cos(0.5), "_Sin");
|
||||
Require(_FSin_nid_postfix(0.5f, 0) == std::sin(0.5f) && _FSin_nid_postfix(0.5f, 2) == std::cos(0.5f), "_FSin");
|
||||
@@ -84,5 +190,7 @@ void TestDispatch() {
|
||||
int main() {
|
||||
TestStod();
|
||||
TestStoll();
|
||||
TestFloatErrors();
|
||||
TestIntegerErrors();
|
||||
TestDispatch();
|
||||
}
|
||||
@@ -104,6 +104,8 @@ static void BadDescriptors() {
|
||||
#ifdef _WIN32
|
||||
const auto previous = _set_invalid_parameter_handler(RecordInvalidParameter);
|
||||
#endif
|
||||
Require(dup2_nid_postfix(pipe[0], closed) == closed);
|
||||
Require(close_nid_postfix(closed) == 0);
|
||||
RequireFailure(close_nid_postfix(closed), GuestEbadf);
|
||||
RequireFailure(close_nid_postfix(-1), GuestEbadf);
|
||||
#ifdef _WIN32
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <thread>
|
||||
|
||||
extern "C" {
|
||||
int APS5_VABI __std_init_once_begin_initialize_clr_nid_postfix(void** once, unsigned long flags, int* pending, void** context);
|
||||
int APS5_VABI __std_init_once_complete_clr_nid_postfix(void** once, unsigned long flags, void* context);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr unsigned long InitFailed = 4;
|
||||
|
||||
void Require(bool condition, const char* message) {
|
||||
if (condition) return;
|
||||
std::fprintf(stderr, "init once: %s\n", message);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
int Begin(void** once) {
|
||||
int pending = -1;
|
||||
Require(__std_init_once_begin_initialize_clr_nid_postfix(once, 0, &pending, nullptr) == 1, "begin returns 1");
|
||||
return pending;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
alignas(8) void* once = nullptr;
|
||||
Require(Begin(&once) == 1, "the first caller runs the initialization");
|
||||
Require(reinterpret_cast<std::uintptr_t>(once) == 1, "a running initialization marks the object");
|
||||
Require(__std_init_once_complete_clr_nid_postfix(&once, InitFailed, nullptr) == 1, "complete a failed initialization");
|
||||
Require(once == nullptr, "a failed initialization resets the object");
|
||||
Require(Begin(&once) == 1, "a caller after a failure runs the initialization again");
|
||||
|
||||
std::atomic<bool> waiterDone{false};
|
||||
std::atomic<int> waiterPending{-1};
|
||||
std::thread waiter([&] {
|
||||
waiterPending = Begin(&once);
|
||||
waiterDone = true;
|
||||
});
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
Require(!waiterDone, "a second caller waits while the initialization runs");
|
||||
Require(__std_init_once_complete_clr_nid_postfix(&once, 0, nullptr) == 1, "complete the initialization");
|
||||
waiter.join();
|
||||
Require(waiterPending == 0, "the waiting caller sees the initialization done");
|
||||
Require(reinterpret_cast<std::uintptr_t>(once) == 2, "a completed initialization marks the object");
|
||||
Require(Begin(&once) == 0, "a later caller does not run the initialization");
|
||||
return 0;
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "prx/libc/include/ApplicationHeap.hpp"
|
||||
#include "prx/libc/include/GuestLocale.hpp"
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cwchar>
|
||||
#include <climits>
|
||||
@@ -36,9 +38,14 @@ std::uint32_t APS5_VABI _Towctrans_nid_postfix(std::uint32_t wc, std::uint64_t d
|
||||
int* APS5_VABI __error_nid_postfix();
|
||||
extern std::uint64_t _ZNSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix;
|
||||
extern std::uint64_t _ZNSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix;
|
||||
extern std::uint64_t _ZNSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix;
|
||||
extern std::uintptr_t _ZTVSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[17];
|
||||
extern std::uintptr_t _ZTVSt7codecvtIwc9_MbstatetE_nid_postfix[13];
|
||||
extern std::uintptr_t _ZTVSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE_nid_postfix[14];
|
||||
std::size_t APS5_VABI _ZNSt7codecvtIcc9_MbstatetE7_GetcatEPPKNSt6locale5facetEPKS2__nid_postfix(GuestLocale::Facet** facet, const GuestLocale::Implementation* const* locale);
|
||||
std::size_t APS5_VABI _ZNSt8numpunctIcE7_GetcatEPPKNSt6locale5facetEPKS1__nid_postfix(GuestLocale::Facet** facet, const GuestLocale::Implementation* const* locale);
|
||||
std::size_t APS5_VABI _ZNSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE7_GetcatEPPKNSt6locale5facetEPKS5__nid_postfix(GuestLocale::Facet** facet, const GuestLocale::Implementation* const* locale);
|
||||
std::size_t APS5_VABI _ZNSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE7_GetcatEPPKNSt6locale5facetEPKS5__nid_postfix(GuestLocale::Facet** facet, const GuestLocale::Implementation* const* locale);
|
||||
|
||||
}
|
||||
|
||||
@@ -46,6 +53,17 @@ static void Require(bool condition) {
|
||||
if (!condition) throw std::runtime_error("Guest locale check failed");
|
||||
}
|
||||
|
||||
void* APS5_VABI TestHeapAllocate(std::size_t bytes) { return std::malloc(bytes); }
|
||||
void APS5_VABI TestHeapFree(void* pointer) { std::free(pointer); }
|
||||
void* APS5_VABI TestHeapCalloc(std::size_t count, std::size_t bytes) { return std::calloc(count, bytes); }
|
||||
void* APS5_VABI TestHeapReallocate(void* pointer, std::size_t bytes) { return std::realloc(pointer, bytes); }
|
||||
void* APS5_VABI TestHeapAlign(std::size_t, std::size_t bytes) { return std::malloc(bytes); }
|
||||
void* APS5_VABI TestHeapRealign(void* pointer, std::size_t bytes, std::size_t) { return std::realloc(pointer, bytes); }
|
||||
int APS5_VABI TestHeapPosixAlign(void** pointer, std::size_t, std::size_t bytes) {
|
||||
*pointer = std::malloc(bytes);
|
||||
return *pointer == nullptr ? 12 : 0;
|
||||
}
|
||||
|
||||
template<typename TAction>
|
||||
static void RequireException(TAction action) {
|
||||
bool caught = false;
|
||||
@@ -273,7 +291,60 @@ static void CheckWideNumPutVtableObject() {
|
||||
}
|
||||
}
|
||||
|
||||
static void CheckFacetGetcat() {
|
||||
const GuestLocale::Implementation* locale = _ZSt21_sceLibcClassicLocale_nid_postfix;
|
||||
GuestLocale::Facet* codecvt = nullptr;
|
||||
Require(_ZNSt7codecvtIcc9_MbstatetE7_GetcatEPPKNSt6locale5facetEPKS2__nid_postfix(&codecvt, &locale) == 2 && codecvt != nullptr);
|
||||
auto* codecvtVtable = reinterpret_cast<const GuestLocale::CodecvtVtable*>(codecvt->vtable);
|
||||
Require(codecvtVtable->alwaysNoconv(codecvt) && codecvtVtable->encoding(codecvt) == 1 && codecvtVtable->maxLength(codecvt) == 1);
|
||||
const char* nextFrom = nullptr;
|
||||
char target[4]{};
|
||||
char* nextTo = nullptr;
|
||||
Require(codecvtVtable->out(codecvt, nullptr, "ab", "ab" + 2, &nextFrom, target, target + 4, &nextTo) == 3);
|
||||
Require(nextFrom == "ab" && nextTo == target);
|
||||
Require(codecvtVtable->in(codecvt, nullptr, "ab", "ab" + 2, &nextFrom, target, target + 4, &nextTo) == 3);
|
||||
nextTo = reinterpret_cast<char*>(0x10);
|
||||
Require(codecvtVtable->unshift(codecvt, nullptr, nullptr, nullptr, &nextTo) == 0 && nextTo == nullptr);
|
||||
Require(codecvtVtable->length(codecvt, nullptr, "ab", "ab" + 2, 5) == 2);
|
||||
Require(codecvtVtable->length(codecvt, nullptr, "ab", "ab" + 2, 1) == 1);
|
||||
GuestLocale::Facet* punct = nullptr;
|
||||
Require(_ZNSt8numpunctIcE7_GetcatEPPKNSt6locale5facetEPKS1__nid_postfix(&punct, &locale) == 4 && punct != nullptr);
|
||||
auto* punctVtable = reinterpret_cast<const GuestLocale::NumpunctVtable*>(punct->vtable);
|
||||
auto* punctFacet = reinterpret_cast<const GuestLocale::NumpunctFacet*>(punct);
|
||||
Require(punctVtable->decimalPoint(punctFacet) == '.' && punctVtable->thousandsSep(punctFacet) == ',');
|
||||
GuestLocale::String name{};
|
||||
Require(punctVtable->grouping(&name, punctFacet) == &name && name.size == 0);
|
||||
const char* contents = name.size <= 15 ? name.buffer : name.pointer;
|
||||
Require(punctVtable->falsename(&name, punctFacet)->size == 5 && std::memcmp(contents, "false", 5) == 0);
|
||||
contents = name.size <= 15 ? name.buffer : name.pointer;
|
||||
Require(punctVtable->truename(&name, punctFacet)->size == 4 && std::memcmp(contents, "true", 4) == 0);
|
||||
GuestLocale::Facet* numPut = nullptr;
|
||||
Require(_ZNSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE7_GetcatEPPKNSt6locale5facetEPKS5__nid_postfix(&numPut, &locale) == 4 && numPut != nullptr);
|
||||
GuestLocale::Facet* timePut = nullptr;
|
||||
Require(_ZNSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE7_GetcatEPPKNSt6locale5facetEPKS5__nid_postfix(&timePut, &locale) == 5 && timePut != nullptr);
|
||||
_ZNSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 11;
|
||||
Require(_ZNSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix == 11);
|
||||
_ZNSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 0;
|
||||
for (GuestLocale::Facet* facet : {codecvt, punct, numPut, timePut}) {
|
||||
facet->vtable->retain(facet);
|
||||
facet->vtable->retain(facet);
|
||||
Require(facet->references == 2);
|
||||
facet->vtable->deleteObject(facet);
|
||||
}
|
||||
Require(_ZNSt7codecvtIcc9_MbstatetE7_GetcatEPPKNSt6locale5facetEPKS2__nid_postfix(nullptr, &locale) == 2);
|
||||
Require(_ZNSt8numpunctIcE7_GetcatEPPKNSt6locale5facetEPKS1__nid_postfix(&punct, &locale) == 4);
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::array<void*, 10> heapApi{};
|
||||
heapApi[0] = reinterpret_cast<void*>(&TestHeapAllocate);
|
||||
heapApi[1] = reinterpret_cast<void*>(&TestHeapFree);
|
||||
heapApi[2] = reinterpret_cast<void*>(&TestHeapCalloc);
|
||||
heapApi[3] = reinterpret_cast<void*>(&TestHeapReallocate);
|
||||
heapApi[4] = reinterpret_cast<void*>(&TestHeapAlign);
|
||||
heapApi[5] = reinterpret_cast<void*>(&TestHeapRealign);
|
||||
heapApi[6] = reinterpret_cast<void*>(&TestHeapPosixAlign);
|
||||
ApplicationHeapRegister_nid_no_patch(heapApi.data());
|
||||
CheckGuestCalls();
|
||||
CheckLocinfoAlignment();
|
||||
CheckCharacterTables();
|
||||
@@ -285,6 +356,7 @@ int main() {
|
||||
CheckInputFacetObjects();
|
||||
CheckCodecvtWideVtableObject();
|
||||
CheckWideNumPutVtableObject();
|
||||
CheckFacetGetcat();
|
||||
_Locksyslock_nid_postfix();
|
||||
_Locksyslock_nid_postfix();
|
||||
_Unlocksyslock_nid_postfix();
|
||||
|
||||
@@ -0,0 +1,625 @@
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include "prx/libkernel/File/include/SharedMemory.hpp"
|
||||
#include "nid/Sha1.hpp"
|
||||
#include <windows.h>
|
||||
#include <aclapi.h>
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cwchar>
|
||||
#include <filesystem>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <barrier>
|
||||
#include <algorithm>
|
||||
|
||||
extern "C" {
|
||||
int APS5_VABI shm_open_nid_postfix(const char*, int, int);
|
||||
int APS5_VABI shm_unlink_nid_postfix(const char*);
|
||||
int APS5_VABI close_nid_postfix(int);
|
||||
int APS5_VABI dup_nid_postfix(int);
|
||||
int APS5_VABI dup2_nid_postfix(int, int);
|
||||
std::int64_t APS5_VABI lseek_nid_postfix(int, std::int64_t, int);
|
||||
int APS5_VABI ftruncate_nid_postfix(int, std::int64_t);
|
||||
int APS5_VABI fchmod_nid_postfix(int, int);
|
||||
std::int64_t APS5_VABI pread_nid_postfix(int, void*, std::size_t, std::int64_t);
|
||||
std::int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int, const void*, std::size_t, std::int64_t);
|
||||
int* APS5_VABI __error_nid_postfix();
|
||||
}
|
||||
|
||||
namespace {
|
||||
constexpr int ReadOnly = 0;
|
||||
constexpr int ReadWrite = 2;
|
||||
constexpr int Create = 0x200;
|
||||
constexpr int Exclusive = 0x800;
|
||||
constexpr std::int64_t RaceSize = 4096;
|
||||
|
||||
void Require(bool condition, const char* message) {
|
||||
if (!condition) throw std::runtime_error(std::string(message) + " (guest errno=" + std::to_string(*__error_nid_postfix()) + ")");
|
||||
}
|
||||
|
||||
void RequireError(int descriptor, int expected, const char* message) {
|
||||
Require(descriptor == -1 && *__error_nid_postfix() == expected, message);
|
||||
}
|
||||
|
||||
void RequireNoAdministratorsAccess(HANDLE handle) {
|
||||
PACL dacl = nullptr;
|
||||
PSECURITY_DESCRIPTOR descriptor = nullptr;
|
||||
const DWORD result = ::GetSecurityInfo(handle, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION,
|
||||
nullptr, nullptr, &dacl, nullptr, &descriptor);
|
||||
if (result != ERROR_SUCCESS || dacl == nullptr) {
|
||||
if (descriptor != nullptr) ::LocalFree(descriptor);
|
||||
Require(false, "reading named shared-memory object permissions failed");
|
||||
}
|
||||
SID_IDENTIFIER_AUTHORITY authority = SECURITY_NT_AUTHORITY;
|
||||
PSID administrators = nullptr;
|
||||
if (::AllocateAndInitializeSid(&authority, 2, SECURITY_BUILTIN_DOMAIN_RID,
|
||||
DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &administrators) == FALSE) {
|
||||
::LocalFree(descriptor);
|
||||
Require(false, "building the Administrators SID failed");
|
||||
}
|
||||
ACL_SIZE_INFORMATION information{};
|
||||
const BOOL gotInformation = ::GetAclInformation(dacl, &information, sizeof(information), AclSizeInformation);
|
||||
bool grantsAdministrators = false;
|
||||
if (gotInformation) {
|
||||
for (DWORD index = 0; index < information.AceCount; ++index) {
|
||||
void* rawAce = nullptr;
|
||||
if (!::GetAce(dacl, index, &rawAce)) continue;
|
||||
const auto* header = static_cast<const ACE_HEADER*>(rawAce);
|
||||
if (header->AceType != ACCESS_ALLOWED_ACE_TYPE) continue;
|
||||
const auto* ace = static_cast<const ACCESS_ALLOWED_ACE*>(rawAce);
|
||||
if (::EqualSid(const_cast<DWORD*>(&ace->SidStart), administrators) != FALSE) {
|
||||
grantsAdministrators = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
::FreeSid(administrators);
|
||||
::LocalFree(descriptor);
|
||||
Require(gotInformation && !grantsAdministrators,
|
||||
"named shared-memory object grants access to the Administrators group");
|
||||
}
|
||||
|
||||
void RequireNoUnlinkedFiles(const std::string& name) {
|
||||
const auto digest = Nid::Sha1(std::vector<std::uint8_t>(name.begin(), name.end()));
|
||||
std::string prefix;
|
||||
for (const auto byte : digest) {
|
||||
prefix += "0123456789abcdef"[byte >> 4];
|
||||
prefix += "0123456789abcdef"[byte & 15];
|
||||
}
|
||||
const auto directory = std::filesystem::temp_directory_path() / L"AnyPS5" / L"shm";
|
||||
if (!std::filesystem::exists(directory)) return;
|
||||
for (const auto& entry : std::filesystem::directory_iterator(directory)) {
|
||||
const auto filename = entry.path().filename().string();
|
||||
Require(!filename.starts_with(prefix + "-unlinked-"),
|
||||
"closing the last descriptor did not reap its unlinked host object");
|
||||
}
|
||||
}
|
||||
|
||||
void VerifyUntrackedDescriptor(int descriptor) {
|
||||
Require(File::IsSharedMemoryDescriptor_nid_no_patch(descriptor), "named descriptor was not tracked");
|
||||
std::vector<wchar_t> path(32768);
|
||||
const DWORD length = ::GetFinalPathNameByHandleW(reinterpret_cast<HANDLE>(::_get_osfhandle(descriptor)),
|
||||
path.data(), static_cast<DWORD>(path.size()), FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
|
||||
Require(length > 0 && length < path.size(), "locating the tracked file failed");
|
||||
const HANDLE handle = ::CreateFileW(path.data(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
Require(handle != INVALID_HANDLE_VALUE, "opening an untracked handle to the named file failed");
|
||||
const int untracked = ::_open_osfhandle(reinterpret_cast<std::intptr_t>(handle), _O_RDONLY | _O_BINARY);
|
||||
if (untracked < 0) ::CloseHandle(handle);
|
||||
Require(untracked >= 0, "creating an untracked descriptor failed");
|
||||
const bool classified = File::IsSharedMemoryDescriptor_nid_no_patch(untracked);
|
||||
Require(::_close(untracked) == 0, "closing the untracked descriptor failed");
|
||||
Require(!classified, "an untracked descriptor entered the named shared-memory path");
|
||||
Require(!File::IsSharedMemoryDescriptor_nid_no_patch(untracked), "a closed untracked descriptor was classified as shared memory");
|
||||
}
|
||||
|
||||
void VerifyConcurrentDescriptorReuse(const std::string& name) {
|
||||
std::barrier start(3);
|
||||
std::vector<int> okay(2, 1);
|
||||
std::vector<std::thread> workers;
|
||||
for (int index = 0; index < 2; ++index) {
|
||||
workers.emplace_back([&, index] {
|
||||
start.arrive_and_wait();
|
||||
for (int iteration = 0; iteration < 64; ++iteration) {
|
||||
const int descriptor = shm_open_nid_postfix(name.c_str(), ReadWrite, 0);
|
||||
if (descriptor < 0) { okay[index] = 0; break; }
|
||||
if (!File::IsSharedMemoryDescriptor_nid_no_patch(descriptor) ||
|
||||
pwrite_nid_disambig1_nid_postfix(descriptor, "x", 1, 64) != 0) okay[index] = 0;
|
||||
if (close_nid_postfix(descriptor) != 0) okay[index] = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
start.arrive_and_wait();
|
||||
for (auto& worker : workers) worker.join();
|
||||
Require(okay[0] && okay[1], "concurrent close/open lost named-descriptor tracking or bounded writes");
|
||||
}
|
||||
|
||||
bool RunResizeWriteRace(const std::string& name) {
|
||||
const int descriptor = shm_open_nid_postfix(name.c_str(), ReadWrite, 0);
|
||||
if (descriptor < 0) return false;
|
||||
bool okay = true;
|
||||
for (int iteration = 0; iteration < 300; ++iteration) {
|
||||
const auto size = (iteration & 1) ? 0 : RaceSize;
|
||||
if (ftruncate_nid_postfix(descriptor, size) != 0) { okay = false; break; }
|
||||
if (pwrite_nid_disambig1_nid_postfix(descriptor, "x", 1, RaceSize / 2) < 0) { okay = false; break; }
|
||||
struct _stat64 status{};
|
||||
if (::_fstat64(descriptor, &status) != 0 || (status.st_size != 0 && status.st_size != RaceSize)) {
|
||||
okay = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
close_nid_postfix(descriptor);
|
||||
return okay;
|
||||
}
|
||||
|
||||
void VerifyCrossProcessResizeWrite(const std::string& name, int descriptor) {
|
||||
wchar_t executable[32768]{};
|
||||
const DWORD length = ::GetModuleFileNameW(nullptr, executable, static_cast<DWORD>(std::size(executable)));
|
||||
Require(length > 0 && length < std::size(executable), "locating the Windows shared-memory test executable failed");
|
||||
std::vector<PROCESS_INFORMATION> workers;
|
||||
for (int index = 0; index < 2; ++index) {
|
||||
std::wstring command = L"\"" + std::wstring(executable, length) + L"\" --race " + std::wstring(name.begin(), name.end());
|
||||
STARTUPINFOW startup{};
|
||||
startup.cb = sizeof(startup);
|
||||
PROCESS_INFORMATION process{};
|
||||
Require(::CreateProcessW(nullptr, command.data(), nullptr, nullptr, FALSE, CREATE_NO_WINDOW,
|
||||
nullptr, nullptr, &startup, &process) != FALSE,
|
||||
"starting a cross-process shared-memory race worker failed");
|
||||
workers.push_back(process);
|
||||
}
|
||||
const bool localSucceeded = RunResizeWriteRace(name);
|
||||
bool childrenSucceeded = true;
|
||||
for (const auto& process : workers) {
|
||||
childrenSucceeded = childrenSucceeded && ::WaitForSingleObject(process.hProcess, 10000) == WAIT_OBJECT_0;
|
||||
DWORD exitCode = 1;
|
||||
childrenSucceeded = childrenSucceeded && ::GetExitCodeProcess(process.hProcess, &exitCode) && exitCode == 0;
|
||||
::CloseHandle(process.hThread);
|
||||
::CloseHandle(process.hProcess);
|
||||
}
|
||||
Require(localSucceeded && childrenSucceeded,
|
||||
"cross-process truncate/write race produced an invalid shared-object size");
|
||||
struct _stat64 status{};
|
||||
Require(::_fstat64(descriptor, &status) == 0 && (status.st_size == 0 || status.st_size == RaceSize),
|
||||
"cross-process truncate/write race left an invalid final size");
|
||||
Require(shm_unlink_nid_postfix(name.c_str()) == 0, "cross-process race cleanup failed");
|
||||
}
|
||||
|
||||
void VerifyResizeReadPosition(const std::string& name) {
|
||||
constexpr std::size_t payloadSize = 2u << 20;
|
||||
const int descriptor = shm_open_nid_postfix(name.c_str(), ReadWrite | Create | Exclusive, 0600);
|
||||
Require(descriptor >= 0 && ftruncate_nid_postfix(descriptor, payloadSize) == 0,
|
||||
"creating the resize/read object failed");
|
||||
const int resizer = dup_nid_postfix(descriptor);
|
||||
Require(resizer >= 0, "duplicating the resize/read descriptor failed");
|
||||
std::vector<char> payload(payloadSize, 'r');
|
||||
Require(pwrite_nid_disambig1_nid_postfix(descriptor, payload.data(), payload.size(), 0) == payloadSize &&
|
||||
lseek_nid_postfix(descriptor, 137, 0) == 137, "preparing the resize/read object failed");
|
||||
std::barrier start(2);
|
||||
bool resized = true;
|
||||
std::thread resizing([&] {
|
||||
start.arrive_and_wait();
|
||||
for (int iteration = 0; iteration < 256; ++iteration)
|
||||
if (ftruncate_nid_postfix(resizer, payloadSize + ((iteration & 1) ? 4096 : 0)) != 0) resized = false;
|
||||
});
|
||||
start.arrive_and_wait();
|
||||
std::vector<char> output(payloadSize);
|
||||
bool read = true;
|
||||
for (int iteration = 0; iteration < 32; ++iteration)
|
||||
if (pread_nid_postfix(descriptor, output.data(), output.size(), 0) != payloadSize || output != payload) read = false;
|
||||
resizing.join();
|
||||
Require(resized && read && lseek_nid_postfix(descriptor, 0, 1) == 137,
|
||||
"concurrent resize/read changed the shared file position or contents");
|
||||
Require(shm_unlink_nid_postfix(name.c_str()) == 0 && close_nid_postfix(descriptor) == 0 && close_nid_postfix(resizer) == 0,
|
||||
"resize/read cleanup failed");
|
||||
RequireNoUnlinkedFiles(name);
|
||||
}
|
||||
|
||||
void Run() {
|
||||
const std::string base = "/anyps5-windows-named-" + std::to_string(::GetCurrentProcessId()) + "-" + std::to_string(::GetTickCount64());
|
||||
const std::string name = base + "-lifecycle";
|
||||
Require(shm_unlink_nid_postfix(name.c_str()) == -1 && *__error_nid_postfix() == 2,
|
||||
"new test name unexpectedly existed");
|
||||
|
||||
*__error_nid_postfix() = 73;
|
||||
const int original = shm_open_nid_postfix(name.c_str(), ReadWrite | Create | Exclusive, 0600);
|
||||
Require(original >= 0 && *__error_nid_postfix() == 73, "exclusive creation failed or changed guest errno");
|
||||
VerifyUntrackedDescriptor(original);
|
||||
Require(ftruncate_nid_postfix(original, 64) == 0, "sizing the named object failed");
|
||||
VerifyConcurrentDescriptorReuse(name);
|
||||
const char originalBytes[] = "original";
|
||||
Require(pwrite_nid_disambig1_nid_postfix(original, originalBytes, sizeof(originalBytes), 0) == sizeof(originalBytes),
|
||||
"write to named object failed");
|
||||
|
||||
*__error_nid_postfix() = 0;
|
||||
RequireError(shm_open_nid_postfix(name.c_str(), ReadWrite | Create | Exclusive, 0600), 17,
|
||||
"exclusive creation replaced an existing named object");
|
||||
const int reader = shm_open_nid_postfix(name.c_str(), ReadOnly, 0);
|
||||
char contents[sizeof(originalBytes)]{};
|
||||
Require(reader >= 0 && pread_nid_postfix(reader, contents, sizeof(contents), 0) == sizeof(contents) &&
|
||||
std::memcmp(contents, originalBytes, sizeof(contents)) == 0, "reopen did not share the original contents");
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(pwrite_nid_disambig1_nid_postfix(reader, "x", 1, 0) == -1 && *__error_nid_postfix() == 9,
|
||||
"read-only descriptor accepted a write");
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(pwrite_nid_disambig1_nid_postfix(reader, "x", 1, 64) == -1 && *__error_nid_postfix() == 9,
|
||||
"read-only descriptor accepted a write at end of file");
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(pwrite_nid_disambig1_nid_postfix(reader, "", 0, 64) == -1 && *__error_nid_postfix() == 9,
|
||||
"read-only descriptor accepted a zero-length write");
|
||||
|
||||
Require(shm_unlink_nid_postfix(name.c_str()) == 0, "unlink of named object failed");
|
||||
*__error_nid_postfix() = 0;
|
||||
RequireError(shm_open_nid_postfix(name.c_str(), ReadOnly, 0), 2, "unlinked name remained openable");
|
||||
std::memset(contents, 0, sizeof(contents));
|
||||
Require(pread_nid_postfix(original, contents, sizeof(contents), 0) == sizeof(contents) &&
|
||||
std::memcmp(contents, originalBytes, sizeof(contents)) == 0, "open descriptor stopped working after unlink");
|
||||
const char updatedByte = 'X';
|
||||
Require(pwrite_nid_disambig1_nid_postfix(original, &updatedByte, 1, 0) == 1,
|
||||
"open descriptor stopped accepting writes after unlink");
|
||||
|
||||
const int replacement = shm_open_nid_postfix(name.c_str(), ReadWrite | Create | Exclusive, 0600);
|
||||
Require(replacement >= 0, "name could not be recreated while old descriptors remained open");
|
||||
Require(ftruncate_nid_postfix(replacement, 64) == 0, "sizing the replacement object failed");
|
||||
Require(fchmod_nid_postfix(original, 0400) == 0,
|
||||
"fchmod failed on the old open object after unlink and name recreation");
|
||||
Require(fchmod_nid_postfix(original, 0600) == 0,
|
||||
"restoring mode on the old open object after name recreation failed");
|
||||
std::memset(contents, 0x7f, sizeof(contents));
|
||||
Require(pread_nid_postfix(replacement, contents, sizeof(contents), 0) == sizeof(contents) &&
|
||||
std::memcmp(contents, "\0\0\0\0\0\0\0\0", sizeof(contents)) == 0,
|
||||
"recreated name still referred to the unlinked object");
|
||||
Require(pwrite_nid_disambig1_nid_postfix(replacement, "y", 1, 0) == 1,
|
||||
"fchmod on the old descriptor changed the replacement object's permissions");
|
||||
const char replacementBytes[] = "replacement";
|
||||
Require(pwrite_nid_disambig1_nid_postfix(replacement, replacementBytes, sizeof(replacementBytes), 0) == sizeof(replacementBytes),
|
||||
"write to replacement object failed");
|
||||
std::memset(contents, 0, sizeof(contents));
|
||||
Require(pread_nid_postfix(original, contents, sizeof(contents), 0) == sizeof(contents) &&
|
||||
contents[0] == updatedByte && std::memcmp(contents + 1, originalBytes + 1, sizeof(originalBytes) - 2) == 0,
|
||||
"replacement write changed the old open object");
|
||||
Require(shm_unlink_nid_postfix(name.c_str()) == 0, "replacement unlink failed");
|
||||
Require(close_nid_postfix(reader) == 0 && close_nid_postfix(original) == 0 && close_nid_postfix(replacement) == 0,
|
||||
"closing lifecycle descriptors failed");
|
||||
RequireNoUnlinkedFiles(name);
|
||||
|
||||
const std::string duplicateName = base + "-duplicate";
|
||||
const int duplicateOriginal = shm_open_nid_postfix(duplicateName.c_str(), ReadWrite | Create | Exclusive, 0600);
|
||||
Require(duplicateOriginal >= 0 && ftruncate_nid_postfix(duplicateOriginal, 64) == 0,
|
||||
"creating the duplicate test object failed");
|
||||
const int duplicate = dup_nid_postfix(duplicateOriginal);
|
||||
Require(duplicate >= 0 && pwrite_nid_disambig1_nid_postfix(duplicate, "d", 1, 0) == 1,
|
||||
"dup lost shared-memory write access");
|
||||
const int duplicateReader = shm_open_nid_postfix(duplicateName.c_str(), ReadOnly, 0);
|
||||
Require(duplicateReader >= 0 && shm_unlink_nid_postfix(duplicateName.c_str()) == 0,
|
||||
"unlinking the duplicate test object failed");
|
||||
Require(close_nid_postfix(duplicateOriginal) == 0, "closing the duplicated original failed");
|
||||
Require(dup2_nid_postfix(-1, duplicate) == -1,
|
||||
"dup2 accepted an invalid source");
|
||||
Require(pwrite_nid_disambig1_nid_postfix(duplicate, "e", 1, 0) == 1,
|
||||
"failed dup2 destroyed its shared-memory target");
|
||||
Require(dup2_nid_postfix(duplicateReader, duplicate) == duplicate,
|
||||
"dup2 could not replace a shared-memory target");
|
||||
Require(pwrite_nid_disambig1_nid_postfix(duplicate, "f", 1, 0) == -1 && *__error_nid_postfix() == 9,
|
||||
"dup2 lost shared-memory read-only access");
|
||||
Require(close_nid_postfix(duplicateReader) == 0, "closing the duplicate reader failed");
|
||||
const std::string replacementName = base + "-duplicate-replacement";
|
||||
const int duplicateReplacement = shm_open_nid_postfix(replacementName.c_str(), ReadWrite | Create | Exclusive, 0600);
|
||||
Require(duplicateReplacement >= 0 && dup2_nid_postfix(duplicateReplacement, duplicate) == duplicate,
|
||||
"dup2 could not replace the last unlinked descriptor");
|
||||
RequireNoUnlinkedFiles(duplicateName);
|
||||
Require(shm_unlink_nid_postfix(replacementName.c_str()) == 0 &&
|
||||
close_nid_postfix(duplicateReplacement) == 0 && close_nid_postfix(duplicate) == 0,
|
||||
"cleaning up dup2 replacement descriptors failed");
|
||||
RequireNoUnlinkedFiles(replacementName);
|
||||
VerifyResizeReadPosition(base + "-resize-read");
|
||||
|
||||
const std::string concurrentName = base + "-exclusive-race";
|
||||
constexpr int contenderCount = 12;
|
||||
std::vector<int> descriptors(contenderCount, -2);
|
||||
std::vector<int> errors(contenderCount, 0);
|
||||
std::vector<std::thread> contenders;
|
||||
contenders.reserve(contenderCount);
|
||||
for (int index = 0; index < contenderCount; ++index) {
|
||||
contenders.emplace_back([&, index] {
|
||||
descriptors[index] = shm_open_nid_postfix(concurrentName.c_str(), ReadWrite | Create | Exclusive, 0600);
|
||||
errors[index] = *__error_nid_postfix();
|
||||
});
|
||||
}
|
||||
for (auto& contender : contenders) contender.join();
|
||||
int created = 0;
|
||||
for (int index = 0; index < contenderCount; ++index) {
|
||||
if (descriptors[index] >= 0) {
|
||||
++created;
|
||||
Require(close_nid_postfix(descriptors[index]) == 0, "closing exclusive-race descriptor failed");
|
||||
} else {
|
||||
Require(errors[index] == 17, "exclusive-race loser did not receive EEXIST");
|
||||
}
|
||||
}
|
||||
Require(created == 1, "exclusive create race produced more or fewer than one winner");
|
||||
Require(shm_unlink_nid_postfix(concurrentName.c_str()) == 0, "exclusive-race cleanup failed");
|
||||
|
||||
const std::string raceName = base + "-cross-process-race";
|
||||
const int raceDescriptor = shm_open_nid_postfix(raceName.c_str(), ReadWrite | Create | Exclusive, 0600);
|
||||
Require(raceDescriptor >= 0 && ftruncate_nid_postfix(raceDescriptor, RaceSize) == 0,
|
||||
"cross-process race object creation failed");
|
||||
VerifyCrossProcessResizeWrite(raceName, raceDescriptor);
|
||||
Require(close_nid_postfix(raceDescriptor) == 0, "closing cross-process race descriptor failed");
|
||||
|
||||
const std::string permissionsName = base + "-permissions";
|
||||
const int restricted = shm_open_nid_postfix(permissionsName.c_str(), ReadWrite | Create | Exclusive, 0400);
|
||||
Require(restricted >= 0, "read-only mode creation failed");
|
||||
RequireNoAdministratorsAccess(reinterpret_cast<HANDLE>(::_get_osfhandle(restricted)));
|
||||
Require(close_nid_postfix(restricted) == 0, "closing read-only mode object failed");
|
||||
*__error_nid_postfix() = 0;
|
||||
RequireError(shm_open_nid_postfix(permissionsName.c_str(), ReadWrite, 0), 13,
|
||||
"mode 0400 allowed a later read-write open");
|
||||
const int restrictedReader = shm_open_nid_postfix(permissionsName.c_str(), ReadOnly, 0);
|
||||
Require(restrictedReader >= 0, "mode 0400 rejected a read-only open");
|
||||
Require(fchmod_nid_postfix(restrictedReader, 0600) == 0,
|
||||
"fchmod could not restore write permission on a named object");
|
||||
const int restoredWriter = shm_open_nid_postfix(permissionsName.c_str(), ReadWrite, 0);
|
||||
Require(restoredWriter >= 0, "mode 0600 after fchmod rejected read-write open");
|
||||
Require(fchmod_nid_postfix(restoredWriter, 0400) == 0,
|
||||
"fchmod could not remove write permission on a named object");
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(shm_unlink_nid_postfix(permissionsName.c_str()) == -1 && *__error_nid_postfix() == 13,
|
||||
"unlink of a read-only named object did not return EACCES");
|
||||
Require(fchmod_nid_postfix(restoredWriter, 0600) == 0,
|
||||
"fchmod could not restore permission before unlink");
|
||||
Require(shm_unlink_nid_postfix(permissionsName.c_str()) == 0,
|
||||
"owner could not unlink a writable named object");
|
||||
Require(close_nid_postfix(restoredWriter) == 0, "closing restored writer failed");
|
||||
Require(close_nid_postfix(restrictedReader) == 0, "closing restricted reader failed");
|
||||
|
||||
const std::string noPermissionsName = base + "-mode-zero";
|
||||
const int noPermissions = shm_open_nid_postfix(noPermissionsName.c_str(), ReadWrite | Create | Exclusive, 0000);
|
||||
Require(noPermissions >= 0 && fchmod_nid_postfix(noPermissions, 0600) == 0,
|
||||
"fchmod could not restore access to a mode 0000 named object");
|
||||
Require(shm_unlink_nid_postfix(noPermissionsName.c_str()) == 0 && close_nid_postfix(noPermissions) == 0,
|
||||
"mode 0000 object cleanup failed");
|
||||
|
||||
*__error_nid_postfix() = 0;
|
||||
RequireError(shm_open_nid_postfix(nullptr, ReadWrite, 0), 14, "null name did not return EFAULT");
|
||||
*__error_nid_postfix() = 0;
|
||||
RequireError(shm_open_nid_postfix("relative", ReadWrite, 0), 22, "relative name did not return EINVAL");
|
||||
std::puts("Windows named shared-memory lifecycle checks passed");
|
||||
}
|
||||
|
||||
bool EnvironmentEntryHasKey(const std::wstring& entry, const wchar_t* key) {
|
||||
const auto separator = entry.find(L'=');
|
||||
return separator != std::wstring::npos && separator == std::wcslen(key) &&
|
||||
::CompareStringOrdinal(entry.data(), static_cast<int>(separator), key, static_cast<int>(separator), TRUE) == CSTR_EQUAL;
|
||||
}
|
||||
|
||||
std::vector<wchar_t> EnvironmentWithTempPath(const std::wstring& path) {
|
||||
std::vector<std::wstring> entries;
|
||||
LPWCH environment = ::GetEnvironmentStringsW();
|
||||
if (environment == nullptr) throw std::runtime_error("reading the Windows environment failed");
|
||||
try {
|
||||
for (const wchar_t* current = environment; *current != L'\0'; current += std::wcslen(current) + 1) {
|
||||
std::wstring entry(current);
|
||||
if (!EnvironmentEntryHasKey(entry, L"TMP") && !EnvironmentEntryHasKey(entry, L"TEMP"))
|
||||
entries.push_back(std::move(entry));
|
||||
}
|
||||
} catch (...) {
|
||||
::FreeEnvironmentStringsW(environment);
|
||||
throw;
|
||||
}
|
||||
::FreeEnvironmentStringsW(environment);
|
||||
entries.push_back(L"TMP=" + path);
|
||||
entries.push_back(L"TEMP=" + path);
|
||||
std::sort(entries.begin(), entries.end(), [](const auto& left, const auto& right) {
|
||||
return ::CompareStringOrdinal(left.c_str(), -1, right.c_str(), -1, TRUE) == CSTR_LESS_THAN;
|
||||
});
|
||||
std::vector<wchar_t> block;
|
||||
for (const auto& entry : entries) {
|
||||
block.insert(block.end(), entry.begin(), entry.end());
|
||||
block.push_back(L'\0');
|
||||
}
|
||||
block.push_back(L'\0');
|
||||
return block;
|
||||
}
|
||||
|
||||
bool SameDirectory(HANDLE left, HANDLE right) {
|
||||
BY_HANDLE_FILE_INFORMATION leftInfo{}, rightInfo{};
|
||||
return ::GetFileInformationByHandle(left, &leftInfo) && ::GetFileInformationByHandle(right, &rightInfo) &&
|
||||
leftInfo.dwVolumeSerialNumber == rightInfo.dwVolumeSerialNumber &&
|
||||
leftInfo.nFileIndexHigh == rightInfo.nFileIndexHigh && leftInfo.nFileIndexLow == rightInfo.nFileIndexLow;
|
||||
}
|
||||
|
||||
std::filesystem::path ExtendedPath(const std::filesystem::path& path) {
|
||||
wchar_t fullPath[32768]{};
|
||||
const DWORD length = ::GetFullPathNameW(path.c_str(), static_cast<DWORD>(std::size(fullPath)), fullPath, nullptr);
|
||||
if (length == 0 || length >= std::size(fullPath))
|
||||
throw std::runtime_error("getting the full private temporary path failed");
|
||||
std::wstring full(fullPath, length);
|
||||
if (full.starts_with(L"\\\\?\\")) return full;
|
||||
if (full.starts_with(L"\\\\")) return L"\\\\?\\UNC\\" + full.substr(2);
|
||||
return L"\\\\?\\" + full;
|
||||
}
|
||||
|
||||
std::filesystem::path DosPath(const std::filesystem::path& path) {
|
||||
const auto& native = path.native();
|
||||
if (native.starts_with(L"\\\\?\\UNC\\")) return L"\\\\" + native.substr(8);
|
||||
if (native.starts_with(L"\\\\?\\")) return native.substr(4);
|
||||
return path;
|
||||
}
|
||||
|
||||
void RemovePrivateDirectory(const std::filesystem::path& directory) {
|
||||
WIN32_FIND_DATAW entry{};
|
||||
const HANDLE search = ::FindFirstFileW((directory / L"*").c_str(), &entry);
|
||||
if (search == INVALID_HANDLE_VALUE) {
|
||||
if (::GetLastError() != ERROR_FILE_NOT_FOUND)
|
||||
throw std::runtime_error("enumerating the private temporary directory for cleanup failed");
|
||||
} else {
|
||||
try {
|
||||
do {
|
||||
if (std::wcscmp(entry.cFileName, L".") == 0 || std::wcscmp(entry.cFileName, L"..") == 0) continue;
|
||||
if (entry.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
|
||||
throw std::runtime_error("the private temporary directory contains a reparse point");
|
||||
const auto child = directory / entry.cFileName;
|
||||
if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) RemovePrivateDirectory(child);
|
||||
else if (!::DeleteFileW(child.c_str()))
|
||||
throw std::runtime_error("removing a private temporary file failed");
|
||||
} while (::FindNextFileW(search, &entry));
|
||||
if (::GetLastError() != ERROR_NO_MORE_FILES)
|
||||
throw std::runtime_error("enumerating the private temporary directory for cleanup failed");
|
||||
} catch (...) {
|
||||
::FindClose(search);
|
||||
throw;
|
||||
}
|
||||
::FindClose(search);
|
||||
}
|
||||
if (!::RemoveDirectoryW(directory.c_str()))
|
||||
throw std::runtime_error("removing the private temporary directory failed");
|
||||
}
|
||||
|
||||
int RunWithShortTempPath(bool longPath) {
|
||||
wchar_t tempBuffer[32768]{};
|
||||
const DWORD tempLength = ::GetTempPathW(static_cast<DWORD>(std::size(tempBuffer)), tempBuffer);
|
||||
if (tempLength == 0 || tempLength >= std::size(tempBuffer))
|
||||
throw std::runtime_error("locating the Windows temporary directory failed");
|
||||
const std::filesystem::path parent(std::wstring(tempBuffer, tempLength));
|
||||
std::filesystem::path baseRoot;
|
||||
bool created = false;
|
||||
for (unsigned attempt = 0; attempt < 16; ++attempt) {
|
||||
baseRoot = parent / (L"AnyPS5-Temp-Regression-" + std::to_wstring(::GetCurrentProcessId()) + L"-" +
|
||||
std::to_wstring(::GetTickCount64()) + L"-" + std::to_wstring(attempt));
|
||||
if (::CreateDirectoryW(baseRoot.c_str(), nullptr)) {
|
||||
created = true;
|
||||
break;
|
||||
}
|
||||
if (::GetLastError() != ERROR_ALREADY_EXISTS) throw std::runtime_error("creating a private temporary directory failed");
|
||||
}
|
||||
if (!created) throw std::runtime_error("creating a unique private temporary directory failed");
|
||||
const auto cleanupRoot = ExtendedPath(baseRoot);
|
||||
std::filesystem::path longRoot = cleanupRoot;
|
||||
const auto cleanup = [&] {
|
||||
RemovePrivateDirectory(cleanupRoot);
|
||||
};
|
||||
|
||||
try {
|
||||
std::vector<wchar_t> shortBuffer(32768);
|
||||
DWORD shortLength = ::GetShortPathNameW(longRoot.c_str(), shortBuffer.data(), static_cast<DWORD>(shortBuffer.size()));
|
||||
if (shortLength == 0)
|
||||
throw std::runtime_error("GetShortPathNameW failed (Win32 error " + std::to_string(::GetLastError()) + ")");
|
||||
if (shortLength >= shortBuffer.size())
|
||||
throw std::runtime_error("GetShortPathNameW required a buffer of " + std::to_string(shortLength) + " characters");
|
||||
std::filesystem::path shortRoot(std::wstring(shortBuffer.data(), shortLength));
|
||||
if (longPath) {
|
||||
bool aliasAvailable = true;
|
||||
bool longAliasReady = false;
|
||||
for (unsigned level = 0; level < 80; ++level) {
|
||||
longRoot /= L"LongSharedMemoryTempDirectoryRegressionSegment";
|
||||
if (!::CreateDirectoryW(longRoot.c_str(), nullptr))
|
||||
throw std::runtime_error("creating the nested long temporary directory failed (Win32 error " + std::to_string(::GetLastError()) + ")");
|
||||
shortLength = ::GetShortPathNameW(longRoot.c_str(), shortBuffer.data(), static_cast<DWORD>(shortBuffer.size()));
|
||||
if (shortLength == 0)
|
||||
throw std::runtime_error("GetShortPathNameW failed for nested path (Win32 error " + std::to_string(::GetLastError()) + ")");
|
||||
if (shortLength >= shortBuffer.size())
|
||||
throw std::runtime_error("GetShortPathNameW required a buffer of " + std::to_string(shortLength) + " characters");
|
||||
shortRoot = std::filesystem::path(std::wstring(shortBuffer.data(), shortLength));
|
||||
if (::CompareStringOrdinal(longRoot.filename().c_str(), -1, shortRoot.filename().c_str(), -1, TRUE) == CSTR_EQUAL) {
|
||||
aliasAvailable = false;
|
||||
break;
|
||||
}
|
||||
if (DosPath(longRoot).native().size() > MAX_PATH + 64 && DosPath(shortRoot).native().size() < MAX_PATH - 64) {
|
||||
longAliasReady = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!aliasAvailable) {
|
||||
std::puts("long-short-temp regression skipped: this volume did not create an 8.3 alias for the nested directory");
|
||||
cleanup();
|
||||
return 77;
|
||||
}
|
||||
if (!longAliasReady)
|
||||
throw std::runtime_error("nested private path did not exceed MAX_PATH with a usable short alias");
|
||||
}
|
||||
if (::CompareStringOrdinal(longRoot.filename().c_str(), -1, shortRoot.filename().c_str(), -1, TRUE) == CSTR_EQUAL) {
|
||||
std::puts("short-temp regression skipped: this volume did not create an 8.3 alias for the private directory");
|
||||
cleanup();
|
||||
return 77;
|
||||
}
|
||||
const HANDLE longHandle = ::CreateFileW(longRoot.c_str(), FILE_READ_ATTRIBUTES,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
||||
if (longHandle == INVALID_HANDLE_VALUE) throw std::runtime_error("opening the long temporary path failed");
|
||||
const HANDLE shortHandle = ::CreateFileW(shortRoot.c_str(), FILE_READ_ATTRIBUTES,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
||||
if (shortHandle == INVALID_HANDLE_VALUE) {
|
||||
::CloseHandle(longHandle);
|
||||
throw std::runtime_error("opening the 8.3 temporary alias failed");
|
||||
}
|
||||
const bool isSameDirectory = SameDirectory(longHandle, shortHandle);
|
||||
::CloseHandle(shortHandle);
|
||||
::CloseHandle(longHandle);
|
||||
if (!isSameDirectory) throw std::runtime_error("the short temporary path did not identify the created directory");
|
||||
std::wprintf(L"%ls regression verified 8.3 directory alias (%llu/%llu characters):\n%ls\n%ls\n",
|
||||
longPath ? L"long-short-temp" : L"short-temp",
|
||||
static_cast<unsigned long long>(longRoot.native().size()),
|
||||
static_cast<unsigned long long>(shortRoot.native().size()), longRoot.c_str(), shortRoot.c_str());
|
||||
std::fflush(stdout);
|
||||
|
||||
wchar_t executable[32768]{};
|
||||
const DWORD executableLength = ::GetModuleFileNameW(nullptr, executable, static_cast<DWORD>(std::size(executable)));
|
||||
if (executableLength == 0 || executableLength >= std::size(executable))
|
||||
throw std::runtime_error("locating the Windows shared-memory test executable failed");
|
||||
std::wstring command = L"\"" + std::wstring(executable, executableLength) + L"\" --short-temp-child";
|
||||
auto environment = EnvironmentWithTempPath(DosPath(shortRoot).native());
|
||||
STARTUPINFOW startup{};
|
||||
startup.cb = sizeof(startup);
|
||||
PROCESS_INFORMATION process{};
|
||||
if (!::CreateProcessW(nullptr, command.data(), nullptr, nullptr, FALSE,
|
||||
CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, environment.data(), nullptr, &startup, &process))
|
||||
throw std::runtime_error("starting the short temporary path child failed");
|
||||
const DWORD wait = ::WaitForSingleObject(process.hProcess, 16000);
|
||||
DWORD exitCode = 1;
|
||||
const bool childSucceeded = wait == WAIT_OBJECT_0 && ::GetExitCodeProcess(process.hProcess, &exitCode) && exitCode == 0;
|
||||
if (wait != WAIT_OBJECT_0) {
|
||||
::TerminateProcess(process.hProcess, 1);
|
||||
::WaitForSingleObject(process.hProcess, 1000);
|
||||
}
|
||||
if (!childSucceeded)
|
||||
std::fprintf(stderr, "%s child failed: wait=%lu exit=%lu\n", longPath ? "long-short-temp" : "short-temp",
|
||||
static_cast<unsigned long>(wait), static_cast<unsigned long>(exitCode));
|
||||
::CloseHandle(process.hThread);
|
||||
::CloseHandle(process.hProcess);
|
||||
cleanup();
|
||||
return childSucceeded ? 0 : 1;
|
||||
} catch (...) {
|
||||
cleanup();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argumentCount, char** arguments) {
|
||||
try {
|
||||
if (argumentCount == 3 && std::string_view(arguments[1]) == "--race")
|
||||
return RunResizeWriteRace(arguments[2]) ? 0 : 1;
|
||||
if (argumentCount == 2 && std::string_view(arguments[1]) == "--short-temp")
|
||||
return RunWithShortTempPath(false);
|
||||
if (argumentCount == 2 && std::string_view(arguments[1]) == "--long-short-temp")
|
||||
return RunWithShortTempPath(true);
|
||||
if (argumentCount == 2 && std::string_view(arguments[1]) == "--short-temp-child") {
|
||||
Run();
|
||||
return 0;
|
||||
}
|
||||
Run();
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::fprintf(stderr, "%s\n", error.what());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,9 @@ int APS5_VABI sceNetCtlRegisterCallbackV6(NetCtlCallback, void*, int*);
|
||||
void APS5_VABI sceNetCtlTerm(void);
|
||||
int APS5_VABI sceNetCtlUnregisterCallback(int);
|
||||
int APS5_VABI sceNetCtlUnregisterCallbackV6(int);
|
||||
int APS5_VABI sceNetCtlRegisterCallbackForNpToolkit(NetCtlCallback, void*, int*);
|
||||
int APS5_VABI sceNetCtlUnregisterCallbackForNpToolkit(int);
|
||||
int APS5_VABI sceNetCtlCheckCallbackForNpToolkit(void);
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -283,6 +286,9 @@ int main() {
|
||||
Require(sceNetCtlRegisterCallbackV6(nullptr, nullptr, nullptr) == kErrInvalidAddr);
|
||||
Require(sceNetCtlUnregisterCallback(-1) == invalidId);
|
||||
Require(sceNetCtlUnregisterCallbackV6(-1) == invalidId);
|
||||
Require(sceNetCtlRegisterCallbackForNpToolkit(nullptr, nullptr, nullptr) == kErrInvalidAddr);
|
||||
Require(sceNetCtlUnregisterCallbackForNpToolkit(-1) == invalidId);
|
||||
Require(sceNetCtlCheckCallbackForNpToolkit() == 0);
|
||||
CheckCallbacks();
|
||||
|
||||
Require(sceNetCtlGetStateV6(nullptr) == kErrInvalidAddr);
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
#include "SceTypes.hpp"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <thread>
|
||||
|
||||
extern "C" {
|
||||
int APS5_VABI sceNetThreadCreate(Pthread*, void*, const char*);
|
||||
void APS5_VABI sceNetThreadExit();
|
||||
int APS5_VABI sceNetThreadJoin(Pthread);
|
||||
int APS5_VABI sceNetEmulationGet();
|
||||
int APS5_VABI sceNetEmulationSet();
|
||||
int* APS5_VABI sceNetErrnoLoc();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int Einval = 22;
|
||||
constexpr int Eopnotsupp = 45;
|
||||
constexpr int NetError = static_cast<int>(0x80410100u);
|
||||
|
||||
struct ThreadParam {
|
||||
void (APS5_VABI *entry)(void*);
|
||||
void* arg;
|
||||
std::uint64_t reserved;
|
||||
std::uint32_t flags;
|
||||
};
|
||||
|
||||
struct Run {
|
||||
std::atomic<int> started{0};
|
||||
std::atomic<bool> pastExit{false};
|
||||
};
|
||||
|
||||
Run run;
|
||||
|
||||
void Require(bool value, const char* what) {
|
||||
if (value) return;
|
||||
std::fprintf(stderr, "%s\n", what);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
void APS5_VABI Count(void* arg) {
|
||||
++static_cast<Run*>(arg)->started;
|
||||
}
|
||||
|
||||
void APS5_VABI ExitEarly(void* arg) {
|
||||
auto* run = static_cast<Run*>(arg);
|
||||
++run->started;
|
||||
sceNetThreadExit();
|
||||
run->pastExit = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
ThreadParam counting{Count, &run, 0, 0};
|
||||
Pthread thread = nullptr;
|
||||
Require(sceNetThreadCreate(&thread, &counting, "anyps5-net-thread") == 0 && thread != nullptr, "sceNetThreadCreate failed");
|
||||
Require(sceNetThreadJoin(thread) == 0, "sceNetThreadJoin failed");
|
||||
Require(run.started == 1, "the thread did not run its entry with its argument");
|
||||
|
||||
Pthread untouched = reinterpret_cast<Pthread>(&run);
|
||||
*sceNetErrnoLoc() = 0;
|
||||
Require(sceNetThreadCreate(&untouched, nullptr, "anyps5-net-thread") == (NetError | Einval), "a null parameter was not refused with EINVAL");
|
||||
Require(*sceNetErrnoLoc() == Einval && untouched == reinterpret_cast<Pthread>(&run), "a refused create set no errno or wrote the thread");
|
||||
|
||||
ThreadParam exiting{ExitEarly, &run, 0, 1};
|
||||
Require(sceNetThreadCreate(&thread, &exiting, nullptr) == 0, "sceNetThreadCreate failed without a name");
|
||||
Require(sceNetThreadJoin(thread) == 0, "joining an exited thread failed");
|
||||
Require(run.started == 2 && !run.pastExit, "sceNetThreadExit did not end the thread");
|
||||
|
||||
static ThreadParam detached{Count, &run, 0, 0};
|
||||
Require(sceNetThreadCreate(nullptr, &detached, "anyps5-net-thread") == 0, "a null thread output was refused");
|
||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
|
||||
while (run.started != 3 && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
Require(run.started == 3, "the thread created without an output did not run");
|
||||
|
||||
*sceNetErrnoLoc() = 0;
|
||||
Require(sceNetEmulationGet() == (NetError | Eopnotsupp) && *sceNetErrnoLoc() == Eopnotsupp, "sceNetEmulationGet did not return EOPNOTSUPP");
|
||||
*sceNetErrnoLoc() = 0;
|
||||
Require(sceNetEmulationSet() == (NetError | Eopnotsupp) && *sceNetErrnoLoc() == Eopnotsupp, "sceNetEmulationSet did not return EOPNOTSUPP");
|
||||
std::puts("net thread tests passed");
|
||||
return 0;
|
||||
}
|
||||
@@ -43,6 +43,7 @@ int APS5_VABI scePadResetLightBar(int);
|
||||
int APS5_VABI scePadSetLightBar(int, const PadLightBarParam*);
|
||||
int APS5_VABI scePadSetMotionSensorState(int, bool);
|
||||
int APS5_VABI scePadGetControllerType(int, std::int32_t*);
|
||||
int APS5_VABI scePadGetInfo_nid_postfix(PadInfo*);
|
||||
}
|
||||
|
||||
static void Require(bool value) { if (!value) std::abort(); }
|
||||
@@ -637,7 +638,14 @@ int main() {
|
||||
Require(scePadGetHandle(user, PAD_PORT_TYPE_SPECIAL, 0) == earlySpecial);
|
||||
Require(scePadClose_nid_postfix(earlySpecial) == PAD_OK);
|
||||
Require(scePadGetHandle(user, PAD_PORT_TYPE_SPECIAL, 0) == noHandle);
|
||||
PadSetPortConnected_nid_postfix(1, true);
|
||||
Require(scePadInit_nid_postfix() == 0);
|
||||
PadInfo initInfo{};
|
||||
Require(scePadGetInfo_nid_postfix(&initInfo) == PAD_OK);
|
||||
Require(initInfo.connectedCount[0] == 2);
|
||||
Require(initInfo.padTypeInfo[0].connectPort == 0);
|
||||
Require(initInfo.padTypeInfo[1].connectPort == 1);
|
||||
PadSetPortConnected_nid_postfix(1, false);
|
||||
Require(scePadUnknown_n3kSX62fgNo() == PAD_OK);
|
||||
const int handle = scePadOpen_nid_postfix(user, 0, 0, nullptr);
|
||||
Require(handle > 0);
|
||||
@@ -689,4 +697,42 @@ int main() {
|
||||
Require(scePadSetAngularVelocityDeadbandState(handle + 1, true) == PAD_ERROR_INVALID_HANDLE);
|
||||
Require(scePadSetAngularVelocityBiasCorrectionState(handle, false) == PAD_OK);
|
||||
Require(scePadSetAngularVelocityBiasCorrectionState(handle + 1, false) == PAD_ERROR_INVALID_HANDLE);
|
||||
PadInfo info{};
|
||||
Require(scePadGetInfo_nid_postfix(&info) == PAD_OK);
|
||||
Require(info.maxConnectCount == PAD_MAX_CONTROLLERS);
|
||||
Require(info.connectedCount[0] == 1);
|
||||
Require(info.padTypeInfo[0].connectPort == 0);
|
||||
|
||||
const int user2 = 0x10000001;
|
||||
Require(scePadGetHandle(user2, 0, 0) == noHandle);
|
||||
const int handle2 = scePadOpen_nid_postfix(user2, 0, 0, nullptr);
|
||||
Require(handle2 == PAD_HANDLE_BASE + 1);
|
||||
Require(scePadGetHandle(user2, 0, 0) == handle2);
|
||||
|
||||
PadInputState p2Input{};
|
||||
p2Input.buttons = static_cast<std::uint32_t>(Pad::PadButton::Triangle);
|
||||
PadPublishInputForPort_nid_postfix(1, p2Input);
|
||||
PadData p2Data{};
|
||||
Require(scePadReadState(handle2, &p2Data) == 0);
|
||||
Require((p2Data.buttons & static_cast<std::uint32_t>(Pad::PadButton::Triangle)) != 0);
|
||||
|
||||
Require(scePadSetVibrationMode(handle2, 1) == 0);
|
||||
std::uint32_t p2AccessState = 7;
|
||||
Require(scePadGetAccessibilityControllerState(handle2, &p2AccessState) == PAD_OK);
|
||||
Require(p2AccessState == 0);
|
||||
|
||||
PadSetPortConnected_nid_postfix(1, true);
|
||||
Require(scePadGetInfo_nid_postfix(&info) == PAD_OK);
|
||||
Require(info.connectedCount[0] == 2);
|
||||
Require(info.padTypeInfo[0].connectPort == 0);
|
||||
Require(info.padTypeInfo[1].connectPort == 1);
|
||||
PadSetPortConnected_nid_postfix(1, false);
|
||||
Require(scePadGetInfo_nid_postfix(&info) == PAD_OK);
|
||||
Require(info.connectedCount[0] == 1);
|
||||
Require(info.padTypeInfo[0].connectPort == 0);
|
||||
|
||||
Require(scePadClose_nid_postfix(handle2) == 0);
|
||||
Require(scePadGetHandle(user2, 0, 0) == noHandle);
|
||||
Require(scePadSetAngularVelocityDeadbandState(handle2, false) == PAD_OK);
|
||||
Require(scePadSetAngularVelocityDeadbandState(handle2 + 1, false) == PAD_ERROR_INVALID_HANDLE);
|
||||
}
|
||||
@@ -5,10 +5,13 @@
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
int APS5_VABI getpid_nid_postfix(void);
|
||||
int APS5_VABI getppid_nid_postfix(void);
|
||||
int APS5_VABI getsid_nid_postfix(int);
|
||||
int APS5_VABI setpgid_nid_postfix(int, int);
|
||||
int APS5_VABI setpgrp_nid_postfix(int, int);
|
||||
int* APS5_VABI __error_nid_postfix();
|
||||
}
|
||||
static void Require(bool value) { if (!value) std::abort(); }
|
||||
@@ -21,6 +24,72 @@ int main() {
|
||||
Require(*__error_nid_postfix() == 3);
|
||||
Require(getsid_nid_postfix(-1) == -1);
|
||||
Require(*__error_nid_postfix() == 3);
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(setpgid_nid_postfix(0, 0) == -1);
|
||||
Require(*__error_nid_postfix() == 1);
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(setpgid_nid_postfix(self, 0) == -1);
|
||||
Require(*__error_nid_postfix() == 1);
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(setpgid_nid_postfix(0, self) == -1);
|
||||
Require(*__error_nid_postfix() == 1);
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(setpgid_nid_postfix(self, self) == -1);
|
||||
Require(*__error_nid_postfix() == 1);
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(setpgid_nid_postfix(0, 123456) == -1);
|
||||
Require(*__error_nid_postfix() == 1);
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(setpgid_nid_postfix(self, 123456) == -1);
|
||||
Require(*__error_nid_postfix() == 1);
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(setpgid_nid_postfix(-1, -1) == -1);
|
||||
Require(*__error_nid_postfix() == 22);
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(setpgid_nid_postfix(0, -1) == -1);
|
||||
Require(*__error_nid_postfix() == 22);
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(setpgid_nid_postfix(self, -1) == -1);
|
||||
Require(*__error_nid_postfix() == 22);
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(setpgid_nid_postfix(-1, 0) == -1);
|
||||
Require(*__error_nid_postfix() == 3);
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(setpgid_nid_postfix(123456, 0) == -1);
|
||||
Require(*__error_nid_postfix() == 3);
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(setpgrp_nid_postfix(0, 0) == -1);
|
||||
Require(*__error_nid_postfix() == 1);
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(setpgrp_nid_postfix(self, 0) == -1);
|
||||
Require(*__error_nid_postfix() == 1);
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(setpgrp_nid_postfix(0, self) == -1);
|
||||
Require(*__error_nid_postfix() == 1);
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(setpgrp_nid_postfix(self, self) == -1);
|
||||
Require(*__error_nid_postfix() == 1);
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(setpgrp_nid_postfix(0, 123456) == -1);
|
||||
Require(*__error_nid_postfix() == 1);
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(setpgrp_nid_postfix(self, 123456) == -1);
|
||||
Require(*__error_nid_postfix() == 1);
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(setpgrp_nid_postfix(-1, -1) == -1);
|
||||
Require(*__error_nid_postfix() == 22);
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(setpgrp_nid_postfix(0, -1) == -1);
|
||||
Require(*__error_nid_postfix() == 22);
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(setpgrp_nid_postfix(self, -1) == -1);
|
||||
Require(*__error_nid_postfix() == 22);
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(setpgrp_nid_postfix(-1, 0) == -1);
|
||||
Require(*__error_nid_postfix() == 3);
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(setpgrp_nid_postfix(123456, 0) == -1);
|
||||
Require(*__error_nid_postfix() == 3);
|
||||
const int parent = getppid_nid_postfix();
|
||||
Require(parent > 0 && parent != getpid_nid_postfix());
|
||||
Require(getppid_nid_postfix() == parent);
|
||||
|
||||
@@ -36,6 +36,7 @@ int APS5_VABI scePthreadRwlockRdlock(PthreadRwlock* rwlock);
|
||||
int APS5_VABI scePthreadRwlockWrlock(PthreadRwlock* rwlock);
|
||||
int APS5_VABI scePthreadRwlockTryrdlock(PthreadRwlock* rwlock);
|
||||
int APS5_VABI scePthreadRwlockUnlock(PthreadRwlock* rwlock);
|
||||
int APS5_VABI scePthreadRwlockattrSettype(PthreadRwlockattr* attr, int type);
|
||||
}
|
||||
|
||||
using TimedLock = int (APS5_VABI *)(PthreadRwlock*, const KernelTimespec*);
|
||||
@@ -402,13 +403,29 @@ static void CheckAttrProcessSharing() {
|
||||
Require(pthread_rwlockattr_destroy_nid_postfix(&attr) == 0);
|
||||
}
|
||||
|
||||
static void CheckAttrSettype() {
|
||||
PthreadRwlockattr attr = nullptr;
|
||||
Require(pthread_rwlockattr_init_nid_postfix(&attr) == 0);
|
||||
for (const int type : {1, 2}) {
|
||||
Require(scePthreadRwlockattrSettype(&attr, type) == SCE_OK);
|
||||
PthreadRwlock rwlock = nullptr;
|
||||
Require(pthread_rwlock_init_nid_postfix(&rwlock, &attr) == 0);
|
||||
Require(pthread_rwlock_wrlock_nid_postfix(&rwlock) == 0 && pthread_rwlock_unlock_nid_postfix(&rwlock) == 0);
|
||||
Require(pthread_rwlock_destroy_nid_postfix(&rwlock) == 0);
|
||||
}
|
||||
for (const int type : {0, 3, -1, static_cast<int>(0x80000000u)}) Require(scePthreadRwlockattrSettype(&attr, type) == SCE_KERNEL_ERROR_EINVAL);
|
||||
Require(pthread_rwlockattr_destroy_nid_postfix(&attr) == 0);
|
||||
Require(scePthreadRwlockattrSettype(&attr, 1) == SCE_KERNEL_ERROR_EINVAL);
|
||||
Require(scePthreadRwlockattrSettype(nullptr, 1) == SCE_KERNEL_ERROR_EINVAL);
|
||||
}
|
||||
|
||||
static void CheckAttrType() {
|
||||
PthreadRwlockattr attr = nullptr;
|
||||
Require(pthread_rwlockattr_init_nid_postfix(&attr) == 0);
|
||||
int type = -1;
|
||||
Require(pthread_rwlockattr_gettype_np_nid_postfix(&attr, &type) == 0 && type == 0);
|
||||
Require(scePthreadRwlockattrGettype(&attr, &type) == SCE_OK && type == 0);
|
||||
for (const int set : {1, 2, 0}) {
|
||||
for (const int set : {1, 2}) {
|
||||
Require(scePthreadRwlockattrSettype(&attr, set) == SCE_OK);
|
||||
Require(pthread_rwlockattr_gettype_np_nid_postfix(&attr, &type) == 0 && type == set);
|
||||
Require(scePthreadRwlockattrGettype(&attr, &type) == SCE_OK && type == set);
|
||||
@@ -426,6 +443,7 @@ static void CheckAttrType() {
|
||||
}
|
||||
|
||||
int main() {
|
||||
CheckAttrSettype();
|
||||
CheckAttrProcessSharing();
|
||||
CheckAttrType();
|
||||
CheckWaitingWriterHoldsBackNewReaders();
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
#include "prx/libc/include/HostEntropy.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
|
||||
extern "C" {
|
||||
std::int64_t APS5_VABI random_nid_postfix();
|
||||
void APS5_VABI srandomdev_nid_postfix();
|
||||
char* APS5_VABI initstate_nid_postfix(std::uint64_t, char*, std::int64_t);
|
||||
char* APS5_VABI setstate_nid_postfix(char*);
|
||||
}
|
||||
|
||||
static void* entropyBuffer;
|
||||
static std::size_t entropySize;
|
||||
static bool failEntropy;
|
||||
|
||||
void HostEntropy_nid_no_patch(void* buffer, std::size_t size) {
|
||||
entropyBuffer = buffer;
|
||||
entropySize = size;
|
||||
if (failEntropy) throw std::runtime_error("entropy unavailable");
|
||||
auto* bytes = static_cast<unsigned char*>(buffer);
|
||||
for (std::size_t i = 0; i < size; ++i) bytes[i] = static_cast<unsigned char>(i * 37 + 11);
|
||||
}
|
||||
|
||||
static void Require(bool condition, const char* message) {
|
||||
if (!condition) {
|
||||
std::fprintf(stderr, "%s\n", message);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
static std::uint32_t Word(const char* buffer, std::size_t index) {
|
||||
std::uint32_t value;
|
||||
std::memcpy(&value, buffer + sizeof(value) * index, sizeof(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
int main() {
|
||||
constexpr std::array<std::size_t, 5> sizes {8, 32, 64, 128, 256};
|
||||
constexpr std::array<std::size_t, 5> degrees {1, 7, 15, 31, 63};
|
||||
constexpr std::array<std::size_t, 5> separations {0, 3, 1, 3, 1};
|
||||
std::array<char, 272> state{};
|
||||
for (std::size_t type = 0; type < sizes.size(); ++type) {
|
||||
state.fill(static_cast<char>(0xa5));
|
||||
char* table = state.data() + 1;
|
||||
char* previous = initstate_nid_postfix(42, table, static_cast<std::int64_t>(sizes[type]));
|
||||
for (int i = 0; i < 5; ++i) random_nid_postfix();
|
||||
const std::uint32_t header = Word(table, 0);
|
||||
srandomdev_nid_postfix();
|
||||
Require(entropyBuffer == table + 4, "srandomdev fills the active state after its header");
|
||||
Require(entropySize == degrees[type] * 4, "srandomdev fills every generator word");
|
||||
Require(Word(table, 0) == header, "srandomdev leaves the saved header untouched");
|
||||
for (std::size_t i = 0; i < entropySize; ++i)
|
||||
Require(static_cast<unsigned char>(table[4 + i]) == static_cast<unsigned char>(i * 37 + 11),
|
||||
"srandomdev neither derives words from one seed nor advances the generator");
|
||||
Require(state[0] == static_cast<char>(0xa5) &&
|
||||
std::all_of(state.begin() + 1 + sizes[type], state.end(), [](char c) { return c == static_cast<char>(0xa5); }),
|
||||
"srandomdev preserves the buffer guards");
|
||||
const auto seeded = state;
|
||||
if (type != 0) {
|
||||
const std::uint32_t sum = Word(table, 1) + Word(table, 1 + separations[type]);
|
||||
Require(random_nid_postfix() == sum / 2, "srandomdev resets the front and rear positions");
|
||||
Require(Word(table, 1 + separations[type]) == sum, "random advances the newly seeded table");
|
||||
} else {
|
||||
std::int64_t x = static_cast<std::int32_t>(Word(table, 1));
|
||||
if (x == 0) x = 123459876;
|
||||
x = 16807 * (x % 127773) - 2836 * (x / 127773);
|
||||
if (x < 0) x += 0x7fffffff;
|
||||
const auto expected = x;
|
||||
Require(random_nid_postfix() == static_cast<std::int64_t>(expected), "the linear generator uses the entropy word");
|
||||
}
|
||||
setstate_nid_postfix(previous);
|
||||
Require(Word(table, 0) == type + (type == 0 ? 0 : 5), "the reseeded state saves its new position");
|
||||
state = seeded;
|
||||
const std::uint32_t resetHeader = static_cast<std::uint32_t>(type);
|
||||
std::memcpy(table, &resetHeader, sizeof(resetHeader));
|
||||
setstate_nid_postfix(table);
|
||||
const auto beforeFailure = state;
|
||||
failEntropy = true;
|
||||
bool threw = false;
|
||||
try {
|
||||
srandomdev_nid_postfix();
|
||||
} catch (const std::runtime_error&) {
|
||||
threw = true;
|
||||
}
|
||||
failEntropy = false;
|
||||
Require(threw, "srandomdev propagates entropy failures");
|
||||
Require(state == beforeFailure, "an entropy failure before writing keeps the state");
|
||||
setstate_nid_postfix(previous);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
extern "C" {
|
||||
std::int64_t APS5_VABI random_nid_postfix();
|
||||
void APS5_VABI srandom_nid_postfix(std::uint64_t);
|
||||
void APS5_VABI srandomdev_nid_postfix();
|
||||
char* APS5_VABI initstate_nid_postfix(std::uint64_t, char*, std::int64_t);
|
||||
char* APS5_VABI setstate_nid_postfix(char*);
|
||||
int* APS5_VABI __error_nid_postfix();
|
||||
@@ -232,5 +233,25 @@ int main() {
|
||||
std::sort(concurrent.begin(), concurrent.end());
|
||||
Require(serial == concurrent, "concurrent calls must draw the serial sequence");
|
||||
|
||||
for (const std::int64_t size : {8, 32, 64, 128, 256}) {
|
||||
std::array<char, 256> entropyState{};
|
||||
char* previous = initstate_nid_postfix(42, entropyState.data(), size);
|
||||
srandomdev_nid_postfix();
|
||||
const std::size_t separation = size == 32 || size == 128 ? 3 : 1;
|
||||
std::int64_t expected;
|
||||
if (size == 8) {
|
||||
std::int64_t x = static_cast<std::int32_t>(Word(entropyState.data(), 1));
|
||||
if (x == 0) x = 123459876;
|
||||
x = 16807 * (x % 127773) - 2836 * (x / 127773);
|
||||
if (x < 0) x += 0x7fffffff;
|
||||
expected = x;
|
||||
} else {
|
||||
const std::uint32_t sum = Word(entropyState.data(), 1) + Word(entropyState.data(), 1 + separation);
|
||||
expected = sum >> 1;
|
||||
}
|
||||
Require(random_nid_postfix() == expected, "srandomdev seeds the shared library's active state from host entropy");
|
||||
setstate_nid_postfix(previous);
|
||||
}
|
||||
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
#include "SceTypes.hpp"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
|
||||
extern "C" {
|
||||
int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name);
|
||||
int APS5_VABI scePthreadJoin(Pthread thread, void** retval);
|
||||
Pthread APS5_VABI scePthreadSelf(void);
|
||||
int APS5_VABI scePthreadGetthreadid(void);
|
||||
int APS5_VABI scePthreadGetschedparam(Pthread thread, int* policy, KernelSchedParam* param);
|
||||
int APS5_VABI scePthreadAttrInit(PthreadAttr* attr);
|
||||
int APS5_VABI scePthreadAttrDestroy(PthreadAttr* attr);
|
||||
int APS5_VABI scePthreadAttrSetinheritsched(PthreadAttr* attr, int inheritSched);
|
||||
int APS5_VABI scePthreadAttrSetschedpolicy(PthreadAttr* attr, int policy);
|
||||
int APS5_VABI scePthreadAttrSetschedparam(PthreadAttr* attr, const KernelSchedParam* param);
|
||||
struct GuestRtPrio {
|
||||
std::uint16_t type;
|
||||
std::uint16_t prio;
|
||||
};
|
||||
int APS5_VABI rtprio_thread_nid_postfix(int function, int lwpid, GuestRtPrio* priority);
|
||||
int* APS5_VABI __error_nid_postfix();
|
||||
}
|
||||
|
||||
static constexpr int SCE_OK = 0;
|
||||
static constexpr int GUEST_SCHED_FIFO = 1;
|
||||
static constexpr int GUEST_SCHED_RR = 3;
|
||||
static constexpr int EXPLICIT_SCHED = 0;
|
||||
static constexpr int RTP_LOOKUP = 0;
|
||||
static constexpr int RTP_SET = 1;
|
||||
static constexpr std::uint16_t RTP_PRIO_REALTIME = 2;
|
||||
static constexpr std::uint16_t RTP_PRIO_FIFO = 10;
|
||||
static constexpr int GUEST_ESRCH = 3;
|
||||
static constexpr int GUEST_EFAULT = 14;
|
||||
|
||||
static void Require(bool value) { if (!value) std::abort(); }
|
||||
|
||||
struct Report {
|
||||
int policy;
|
||||
int priority;
|
||||
std::atomic<int> tid{0};
|
||||
GuestRtPrio lookup{7, 7};
|
||||
int lookupResult = -1;
|
||||
std::atomic<bool> release{false};
|
||||
};
|
||||
|
||||
static void* APS5_VABI Entry(void* arg) {
|
||||
auto* report = static_cast<Report*>(arg);
|
||||
report->lookupResult = rtprio_thread_nid_postfix(RTP_LOOKUP, 0, &report->lookup);
|
||||
report->tid.store(scePthreadGetthreadid(), std::memory_order_release);
|
||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10);
|
||||
while (!report->release.load(std::memory_order_acquire)) {
|
||||
Require(std::chrono::steady_clock::now() < deadline);
|
||||
std::this_thread::yield();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static int LookupError(int function, int lwpid, GuestRtPrio* priority) {
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(rtprio_thread_nid_postfix(function, lwpid, priority) == -1);
|
||||
return *__error_nid_postfix();
|
||||
}
|
||||
|
||||
static void CheckThread(int policy, int priority, std::uint16_t expectedType) {
|
||||
Report report{policy, priority};
|
||||
PthreadAttr attr = nullptr;
|
||||
KernelSchedParam param{priority};
|
||||
Require(scePthreadAttrInit(&attr) == SCE_OK);
|
||||
Require(scePthreadAttrSetinheritsched(&attr, EXPLICIT_SCHED) == SCE_OK);
|
||||
Require(scePthreadAttrSetschedpolicy(&attr, policy) == SCE_OK);
|
||||
Require(scePthreadAttrSetschedparam(&attr, ¶m) == SCE_OK);
|
||||
Pthread thread = nullptr;
|
||||
Require(scePthreadCreate(&thread, &attr, Entry, &report, "rtprio") == SCE_OK);
|
||||
Require(scePthreadAttrDestroy(&attr) == SCE_OK);
|
||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10);
|
||||
while (report.tid.load(std::memory_order_acquire) == 0) {
|
||||
Require(std::chrono::steady_clock::now() < deadline);
|
||||
std::this_thread::yield();
|
||||
}
|
||||
Require(report.lookupResult == 0 && report.lookup.type == expectedType && report.lookup.prio == priority);
|
||||
GuestRtPrio fromCreator{7, 7};
|
||||
Require(rtprio_thread_nid_postfix(RTP_LOOKUP, report.tid.load(), &fromCreator) == 0);
|
||||
Require(fromCreator.type == expectedType && fromCreator.prio == priority);
|
||||
bool threw = false;
|
||||
try { rtprio_thread_nid_postfix(RTP_SET, report.tid.load(), &fromCreator); } catch (const std::exception&) { threw = true; }
|
||||
Require(threw);
|
||||
report.release.store(true, std::memory_order_release);
|
||||
Require(scePthreadJoin(thread, nullptr) == SCE_OK);
|
||||
Require(LookupError(RTP_LOOKUP, report.tid.load(), &fromCreator) == GUEST_ESRCH);
|
||||
}
|
||||
|
||||
int main() {
|
||||
int policy = -1;
|
||||
KernelSchedParam param{};
|
||||
Require(scePthreadGetschedparam(scePthreadSelf(), &policy, ¶m) == SCE_OK);
|
||||
Require(policy == GUEST_SCHED_FIFO || policy == GUEST_SCHED_RR);
|
||||
GuestRtPrio self{7, 7};
|
||||
Require(rtprio_thread_nid_postfix(RTP_LOOKUP, 0, &self) == 0);
|
||||
Require(self.type == (policy == GUEST_SCHED_FIFO ? RTP_PRIO_FIFO : RTP_PRIO_REALTIME) && self.prio == param.sched_priority);
|
||||
GuestRtPrio byId{7, 7};
|
||||
Require(rtprio_thread_nid_postfix(RTP_LOOKUP, scePthreadGetthreadid(), &byId) == 0);
|
||||
Require(byId.type == self.type && byId.prio == self.prio);
|
||||
CheckThread(GUEST_SCHED_FIFO, 512, RTP_PRIO_FIFO);
|
||||
CheckThread(GUEST_SCHED_RR, 600, RTP_PRIO_REALTIME);
|
||||
CheckThread(GUEST_SCHED_FIFO, 256, RTP_PRIO_FIFO);
|
||||
Require(LookupError(2, 0, &self) == GUEST_ESRCH);
|
||||
Require(LookupError(RTP_LOOKUP, 0, nullptr) == GUEST_EFAULT);
|
||||
Require(LookupError(RTP_LOOKUP, 1, &self) == GUEST_ESRCH);
|
||||
Require(LookupError(RTP_LOOKUP, 999999, &self) == GUEST_ESRCH);
|
||||
bool threw = false;
|
||||
try { rtprio_thread_nid_postfix(RTP_SET, 0, &self); } catch (const std::exception&) { threw = true; }
|
||||
Require(threw);
|
||||
}
|
||||
@@ -47,6 +47,7 @@ int APS5_VABI sceNetListen(int, int);
|
||||
int APS5_VABI sceNetGetsockname(int, void*, std::uint32_t*);
|
||||
int APS5_VABI sceNetGetpeername(int, void*, std::uint32_t*);
|
||||
int APS5_VABI sceNetGetSockInfo(int, void*, int, int);
|
||||
int APS5_VABI sceNetGetSockInfo6(int, void*, int, int);
|
||||
int APS5_VABI sceNetConnect(int, const void*, std::uint32_t);
|
||||
int APS5_VABI sceNetAccept(int, void*, std::uint32_t*);
|
||||
std::int64_t APS5_VABI sceNetSend(int, const void*, std::size_t, int);
|
||||
@@ -90,6 +91,10 @@ int APS5_VABI sceNetCtlRegisterCallback(NetCtlCallback, void*, int*);
|
||||
int APS5_VABI sceNetCtlUnregisterCallback(int);
|
||||
int APS5_VABI sceNetCtlRegisterCallbackV6(NetCtlCallback, void*, int*);
|
||||
int APS5_VABI sceNetCtlUnregisterCallbackV6(int);
|
||||
int APS5_VABI sceNetCtlCheckCallback(void);
|
||||
int APS5_VABI sceNetCtlRegisterCallbackForNpToolkit(NetCtlCallback, void*, int*);
|
||||
int APS5_VABI sceNetCtlUnregisterCallbackForNpToolkit(int);
|
||||
int APS5_VABI sceNetCtlCheckCallbackForNpToolkit(void);
|
||||
std::uint64_t NetCtlReadByteCounter_nid_no_patch(const char*);
|
||||
int APS5_VABI select_nid_postfix(int, void*, void*, void*, const void*);
|
||||
int APS5_VABI open_nid_postfix(const char*, int, int);
|
||||
@@ -170,6 +175,19 @@ public:
|
||||
|
||||
static void APS5_VABI IgnoreNetCtlEvent(int, void*) {}
|
||||
|
||||
struct NetCtlEventRecord {
|
||||
int calls = 0;
|
||||
int event = 0;
|
||||
void* arg = nullptr;
|
||||
};
|
||||
|
||||
static void APS5_VABI RecordNetCtlEvent(int event, void* arg) {
|
||||
auto* record = static_cast<NetCtlEventRecord*>(arg);
|
||||
++record->calls;
|
||||
record->event = event;
|
||||
record->arg = arg;
|
||||
}
|
||||
|
||||
static bool Failed(std::int64_t result, int error) {
|
||||
return result == static_cast<int>(0x80410100u | static_cast<unsigned>(error)) && *sceNetErrnoLoc() == error;
|
||||
}
|
||||
@@ -1631,6 +1649,185 @@ static void CheckSockInfo() {
|
||||
Require(RequireSockInfoList(0) == earlier);
|
||||
}
|
||||
|
||||
static std::array<std::uint8_t, 0xa0> RequireSockInfo6(int socket, int type, bool ipv6, bool nonblock, const std::array<std::uint8_t, 28>& local,
|
||||
const std::array<std::uint8_t, 28>& remote, int requestFlags) {
|
||||
std::array<std::uint8_t, 0x140> records;
|
||||
records.fill(0xa5);
|
||||
Require(sceNetGetSockInfo6(socket, records.data(), 2, requestFlags) == 1);
|
||||
std::array<std::uint8_t, 0xa0> expected{};
|
||||
std::memcpy(expected.data() + 0x24, &socket, sizeof(socket));
|
||||
expected[0x28] = static_cast<std::uint8_t>(type);
|
||||
if (ipv6) {
|
||||
std::memcpy(expected.data() + 0x6c, local.data() + 8, 16);
|
||||
std::memcpy(expected.data() + 0x7c, remote.data() + 8, 16);
|
||||
} else {
|
||||
std::memcpy(expected.data() + 0x34, local.data() + 4, 4);
|
||||
std::memcpy(expected.data() + 0x38, remote.data() + 4, 4);
|
||||
}
|
||||
std::memcpy(expected.data() + 0x3c, local.data() + 2, 2);
|
||||
std::memcpy(expected.data() + 0x3e, remote.data() + 2, 2);
|
||||
const int flags = 1 | (nonblock ? 0x200000 : 0) | (ipv6 ? 0x400000 : 0);
|
||||
std::memcpy(expected.data() + 0x48, &flags, sizeof(flags));
|
||||
for (const auto [offset, option] : {std::array{0x64, 0x1002}, std::array{0x68, 0x1001}}) {
|
||||
int size = 0;
|
||||
std::uint32_t length = sizeof(size);
|
||||
Require(sceNetGetsockopt(socket, 0xffff, option, &size, &length) == 0 && size > 0);
|
||||
std::memcpy(expected.data() + offset, &size, sizeof(size));
|
||||
}
|
||||
Require(std::equal(expected.begin(), expected.end(), records.begin()));
|
||||
Require(std::all_of(records.begin() + 0xa0, records.end(), [](auto byte) { return byte == 0xa5; }));
|
||||
return expected;
|
||||
}
|
||||
|
||||
static std::vector<int> RequireSockInfo6List(int flags) {
|
||||
const int count = sceNetGetSockInfo6(-1, nullptr, 0, flags);
|
||||
Require(count >= 0);
|
||||
std::vector<std::uint8_t> records(static_cast<std::size_t>(count + 1) * 0xa0, 0xa5);
|
||||
Require(sceNetGetSockInfo6(-1, records.data(), count + 1, flags) == count);
|
||||
Require(std::all_of(records.end() - 0xa0, records.end(), [](auto byte) { return byte == 0xa5; }));
|
||||
std::vector<std::uint8_t> unbounded(records.size(), 0xa5);
|
||||
Require(sceNetGetSockInfo6(-1, unbounded.data(), INT32_MAX, flags) == count && unbounded == records);
|
||||
std::vector<int> sockets;
|
||||
for (int index = 0; index < count; ++index) {
|
||||
const auto listed = records.begin() + index * 0xa0;
|
||||
int socket = 0;
|
||||
int status = 0;
|
||||
std::memcpy(&socket, &listed[0x24], sizeof(socket));
|
||||
std::memcpy(&status, &listed[0x48], sizeof(status));
|
||||
Require(sockets.empty() || socket > sockets.back());
|
||||
Require((flags & 0x1000) != 0 || (status & 0x400000) != 0);
|
||||
std::array<std::uint8_t, 0xa0> single;
|
||||
Require(sceNetGetSockInfo6(socket, single.data(), 1, flags) == 1 && std::equal(single.begin(), single.end(), listed));
|
||||
sockets.push_back(socket);
|
||||
}
|
||||
for (const int limit : {1, 2}) {
|
||||
const int written = std::min(count, limit);
|
||||
std::vector<std::uint8_t> limited(records.size(), 0xa5);
|
||||
Require(sceNetGetSockInfo6(-1, limited.data(), limit, flags) == written);
|
||||
Require(std::equal(limited.begin(), limited.begin() + written * 0xa0, records.begin()));
|
||||
Require(std::all_of(limited.begin() + written * 0xa0, limited.end(), [](auto byte) { return byte == 0xa5; }));
|
||||
}
|
||||
return sockets;
|
||||
}
|
||||
|
||||
static std::vector<int> WithSockets(std::vector<int> sockets, std::initializer_list<int> added) {
|
||||
sockets.insert(sockets.end(), added);
|
||||
std::sort(sockets.begin(), sockets.end());
|
||||
return sockets;
|
||||
}
|
||||
|
||||
static void CheckSockInfo6() {
|
||||
const auto earlier = RequireSockInfo6List(0);
|
||||
const auto earlierAll = RequireSockInfo6List(0x1000);
|
||||
Require(RequireSockInfo6List(0x40) == earlier && RequireSockInfo6List(0x1040) == earlierAll);
|
||||
Require(std::includes(earlierAll.begin(), earlierAll.end(), earlier.begin(), earlier.end()));
|
||||
|
||||
const std::array<std::uint8_t, 28> unnamed{};
|
||||
const int datagram = sceNetSocket("sockinfo6-inet", 2, 2, 17);
|
||||
Require(datagram >= 0);
|
||||
std::array<std::uint8_t, 28> inetAddress{16, 2, 0, 0, 127, 0, 0, 1};
|
||||
Require(sceNetBind_nid_postfix(datagram, inetAddress.data(), 16) == 0);
|
||||
std::uint32_t size = 16;
|
||||
Require(sceNetGetsockname(datagram, inetAddress.data(), &size) == 0 && size == 16);
|
||||
const int nonblocking = 1;
|
||||
Require(sceNetSetsockopt(datagram, 0xffff, 0x1200, &nonblocking, sizeof(nonblocking)) == 0);
|
||||
RequireSockInfo6(datagram, 2, false, true, inetAddress, unnamed, 0x1000);
|
||||
RequireSockInfo6(datagram, 2, false, true, inetAddress, unnamed, 0x1040);
|
||||
|
||||
std::array<std::uint8_t, 0xa0> record;
|
||||
record.fill(0xa5);
|
||||
const auto untouched = [&record] { return std::all_of(record.begin(), record.end(), [](auto byte) { return byte == 0xa5; }); };
|
||||
Require(Failed(sceNetGetSockInfo6(datagram, nullptr, 1, 0x1000), 22));
|
||||
Require(Failed(sceNetGetSockInfo6(datagram, record.data(), 0, 0x1000), 22));
|
||||
Require(Failed(sceNetGetSockInfo6(datagram, record.data(), -1, 0x1000), 22));
|
||||
Require(Failed(sceNetGetSockInfo6(-1, record.data(), 0, 0), 22));
|
||||
Require(Failed(sceNetGetSockInfo6(-1, record.data(), -5, 0x1000), 22));
|
||||
for (const int flags : {1, 0x41, 0x80, 0x1001, 0x2000, 0x400000, 0x1048}) {
|
||||
Require(Failed(sceNetGetSockInfo6(datagram, record.data(), 1, flags), 22));
|
||||
Require(Failed(sceNetGetSockInfo6(-1, nullptr, 0, flags), 22));
|
||||
Require(Failed(sceNetGetSockInfo6(-1, record.data(), 1, flags), 22));
|
||||
}
|
||||
Require(Failed(sceNetGetSockInfo6(datagram, record.data(), 1, 0), 47));
|
||||
Require(Failed(sceNetGetSockInfo6(datagram, record.data(), 1, 0x40), 47));
|
||||
Require(untouched());
|
||||
const int closed = sceNetSocket("sockinfo6-closed", 2, 2, 17);
|
||||
Require(closed >= 0 && sceNetSocketClose(closed) == 0);
|
||||
for (const int flags : {0, 0x40, 0x1000, 0x1040}) {
|
||||
Require(Failed(sceNetGetSockInfo6(closed, record.data(), 1, flags), 9));
|
||||
Require(Failed(sceNetGetSockInfo6(INT32_MAX, record.data(), 1, flags), 9));
|
||||
}
|
||||
Require(untouched());
|
||||
Require(RequireSockInfo6List(0) == earlier);
|
||||
Require(RequireSockInfo6List(0x1000) == WithSockets(earlierAll, {datagram}));
|
||||
|
||||
if (ipv6Unavailable) {
|
||||
Require(sceNetSocketClose(datagram) == 0);
|
||||
Require(Failed(sceNetGetSockInfo6(datagram, record.data(), 1, 0x1000), 9));
|
||||
Require(RequireSockInfo6List(0x1000) == earlierAll);
|
||||
return;
|
||||
}
|
||||
|
||||
const int udp = sceNetSocket("sockinfo6-datagram", 28, 2, 17);
|
||||
Require(udp >= 0);
|
||||
RequireSockInfo6(udp, 2, true, false, unnamed, unnamed, 0);
|
||||
std::array<std::uint8_t, 28> udpAddress{28, 28};
|
||||
udpAddress[23] = 1;
|
||||
Require(sceNetBind_nid_postfix(udp, udpAddress.data(), udpAddress.size()) == 0);
|
||||
size = udpAddress.size();
|
||||
Require(sceNetGetsockname(udp, udpAddress.data(), &size) == 0 && size == udpAddress.size());
|
||||
Require(udpAddress[2] != 0 || udpAddress[3] != 0);
|
||||
RequireSockInfo6(udp, 2, true, false, udpAddress, unnamed, 0);
|
||||
RequireSockInfo6(udp, 2, true, false, udpAddress, unnamed, 0x40);
|
||||
RequireSockInfo6(udp, 2, true, false, udpAddress, unnamed, 0x1000);
|
||||
RequireSockInfo6(udp, 2, true, false, udpAddress, unnamed, 0x1040);
|
||||
|
||||
const int listener = sceNetSocket("sockinfo6-listener", 28, 1, 6);
|
||||
std::array<std::uint8_t, 28> listening{28, 28};
|
||||
listening[23] = 1;
|
||||
Require(listener >= 0 && sceNetBind_nid_postfix(listener, listening.data(), listening.size()) == 0 && sceNetListen(listener, 1) == 0);
|
||||
size = listening.size();
|
||||
Require(sceNetGetsockname(listener, listening.data(), &size) == 0 && size == listening.size());
|
||||
RequireSockInfo6(listener, 1, true, false, listening, unnamed, 0);
|
||||
const int client = sceNetSocket("sockinfo6-client", 28, 1, 6);
|
||||
Require(client >= 0 && sceNetConnect(client, listening.data(), listening.size()) == 0);
|
||||
const int accepted = sceNetAccept(listener, nullptr, nullptr);
|
||||
Require(accepted >= 0);
|
||||
std::array<std::uint8_t, 28> clientAddress{};
|
||||
size = clientAddress.size();
|
||||
Require(sceNetGetsockname(client, clientAddress.data(), &size) == 0 && size == clientAddress.size());
|
||||
Require(clientAddress[2] != 0 || clientAddress[3] != 0);
|
||||
RequireSockInfo6(client, 1, true, false, clientAddress, listening, 0);
|
||||
RequireSockInfo6(accepted, 1, true, false, listening, clientAddress, 0);
|
||||
|
||||
const int peer = sceNetSocket("sockinfo6-peer", 28, 2, 17);
|
||||
std::array<std::uint8_t, 28> peerAddress{28, 28};
|
||||
peerAddress[23] = 1;
|
||||
Require(peer >= 0 && sceNetBind_nid_postfix(peer, peerAddress.data(), peerAddress.size()) == 0);
|
||||
size = peerAddress.size();
|
||||
Require(sceNetGetsockname(peer, peerAddress.data(), &size) == 0 && size == peerAddress.size());
|
||||
Require(sceNetConnect(peer, udpAddress.data(), udpAddress.size()) == 0);
|
||||
RequireSockInfo6(peer, 2, true, false, peerAddress, udpAddress, 0);
|
||||
const std::array<int, 2> buffers{65536, 32768};
|
||||
Require(sceNetSetsockopt(peer, 0xffff, 0x1200, &nonblocking, sizeof(nonblocking)) == 0);
|
||||
Require(sceNetSetsockopt(peer, 0xffff, 0x1002, &buffers[0], sizeof(buffers[0])) == 0);
|
||||
Require(sceNetSetsockopt(peer, 0xffff, 0x1001, &buffers[1], sizeof(buffers[1])) == 0);
|
||||
const auto configured = RequireSockInfo6(peer, 2, true, true, peerAddress, udpAddress, 0);
|
||||
Require(std::memcmp(configured.data() + 0x64, buffers.data(), sizeof(buffers)) == 0);
|
||||
Require(std::all_of(configured.begin() + 0x34, configured.begin() + 0x3c, [](auto byte) { return byte == 0; }));
|
||||
Require(sceNetGetSockInfo6(peer, record.data(), 1, 0x1040) == 1 && std::equal(configured.begin(), configured.end(), record.begin()));
|
||||
|
||||
Require(RequireSockInfo6List(0) == WithSockets(earlier, {udp, listener, client, accepted, peer}));
|
||||
Require(RequireSockInfo6List(0x40) == WithSockets(earlier, {udp, listener, client, accepted, peer}));
|
||||
Require(RequireSockInfo6List(0x1000) == WithSockets(earlierAll, {datagram, udp, listener, client, accepted, peer}));
|
||||
Require(Failed(sceNetGetSockInfo6(datagram, record.data(), 1, 0), 47));
|
||||
|
||||
for (const int descriptor : {peer, accepted, client, listener, udp}) Require(sceNetSocketClose(descriptor) == 0);
|
||||
Require(Failed(sceNetGetSockInfo6(udp, record.data(), 1, 0), 9));
|
||||
Require(RequireSockInfo6List(0) == earlier);
|
||||
Require(sceNetSocketClose(datagram) == 0);
|
||||
Require(RequireSockInfo6List(0x1000) == earlierAll);
|
||||
}
|
||||
|
||||
static void CheckDatagramDisconnect() {
|
||||
const int peer = sceNetSocket("disconnect-peer", 2, 2, 17);
|
||||
const int other = sceNetSocket("disconnect-other", 2, 2, 17);
|
||||
@@ -1730,6 +1927,7 @@ int main() {
|
||||
CheckDatagramDisconnect();
|
||||
CheckCloseKernelSocket();
|
||||
CheckSockInfo();
|
||||
CheckSockInfo6();
|
||||
|
||||
const int closed = sceNetSocket("closed-by-kernel", 2, 2, 17);
|
||||
Require(closed >= 0);
|
||||
@@ -2219,6 +2417,46 @@ int main() {
|
||||
}
|
||||
Require(sceNetCtlUnregisterCallbackV6(0) == 0);
|
||||
}
|
||||
{
|
||||
int cid = -1;
|
||||
Require(sceNetCtlRegisterCallbackForNpToolkit(nullptr, nullptr, &cid) == static_cast<int>(0x80412107));
|
||||
Require(sceNetCtlRegisterCallbackForNpToolkit(IgnoreNetCtlEvent, nullptr, nullptr) == static_cast<int>(0x80412107));
|
||||
int cids[8] = {};
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
Require(sceNetCtlRegisterCallbackForNpToolkit(IgnoreNetCtlEvent, nullptr, &cids[i]) == 0);
|
||||
Require(cids[i] == i);
|
||||
}
|
||||
Require(sceNetCtlRegisterCallbackForNpToolkit(IgnoreNetCtlEvent, nullptr, &cid) == static_cast<int>(0x80412103));
|
||||
Require(sceNetCtlRegisterCallback(IgnoreNetCtlEvent, nullptr, &cid) == 0 && cid == 0);
|
||||
Require(sceNetCtlUnregisterCallback(0) == 0);
|
||||
Require(sceNetCtlUnregisterCallbackForNpToolkit(-1) == static_cast<int>(0x80412105));
|
||||
Require(sceNetCtlUnregisterCallbackForNpToolkit(8) == static_cast<int>(0x80412105));
|
||||
Require(sceNetCtlUnregisterCallbackForNpToolkit(3) == 0);
|
||||
Require(sceNetCtlRegisterCallbackForNpToolkit(IgnoreNetCtlEvent, nullptr, &cid) == 0);
|
||||
Require(cid == 3);
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
Require(sceNetCtlUnregisterCallbackForNpToolkit(i) == 0);
|
||||
}
|
||||
|
||||
NetCtlEventRecord toolkit;
|
||||
NetCtlEventRecord plain;
|
||||
Require(sceNetCtlRegisterCallbackForNpToolkit(RecordNetCtlEvent, &toolkit, &cid) == 0 && cid == 0);
|
||||
Require(sceNetCtlCheckCallback() == 0 && toolkit.calls == 0);
|
||||
Require(sceNetCtlCheckCallbackForNpToolkit() == 0);
|
||||
Require(toolkit.calls == 1 && toolkit.arg == &toolkit && (toolkit.event == 1 || toolkit.event == 3));
|
||||
int current = -1;
|
||||
Require(sceNetCtlGetState(¤t) == 0 && toolkit.event == (current == 3 ? 3 : 1));
|
||||
Require(sceNetCtlCheckCallbackForNpToolkit() == 0 && toolkit.calls == 1);
|
||||
int plainCid = -1;
|
||||
Require(sceNetCtlRegisterCallback(RecordNetCtlEvent, &plain, &plainCid) == 0);
|
||||
Require(sceNetCtlCheckCallbackForNpToolkit() == 0 && plain.calls == 0);
|
||||
Require(sceNetCtlCheckCallback() == 0 && plain.calls == 1 && toolkit.calls == 1);
|
||||
Require(sceNetCtlUnregisterCallback(plainCid) == 0);
|
||||
Require(sceNetCtlUnregisterCallbackForNpToolkit(cid) == 0);
|
||||
Require(sceNetCtlRegisterCallbackForNpToolkit(RecordNetCtlEvent, &toolkit, &cid) == 0);
|
||||
Require(sceNetCtlCheckCallbackForNpToolkit() == 0 && toolkit.calls == 2);
|
||||
Require(sceNetCtlUnregisterCallbackForNpToolkit(cid) == 0);
|
||||
}
|
||||
if (ipv6Unavailable) {
|
||||
std::puts("skipped the IPv6 socket checks, the host has no usable IPv6 loopback");
|
||||
return 77;
|
||||
|
||||
@@ -100,14 +100,12 @@ int main() {
|
||||
Require(setpriority_nid_postfix(PrioProcess, 0, -99) == 0 && getpriority_nid_postfix(PrioProcess, 0) == -20);
|
||||
Require(setpriority_nid_postfix(PrioProcess, 0, 0) == 0 && getpriority_nid_postfix(PrioProcess, 0) == 0);
|
||||
GuestRtPrio rtprio{7, 7};
|
||||
Require(rtprio_thread_nid_postfix(0, 0, &rtprio) == 0 && rtprio.type == 10 && rtprio.prio == 700);
|
||||
bool rtprioThrew = false;
|
||||
try { rtprio_thread_nid_postfix(0, 0, &rtprio); } catch (const std::exception&) { rtprioThrew = true; }
|
||||
Require(rtprioThrew);
|
||||
rtprioThrew = false;
|
||||
try { rtprio_thread_nid_postfix(1, 0, &rtprio); } catch (const std::exception&) { rtprioThrew = true; }
|
||||
Require(rtprioThrew);
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(rtprio_thread_nid_postfix(2, 0, &rtprio) == -1 && *__error_nid_postfix() == 22);
|
||||
Require(rtprio_thread_nid_postfix(2, 0, &rtprio) == -1 && *__error_nid_postfix() == 3);
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(rtprio_thread_nid_postfix(0, 0, nullptr) == -1 && *__error_nid_postfix() == 14);
|
||||
*__error_nid_postfix() = 0;
|
||||
|
||||
Loaded 100 of 147 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user