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:
orezraeyandClaude Opus 5.5 committed 2026-10-24 15:30:45 -03:00
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;
}
}
-20
View File
@@ -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;
+5 -1
View File
@@ -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();
}
}
+7 -1
View File
@@ -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)
+148 -18
View File
@@ -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);
}
}
+15 -13
View File
@@ -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) {
+75 -33
View File
@@ -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;
}
+2
View File
@@ -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;
+16 -3
View File
@@ -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
+227 -133
View File
@@ -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);
}
+104 -26
View File
@@ -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
+8 -2
View File
@@ -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
+167 -82
View File
@@ -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);
}
}
+88 -2
View File
@@ -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;
}
+9
View File
@@ -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)
+3
View File
@@ -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) + "\"");
}
+102 -22
View File
@@ -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) {
+118
View File
@@ -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.
+40
View File
@@ -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);
}
}
+11
View File
@@ -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");
+23
View File
@@ -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()
+108 -3
View File
@@ -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);
+3 -1
View File
@@ -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);
+549 -14
View File
@@ -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) {
+14 -2
View File
@@ -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);
+6 -2
View File
@@ -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;
}
+18
View File
@@ -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();
}
+2
View File
@@ -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
+53
View File
@@ -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;
}
+72
View File
@@ -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();
+625
View File
@@ -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;
}
}
+6
View File
@@ -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);
+86
View File
@@ -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;
}
+46
View File
@@ -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);
}
+69
View File
@@ -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);
+19 -1
View File
@@ -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();
+97
View File
@@ -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);
}
}
+21
View File
@@ -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;
}
+117
View File
@@ -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, &param) == 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, &param) == 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);
}
+238
View File
@@ -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(&current) == 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;
+2 -4
View File
@@ -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