Merge branch 'main' into windows/libc-tanh-msun

This commit is contained in:
Dean Galvin committed 2026-10-26 18:37:39 +13:00
commit 42a75519e3
83 files changed
+3064 -246

No files matched your search

+9
View File
@@ -269,6 +269,15 @@ if(BUILD_TESTING)
set_tests_properties(guest_kernel_sys_sdk_version PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel_sys>")
endif()
add_test_executable(guest_kernel_sys_console_identity_tests tests/GuestKernelSysConsoleIdentity.cpp)
target_include_directories(guest_kernel_sys_console_identity_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_kernel_sys_console_identity_tests PRIVATE libkernel_sys libkernel libc)
configure_windows_unwind(guest_kernel_sys_console_identity_tests)
add_test(NAME guest_kernel_sys_console_identity COMMAND guest_kernel_sys_console_identity_tests)
if(WIN32)
set_tests_properties(guest_kernel_sys_console_identity PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel_sys>")
endif()
add_test_executable(nid_buffer_validation_tests nid/tests/BufferValidation.cpp)
target_include_directories(nid_buffer_validation_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/nid/include)
target_compile_features(nid_buffer_validation_tests PRIVATE cxx_std_20)
+5 -1
View File
@@ -1587,8 +1587,12 @@ if(BUILD_TESTING)
set_tests_properties(agc_pipeline_library_off PROPERTIES ENVIRONMENT "APS5_NO_GPL=1")
add_test(NAME agc_pipeline_library_fast_only COMMAND agc_driver_recorder_tests --pipeline-library-only)
set_tests_properties(agc_pipeline_library_fast_only PROPERTIES ENVIRONMENT "APS5_NO_PIPELINE_LTO=1" SKIP_RETURN_CODE 77)
add_test(NAME agc_pipeline_library_lto_info COMMAND agc_driver_recorder_tests --pipeline-library-lto-info-only)
add_test(NAME agc_pipeline_library_lto_info_fast_only COMMAND agc_driver_recorder_tests --pipeline-library-lto-info-only)
set_tests_properties(agc_pipeline_library_lto_info agc_pipeline_library_lto_info_fast_only PROPERTIES SKIP_RETURN_CODE 77)
set_tests_properties(agc_pipeline_library_lto_info_fast_only PROPERTIES ENVIRONMENT "APS5_NO_PIPELINE_LTO=1")
if(WIN32)
set_tests_properties(agc_pipeline_library agc_pipeline_library_off agc_pipeline_library_fast_only PROPERTIES ENVIRONMENT_MODIFICATION
set_tests_properties(agc_pipeline_library agc_pipeline_library_off agc_pipeline_library_fast_only agc_pipeline_library_lto_info agc_pipeline_library_lto_info_fast_only PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
set_tests_properties(agc_pipeline_cache agc_pipeline_cache_file PROPERTIES SKIP_RETURN_CODE 77)
@@ -33,6 +33,8 @@ extern "C" void AgcDriverSuspendPoint_nid_postfix();
extern "C" void AgcDriverSerialProgress_nid_postfix(std::uint64_t* accepted, std::uint64_t* completed);
extern "C" std::size_t AgcDriverAwaitedWaits_nid_postfix(AgcDriver::AwaitedWait* waits, std::size_t capacity);
extern "C" std::uint64_t AgcDriverDispatchesAdoptedAhead_nid_postfix();
extern "C" std::uint64_t AgcDriverDispatchesRejectedAhead_nid_postfix();
extern "C" void AgcDriverSetAheadAdoptHook_nid_postfix(void (*hook)());
extern "C" void AgcDriverRegisterShader_nid_postfix(const Shader* shader);
extern "C" void AgcDriverResolveShaderAbi_nid_postfix(const Shader* shader, std::span<const ShaderRegister> context, std::span<const ShaderRegister> primitive);
extern "C" void AgcDriverResolveGraphicsStagesAbi_nid_postfix(std::span<const Shader* const> stages, std::span<const ShaderRegister> context, std::span<const ShaderRegister> primitive);
@@ -59,6 +59,8 @@ public:
void Shutdown();
void WaitIdle();
std::uint64_t DispatchesAdoptedAhead() const;
std::uint64_t DispatchesRejectedAhead() const;
void SetAheadAdoptHook(void (*hook)());
void CheckFailure();
void ReportFailure(std::exception_ptr error);
void Submit(const Packet* packet, std::uint32_t queue, bool cpuFlip = false);
@@ -317,7 +319,7 @@ private:
std::chrono::steady_clock::time_point resolvedAt{};
};
struct GroupCaptureStats {
std::uint64_t runs = 0, resolved = 0, resolvedHits = 0, adopted = 0, addressMismatches = 0, keyMismatches = 0, failures = 0, stale = 0, labelOverlaps = 0, writtenBefore = 0, deviceRejects = 0, argumentMismatches = 0;
std::uint64_t runs = 0, resolved = 0, resolvedHits = 0, adopted = 0, addressMismatches = 0, keyMismatches = 0, memoryMismatches = 0, failures = 0, stale = 0, labelOverlaps = 0, writtenBefore = 0, deviceRejects = 0, argumentMismatches = 0;
double resolveMs = 0, resolveMaxMs = 0, ageMs = 0, ageMaxMs = 0;
std::chrono::steady_clock::time_point reported = std::chrono::steady_clock::now();
};
@@ -474,6 +476,8 @@ private:
std::atomic<std::uint64_t> evidenceReads{0};
std::atomic<std::uint64_t> evidenceValidations{0};
std::atomic<std::uint64_t> dispatchesAdoptedAhead{0};
std::atomic<std::uint64_t> dispatchesRejectedAhead{0};
std::atomic<void (*)()> aheadAdoptHook{nullptr};
AgcDriver::Mutex writtenBuffersMutex;
std::deque<WrittenBuffer> writtenBuffers;
@@ -275,8 +275,8 @@ public:
// blit-submit times after the flip packet; noted in-place reads checked
// (APS5_FLIP_READ_CHECK=1) and found overwritten before the blit's fence.
struct PresentStatistics {
std::uint64_t presents, gpuUnread, batchesAheadOfBlit, batchesAfterFlip, readsChecked, readsOverwritten, acquireDrops;
double gpuBusyMs, gpuGapMs, lastSubmitAfterFlipMs, blitSubmitAfterFlipMs;
std::uint64_t presents, gpuUnread, batchesAheadOfBlit, batchesAfterFlip, readsChecked, readsOverwritten, acquireDrops, keyWriterWaits;
double gpuBusyMs, gpuGapMs, lastSubmitAfterFlipMs, blitSubmitAfterFlipMs, keyWriterWaitMs;
};
static PresentStatistics PresentCounts();
struct PresyncStatistics {
@@ -16,6 +16,7 @@
#include "Optimization/ResourceProgram.hpp"
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <optional>
#include <stdexcept>
#include <utility>
@@ -57,6 +58,19 @@ void ReportDispatchItems(bool profile) {
std::fprintf(stderr, "[dispatch-item] %llu commit items (%llu recipes rebuilt on the committer); drained instead, cumulative:%s\n", static_cast<unsigned long long>(itemCommits.load(std::memory_order_relaxed)), static_cast<unsigned long long>(itemRebuilds.load(std::memory_order_relaxed)), drained.c_str());
}
bool CapturedCurrent(std::span<const ShaderRecompiler::MemoryRegion> captured, const ShaderSnapshot& code) {
for (const auto& region : captured) {
const auto address = region.guestAddress;
const auto bytes = region.bytes.size();
if (bytes == 0 || (address >= code.codeAddress && address < code.codeAddress + code.code.size() * sizeof(std::uint32_t)) || (!code.header.empty() && address >= code.headerAddress && address < code.headerAddress + code.header.size())) continue;
if (Driver::QueryPendingSpan(address, bytes, {}) != ShaderMemory::PendingWrite::None || !GuestMemory::Accessible(reinterpret_cast<const void*>(address), bytes)) return false;
GuestMemory::GuardRead(address, bytes);
GuestMemory::FlushGpuWrites(address, bytes);
if (std::memcmp(region.bytes.data(), reinterpret_cast<const void*>(address), bytes) != 0) return false;
}
return true;
}
}
bool Driver::pipelineDispatch() {
@@ -281,6 +295,14 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
fromAhead = false;
++groupStats.keyMismatches;
}
if (fromAhead) {
if (const auto hook = aheadAdoptHook.load(std::memory_order_acquire)) hook();
if (!CapturedCurrent(ahead.captured, snapshot)) {
fromAhead = false;
++dispatchesRejectedAhead;
++groupStats.memoryMismatches;
}
}
phaseTiming.Phase(PhaseKey);
timing.Mark("decode_key_device_setup");
bool readsClear = false;
@@ -42,6 +42,14 @@ std::uint64_t Driver::DispatchesAdoptedAhead() const {
return dispatchesAdoptedAhead.load(std::memory_order_relaxed);
}
std::uint64_t Driver::DispatchesRejectedAhead() const {
return dispatchesRejectedAhead.load(std::memory_order_relaxed);
}
void Driver::SetAheadAdoptHook(void (*hook)()) {
aheadAdoptHook.store(hook, std::memory_order_release);
}
namespace {
std::size_t& PreviewedUntil() {
@@ -326,9 +334,9 @@ void Driver::reportGroupCapture(std::uint32_t queue) {
auto& stats = groupCaptureStats();
const auto now = std::chrono::steady_clock::now();
if (now - stats.reported < std::chrono::seconds(10)) return;
AgcDriver::ProfilePrint_nid_no_patch("[groupcap] queue 0x%x (10 s): %llu runs resolved %llu dispatches (%llu cache hits) in %.1f ms (max %.2f ms); %llu adopted %.2f ms after the resolve on average, max %.2f ms; mismatches: address %llu key %llu; %llu resolve failures, %llu stale, %llu label overlaps, %llu read an earlier dispatch's output, %llu rejected by the device, %llu dropped because the argument re-read differed\n",
AgcDriver::ProfilePrint_nid_no_patch("[groupcap] queue 0x%x (10 s): %llu runs resolved %llu dispatches (%llu cache hits) in %.1f ms (max %.2f ms); %llu adopted %.2f ms after the resolve on average, max %.2f ms; mismatches: address %llu key %llu memory %llu; %llu resolve failures, %llu stale, %llu label overlaps, %llu read an earlier dispatch's output, %llu rejected by the device, %llu dropped because the argument re-read differed\n",
queue, static_cast<unsigned long long>(stats.runs), static_cast<unsigned long long>(stats.resolved), static_cast<unsigned long long>(stats.resolvedHits), stats.resolveMs, stats.resolveMaxMs,
static_cast<unsigned long long>(stats.adopted), stats.adopted != 0 ? stats.ageMs / static_cast<double>(stats.adopted) : 0.0, stats.ageMaxMs, static_cast<unsigned long long>(stats.addressMismatches), static_cast<unsigned long long>(stats.keyMismatches),
static_cast<unsigned long long>(stats.adopted), stats.adopted != 0 ? stats.ageMs / static_cast<double>(stats.adopted) : 0.0, stats.ageMaxMs, static_cast<unsigned long long>(stats.addressMismatches), static_cast<unsigned long long>(stats.keyMismatches), static_cast<unsigned long long>(stats.memoryMismatches),
static_cast<unsigned long long>(stats.failures), static_cast<unsigned long long>(stats.stale), static_cast<unsigned long long>(stats.labelOverlaps), static_cast<unsigned long long>(stats.writtenBefore), static_cast<unsigned long long>(stats.deviceRejects), static_cast<unsigned long long>(stats.argumentMismatches));
stats = GroupCaptureStats{};
stats.reported = now;
@@ -157,6 +157,14 @@ extern "C" std::uint64_t AgcDriverDispatchesAdoptedAhead_nid_postfix() {
return AgcDriver::DriverDetail::Driver::Get().DispatchesAdoptedAhead();
}
extern "C" std::uint64_t AgcDriverDispatchesRejectedAhead_nid_postfix() {
return AgcDriver::DriverDetail::Driver::Get().DispatchesRejectedAhead();
}
extern "C" void AgcDriverSetAheadAdoptHook_nid_postfix(void (*hook)()) {
AgcDriver::DriverDetail::Driver::Get().SetAheadAdoptHook(hook);
}
extern "C" void AgcDriverRegisterVideoOutput_nid_postfix(std::uint32_t handle, const std::shared_ptr<AgcDriver::IVideoOutput>& output) {
AgcDriver::RegisterVideoOutput(handle, output);
}
@@ -206,7 +206,7 @@ void Driver::reportPresents(double waitedMs, std::size_t inFlight) {
const auto flips = flipsCounted.load() - previousFlips;
const double perFlip = flips != 0 ? 1.0 / static_cast<double>(flips) : 0.0;
const auto serial = flipSerial.load();
AgcDriver::ProfilePrint_nid_no_patch( "[present] %llu presents over 10 s (%llu dropped: no free swapchain image; %zu may trail on the GPU); %s p50 %.2f ms, p90 %.2f ms, max %.2f ms over %zu; per frame, presented or dropped: GPU busy %.1f ms, idle gaps %.1f ms (%llu batches without a completion record); per flip: %.1f batches recorded (%.1f unsignaled at the flip); per present: %.1f batches ahead of the blit, %.1f after it; after the flip packet: last submit %.1f ms, blit submit %.1f ms; in-place reads overwritten before execution: %llu of %llu checked\n", static_cast<unsigned long long>(presents), static_cast<unsigned long long>(dropped), inFlight, inFlight != 0 ? "inflight_wait" : "render_fence_wait", percentile(0.5), percentile(0.9), waits.empty() ? 0.0 : waits.back(), waits.size(), (counts.gpuBusyMs - previous.gpuBusyMs) * perFrame, (counts.gpuGapMs - previous.gpuGapMs) * perFrame, static_cast<unsigned long long>(counts.gpuUnread - previous.gpuUnread), static_cast<double>(serial - windowSerial) * perFlip, static_cast<double>(flipBatchesUnsignaled.load() - previousUnsignaled) * perFlip, static_cast<double>(counts.batchesAheadOfBlit - previous.batchesAheadOfBlit) * perPresent, static_cast<double>(counts.batchesAfterFlip - previous.batchesAfterFlip) * perPresent, (counts.lastSubmitAfterFlipMs - previous.lastSubmitAfterFlipMs) * perPresent, (counts.blitSubmitAfterFlipMs - previous.blitSubmitAfterFlipMs) * perPresent, static_cast<unsigned long long>(counts.readsOverwritten - previous.readsOverwritten), static_cast<unsigned long long>(counts.readsChecked - previous.readsChecked));
AgcDriver::ProfilePrint_nid_no_patch( "[present] %llu presents over 10 s (%llu dropped: no free swapchain image; %zu may trail on the GPU); %s p50 %.2f ms, p90 %.2f ms, max %.2f ms over %zu; per frame, presented or dropped: GPU busy %.1f ms, idle gaps %.1f ms (%llu batches without a completion record); per flip: %.1f batches recorded (%.1f unsignaled at the flip); per present: %.1f batches ahead of the blit, %.1f after it; after the flip packet: last submit %.1f ms, blit submit %.1f ms; in-place reads overwritten before execution: %llu of %llu checked; waits for the display keys' writer outside the GPU mutex: %llu, %.1f ms\n", static_cast<unsigned long long>(presents), static_cast<unsigned long long>(dropped), inFlight, inFlight != 0 ? "inflight_wait" : "render_fence_wait", percentile(0.5), percentile(0.9), waits.empty() ? 0.0 : waits.back(), waits.size(), (counts.gpuBusyMs - previous.gpuBusyMs) * perFrame, (counts.gpuGapMs - previous.gpuGapMs) * perFrame, static_cast<unsigned long long>(counts.gpuUnread - previous.gpuUnread), static_cast<double>(serial - windowSerial) * perFlip, static_cast<double>(flipBatchesUnsignaled.load() - previousUnsignaled) * perFlip, static_cast<double>(counts.batchesAheadOfBlit - previous.batchesAheadOfBlit) * perPresent, static_cast<double>(counts.batchesAfterFlip - previous.batchesAfterFlip) * perPresent, (counts.lastSubmitAfterFlipMs - previous.lastSubmitAfterFlipMs) * perPresent, (counts.blitSubmitAfterFlipMs - previous.blitSubmitAfterFlipMs) * perPresent, static_cast<unsigned long long>(counts.readsOverwritten - previous.readsOverwritten), static_cast<unsigned long long>(counts.readsChecked - previous.readsChecked), static_cast<unsigned long long>(counts.keyWriterWaits - previous.keyWriterWaits), counts.keyWriterWaitMs - previous.keyWriterWaitMs);
previous = counts;
previousFlips = flipsCounted.load();
previousUnsignaled = flipBatchesUnsignaled.load();
@@ -138,7 +138,7 @@ std::shared_ptr<const ShaderSnapshot> ReadRawShader(std::uint64_t address) {
cache.erase(found);
}
const auto bytes = result->code.size() * sizeof(std::uint32_t);
while (!cache.empty() && (cache.size() >= 64 || cacheBytes + bytes > 8 * 1024 * 1024)) {
while (!cache.empty() && (cache.size() >= 4096 || cacheBytes + bytes > 64 * 1024 * 1024)) {
cacheBytes -= cache.back()->code.size() * sizeof(std::uint32_t);
cache.pop_back();
}
@@ -3363,6 +3363,15 @@ FrameDumps& Dumps() {
}
bool VulkanDevice::PresentDisplayBuffer(const DisplayBuffer& buffer) {
if (buffer.dccAddress != 0 && buffer.tilingMode == 0) {
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
const auto before = profile ? Graphics::Recorder::ThreadWaitedMs() : 0.0;
state->recorder->SyncThrough(buffer.dccAddress, DisplayBufferKeyBytes(buffer), true);
if (const auto waited = profile ? Graphics::Recorder::ThreadWaitedMs() - before : 0.0; waited > 0.0) {
++presentCounters.keyWriterWaits;
presentCounters.keyWriterWaitMs += waited;
}
}
static const bool storeAtFlip = Graphics::StorageTexture::StoreAtFlipRequested(std::getenv("APS5_STORE_AT_FLIP"));
if (storeAtFlip) Graphics::StorageTexture::FlushAllPending("flip");
else Graphics::StorageTexture::FlushDumpSelected("flip");
@@ -185,7 +185,8 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip
std::vector<VkPipeline> libraries;
for (std::size_t part = 0; part < flags.size(); ++part) {
if (part == 0 && mesh) continue;
const auto key = Combined(*partKeys[part], *contextKeys[part]);
auto key = Combined(*partKeys[part], *contextKeys[part]);
key.push_back(static_cast<std::byte>(optimize));
auto& cache = device.parts[part];
if (const auto found = cache.find(key); found != cache.end()) {
libraries.push_back(found->second);
@@ -195,7 +196,7 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip
VkGraphicsPipelineLibraryCreateInfoEXT library{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_LIBRARY_CREATE_INFO_EXT, flags[part] == VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT ? &rendering : &shaderRendering};
library.flags = flags[part];
VkGraphicsPipelineCreateInfo create{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, &library};
create.flags = VK_PIPELINE_CREATE_LIBRARY_BIT_KHR | VK_PIPELINE_CREATE_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT;
create.flags = optimize ? VK_PIPELINE_CREATE_LIBRARY_BIT_KHR | VK_PIPELINE_CREATE_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT : VK_PIPELINE_CREATE_LIBRARY_BIT_KHR;
create.pDynamicState = info.pDynamicState;
create.layout = partLayouts[part];
switch (flags[part]) {
@@ -2084,9 +2084,10 @@ void ShaderResources::buildPrepare(std::span<const CompiledShader> shaders, cons
const bool classified = element < binding.bufferWritten.size() && element < binding.bufferRead.size() && element < binding.bufferAtomic.size();
allocations[index].writeOnly = classified && allocations[index].guest && allocations[index].written && binding.bufferWritten[element] && !read && !atomic;
const auto& push = shader.program->pushConstants;
if (!push.empty()) {
const auto pushDataBytes = shader.program->stencilReferences.empty() ? push.size() : shader.program->stencilReferenceDword * sizeof(std::uint32_t);
if (pushDataBytes != 0u) {
const auto position = shader.program->memoryOffsetDword * 4u + element;
Require(position < push.size(), "guest buffer offset lies outside the shader's push constants");
Require(position < pushDataBytes, "guest buffer offset lies outside the shader's push constants");
allocations[index].pushByte = static_cast<std::int32_t>(shader.pushConstantOffset + position);
} else {
allocations[index].dataByte = shader.program->memoryOffsetDword * 4u + element;
@@ -32,7 +32,7 @@ struct LoadCase {
std::uint32_t components = 1;
};
std::vector<std::uint32_t> emitLoad(const LoadCase& test, bool active, std::uint32_t offset, std::uint32_t records, bool robust) {
std::vector<std::uint32_t> emitLoad(const LoadCase& test, bool active, std::uint32_t offset, std::uint32_t records, bool robust, bool valid = true) {
IrProgram program;
program.SetWaveSize(32);
program.Resources().stage = IrShaderStage::Compute;
@@ -76,7 +76,8 @@ std::vector<std::uint32_t> emitLoad(const LoadCase& test, bool active, std::uint
program.Metadata().bindings.pushDataStartDword = 0;
program.Metadata().bindings.memoryOffsetCount = 1;
auto& size = constant(records);
auto& handle = emit(IrOpcode::GetBufferResource, IrType::BufferResource, {&zero, &zero, &size, &zero});
auto& base = constant(valid ? 0x100u : 0u);
auto& handle = emit(IrOpcode::GetBufferResource, IrType::BufferResource, {&base, &zero, &size, &zero});
emit(test.opcode, test.type, {&handle, &zero, &address, &zero, &enabled});
} else {
if (test.kind == ResourceKind::Gds) program.Metadata().bindings.descriptors.push_back({DescriptorBindingKind::Gds, {0}});
@@ -104,8 +105,8 @@ std::vector<std::uint32_t> emitLoad(const LoadCase& test, bool active, std::uint
class AccessTrace {
public:
AccessTrace(const std::vector<std::uint32_t>& module, std::uint32_t misalignment = 0, bool valid = true)
: words(module), misalignment(misalignment), valid(valid) {
AccessTrace(const std::vector<std::uint32_t>& module, std::uint32_t misalignment = 0)
: words(module), misalignment(misalignment) {
require(words.size() >= 5, "SPIR-V module header is missing");
for (std::size_t offset = 5; offset < words.size();) {
const auto count = words[offset] >> 16;
@@ -206,7 +207,6 @@ private:
const auto spec = specializations.at(id);
if (spec == PipelineSpecialization::BufferBase) return 0;
if (spec == PipelineSpecialization::BufferBase + 1) return 0x30001000;
if (spec == PipelineSpecialization::BufferBase + 2) return valid;
if (spec == PipelineSpecialization::BufferBase + 3) return misalignment;
if (spec == PipelineSpecialization::PushDataOffset) return 0;
throw std::runtime_error("unexpected specialization in access fixture");
@@ -250,7 +250,6 @@ private:
const std::vector<std::uint32_t>& words;
std::uint32_t misalignment;
bool valid;
std::uint32_t entryFunction = 0;
std::map<std::uint32_t, std::span<const std::uint32_t>> definitions;
std::map<std::uint32_t, std::string> names;
@@ -281,8 +280,8 @@ std::uint32_t ldsLength(const std::vector<std::uint32_t>& words) {
}
void expectReads(const LoadCase& test, bool robust, bool active, std::uint32_t offset, std::vector<std::uint32_t> expected, std::uint32_t records = 64, std::uint32_t misalignment = 0, bool valid = true) {
const auto module = emitLoad(test, active, offset, records, robust);
const auto actual = AccessTrace(module, misalignment, valid).Run();
const auto module = emitLoad(test, active, offset, records, robust, valid);
const auto actual = AccessTrace(module, misalignment).Run();
if (actual != expected) {
std::string detail = std::string(robust ? "robust" : "plain") + " guest reads:";
for (const auto index : actual) detail += " " + std::to_string(index);
@@ -11058,6 +11058,97 @@ void denormalClearTests(const Device& device, Recorder& recorder) {
recorder.Sync();
}
class LibraryCreateFlags {
public:
explicit LibraryCreateFlags(const Context& context) : resolver(context.deviceProc), device(context.device) {
create = function<PFN_vkCreateGraphicsPipelines>("vkCreateGraphicsPipelines");
active = this;
}
~LibraryCreateFlags() { active = nullptr; }
LibraryCreateFlags(const LibraryCreateFlags&) = delete;
LibraryCreateFlags& operator=(const LibraryCreateFlags&) = delete;
PFN_vkGetDeviceProcAddr Resolver() const { return resolve; }
std::uint32_t Libraries() const { return libraries.load(); }
std::uint32_t Retaining() const { return retaining.load(); }
private:
template<typename TFunction>
TFunction function(const char* name) const { return reinterpret_cast<TFunction>(resolver(device, name)); }
static VKAPI_ATTR VkResult VKAPI_CALL createPipelines(VkDevice device, VkPipelineCache cache, std::uint32_t count, const VkGraphicsPipelineCreateInfo* infos, const VkAllocationCallbacks* callbacks, VkPipeline* pipelines) {
const auto result = active->create(device, cache, count, infos, callbacks, pipelines);
for (std::uint32_t index = 0; index < count; ++index) {
if (pipelines[index] == VK_NULL_HANDLE || (infos[index].flags & VK_PIPELINE_CREATE_LIBRARY_BIT_KHR) == 0) continue;
active->libraries.fetch_add(1);
if ((infos[index].flags & VK_PIPELINE_CREATE_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT) != 0) active->retaining.fetch_add(1);
}
return result;
}
static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL resolve(VkDevice device, const char* name) {
if (std::strcmp(name, "vkCreateGraphicsPipelines") == 0) return reinterpret_cast<PFN_vkVoidFunction>(createPipelines);
return active->resolver(device, name);
}
PFN_vkGetDeviceProcAddr resolver;
VkDevice device;
PFN_vkCreateGraphicsPipelines create = nullptr;
std::atomic<std::uint32_t> libraries{0};
std::atomic<std::uint32_t> retaining{0};
static inline LibraryCreateFlags* active = nullptr;
};
void pipelineLibraryLtoInfoTests(const Device& device) {
std::lock_guard gpu(GpuMutex());
const auto& context = device.GetContext();
LibraryCreateFlags created(context);
struct Cleanup {
VkDevice device;
~Cleanup() { ClearCachedPipelines(device); }
} cleanup{context.device};
ClearCachedPipelines(context.device);
if (!context.graphicsPipelineLibrary) {
std::cout << "graphics pipeline libraries unavailable or disabled: no library retains link-time optimization info\n";
return;
}
ShaderRecompiler::RecompileResult vertex, fragment;
vertex.variantId = 41;
fragment.variantId = 42;
vertex.spirv = std::vector<std::uint32_t>(std::begin(TRIANGLE_vert_SPV), std::end(TRIANGLE_vert_SPV));
fragment.spirv = std::vector<std::uint32_t>(std::begin(TRIANGLE_frag_SPV), std::end(TRIANGLE_frag_SPV));
const std::array shaders{CompiledShader{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, CompiledShader{ShaderRecompiler::ShaderStage::Fragment, &fragment, 0}};
State state{};
state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
state.hasColorTarget = true;
state.color.format = VK_FORMAT_R8G8B8A8_UNORM;
state.colors.push_back(state.color);
state.blend.colorWriteMask = 15;
state.blends.push_back(state.blend);
ShaderResources resources(context, shaders, state.color, static_cast<std::uint32_t>(state.colors.size()), 0, 0);
VertexInputLayout input;
auto fast = context;
fast.deviceProc = created.Resolver();
fast.pipelineLinkTimeOptimization = false;
auto optimizing = fast;
optimizing.pipelineLinkTimeOptimization = true;
const bool lto = std::getenv("APS5_NO_PIPELINE_LTO") == nullptr;
const auto fastPipeline = CachedPipeline(fast, state, input, resources, shaders, VK_IMAGE_LAYOUT_GENERAL);
const auto fastLibraries = created.Libraries();
Require(fastLibraries != 0, "the pipeline was not linked from libraries created through the traced device");
Require(created.Retaining() == 0, std::to_string(created.Retaining()) + " of the " + std::to_string(fastLibraries) + " libraries made for a context without optimized links were created to retain link-time optimization info");
WaitForOptimizedPipelines(context.device);
Require(!fastPipeline->Optimized(), "a pipeline of a context without optimized links was optimized in the background");
const auto optimizedPipeline = CachedPipeline(optimizing, state, input, resources, shaders, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
Require(optimizedPipeline != fastPipeline, "the two attachment layouts shared one cached pipeline");
const auto libraries = created.Libraries() - fastLibraries;
if (lto) {
Require(libraries == fastLibraries, "a link that will be optimized made " + std::to_string(libraries) + " libraries, expected " + std::to_string(fastLibraries) + ": it reused libraries made without link-time optimization info");
Require(created.Retaining() == libraries, std::to_string(created.Retaining()) + " of the " + std::to_string(libraries) + " libraries of a link that will be optimized were created to retain link-time optimization info");
} else {
Require(libraries == 0 && created.Retaining() == 0, "with APS5_NO_PIPELINE_LTO set, forcing pipelineLinkTimeOptimization changed the libraries or their create flags");
}
WaitForOptimizedPipelines(context.device);
Require(optimizedPipeline->Optimized() == lto, lto ? "the pipeline linked from libraries that retain link-time optimization info was not optimized in the background" : "a pipeline was optimized with APS5_NO_PIPELINE_LTO set");
std::cout << "Pipeline library link-time optimization info tests passed: " << fastLibraries << " libraries without the info, " << created.Retaining() << " with it\n";
}
void pipelineLibraryTests(const Device& device) {
std::lock_guard gpu(GpuMutex());
const auto& context = device.GetContext();
@@ -11438,6 +11529,10 @@ int main(int argc, char** argv) {
}
return sparseRunImportTests(device, true) ? 0 : 77;
}
if (argc == 2 && std::string_view(argv[1]) == "--pipeline-library-lto-info-only") {
pipelineLibraryLtoInfoTests(device);
return 0;
}
if (argc == 2 && std::string_view(argv[1]) == "--pipeline-library-only") {
pipelineLibraryTests(device);
return 0;
@@ -952,7 +952,8 @@ int main(int argc, char** argv) {
});
for (auto& reader : readers) reader.join();
for (const auto& snapshot : concurrent) check(snapshot == branched, "concurrent raw compute readers lost snapshot reuse");
alignas(256) std::array<std::array<std::uint32_t, 64>, 65> programs{};
struct alignas(256) RawProgram : std::array<std::uint32_t, 64> {};
std::vector<RawProgram> programs(4097);
for (auto& program : programs) program[0] = 0xbf810000;
const auto firstAddress = reinterpret_cast<std::uintptr_t>(programs.front().data());
readers.clear();
@@ -962,6 +963,9 @@ int main(int argc, char** argv) {
for (auto& reader : readers) reader.join();
for (const auto& snapshot : concurrent) check(snapshot == concurrent.front(), "concurrent raw compute misses duplicated snapshots");
const auto evicted = AgcDriver::DriverDetail::ReadRawShader(firstAddress);
for (std::size_t i = 1; i <= 400; ++i)
AgcDriver::DriverDetail::ReadRawShader(reinterpret_cast<std::uintptr_t>(programs[i].data()));
check(AgcDriver::DriverDetail::ReadRawShader(firstAddress) == evicted, "raw shader cache evicted a program while a title used only 401");
for (std::size_t i = 1; i < programs.size(); ++i)
AgcDriver::DriverDetail::ReadRawShader(reinterpret_cast<std::uintptr_t>(programs[i].data()));
check(AgcDriver::DriverDetail::ReadRawShader(firstAddress) != evicted && evicted->code[0] == 0xbf810000,
@@ -0,0 +1,208 @@
#include "VulkanTestDevice.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Submit/include/Acb.hpp"
#include <array>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <initializer_list>
#include <iostream>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>
namespace {
void Require(bool condition, const std::string& message) {
if (!condition) throw std::runtime_error(message);
}
alignas(256) std::array<std::uint32_t, 7> CounterCode{0xf4080200u, 0xfa000000u, 0xbf8cc07fu, 0x7e000281u, 0xe0c80000u, 0x80020000u, 0xbf810000u};
alignas(256) std::array<std::uint32_t, 7> StoreCode{0xf4080200u, 0xfa000000u, 0xbf8cc07fu, 0x7e000202u, 0xe0700000u, 0x80020000u, 0xbf810000u};
alignas(256) std::array<std::uint32_t, 15> SpinCode{0xf4080200u, 0xfa000000u, 0xbe840381u, 0x9304ff04u, 0x0019660du, 0x8004ff04u, 0x3c6ef35fu, 0x80828102u, 0xbf078002u, 0xbf85fff9u, 0xbf8cc07fu, 0x7e000204u, 0xe0700000u, 0x80020000u, 0xbf810000u};
constexpr std::uint32_t Compute = 0x20u;
constexpr std::uint32_t OtherCompute = 0x28u;
constexpr std::uint32_t SpinIterations = 1u << 20u;
alignas(256) std::array<std::uint32_t, 4> Stale{};
alignas(256) std::array<std::uint32_t, 4> Fresh{};
alignas(256) std::array<std::uint32_t, 4> Target{};
alignas(256) std::array<std::uint32_t, 4> TargetDescriptor{};
alignas(256) std::array<std::uint32_t, 4> Spun{};
alignas(256) std::array<std::uint32_t, 4> SpunDescriptor{};
alignas(256) volatile std::uint32_t Gate = 1;
alignas(256) volatile std::uint32_t Done = 0;
alignas(256) volatile std::uint32_t OtherDone = 0;
enum class Window { None, CpuStore, QueuedGpuStore };
std::atomic<Window> Armed{Window::None};
std::uint32_t SpinDispatches = 16;
std::vector<std::uint32_t> OtherWords;
bool SawPending = false;
std::uint32_t Low(const volatile void* pointer) {
return static_cast<std::uint32_t>(reinterpret_cast<std::uintptr_t>(pointer));
}
std::uint32_t High(const volatile void* pointer) {
return static_cast<std::uint32_t>(static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(pointer)) >> 32u);
}
std::array<std::uint32_t, 4> Descriptor(const void* pointer, std::uint32_t bytes) {
return {Low(pointer), High(pointer) & 0xffffu, bytes, 0x31016facu};
}
void SetShaderRegister(std::vector<std::uint32_t>& words, std::uint32_t offset, std::uint32_t value) {
words.insert(words.end(), {0xc0017600u, offset, value});
}
void Program(std::vector<std::uint32_t>& words, const void* code, std::initializer_list<std::uint32_t> userData) {
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(code));
SetShaderRegister(words, 0x20c, static_cast<std::uint32_t>(address >> 8u));
SetShaderRegister(words, 0x20d, static_cast<std::uint32_t>(address >> 40u) & 0xffu);
for (const std::uint32_t offset : {0x207u, 0x208u, 0x209u}) SetShaderRegister(words, offset, 1);
SetShaderRegister(words, 0x212, 0);
SetShaderRegister(words, 0x213, static_cast<std::uint32_t>(userData.size()) << 1u);
std::uint32_t offset = 0x240;
for (const auto value : userData) SetShaderRegister(words, offset++, value);
}
void Dispatch(std::vector<std::uint32_t>& words, const void* code, std::initializer_list<std::uint32_t> userData) {
Program(words, code, userData);
words.insert(words.end(), {0xc0031500u, 1, 1, 1, 0x8041u});
}
void WriteData(std::vector<std::uint32_t>& words, const volatile std::uint32_t* target, std::uint32_t value) {
words.insert(words.end(), {0xc0033700u, 0x00100200u, Low(target), High(target), value});
}
void Submit(std::uint32_t queue, std::vector<std::uint32_t>& words) {
Packet packet{words.data(), static_cast<std::uint32_t>(words.size()), 0, {}};
Require(sceAgcDriverSubmitAcb(queue, &packet) == 0, "compute submit failed");
}
void QueueStoreAhead() {
OtherWords.clear();
for (std::uint32_t index = 0; index < SpinDispatches; ++index) Dispatch(OtherWords, SpinCode.data(), {Low(SpunDescriptor.data()), High(SpunDescriptor.data()), SpinIterations});
Dispatch(OtherWords, StoreCode.data(), {Low(TargetDescriptor.data()), High(TargetDescriptor.data()), Low(Fresh.data())});
WriteData(OtherWords, &OtherDone, 1);
Submit(OtherCompute, OtherWords);
const auto start = std::chrono::steady_clock::now();
while (std::chrono::steady_clock::now() - start < std::chrono::seconds(5) && OtherDone == 0) {
if (AgcDriver::DriverDetail::Driver::QueryPendingSpan(reinterpret_cast<std::uintptr_t>(Target.data()), sizeof(Target), {}) != AgcDriver::ShaderMemory::PendingWrite::None) {
SawPending = true;
return;
}
std::this_thread::yield();
}
}
void BeforeAdoption() {
const auto window = Armed.exchange(Window::None);
if (window == Window::CpuStore) reinterpret_cast<volatile std::uint32_t&>(Target[0]) = Low(Fresh.data());
else if (window == Window::QueuedGpuStore) QueueStoreAhead();
}
void WaitIdle() {
AgcDriverWaitIdle_nid_postfix();
for (const auto* buffer : {&Stale, &Fresh, &Target}) AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast<std::uintptr_t>(buffer->data()), sizeof(*buffer));
}
void Reset() {
WaitIdle();
Stale = {};
Fresh = {};
Target = Descriptor(Stale.data(), sizeof(Stale));
Done = 0;
OtherDone = 0;
SawPending = false;
}
void Gated(Window window) {
Reset();
std::vector<std::uint32_t> words;
words.insert(words.end(), {0xc0053c00u, 0x13u, Low(&Gate), High(&Gate), 1u, 0xffffffffu, 0x19u});
Dispatch(words, CounterCode.data(), {Low(Target.data()), High(Target.data())});
WriteData(words, &Done, 1);
Armed = window;
Submit(Compute, words);
WaitIdle();
Armed = Window::None;
Require(Done == 1, "the gated submission did not finish");
}
std::string Counts() {
return "(stale " + std::to_string(Stale[0]) + ", fresh " + std::to_string(Fresh[0]) + ")";
}
}
int main() {
try {
auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
device.reset();
Require(High(Stale.data()) == High(Fresh.data()), "the stale and fresh buffers differ in their high address word");
TargetDescriptor = Descriptor(Target.data(), sizeof(Target));
SpunDescriptor = Descriptor(Spun.data(), sizeof(Spun));
AgcDriverSetAheadAdoptHook_nid_postfix(&BeforeAdoption);
Reset();
std::vector<std::uint32_t> warmUp;
Dispatch(warmUp, CounterCode.data(), {Low(Target.data()), High(Target.data())});
WriteData(warmUp, &Done, 1);
Submit(Compute, warmUp);
std::vector<std::uint32_t> otherWarmUp;
Dispatch(otherWarmUp, SpinCode.data(), {Low(SpunDescriptor.data()), High(SpunDescriptor.data()), SpinIterations});
Dispatch(otherWarmUp, StoreCode.data(), {Low(TargetDescriptor.data()), High(TargetDescriptor.data()), Low(Fresh.data())});
Submit(OtherCompute, otherWarmUp);
WaitIdle();
Require(Done == 1 && Target[0] == Low(Fresh.data()), "the warm-up dispatches did not run");
Gated(Window::None);
Require(Stale[0] == 1 && Fresh[0] == 0, "the gated dispatch without a store in the window did not count into the stale buffer " + Counts());
const auto adopted = AgcDriverDispatchesAdoptedAhead_nid_postfix();
Require(adopted != 0, "the gated dispatch was not resolved ahead and adopted");
auto rejected = AgcDriverDispatchesRejectedAhead_nid_postfix();
Gated(Window::CpuStore);
Require(Fresh[0] == 1 && Stale[0] == 0, "a dispatch resolved ahead whose descriptor the CPU replaced before its adoption used the old descriptor " + Counts());
Require(AgcDriverDispatchesRejectedAhead_nid_postfix() == rejected + 1, "the CPU store in the adoption window was not caught by the adoption check");
std::cout << "cpu store before adoption: passed\n";
bool conclusive = false;
for (int attempt = 0; attempt < 4 && !conclusive; ++attempt, SpinDispatches *= 4) {
rejected = AgcDriverDispatchesRejectedAhead_nid_postfix();
Gated(Window::QueuedGpuStore);
conclusive = SawPending;
if (!conclusive) {
std::cout << "queued gpu store before adoption: the store finished before the adoption with " << SpinDispatches << " spin dispatches, retrying\n";
continue;
}
Require(Fresh[0] == 1 && Stale[0] == 0, "a dispatch resolved ahead whose descriptor a GPU store queued on another queue replaces before its adoption used the old descriptor " + Counts());
Require(AgcDriverDispatchesRejectedAhead_nid_postfix() == rejected + 1, "the queued GPU store in the adoption window was not caught by the adoption check");
}
AgcDriverSetAheadAdoptHook_nid_postfix(nullptr);
if (!conclusive) {
std::cout << "queued gpu store before adoption: never observed the store pending at the adoption\n";
AgcDriverShutdown_nid_postfix();
return VulkanTestSkipped;
}
std::cout << "queued gpu store before adoption: passed\n";
std::cout << "dispatch ahead adopt check test passed\n";
AgcDriverShutdown_nid_postfix();
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
try {
AgcDriverShutdown_nid_postfix();
} catch (const std::exception& shutdownError) {
std::cerr << shutdownError.what() << '\n';
}
return 1;
}
}
@@ -64,6 +64,8 @@ struct PipelineTrace {
std::mutex mutex;
std::condition_variable released;
bool allowOptimization = false;
std::uint32_t libraries = 0;
std::uint32_t retaining = 0;
std::vector<VkPipeline> fast;
std::vector<VkPipeline> optimized;
VkCommandBuffer boundCommands = VK_NULL_HANDLE;
@@ -90,7 +92,12 @@ VKAPI_ATTR VkResult VKAPI_CALL CreatePipelines(VkDevice device, VkPipelineCache
const auto result = Trace->create(device, cache, count, infos, allocator, pipelines);
std::lock_guard lock(Trace->mutex);
for (std::uint32_t index = 0; index < count; ++index) {
if (pipelines[index] == VK_NULL_HANDLE || (infos[index].flags & VK_PIPELINE_CREATE_LIBRARY_BIT_KHR) != 0) continue;
if (pipelines[index] == VK_NULL_HANDLE) continue;
if ((infos[index].flags & VK_PIPELINE_CREATE_LIBRARY_BIT_KHR) != 0) {
++Trace->libraries;
if ((infos[index].flags & VK_PIPELINE_CREATE_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT) != 0) ++Trace->retaining;
continue;
}
auto& handles = (infos[index].flags & VK_PIPELINE_CREATE_LINK_TIME_OPTIMIZATION_BIT_EXT) != 0 ? Trace->optimized : Trace->fast;
handles.push_back(pipelines[index]);
}
@@ -217,12 +224,17 @@ void Run(AgcDriver::VulkanDevice& device, Context context, PipelineTrace& trace,
CheckPixels(masked, phase);
};
render(false, "fast link");
{
std::lock_guard lock(trace.mutex);
Require(trace.libraries != 0, "the readback pipeline's libraries were not created through the traced device");
Require(trace.retaining == (optimize ? trace.libraries : 0u), std::to_string(trace.retaining) + " of the " + std::to_string(trace.libraries) + " libraries were created to retain link-time optimization info, expected " + (optimize ? "all of them" : "none, since no optimized link will be made"));
}
trace.ReleaseOptimization();
WaitForOptimizedPipelines(context.device);
Require(PipelineLibraryCountersOf(context.device).linked == 1, "the readback pipeline was not linked from graphics libraries");
Require(trace.optimized.size() == (optimize ? 1u : 0u), "the optimized pipeline creation count differs from the requested mode");
render(optimize, "completed link");
std::printf("pipeline readback passed: fast draws %u, optimized draws %u, optimized creations %zu\n", optimize ? 1u : 2u, optimize ? 1u : 0u, trace.optimized.size());
std::printf("pipeline readback passed: fast draws %u, optimized draws %u, optimized creations %zu, libraries %u (%u retaining link-time optimization info)\n", optimize ? 1u : 2u, optimize ? 1u : 0u, trace.optimized.size(), trace.libraries, trace.retaining);
}
}
@@ -0,0 +1,202 @@
#include "prx/libSceAgcDriver/Execution/include/DisplayBuffer.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#endif
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <exception>
#include <iostream>
#include <mutex>
#include <optional>
#include <span>
#include <string>
#include <vector>
namespace {
using AgcDriver::Graphics::Require;
using ShaderRecompiler::ShaderStage;
constexpr std::uint32_t Threads = 32;
constexpr std::uint32_t Side = 128;
constexpr std::uint64_t DisplayFormat = 0x8000000000000000ull;
constexpr std::size_t SurfaceBytes = 65536;
constexpr std::size_t KeysOffset = SurfaceBytes;
constexpr std::size_t KeysCapacity = 32768;
constexpr std::size_t CopiedKeys = 256;
constexpr std::size_t SourceKeysOffset = KeysOffset + KeysCapacity;
constexpr std::size_t BlockBytes = 2 * 65536;
constexpr std::uint8_t Uncompressed = 0xff;
constexpr std::uint8_t Cleared = 0x00;
alignas(256) constexpr std::array<std::uint32_t, 7> CopyKeysCode{
0x34020083, 0xe0341000, 0x80000201, 0xbf8c3f70, 0xe0741000, 0x80010201, 0xbf810000,
};
std::uint64_t AddressOf(const void* data) {
return static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(data));
}
class GuestBlock {
public:
GuestBlock() {
#ifdef _WIN32
block = static_cast<std::uint8_t*>(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
#else
block = static_cast<std::uint8_t*>(std::aligned_alloc(65536, BlockBytes));
#endif
Require(block != nullptr, "present key writer wait: cannot allocate the guest block");
GuestAllocations::Mutation().Add(block, BlockBytes, true, true, true);
}
~GuestBlock() {
GuestAllocations::Mutation().Remove(block);
#ifdef _WIN32
VirtualFree(block, 0, MEM_RELEASE);
#else
std::free(block);
#endif
}
GuestBlock(const GuestBlock&) = delete;
GuestBlock& operator=(const GuestBlock&) = delete;
std::uint8_t* Data() { return block; }
private:
std::uint8_t* block = nullptr;
};
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t bytes) {
const auto address = AddressOf(data);
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu), bytes, 0x31016facu};
}
ShaderRecompiler::RecompileResult Compile(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> code, const std::vector<std::uint32_t>& userData) {
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1};
ShaderRecompiler::RecompileRequest request{
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
{32, 0, userData, compute, std::nullopt, std::nullopt, memory},
device.Target(),
{0, 0, 0, 128}
};
request.useCache = false;
return ShaderRecompiler::Recompile(request);
}
struct Scenario {
std::uint8_t* surface;
std::uint8_t* keys;
std::uint8_t* source;
std::size_t keyBytes;
AgcDriver::DisplayBuffer buffer;
std::vector<std::uint32_t> copyData;
};
Scenario Prepare(std::uint8_t* block) {
Scenario scenario{block, block + KeysOffset, block + SourceKeysOffset, 0, {AddressOf(block), DisplayFormat, Side, Side}, std::vector<std::uint32_t>(16, 0u)};
scenario.buffer.dccAddress = AddressOf(scenario.keys);
scenario.keyBytes = AgcDriver::DisplayBufferKeyBytes(scenario.buffer);
Require(AgcDriver::DisplayBufferSize(scenario.buffer) <= SurfaceBytes && scenario.keyBytes >= CopiedKeys && scenario.keyBytes <= KeysCapacity, "present key writer wait: the display buffer does not fit the guest block");
const auto source = BufferDescriptor(scenario.source, CopiedKeys);
const auto destination = BufferDescriptor(scenario.keys, CopiedKeys);
std::copy(source.begin(), source.end(), scenario.copyData.begin());
std::copy(destination.begin(), destination.end(), scenario.copyData.begin() + 4);
return scenario;
}
void Reset(const Scenario& scenario) {
std::memset(scenario.surface, 0x55, SurfaceBytes);
std::memset(scenario.keys, Uncompressed, scenario.keyBytes);
std::memset(scenario.keys, Cleared, CopiedKeys);
std::memset(scenario.source, Uncompressed, CopiedKeys);
AgcDriver::GuestMemory::BumpCollectEpoch();
}
std::string PresentFailure(AgcDriver::VulkanDevice& device, const AgcDriver::DisplayBuffer& buffer) {
try {
device.PresentDisplayBuffer(buffer);
} catch (const std::exception& error) {
return error.what();
}
return "the headless present succeeded";
}
const std::string NoSwapchain = "Vulkan presentation: device has no swapchain";
bool CanCopyKeys(AgcDriver::VulkanDevice& device, const Scenario& scenario) {
const std::array<std::uint32_t, 4> pattern{0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu};
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
const bool filled = device.FillBuffer(AddressOf(scenario.keys), CopiedKeys, pattern);
device.WaitIdle();
return filled;
}
void RunPendingWriter(AgcDriver::VulkanDevice& device, const Scenario& scenario) {
Reset(scenario);
const auto copy = Compile(device, CopyKeysCode, scenario.copyData);
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
Require(AgcDriver::GuestMemory::GpuMutex().DepthOnThisThread() == 1, "present key writer wait: the presenter's hold is not the outermost");
device.Dispatch(copy, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(CopyKeysCode.data()));
Require(AgcDriver::Graphics::DccKeyWritersPending(scenario.buffer.dccAddress, scenario.keyBytes), "a recorded kernel that copies data over the display keys is not seen as a pending key writer");
const auto holds = AgcDriver::GuestMemory::GpuHoldSerial();
const auto failure = PresentFailure(device, scenario.buffer);
const auto acquired = AgcDriver::GuestMemory::GpuHoldSerial() - holds;
Require(failure == NoSwapchain, "a present behind a pending writer of the display keys stopped before the swapchain: " + failure);
Require(acquired != 0, "the presenter synchronized with the display keys' writer without releasing the GPU mutex");
Require(!AgcDriver::Graphics::DccKeyWritersPending(scenario.buffer.dccAddress, scenario.keyBytes), "the display keys' writer is still pending after the present");
Require(scenario.keys[0] == Uncompressed && scenario.keys[CopiedKeys - 1] == Uncompressed, "the present read the display keys before their writer landed");
}
void RunNoWriter(AgcDriver::VulkanDevice& device, const Scenario& scenario) {
std::memset(scenario.keys, Uncompressed, scenario.keyBytes);
AgcDriver::GuestMemory::BumpCollectEpoch();
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
device.WaitIdle();
Require(!AgcDriver::Graphics::DccKeyWritersPending(scenario.buffer.dccAddress, scenario.keyBytes), "the idle device still reports a pending writer of the display keys");
const auto holds = AgcDriver::GuestMemory::GpuHoldSerial();
const auto failure = PresentFailure(device, scenario.buffer);
const auto acquired = AgcDriver::GuestMemory::GpuHoldSerial() - holds;
Require(failure == NoSwapchain, "a present with no pending writer of the display keys stopped before the swapchain: " + failure);
Require(acquired == 0, "a present with no pending writer of the display keys released the GPU mutex");
}
}
int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
if (!device->CanWaitUnlocked()) {
std::puts("skipped, the device has no timeline semaphores: the presenter cannot wait without the GPU mutex");
return VulkanTestSkipped;
}
GuestBlock block;
const auto scenario = Prepare(block.Data());
if (!CanCopyKeys(*device, scenario)) {
std::puts("skipped, host imports are unavailable: the key writer of the test is a copied writer");
return VulkanTestSkipped;
}
RunPendingWriter(*device, scenario);
RunNoWriter(*device, scenario);
std::puts("present key writer wait tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}
@@ -88,7 +88,7 @@ ShaderRecompiler::ShaderPixelStageInfo PixelInfo(std::uint32_t shaderControl) {
return AgcDriver::Graphics::DecodePixelStageInfo(context, {0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u});
}
void Draw(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> pixelCode, std::uint32_t shaderControl, const VkStencilOpState& stencil, std::uint32_t vertices = 3u, std::uint32_t instances = 1u, bool depthFail = false, const std::function<void(AgcDriver::Graphics::State&)>& alter = {}, const std::function<void(AgcDriver::Pm4::DrawParameters&)>& alterDraw = {}, std::span<const std::array<float, 4>> vertexData = Vertices, std::span<const std::uint32_t> vertexCode = VertexCode, std::span<const std::uint32_t> pixelData = {}, std::span<const ShaderRecompiler::MemoryRegion> pixelSrt = {}, const std::function<void(ShaderRecompiler::RecompileResult&)>& alterVertex = {}) {
void Draw(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> pixelCode, std::uint32_t shaderControl, const VkStencilOpState& stencil, std::uint32_t vertices = 3u, std::uint32_t instances = 1u, bool depthFail = false, const std::function<void(AgcDriver::Graphics::State&)>& alter = {}, const std::function<void(AgcDriver::Pm4::DrawParameters&)>& alterDraw = {}, std::span<const std::array<float, 4>> vertexData = Vertices, std::span<const std::uint32_t> vertexCode = VertexCode, std::span<const std::uint32_t> pixelData = {}, std::span<const ShaderRecompiler::MemoryRegion> pixelSrt = {}, const std::function<void(ShaderRecompiler::RecompileResult&)>& alterVertex = {}, std::uint32_t pixelPushBytes = 0u) {
Pixels.fill(Kept);
auto target = device.Target();
std::vector<std::uint32_t> capabilities(target.supportedCapabilities.begin(), target.supportedCapabilities.end());
@@ -113,11 +113,13 @@ void Draw(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> pixelC
auto pixelUserData = pixelData.empty() ? std::vector<std::uint32_t>(8, 0u) : std::vector<std::uint32_t>(pixelData.begin(), pixelData.end());
std::vector<ShaderRecompiler::MemoryRegion> pixelMemory{{reinterpret_cast<std::uintptr_t>(pixelCode.data()), std::as_bytes(pixelCode)}};
pixelMemory.insert(pixelMemory.end(), pixelSrt.begin(), pixelSrt.end());
auto pixelLayout = PixelPushLayout(vertexPush, target);
if (pixelPushBytes != 0u) pixelLayout.pushConstantSizeBytes = pixelPushBytes;
ShaderRecompiler::RecompileRequest fragment{
{ShaderStage::Fragment, reinterpret_cast<std::uintptr_t>(pixelCode.data()), pixelCode, 0, {}},
{WaveSize, 0, pixelUserData, std::nullopt, PixelInfo(shaderControl), std::nullopt, pixelMemory},
target,
PixelPushLayout(vertexPush, target)
pixelLayout
};
fragment.useCache = false;
const auto pixelResult = ShaderRecompiler::Recompile(fragment);
@@ -350,6 +352,14 @@ int main() {
const std::array<std::uint32_t, 1> colorData{0x3f800000u};
Draw(*device, userColor, exported, replace(), 3u, 1u, false, {}, {}, Vertices, VertexCode, colorData);
Check(all, "dynamic reference preserves the adjacent shader-data push payload");
auto bufferColor = std::vector<std::uint32_t>(alternating.begin(), alternating.end());
*std::find(bufferColor.begin(), bufferColor.end(), 0x7e0e02f2u) = 0x7e0e0208u;
bufferColor.insert(bufferColor.begin(), {0xf4200200u, 0xfa000000u, 0xbf8cc07fu});
alignas(256) const std::array<std::uint32_t, 4> colorBuffer{0x3f800000u, 0u, 0u, 0u};
const auto colorDescriptor = BufferDescriptor(colorBuffer.data(), 4u, static_cast<std::uint32_t>(colorBuffer.size()));
const std::array<ShaderRecompiler::MemoryRegion, 1> colorMemory{{{reinterpret_cast<std::uintptr_t>(colorBuffer.data()), std::as_bytes(std::span(colorBuffer))}}};
Draw(*device, bufferColor, exported, replace(), 3u, 1u, false, {}, {}, Vertices, VertexCode, colorDescriptor, colorMemory, {}, sizeof(std::uint32_t));
Check(all, "dynamic reference keeps guest buffer offsets in shader data outside the push constants");
auto guarded = std::vector<std::uint32_t>(alternating.begin(), alternating.end());
*std::find(guarded.begin(), guarded.end(), 0x7e0e02f2u) = 0x7e0e020cu;
guarded.insert(guarded.begin(), {0xf4040280u, 0xfa000018u, 0xf4000305u, 0xfa000000u, 0xbf8cc07fu});
@@ -369,6 +379,29 @@ int main() {
catch (const std::exception& error) { faultThrown = std::string(error.what()).find("BDA") != std::string::npos; }
Require(faultThrown, "poisoned readonly SRT replay did not throw a host fault");
Require(std::ranges::all_of(Pixels, [](std::byte value) { return value == Kept; }), "poisoned replay published guest color results before its fault");
alignas(256) std::array<std::uint32_t, Width + 1u> columnValues{};
alignas(256) std::array<float, Width> columnTexels{};
columnValues[0] = 0x2fu;
for (std::uint32_t x = 0; x < Width; ++x) {
columnValues[x + 1u] = 0x30u + x;
columnTexels[x] = 1.0f;
}
constexpr std::array<std::uint32_t, 15> bufferImageCode{
0x7e060f00u, 0x7e080280u, 0xf0001108u, 0x00010503u, 0xe0302000u, 0x80000603u, 0xe0302000u, 0x80030803u,
0xbf8c3f70u, 0x7e0e02f2u, 0xf8000082u, 0x00000600u, 0xf800180fu, 0x07050805u, 0xbf810000u,
};
const auto columnBuffer = BufferDescriptor(columnValues.data() + 1, 4u, Width);
const auto alignedBuffer = BufferDescriptor(columnValues.data(), 4u, Width + 1u);
const auto texelAddress = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(columnTexels.data()));
const std::array<std::uint32_t, 8> columnTexture{static_cast<std::uint32_t>(texelAddress >> 8u), static_cast<std::uint32_t>((texelAddress >> 40u) & 0xffu) | (22u << 20u) | (((Width - 1u) & 3u) << 30u), (Width - 1u) >> 2u, 0xfacu | (9u << 28u), 0u, 0u, 0u, 0u};
std::vector<std::uint32_t> bufferImageData(columnBuffer.begin(), columnBuffer.end());
bufferImageData.insert(bufferImageData.end(), columnTexture.begin(), columnTexture.end());
bufferImageData.insert(bufferImageData.end(), alignedBuffer.begin(), alignedBuffer.end());
Draw(*device, PlainPixelCode, plain, replace(0u));
Draw(*device, bufferImageCode, exported, replace(), 3u, 1u, false, {}, {}, Vertices, VertexCode, bufferImageData);
Check(all, "a replayed export beside an image draws the image's texels");
Draw(*device, PlainPixelCode, plain, equal(0x35u));
Check([](std::uint32_t x, std::uint32_t) { return x == 5u; }, "a replayed export beside an image reads its guest buffer at a 4-byte offset");
std::puts("stencil replay tests passed");
return 0;
} catch (const std::exception& error) {
+3 -1
View File
@@ -8,9 +8,11 @@ if(BUILD_TESTING)
target_include_directories(fiber_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(fiber_tests PRIVATE libSceFiber libc)
add_test(NAME fiber COMMAND fiber_tests)
add_test(NAME fiber_contextless COMMAND fiber_tests --contextless)
add_asan_test_options(fiber detect_stack_use_after_return=0)
add_asan_test_options(fiber_contextless detect_stack_use_after_return=0)
if(WIN32)
set_tests_properties(fiber PROPERTIES ENVIRONMENT_MODIFICATION
set_tests_properties(fiber fiber_contextless PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceFiber>")
endif()
endif()
+29 -17
View File
@@ -106,14 +106,17 @@ static bool TraceFibers() {
static void CompletePendingSuspend() {
auto& thread = ThreadState();
if (thread.pendingSuspend) {
thread.pendingSuspend->state.store(FiberState::Suspended, std::memory_order_release);
auto* fiber = thread.pendingSuspend;
const auto state = fiber->context ? FiberState::Suspended : FiberState::Idle;
if (!fiber->context) fiber->savedStack = nullptr;
fiber->state.store(state, std::memory_order_release);
thread.pendingSuspend = nullptr;
}
}
#ifdef _WIN32
extern "C" void Aps5FiberSwitchStack_nid_no_patch(void** save, void* load);
extern "C" void Aps5FiberSwitchStack_nid_no_patch(void** save, void** load, Fiber* start);
extern "C" void Aps5FiberTrampoline_nid_no_patch();
asm(R"(
@@ -143,7 +146,12 @@ Aps5FiberSwitchStack_nid_no_patch:
stmxcsr 0xa0(%rsp)
fnstcw 0xa4(%rsp)
mov %rsp, (%rcx)
mov %rdx, %rsp
mov (%rdx), %rsp
test %r8, %r8
jz 1f
mov %r8, %r12
jmp Aps5FiberTrampoline_nid_no_patch
1:
movaps 0x00(%rsp), %xmm6
movaps 0x10(%rsp), %xmm7
movaps 0x20(%rsp), %xmm8
@@ -214,7 +222,7 @@ static void UnpinStack(const void* context, std::uint64_t bytes) {
#else
extern "C" void Aps5FiberSwitchStack_nid_no_patch(void** save, void* load);
extern "C" void Aps5FiberSwitchStack_nid_no_patch(void** save, void** load, Fiber* start);
extern "C" void Aps5FiberTrampoline_nid_no_patch();
asm(".text\n"
@@ -229,7 +237,12 @@ asm(".text\n"
stmxcsr 0(%rsp)
fnstcw 4(%rsp)
mov %rsp, (%rdi)
mov %rsi, %rsp
mov (%rsi), %rsp
test %rdx, %rdx
jz 1f
mov %rdx, %r12
jmp )" APS5_ASM_SYMBOL("Aps5FiberTrampoline_nid_no_patch") R"(
1:
ldmxcsr 0(%rsp)
fldcw 4(%rsp)
add $8, %rsp
@@ -337,6 +350,7 @@ static std::uint8_t* StackTop(const Fiber* fiber) {
}
static std::uint64_t* SizedWindow(const Fiber* fiber) {
if (!fiber->context) return nullptr;
return reinterpret_cast<std::uint64_t*>(StackTop(fiber) - fiber->contextSize);
}
@@ -362,6 +376,10 @@ static Fiber* AsFiber(FiberObject* object) {
extern "C" [[noreturn]] void Aps5FiberMain_nid_no_patch(Fiber* fiber) {
CompletePendingSuspend();
if (!fiber->context && fiber->setFpuRegisters) {
_mm_setcsr(FIBER_DEFAULT_MXCSR);
asm volatile("fldcw %0" : : "m"(FIBER_DEFAULT_FPU_CONTROL));
}
fiber->entry(fiber->argOnInitialize, ThreadState().transfer);
throw std::runtime_error(std::string("sceFiber: entry function of fiber '") + fiber->name + "' returned");
}
@@ -392,13 +410,7 @@ static bool AcquireForResume(Fiber* target) {
}
if (state != FiberState::Idle && state != FiberState::Suspended) return false;
if (target->state.compare_exchange_weak(state, FiberState::Running, std::memory_order_acq_rel)) {
if (state == FiberState::Idle) {
if (!target->context) {
target->state.store(FiberState::Idle, std::memory_order_release);
throw std::runtime_error(std::string("sceFiber: fiber '") + target->name + "' has no context; running a fiber on the stack of its caller is not supported");
}
PrepareInitialStack(target);
}
if (state == FiberState::Idle && target->context) PrepareInitialStack(target);
return true;
}
}
@@ -446,7 +458,7 @@ static int32_t AcquireWithContext(Fiber* target, void* addr_context, uint64_t si
static void Resume(Fiber* target, void** save, std::uint64_t argOnRun) {
const auto* frame = static_cast<const InitialFrame*>(target->savedStack);
if (frame->returnAddress == 0) {
if (target->context && frame->returnAddress == 0) {
std::fprintf(stderr, "[fiber] resuming '%s' with a wiped context: saved=%p context=%p size=0x%llx\n", target->name, target->savedStack,
static_cast<void*>(target->context), static_cast<unsigned long long>(target->contextSize));
const auto* words = static_cast<const std::uint64_t*>(target->savedStack);
@@ -456,8 +468,8 @@ static void Resume(Fiber* target, void** save, std::uint64_t argOnRun) {
}
ThreadState().current = target;
ThreadState().transfer = argOnRun;
SetBounds(FiberBounds(target));
Aps5FiberSwitchStack_nid_no_patch(save, target->savedStack);
SetBounds(target->context ? FiberBounds(target) : ThreadState().threadBounds);
Aps5FiberSwitchStack_nid_no_patch(save, target->context ? &target->savedStack : &ThreadState().threadStack, target->context ? nullptr : target);
}
static int32_t InitializeFiber(FiberObject* object, const char* name, GuestFiberEntry entry, uint64_t arg_on_initialize, void* addr_context, uint64_t size_context, uint32_t flags) {
@@ -558,7 +570,7 @@ int32_t APS5_VABI sceFiberReturnToThread(uint64_t arg_on_return, uint64_t* arg_o
ThreadState().current = nullptr;
ThreadState().transfer = arg_on_return;
SetBounds(ThreadState().threadBounds);
Aps5FiberSwitchStack_nid_no_patch(&self->savedStack, ThreadState().threadStack);
Aps5FiberSwitchStack_nid_no_patch(&self->savedStack, &ThreadState().threadStack, nullptr);
CompletePendingSuspend();
if (arg_on_run) *arg_on_run = ThreadState().transfer;
return SCE_OK;
@@ -581,7 +593,7 @@ int32_t APS5_VABI sceFiberGetInfo(FiberObject* object, FiberInfo* fiber_info) {
fiber_info->size_context = fiber->contextSize;
std::memcpy(fiber_info->name, fiber->name, sizeof(fiber_info->name));
fiber_info->size_context_margin = static_cast<uint64_t>(-1);
if (fiber->contextSizeCheck) {
if (fiber->contextSizeCheck && fiber->context) {
const auto* words = SizedWindow(fiber);
const auto* end = words + fiber->contextSize / sizeof(std::uint64_t);
fiber_info->size_context_margin = static_cast<uint64_t>(std::find_if(words, end, [](std::uint64_t word) { return word != FIBER_CONTEXT_FILL; }) - words) * sizeof(std::uint64_t);
@@ -6,6 +6,9 @@
extern "C" {
std::int32_t APS5_VABI FT_Outline_GetInsideBorder_nid_postfix(const FreeType::GuestOutline* outline);
std::int32_t APS5_VABI FT_Outline_GetOutsideBorder_nid_postfix(const FreeType::GuestOutline* outline);
int APS5_VABI FT_Init_FreeType_nid_postfix(FreeType::GuestLibrary** alibrary);
int APS5_VABI FT_Done_FreeType_nid_postfix(FreeType::GuestLibrary* library);
int APS5_VABI FT_New_Face_nid_postfix(FreeType::GuestLibrary* library, const char* filepathname, std::int64_t faceIndex, FreeType::GuestFaceRec** aface);
@@ -0,0 +1,68 @@
#include "prx/libSceFreeType/include/Api.hpp"
#include <algorithm>
#include <bit>
#include <cstdint>
#include <stdexcept>
namespace {
bool Clockwise(const FreeType::GuestOutline* outline) {
if (!outline || outline->n_points == 0) return true;
if (!outline->points) throw std::invalid_argument("FreeType outline border: null points");
auto minimum = outline->points[0];
auto maximum = minimum;
for (unsigned i = 1; i < outline->n_points; ++i) {
const auto point = outline->points[i];
minimum.x = std::min(minimum.x, point.x);
minimum.y = std::min(minimum.y, point.y);
maximum.x = std::max(maximum.x, point.x);
maximum.y = std::max(maximum.y, point.y);
}
if (minimum.x == maximum.x || minimum.y == maximum.y) return false;
constexpr std::int64_t limit = 0x1000000;
if (minimum.x < -limit || minimum.y < -limit || maximum.x > limit || maximum.y > limit) return false;
if (outline->n_contours == 0) return false;
if (!outline->contours) throw std::invalid_argument("FreeType outline border: null contours");
unsigned first = 0;
for (unsigned contour = 0; contour < outline->n_contours; ++contour) {
const unsigned last = outline->contours[contour];
if (last < first || last >= outline->n_points)
throw std::invalid_argument("FreeType outline border: invalid contour endpoint");
first = last + 1;
}
const auto magnitude = [](std::int64_t value) { return static_cast<std::uint32_t>(value < 0 ? -value : value); };
const auto shift = [](std::uint32_t value) { const auto bits = std::bit_width(value); return bits > 15 ? bits - 15 : 0; };
const auto xShift = shift(magnitude(maximum.x) | magnitude(minimum.x));
const auto yShift = shift(static_cast<std::uint32_t>(maximum.y - minimum.y));
std::int64_t area = 0;
first = 0;
for (unsigned contour = 0; contour < outline->n_contours; ++contour) {
const unsigned last = outline->contours[contour];
auto previous = outline->points[last];
previous.x >>= xShift;
previous.y >>= yShift;
for (unsigned i = first; i <= last; ++i) {
auto current = outline->points[i];
current.x >>= xShift;
current.y >>= yShift;
area += (current.y - previous.y) * (current.x + previous.x);
previous = current;
}
first = last + 1;
}
return area < 0;
}
}
extern "C" {
std::int32_t APS5_VABI FT_Outline_GetInsideBorder_nid_postfix(const FreeType::GuestOutline* outline) {
return Clockwise(outline) ? 1 : 0;
}
std::int32_t APS5_VABI FT_Outline_GetOutsideBorder_nid_postfix(const FreeType::GuestOutline* outline) {
return Clockwise(outline) ? 0 : 1;
}
}
@@ -0,0 +1,38 @@
add_library(libSceFreeTypeOl SHARED EXCLUDE_FROM_ALL Export.cpp ../libSceFreeType/src/OutlineBorders.cpp)
target_include_directories(libSceFreeTypeOl PRIVATE ${LIBS_INCLUDE_DIR})
configure_windows_unwind(libSceFreeTypeOl)
if(BUILD_TESTING)
add_test_executable(guest_freetype_outline_border_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeOutlineBorder.cpp)
target_include_directories(guest_freetype_outline_border_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_freetype_outline_border_tests PRIVATE freetype ${CMAKE_DL_LIBS})
if(WIN32)
target_link_libraries(guest_freetype_outline_border_tests PRIVATE libc)
endif()
configure_windows_unwind(guest_freetype_outline_border_tests)
add_custom_target(freetype_outline_border_patched DEPENDS
${PATCHED_DIR}/libSceFreeTypeOl.prx ${PATCHED_DIR}/libSceFreeTypeOt.prx ${PATCHED_DIR}/libkernel.prx ${LIBC_PATCHED})
add_dependencies(guest_freetype_outline_border_tests freetype_outline_border_patched)
add_test(NAME guest_freetype_outline_border_reference COMMAND guest_freetype_outline_border_tests reference)
foreach(module IN ITEMS Ol Ot)
foreach(border IN ITEMS inside outside)
add_test(NAME guest_freetype_outline_border_${module}_${border}
COMMAND guest_freetype_outline_border_tests $<TARGET_FILE:libSceFreeType${module}> ${border} raw)
add_test(NAME guest_freetype_outline_border_${module}_${border}_patched
COMMAND guest_freetype_outline_border_tests ${PATCHED_DIR}/libSceFreeType${module}.prx ${border} patched)
set_tests_properties(guest_freetype_outline_border_${module}_${border}
guest_freetype_outline_border_${module}_${border}_patched PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_freetype_outline_border_${module}_${border} PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceFreeTypeOl>")
set_tests_properties(guest_freetype_outline_border_${module}_${border}_patched PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:${PATCHED_DIR}")
endif()
endforeach()
endforeach()
set_tests_properties(guest_freetype_outline_border_reference PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_freetype_outline_border_reference PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceFreeTypeOl>")
endif()
endif()
@@ -1 +1,3 @@
#pragma once
#include "prx/libSceFreeType/include/Api.hpp"
@@ -0,0 +1,3 @@
add_library(libSceFreeTypeOt SHARED EXCLUDE_FROM_ALL Export.cpp ../libSceFreeType/src/OutlineBorders.cpp)
target_include_directories(libSceFreeTypeOt PRIVATE ${LIBS_INCLUDE_DIR})
configure_windows_unwind(libSceFreeTypeOt)
+740 -1
View File
@@ -5,6 +5,745 @@
extern "C" {
APS5_DUMMY_FUN
int APS5_VABI sceFreeTypeOtUnknown_UD7IFu7ML2U_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("UD7IFu7ML2U", sceFreeTypeOtUnknown_UD7IFu7ML2U_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_2T7lbS_CSzA_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("2T7lbS+CSzA", sceFreeTypeOtUnknown_2T7lbS_CSzA_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_MGEIQsujKaY_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("MGEIQsujKaY", sceFreeTypeOtUnknown_MGEIQsujKaY_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_mkmgcRnAJog_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("mkmgcRnAJog", sceFreeTypeOtUnknown_mkmgcRnAJog_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_m18bTUNF_0g_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("m18bTUNF-0g", sceFreeTypeOtUnknown_m18bTUNF_0g_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_vTZ34rw1sfg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("vTZ34rw1sfg", sceFreeTypeOtUnknown_vTZ34rw1sfg_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_PvCNusrp9xQ_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("PvCNusrp9xQ", sceFreeTypeOtUnknown_PvCNusrp9xQ_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_dlM4ASZdcjk_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("dlM4ASZdcjk", sceFreeTypeOtUnknown_dlM4ASZdcjk_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_Ev3BqJ_U1Fg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Ev3BqJ-U1Fg", sceFreeTypeOtUnknown_Ev3BqJ_U1Fg_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_ZhtAXOoLNyo_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("ZhtAXOoLNyo", sceFreeTypeOtUnknown_ZhtAXOoLNyo_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_73vg_nRH68g_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("73vg+nRH68g", sceFreeTypeOtUnknown_73vg_nRH68g_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_wBKAYO7yEXw_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("wBKAYO7yEXw", sceFreeTypeOtUnknown_wBKAYO7yEXw_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_nSIux4OQyJM_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("nSIux4OQyJM", sceFreeTypeOtUnknown_nSIux4OQyJM_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_Dr3F3qrSQ2c_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Dr3F3qrSQ2c", sceFreeTypeOtUnknown_Dr3F3qrSQ2c_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_avLzN0tTxLI_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("avLzN0tTxLI", sceFreeTypeOtUnknown_avLzN0tTxLI_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_0rKmI1ZbMAc_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("0rKmI1ZbMAc", sceFreeTypeOtUnknown_0rKmI1ZbMAc_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_BBRhYfYBDgU_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("BBRhYfYBDgU", sceFreeTypeOtUnknown_BBRhYfYBDgU_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_c1nbr8oSWlg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("c1nbr8oSWlg", sceFreeTypeOtUnknown_c1nbr8oSWlg_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_AVEEb57HhUw_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("AVEEb57HhUw", sceFreeTypeOtUnknown_AVEEb57HhUw_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_x8y0qzLiYOU_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("x8y0qzLiYOU", sceFreeTypeOtUnknown_x8y0qzLiYOU_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_VW4GD_TaIUc_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("VW4GD-TaIUc", sceFreeTypeOtUnknown_VW4GD_TaIUc_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_GiuNQjoj4fs_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("GiuNQjoj4fs", sceFreeTypeOtUnknown_GiuNQjoj4fs_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_5qqbXOGlHw4_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("5qqbXOGlHw4", sceFreeTypeOtUnknown_5qqbXOGlHw4_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_4LY5pyZ5IQk_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("4LY5pyZ5IQk", sceFreeTypeOtUnknown_4LY5pyZ5IQk_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_zE0V_0aouZ4_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("zE0V+0aouZ4", sceFreeTypeOtUnknown_zE0V_0aouZ4_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_LsyswTUNTRs_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("LsyswTUNTRs", sceFreeTypeOtUnknown_LsyswTUNTRs_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_AfL_nqNC5fY_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("AfL-nqNC5fY", sceFreeTypeOtUnknown_AfL_nqNC5fY_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_WIvTHYs7ZQQ_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("WIvTHYs7ZQQ", sceFreeTypeOtUnknown_WIvTHYs7ZQQ_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_yrtsLmTfa48_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("yrtsLmTfa48", sceFreeTypeOtUnknown_yrtsLmTfa48_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_tCMhTh7h8D4_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("tCMhTh7h8D4", sceFreeTypeOtUnknown_tCMhTh7h8D4_nid_no_patch);
int APS5_VABI sceFreeTypeOtNewSize_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("zyezaHweY3U", sceFreeTypeOtNewSize_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_C_PpdS5zris_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("C-PpdS5zris", sceFreeTypeOtUnknown_C_PpdS5zris_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_g8eLxL9VAhU_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("g8eLxL9VAhU", sceFreeTypeOtUnknown_g8eLxL9VAhU_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_OeNH41MA2Vs_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("OeNH41MA2Vs", sceFreeTypeOtUnknown_OeNH41MA2Vs_nid_no_patch);
int APS5_VABI sceFreeTypeOtOutlineCopy_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Bhm8Injl0KA", sceFreeTypeOtOutlineCopy_nid_no_patch);
int APS5_VABI sceFreeTypeOtOutlineDecompose_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("++lgV9pRsO4", sceFreeTypeOtOutlineDecompose_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_BqGJsJcFYaM_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("BqGJsJcFYaM", sceFreeTypeOtUnknown_BqGJsJcFYaM_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_umIqupy8UU0_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("umIqupy8UU0", sceFreeTypeOtUnknown_umIqupy8UU0_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_l6Cf9rb9fZQ_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("l6Cf9rb9fZQ", sceFreeTypeOtUnknown_l6Cf9rb9fZQ_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_BgUMBGvhATU_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("BgUMBGvhATU", sceFreeTypeOtUnknown_BgUMBGvhATU_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_0Aal_TIC4so_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("0Aal-TIC4so", sceFreeTypeOtUnknown_0Aal_TIC4so_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_4RRjK_Wd1lU_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("4RRjK-Wd1lU", sceFreeTypeOtUnknown_4RRjK_Wd1lU_nid_no_patch);
int APS5_VABI sceFreeTypeOtOutlineGetOrientation_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("weVsBCT+TTQ", sceFreeTypeOtOutlineGetOrientation_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_QH2emcgeFck_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("QH2emcgeFck", sceFreeTypeOtUnknown_QH2emcgeFck_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_NG7OgYsgcF8_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("NG7OgYsgcF8", sceFreeTypeOtUnknown_NG7OgYsgcF8_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_9km02mPqPJE_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("9km02mPqPJE", sceFreeTypeOtUnknown_9km02mPqPJE_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_0Lv6K1d47jg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("0Lv6K1d47jg", sceFreeTypeOtUnknown_0Lv6K1d47jg_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_8fAMdZCnewA_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("8fAMdZCnewA", sceFreeTypeOtUnknown_8fAMdZCnewA_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_LsB538_IPrE_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("LsB538+IPrE", sceFreeTypeOtUnknown_LsB538_IPrE_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_hz_J3hohXW8_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("hz-J3hohXW8", sceFreeTypeOtUnknown_hz_J3hohXW8_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_KVbmKCGGowg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("KVbmKCGGowg", sceFreeTypeOtUnknown_KVbmKCGGowg_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_hF3stKUWbWg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("hF3stKUWbWg", sceFreeTypeOtUnknown_hF3stKUWbWg_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_p5GNJaXO2yE_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("p5GNJaXO2yE", sceFreeTypeOtUnknown_p5GNJaXO2yE_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_8cnVx1yH9_I_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("8cnVx1yH9-I", sceFreeTypeOtUnknown_8cnVx1yH9_I_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_HVSaZbxYi2s_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("HVSaZbxYi2s", sceFreeTypeOtUnknown_HVSaZbxYi2s_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_qAPXM5DN62Q_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("qAPXM5DN62Q", sceFreeTypeOtUnknown_qAPXM5DN62Q_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_5ipBcLYVc2k_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("5ipBcLYVc2k", sceFreeTypeOtUnknown_5ipBcLYVc2k_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_8A0cAU4yBVY_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("8A0cAU4yBVY", sceFreeTypeOtUnknown_8A0cAU4yBVY_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_z9Wsaf34JA4_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("z9Wsaf34JA4", sceFreeTypeOtUnknown_z9Wsaf34JA4_nid_no_patch);
int APS5_VABI sceFreeTypeOtRequestSize_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("PaDk9bdmdhU", sceFreeTypeOtRequestSize_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_Z8KG6700sw4_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Z8KG6700sw4", sceFreeTypeOtUnknown_Z8KG6700sw4_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_uK7vGxAQVxM_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("uK7vGxAQVxM", sceFreeTypeOtUnknown_uK7vGxAQVxM_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_zetC8Tp1S8w_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("zetC8Tp1S8w", sceFreeTypeOtUnknown_zetC8Tp1S8w_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_DAmwG6U7YIc_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("DAmwG6U7YIc", sceFreeTypeOtUnknown_DAmwG6U7YIc_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_Yu9oXRVmTTo_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Yu9oXRVmTTo", sceFreeTypeOtUnknown_Yu9oXRVmTTo_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_ihIHD8BTNxI_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("ihIHD8BTNxI", sceFreeTypeOtUnknown_ihIHD8BTNxI_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_yCEJIsjIOO8_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("yCEJIsjIOO8", sceFreeTypeOtUnknown_yCEJIsjIOO8_nid_no_patch);
int APS5_VABI sceFreeTypeOtSin_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("L1uHkWTazkI", sceFreeTypeOtSin_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_C7N2GuwqTA0_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("C7N2GuwqTA0", sceFreeTypeOtUnknown_C7N2GuwqTA0_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_JgtSc0g3G6E_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("JgtSc0g3G6E", sceFreeTypeOtUnknown_JgtSc0g3G6E_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_gVf7xRLdWm8_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("gVf7xRLdWm8", sceFreeTypeOtUnknown_gVf7xRLdWm8_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_FHdeCFVf_f8_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("FHdeCFVf-f8", sceFreeTypeOtUnknown_FHdeCFVf_f8_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_Qmgc0XACzRg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Qmgc0XACzRg", sceFreeTypeOtUnknown_Qmgc0XACzRg_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_0rGx_oFMLyk_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("0rGx-oFMLyk", sceFreeTypeOtUnknown_0rGx_oFMLyk_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_thA48N_0VwQ_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("thA48N-0VwQ", sceFreeTypeOtUnknown_thA48N_0VwQ_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_jl0VxYZb8_o_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("jl0VxYZb8-o", sceFreeTypeOtUnknown_jl0VxYZb8_o_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_Er0Lin_u20o_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Er0Lin-u20o", sceFreeTypeOtUnknown_Er0Lin_u20o_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_KHj92a3WDn4_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("KHj92a3WDn4", sceFreeTypeOtUnknown_KHj92a3WDn4_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_tDssQCH2syY_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("tDssQCH2syY", sceFreeTypeOtUnknown_tDssQCH2syY_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_dXJK59vXRxs_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("dXJK59vXRxs", sceFreeTypeOtUnknown_dXJK59vXRxs_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_BB4gl7SgLdk_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("BB4gl7SgLdk", sceFreeTypeOtUnknown_BB4gl7SgLdk_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_Fv8MWCUH8k8_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Fv8MWCUH8k8", sceFreeTypeOtUnknown_Fv8MWCUH8k8_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_RzgDJTjQst4_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("RzgDJTjQst4", sceFreeTypeOtUnknown_RzgDJTjQst4_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_4yt9cD1qcEI_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("4yt9cD1qcEI", sceFreeTypeOtUnknown_4yt9cD1qcEI_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_u6Xquj1bFwc_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("u6Xquj1bFwc", sceFreeTypeOtUnknown_u6Xquj1bFwc_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown__Xm1cLEpBec_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("-Xm1cLEpBec", sceFreeTypeOtUnknown__Xm1cLEpBec_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_uDXTnGbAz44_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("uDXTnGbAz44", sceFreeTypeOtUnknown_uDXTnGbAz44_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_iruPsrXCN7U_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("iruPsrXCN7U", sceFreeTypeOtUnknown_iruPsrXCN7U_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_a7RlARfexoo_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("a7RlARfexoo", sceFreeTypeOtUnknown_a7RlARfexoo_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_jX1zq1i8KfI_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("jX1zq1i8KfI", sceFreeTypeOtUnknown_jX1zq1i8KfI_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_U_0WKVAePww_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("U+0WKVAePww", sceFreeTypeOtUnknown_U_0WKVAePww_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_dlbnv6Wass8_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("dlbnv6Wass8", sceFreeTypeOtUnknown_dlbnv6Wass8_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_cmd41395g7Y_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("cmd41395g7Y", sceFreeTypeOtUnknown_cmd41395g7Y_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_SX5I_XzH8HI_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("SX5I+XzH8HI", sceFreeTypeOtUnknown_SX5I_XzH8HI_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_Bmi8PdvsM3M_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Bmi8PdvsM3M", sceFreeTypeOtUnknown_Bmi8PdvsM3M_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_jJh4SMt8NFQ_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("jJh4SMt8NFQ", sceFreeTypeOtUnknown_jJh4SMt8NFQ_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_1z6bBg1qBfU_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("1z6bBg1qBfU", sceFreeTypeOtUnknown_1z6bBg1qBfU_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_mF0B64O954g_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("mF0B64O954g", sceFreeTypeOtUnknown_mF0B64O954g_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_c002A4zTOgA_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("c002A4zTOgA", sceFreeTypeOtUnknown_c002A4zTOgA_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_mcT796Qgj_g_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("mcT796Qgj+g", sceFreeTypeOtUnknown_mcT796Qgj_g_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_b5gZp_r3WWI_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("b5gZp-r3WWI", sceFreeTypeOtUnknown_b5gZp_r3WWI_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_S_n0Md8Rjfw_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("S+n0Md8Rjfw", sceFreeTypeOtUnknown_S_n0Md8Rjfw_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_6a1p3QgXvo0_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("6a1p3QgXvo0", sceFreeTypeOtUnknown_6a1p3QgXvo0_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_JFSCOXTEvfg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("JFSCOXTEvfg", sceFreeTypeOtUnknown_JFSCOXTEvfg_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_xejRyEbsLcw_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("xejRyEbsLcw", sceFreeTypeOtUnknown_xejRyEbsLcw_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_R1mZutnlTiM_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("R1mZutnlTiM", sceFreeTypeOtUnknown_R1mZutnlTiM_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_4J4Baf8ana8_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("4J4Baf8ana8", sceFreeTypeOtUnknown_4J4Baf8ana8_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_Az4H9FlfxCA_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Az4H9FlfxCA", sceFreeTypeOtUnknown_Az4H9FlfxCA_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_xj3tNJptfLI_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("xj3tNJptfLI", sceFreeTypeOtUnknown_xj3tNJptfLI_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_TPKhRt1QIeE_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("TPKhRt1QIeE", sceFreeTypeOtUnknown_TPKhRt1QIeE_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_6_yXRlVrjbc_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("6+yXRlVrjbc", sceFreeTypeOtUnknown_6_yXRlVrjbc_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown__RLa_6fbshA_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("+RLa-6fbshA", sceFreeTypeOtUnknown__RLa_6fbshA_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_pBqh_Uh_gns_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("pBqh-Uh-gns", sceFreeTypeOtUnknown_pBqh_Uh_gns_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_2rcvKZfm5rI_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("2rcvKZfm5rI", sceFreeTypeOtUnknown_2rcvKZfm5rI_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_7cIS2cFG3PE_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("7cIS2cFG3PE", sceFreeTypeOtUnknown_7cIS2cFG3PE_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_LfyUi_LkdCk_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("LfyUi-LkdCk", sceFreeTypeOtUnknown_LfyUi_LkdCk_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_rzIWh7FJnmU_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("rzIWh7FJnmU", sceFreeTypeOtUnknown_rzIWh7FJnmU_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_cKndiZnt0EA_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("cKndiZnt0EA", sceFreeTypeOtUnknown_cKndiZnt0EA_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_eqGCaeizvWM_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("eqGCaeizvWM", sceFreeTypeOtUnknown_eqGCaeizvWM_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_RvFx7dv9ThU_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("RvFx7dv9ThU", sceFreeTypeOtUnknown_RvFx7dv9ThU_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_Po8kyd3DlIw_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Po8kyd3DlIw", sceFreeTypeOtUnknown_Po8kyd3DlIw_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_B3uU5aDZn94_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("B3uU5aDZn94", sceFreeTypeOtUnknown_B3uU5aDZn94_nid_no_patch);
int APS5_VABI sceFreeTypeOtTan_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("dojyaLKzKN0", sceFreeTypeOtTan_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_08_mhD_AAds_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("08-mhD-AAds", sceFreeTypeOtUnknown_08_mhD_AAds_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_64XMq6sDxUo_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("64XMq6sDxUo", sceFreeTypeOtUnknown_64XMq6sDxUo_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_4xG87W9dTSg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("4xG87W9dTSg", sceFreeTypeOtUnknown_4xG87W9dTSg_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_4_SScYDT5Pg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("4+SScYDT5Pg", sceFreeTypeOtUnknown_4_SScYDT5Pg_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_2rmvbvOCyjY_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("2rmvbvOCyjY", sceFreeTypeOtUnknown_2rmvbvOCyjY_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_1jmC2QcpRl0_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("1jmC2QcpRl0", sceFreeTypeOtUnknown_1jmC2QcpRl0_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_HMcNe70D9TI_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("HMcNe70D9TI", sceFreeTypeOtUnknown_HMcNe70D9TI_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_6wJwUIVwAxg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("6wJwUIVwAxg", sceFreeTypeOtUnknown_6wJwUIVwAxg_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_PrZtZyFwM6c_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("PrZtZyFwM6c", sceFreeTypeOtUnknown_PrZtZyFwM6c_nid_no_patch);
int APS5_VABI sceFreeTypeOtVectorUnit_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("3WpTQrvGUcU", sceFreeTypeOtVectorUnit_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_P6WOAeb0lJ8_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("P6WOAeb0lJ8", sceFreeTypeOtUnknown_P6WOAeb0lJ8_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_XGVABnpNksQ_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("XGVABnpNksQ", sceFreeTypeOtUnknown_XGVABnpNksQ_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_Ln9I6YfmYJU_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Ln9I6YfmYJU", sceFreeTypeOtUnknown_Ln9I6YfmYJU_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_7cVMwHmN308_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("7cVMwHmN308", sceFreeTypeOtUnknown_7cVMwHmN308_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_AEU2tkjdZ2w_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("AEU2tkjdZ2w", sceFreeTypeOtUnknown_AEU2tkjdZ2w_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_2lSRg9FfIOY_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("2lSRg9FfIOY", sceFreeTypeOtUnknown_2lSRg9FfIOY_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_fPrLu1YGOaw_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("fPrLu1YGOaw", sceFreeTypeOtUnknown_fPrLu1YGOaw_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_t3vCgnT5YL0_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("t3vCgnT5YL0", sceFreeTypeOtUnknown_t3vCgnT5YL0_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_8bkQ_YRnpOE_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("8bkQ+YRnpOE", sceFreeTypeOtUnknown_8bkQ_YRnpOE_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_q4qhGebYuGI_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("q4qhGebYuGI", sceFreeTypeOtUnknown_q4qhGebYuGI_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_ni_K_leoQNg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("ni-K-leoQNg", sceFreeTypeOtUnknown_ni_K_leoQNg_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_XfcgJB6V2a0_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("XfcgJB6V2a0", sceFreeTypeOtUnknown_XfcgJB6V2a0_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_TWzf5iykiGw_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("TWzf5iykiGw", sceFreeTypeOtUnknown_TWzf5iykiGw_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown__b4iVKArt94_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("-b4iVKArt94", sceFreeTypeOtUnknown__b4iVKArt94_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_uSNqxrARpEQ_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("uSNqxrARpEQ", sceFreeTypeOtUnknown_uSNqxrARpEQ_nid_no_patch);
}
@@ -1 +1,3 @@
#pragma once
#include "prx/libSceFreeType/include/Api.hpp"
+30 -15
View File
@@ -1,4 +1,5 @@
#include "prx/libSceNpGameIntent/include/Api.hpp"
#include <atomic>
#include <cstdint>
#include <cstddef>
#include <cstring>
@@ -8,43 +9,57 @@
namespace {
constexpr int NpGameIntentErrorNotInitialized = -2141898750;
constexpr int NpGameIntentErrorInvalidArgument = -2141898748;
constexpr int NpGameIntentErrorIntentNotFound = -2141898746;
constexpr int NpGameIntentErrorValueNotFound = -2141898745;
constexpr int NpGameIntentUserIdInvalid = -1;
std::atomic_bool& NpGameIntentInitialized() {
static std::atomic_bool initialized{false};
return initialized;
}
}
extern "C" {
int APS5_VABI sceNpGameIntentGetPropertyValueString(const NpGameIntentData* intentData, const char* key, char* valueBuf, size_t bufSize) {
if (intentData == nullptr || key == nullptr || valueBuf == nullptr || bufSize == 0) {
APS5_INVALID_ARG_EX;
}
if (!NpGameIntentInitialized()) {
return NpGameIntentErrorNotInitialized;
}
if (intentData == nullptr || key == nullptr || key[0] == '\0' || valueBuf == nullptr || bufSize == 0) {
return NpGameIntentErrorInvalidArgument;
}
valueBuf[0] = '\0';
return NpGameIntentErrorValueNotFound;
valueBuf[0] = '\0';
return NpGameIntentErrorValueNotFound;
}
int APS5_VABI sceNpGameIntentInitialize(const void* initParam) {
(void)initParam;
return 0;
(void)initParam;
NpGameIntentInitialized() = true;
return 0;
}
int APS5_VABI sceNpGameIntentReceiveIntent(NpGameIntentInfo* intentInfo) {
if (intentInfo == nullptr) {
APS5_INVALID_ARG_EX;
}
if (!NpGameIntentInitialized()) {
return NpGameIntentErrorNotInitialized;
}
if (intentInfo == nullptr || intentInfo->size != 0x4140) {
return NpGameIntentErrorInvalidArgument;
}
intentInfo->user_id = NpGameIntentUserIdInvalid;
std::memset(intentInfo->intent_type, 0, sizeof(intentInfo->intent_type));
std::memset(&intentInfo->intent_data, 0, sizeof(intentInfo->intent_data));
intentInfo->user_id = NpGameIntentUserIdInvalid;
std::memset(intentInfo->intent_type, 0, sizeof(intentInfo->intent_type));
std::memset(&intentInfo->intent_data, 0, sizeof(intentInfo->intent_data));
return NpGameIntentErrorIntentNotFound;
return NpGameIntentErrorIntentNotFound;
}
int APS5_VABI sceNpGameIntentTerminate(void) {
return 0;
NpGameIntentInitialized() = false;
return 0;
}
int APS5_VABI sceNpGameIntentInitParamInit(NpGameIntentInitParam* param) {
+95 -47
View File
@@ -1,6 +1,7 @@
#include "prx/libSceNpUtility/include/Api.hpp"
#include <cstdint>
#include <cstddef>
#include <condition_variable>
#include <cstring>
#include <mutex>
#include <unordered_map>
@@ -16,6 +17,7 @@ static constexpr int SCE_NP_COMMUNITY_ERROR_ABORTED = static_cast<int>(0x8055070
static constexpr int SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT = static_cast<int>(0x8055070C);
static constexpr int SCE_NP_COMMUNITY_ERROR_INVALID_ID = static_cast<int>(0x8055070E);
static constexpr int SCE_NP_COMMUNITY_ERROR_INVALID_TYPE = static_cast<int>(0x80550711);
static constexpr int SCE_NP_LOOKUP_ERROR_SYNCHRONOUS_REQUEST = static_cast<int>(0x80550307);
static constexpr int POLL_FINISHED = 0;
static constexpr int POLL_RUNNING = 1;
static constexpr std::size_t MAX_TITLE_CONTEXTS = 32;
@@ -23,7 +25,9 @@ static constexpr std::size_t MAX_REQUESTS = 32;
static constexpr int WORD_FILTER_TITLE_CTX_ID_OFFSET = 0x01000000;
static constexpr int WORD_FILTER_REQUEST_ID_OFFSET = 0x02000000;
static constexpr std::size_t WORD_FILTER_COMMENT_MAX_LENGTH = 0x400;
static constexpr std::uint64_t WORD_FILTER_ASYNC_PARAMETER_SIZE = 0x18;
static constexpr std::uint64_t ASYNC_REQUEST_PARAMETER_SIZE = 0x18;
static constexpr std::uint32_t RESOLVE_TIMEOUT_MIN = 1000000;
static constexpr std::uint32_t TRANSFER_TIMEOUT_MIN = 10000000;
namespace {
struct Request {
@@ -38,6 +42,7 @@ struct Request {
struct Service {
Service(int firstTitleContext, int firstRequest) : nextTitleContext(firstTitleContext), nextRequest(firstRequest) {}
std::mutex mutex;
std::condition_variable finished;
std::unordered_set<int> titleContexts;
std::unordered_map<int, Request> requests;
int nextTitleContext;
@@ -60,6 +65,7 @@ int DeleteTitleContext(Service& service, int titleCtxId) {
std::lock_guard lock(service.mutex);
if (!service.titleContexts.erase(titleCtxId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ID;
std::erase_if(service.requests, [titleCtxId](const auto& entry) { return entry.second.titleCtxId == titleCtxId; });
service.finished.notify_all();
return 0;
}
@@ -74,7 +80,9 @@ int CreateRequest(Service& service, int titleCtxId, bool async) {
int DeleteRequest(Service& service, int reqId) {
std::lock_guard lock(service.mutex);
return service.requests.erase(reqId) ? 0 : SCE_NP_COMMUNITY_ERROR_INVALID_ID;
if (!service.requests.erase(reqId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ID;
service.finished.notify_all();
return 0;
}
int StartOffline(Service& service, int reqId) {
@@ -86,9 +94,62 @@ int StartOffline(Service& service, int reqId) {
request->second.used = true;
request->second.finished = true;
request->second.result = SCE_NP_ERROR_SIGNED_OUT;
service.finished.notify_all();
return request->second.async ? 0 : SCE_NP_ERROR_SIGNED_OUT;
}
int AbortRequest(Service& service, int reqId) {
std::lock_guard lock(service.mutex);
const auto request = service.requests.find(reqId);
if (request == service.requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID;
request->second.aborted = true;
if (!request->second.finished) {
request->second.finished = true;
request->second.result = SCE_NP_COMMUNITY_ERROR_ABORTED;
service.finished.notify_all();
}
return 0;
}
int PollAsync(Service& service, int reqId, int* result, int synchronousError) {
if (!result) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT;
std::lock_guard lock(service.mutex);
const auto request = service.requests.find(reqId);
if (request == service.requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID;
if (!request->second.async) return synchronousError;
if (!request->second.finished) return POLL_RUNNING;
*result = request->second.result;
return POLL_FINISHED;
}
int WaitAsync(Service& service, int reqId, int* result, int synchronousError) {
if (!result) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT;
std::unique_lock lock(service.mutex);
auto request = service.requests.find(reqId);
if (request == service.requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID;
if (!request->second.async) return synchronousError;
service.finished.wait(lock, [&] {
request = service.requests.find(reqId);
return request == service.requests.end() || request->second.finished;
});
if (request == service.requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID;
*result = request->second.result;
return 0;
}
bool ValidTimeout(std::uint32_t timeout, std::uint32_t minimum) {
return timeout == 0 || timeout >= minimum;
}
int SetTimeout(Service& service, int id, int resolveRetry, std::uint32_t resolveTimeout, std::uint32_t connTimeout, std::uint32_t sendTimeout, std::uint32_t recvTimeout) {
if (resolveRetry < 0 || !ValidTimeout(resolveTimeout, RESOLVE_TIMEOUT_MIN) || !ValidTimeout(connTimeout, TRANSFER_TIMEOUT_MIN) ||
!ValidTimeout(sendTimeout, TRANSFER_TIMEOUT_MIN) || !ValidTimeout(recvTimeout, TRANSFER_TIMEOUT_MIN))
return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT;
if (resolveRetry == 0 && resolveTimeout == 0 && connTimeout == 0 && sendTimeout == 0 && recvTimeout == 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT;
std::lock_guard lock(service.mutex);
return service.titleContexts.contains(id) || service.requests.contains(id) ? 0 : SCE_NP_COMMUNITY_ERROR_INVALID_ID;
}
int CheckComment(const char* comment) {
return strnlen(comment, WORD_FILTER_COMMENT_MAX_LENGTH + 1) > WORD_FILTER_COMMENT_MAX_LENGTH ? SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT : 0;
}
@@ -237,14 +298,6 @@ int APS5_VABI sceNpBandwidthTestShutdownWithDetailedInfo() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpLookupAbortRequest() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpLookupCreateAsyncRequest() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpLookupCreateTitleCtxA() {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -313,18 +366,6 @@ int APS5_VABI sceNpLookupNetTerm() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpLookupPollAsync() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpLookupSetTimeout() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpLookupWaitAsync() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpServiceChecker2IntAbortRequest() {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -421,14 +462,6 @@ int APS5_VABI sceNpWordFilterCreateTitleCtxA() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWordFilterSetTimeout() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNpWordFilterWaitAsync() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
extern "C" {
@@ -438,6 +471,15 @@ int APS5_VABI sceNpBandwidthTestInitStart() {
return 0;
}
int APS5_VABI sceNpLookupAbortRequest(int reqId) {
return AbortRequest(lookup, reqId);
}
int APS5_VABI sceNpLookupCreateAsyncRequest(int titleCtxId, const std::uint64_t* param) {
if (!param || *param != ASYNC_REQUEST_PARAMETER_SIZE) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT;
return CreateRequest(lookup, titleCtxId, true);
}
int APS5_VABI sceNpLookupCreateRequest(int titleCtxId) {
return CreateRequest(lookup, titleCtxId, false);
}
@@ -460,16 +502,20 @@ int APS5_VABI sceNpLookupNpId(int reqId, const NpOnlineId* onlineId, void* npId,
return StartOffline(lookup, reqId);
}
int APS5_VABI sceNpLookupPollAsync(int reqId, int* result) {
return PollAsync(lookup, reqId, result, SCE_NP_LOOKUP_ERROR_SYNCHRONOUS_REQUEST);
}
int APS5_VABI sceNpLookupSetTimeout(int id, int resolveRetry, std::uint32_t resolveTimeout, std::uint32_t connTimeout, std::uint32_t sendTimeout, std::uint32_t recvTimeout) {
return SetTimeout(lookup, id, resolveRetry, resolveTimeout, connTimeout, sendTimeout, recvTimeout);
}
int APS5_VABI sceNpLookupWaitAsync(int reqId, int* result) {
return WaitAsync(lookup, reqId, result, SCE_NP_LOOKUP_ERROR_SYNCHRONOUS_REQUEST);
}
int APS5_VABI sceNpWordFilterAbortRequest(int reqId) {
std::lock_guard lock(wordFilter.mutex);
const auto request = wordFilter.requests.find(reqId);
if (request == wordFilter.requests.end()) return SCE_NP_COMMUNITY_ERROR_INVALID_ID;
request->second.aborted = true;
if (!request->second.finished) {
request->second.finished = true;
request->second.result = SCE_NP_COMMUNITY_ERROR_ABORTED;
}
return 0;
return AbortRequest(wordFilter, reqId);
}
int APS5_VABI sceNpWordFilterCensorComment(int reqId, const char* comment, void* option) {
@@ -480,7 +526,7 @@ int APS5_VABI sceNpWordFilterCensorComment(int reqId, const char* comment, void*
}
int APS5_VABI sceNpWordFilterCreateAsyncRequest(int titleCtxId, const std::uint64_t* param) {
if (!param || *param != WORD_FILTER_ASYNC_PARAMETER_SIZE) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT;
if (!param || *param != ASYNC_REQUEST_PARAMETER_SIZE) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT;
return CreateRequest(wordFilter, titleCtxId, true);
}
@@ -501,13 +547,7 @@ int APS5_VABI sceNpWordFilterDeleteTitleCtx(int titleCtxId) {
}
int APS5_VABI sceNpWordFilterPollAsync(int reqId, int* result) {
if (!result) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT;
std::lock_guard lock(wordFilter.mutex);
const auto request = wordFilter.requests.find(reqId);
if (request == wordFilter.requests.end() || !request->second.async) return SCE_NP_COMMUNITY_ERROR_INVALID_ID;
if (!request->second.finished) return POLL_RUNNING;
*result = request->second.result;
return POLL_FINISHED;
return PollAsync(wordFilter, reqId, result, SCE_NP_COMMUNITY_ERROR_INVALID_ID);
}
int APS5_VABI sceNpWordFilterSanitizeComment(int reqId, const char* comment, char* sanitizedComment, void* option) {
@@ -517,4 +557,12 @@ int APS5_VABI sceNpWordFilterSanitizeComment(int reqId, const char* comment, cha
return StartOffline(wordFilter, reqId);
}
int APS5_VABI sceNpWordFilterSetTimeout(int id, int resolveRetry, std::uint32_t resolveTimeout, std::uint32_t connTimeout, std::uint32_t sendTimeout, std::uint32_t recvTimeout) {
return SetTimeout(wordFilter, id, resolveRetry, resolveTimeout, connTimeout, sendTimeout, recvTimeout);
}
int APS5_VABI sceNpWordFilterWaitAsync(int reqId, int* result) {
return WaitAsync(wordFilter, reqId, result, SCE_NP_COMMUNITY_ERROR_INVALID_ID);
}
}
@@ -23,6 +23,13 @@ int APS5_VABI sceNpWordFilterCreateTitleCtx(const void* selfNpId);
int APS5_VABI sceNpWordFilterDeleteRequest(int reqId);
int APS5_VABI sceNpWordFilterDeleteTitleCtx(int titleCtxId);
int APS5_VABI sceNpWordFilterPollAsync(int reqId, int* result);
int APS5_VABI sceNpLookupAbortRequest(int reqId);
int APS5_VABI sceNpLookupCreateAsyncRequest(int titleCtxId, const std::uint64_t* param);
int APS5_VABI sceNpLookupPollAsync(int reqId, int* result);
int APS5_VABI sceNpLookupSetTimeout(int id, int resolveRetry, std::uint32_t resolveTimeout, std::uint32_t connTimeout, std::uint32_t sendTimeout, std::uint32_t recvTimeout);
int APS5_VABI sceNpLookupWaitAsync(int reqId, int* result);
int APS5_VABI sceNpWordFilterSetTimeout(int id, int resolveRetry, std::uint32_t resolveTimeout, std::uint32_t connTimeout, std::uint32_t sendTimeout, std::uint32_t recvTimeout);
int APS5_VABI sceNpWordFilterWaitAsync(int reqId, int* result);
int APS5_VABI sceNpWordFilterSanitizeComment(int reqId, const char* comment, char* sanitizedComment, void* option);
int APS5_VABI sceNpAppInfoIntAbortRequest();
@@ -53,8 +60,6 @@ int APS5_VABI sceNpAppLaunchLinkIntGetCompatibleTitleIdList();
int APS5_VABI sceNpAppLaunchLinkIntGetCompatibleTitleIdNum();
int APS5_VABI sceNpAppLaunchLinkIntInitialize();
int APS5_VABI sceNpBandwidthTestShutdownWithDetailedInfo();
int APS5_VABI sceNpLookupAbortRequest();
int APS5_VABI sceNpLookupCreateAsyncRequest();
int APS5_VABI sceNpLookupCreateTitleCtxA();
int APS5_VABI sceNpLookupNetAbortRequest();
int APS5_VABI sceNpLookupNetCensorComment();
@@ -72,9 +77,6 @@ int APS5_VABI sceNpLookupNetNpId();
int APS5_VABI sceNpLookupNetSanitizeComment();
int APS5_VABI sceNpLookupNetSetTimeout();
int APS5_VABI sceNpLookupNetTerm();
int APS5_VABI sceNpLookupPollAsync();
int APS5_VABI sceNpLookupSetTimeout();
int APS5_VABI sceNpLookupWaitAsync();
int APS5_VABI sceNpServiceChecker2IntAbortRequest();
int APS5_VABI sceNpServiceChecker2IntCreateRequest();
int APS5_VABI sceNpServiceChecker2IntDestroyRequest();
@@ -99,7 +101,5 @@ int APS5_VABI sceNpTitleMetadataIntGetNpTitleId();
int APS5_VABI sceNpUtilityInit();
int APS5_VABI sceNpUtilityTerm();
int APS5_VABI sceNpWordFilterCreateTitleCtxA();
int APS5_VABI sceNpWordFilterSetTimeout();
int APS5_VABI sceNpWordFilterWaitAsync();
}
@@ -38,34 +38,8 @@ int APS5_VABI sceVideoOutCursorSetPosition(int32_t, int32_t, uint32_t, uint32_t)
return 0;
}
int APS5_VABI sceVideoOutAdjustColor_(int handle, const VideoOutColorSettings* settings, uint32_t settings_size) {
if (settings_size < sizeof(VideoOutColorSettings)) throw std::runtime_error("sceVideoOutAdjustColor_: VIDEO_OUT_ERROR_INVALID_VALUE");
return sceVideoOutAdjustColor(handle, settings);
}
int APS5_VABI sceVideoOutColorSettingsSetGamma_(VideoOutColorSettings* settings, float gamma, uint32_t settings_size) {
if (settings_size < sizeof(VideoOutColorSettings)) throw std::runtime_error("sceVideoOutColorSettingsSetGamma_: VIDEO_OUT_ERROR_INVALID_VALUE");
return sceVideoOutColorSettingsSetGamma(settings, gamma);
}
int APS5_VABI sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion_(VideoOutColorSpaceConversionSettings* settings, uint32_t settings_size) {
if (settings_size < sizeof(VideoOutColorSpaceConversionSettings)) throw std::runtime_error("sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion_: VIDEO_OUT_ERROR_INVALID_VALUE");
return sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion(settings);
}
int APS5_VABI sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion_(VideoOutColorSpaceConversionSettings* settings, float lumaScale, uint32_t option, uint32_t settings_size) {
if (settings_size < sizeof(VideoOutColorSpaceConversionSettings)) throw std::runtime_error("sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion_: VIDEO_OUT_ERROR_INVALID_VALUE");
return sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion(settings, lumaScale, option);
}
int APS5_VABI sceVideoOutAdjustColorSpaceConversion_(int handle, const VideoOutColorSpaceConversionSettings* settings, uint32_t settings_size) {
if (settings_size < sizeof(VideoOutColorSpaceConversionSettings)) throw std::runtime_error("sceVideoOutAdjustColorSpaceConversion_: VIDEO_OUT_ERROR_INVALID_VALUE");
return sceVideoOutAdjustColorSpaceConversion(handle, settings);
}
int APS5_VABI sceDbgVideoOutAddOutputModeVr60Privilege() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
@@ -371,6 +371,31 @@ int APS5_VABI sceVideoOutAdjustColorSpaceConversion(int handle, const VideoOutCo
LibcAwaitExit_nid_postfix();
}
int APS5_VABI sceVideoOutAdjustColor_(int handle, const VideoOutColorSettings* settings, uint32_t settings_size) {
if (settings_size < sizeof(VideoOutColorSettings)) throw std::runtime_error("sceVideoOutAdjustColor_: VIDEO_OUT_ERROR_INVALID_VALUE");
return sceVideoOutAdjustColor(handle, settings);
}
int APS5_VABI sceVideoOutColorSettingsSetGamma_(VideoOutColorSettings* settings, float gamma, uint32_t settings_size) {
if (settings_size < sizeof(VideoOutColorSettings)) throw std::runtime_error("sceVideoOutColorSettingsSetGamma_: VIDEO_OUT_ERROR_INVALID_VALUE");
return sceVideoOutColorSettingsSetGamma(settings, gamma);
}
int APS5_VABI sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion_(VideoOutColorSpaceConversionSettings* settings, uint32_t settings_size) {
if (settings_size < sizeof(VideoOutColorSpaceConversionSettings)) throw std::runtime_error("sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion_: VIDEO_OUT_ERROR_INVALID_VALUE");
return sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion(settings);
}
int APS5_VABI sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion_(VideoOutColorSpaceConversionSettings* settings, float lumaScale, uint32_t option, uint32_t settings_size) {
if (settings_size < sizeof(VideoOutColorSpaceConversionSettings)) throw std::runtime_error("sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion_: VIDEO_OUT_ERROR_INVALID_VALUE");
return sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion(settings, lumaScale, option);
}
int APS5_VABI sceVideoOutAdjustColorSpaceConversion_(int handle, const VideoOutColorSpaceConversionSettings* settings, uint32_t settings_size) {
if (settings_size < sizeof(VideoOutColorSpaceConversionSettings)) throw std::runtime_error("sceVideoOutAdjustColorSpaceConversion_: VIDEO_OUT_ERROR_INVALID_VALUE");
return sceVideoOutAdjustColorSpaceConversion(handle, settings);
}
int APS5_VABI sceVideoOutSetGlobalBlendSpace(int handle, uint32_t space, uint32_t option) try {
auto cfg = VideoOutDriver::Get().GetConfig(handle);
if (cfg == nullptr) {
@@ -2,6 +2,7 @@
#include "prx/libSceVideoOut/include/VideoOutDriver.hpp"
#include "prx/libSceVideoOut/include/Buffer.hpp"
#include "prx/libSceVideoOut/include/Output.hpp"
#include "prx/libSceVideoOut/include/VideoOut.hpp"
#include "prx/libSceVideoOut/include/Event.hpp"
#include "prx/libkernel/Equeue/Equeue.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
@@ -445,6 +446,22 @@ void testControls() {
}
VideoOutColorSettings settings{};
expectFailure([&] { sceVideoOutColorSettingsSetGamma(&settings, std::numeric_limits<float>::quiet_NaN()); });
expectFailure([&] { sceVideoOutColorSettingsSetGamma_(&settings, 1.0f, sizeof(VideoOutColorSettings) - 1); });
check(sceVideoOutColorSettingsSetGamma_(&settings, 1.2f, sizeof(VideoOutColorSettings)) == 0, "gamma wrapper rejected valid size");
check(settings.gamma == 1.2f, "gamma wrapper failed to forward value");
expectFailure([&] { sceVideoOutAdjustColor_(handle, &settings, sizeof(VideoOutColorSettings) - 1); });
check(sceVideoOutAdjustColor_(handle, &settings, sizeof(VideoOutColorSettings)) == 0, "adjust color wrapper rejected valid size");
VideoOutColorSpaceConversionSettings cscSettings{};
expectFailure([&] { sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion_(&cscSettings, sizeof(VideoOutColorSpaceConversionSettings) - 1); });
check(sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion_(&cscSettings, sizeof(VideoOutColorSpaceConversionSettings)) == 0, "default SDR to HDR wrapper rejected valid size");
expectFailure([&] { sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion_(&cscSettings, 1.0f, 0, sizeof(VideoOutColorSpaceConversionSettings) - 1); });
check(sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion_(&cscSettings, 1.0f, 0, sizeof(VideoOutColorSpaceConversionSettings)) == 0, "SDR to HDR conversion wrapper rejected valid size");
expectFailure([&] { sceVideoOutAdjustColorSpaceConversion_(handle, &cscSettings, sizeof(VideoOutColorSpaceConversionSettings) - 1); });
check(sceVideoOutAdjustColorSpaceConversion_(handle, &cscSettings, sizeof(VideoOutColorSpaceConversionSettings)) == 0, "adjust color space conversion wrapper rejected valid size");
KernelEqueue queue = 0;
check(sceKernelCreateEqueue(&queue, "VideoOut test") == 0, "event queue creation failed");
auto owner = EqueuePin_nid_postfix(queue);
+3 -3
View File
@@ -329,18 +329,18 @@ constexpr auto g_lowerTable = MakeCaseTable(false);
constexpr auto g_upperTable = MakeCaseTable(true);
void ValidateCharacter(int value) {
if (value != EOF && (value < 0 || value > UCHAR_MAX)) throw std::invalid_argument("Invalid character value");
if (value < SCHAR_MIN || value > UCHAR_MAX) throw std::invalid_argument("Invalid character value");
}
int ClassifyCharacter(int value, short mask) {
ValidateCharacter(value);
if (value == EOF) return 0;
return g_classificationTable[static_cast<std::size_t>(value) + 1] & mask;
return g_classificationTable[static_cast<std::size_t>(value < 0 ? value + UCHAR_MAX + 1 : value) + 1] & mask;
}
int ConvertCharacter(int value, bool upper) {
ValidateCharacter(value);
if (value == EOF) return EOF;
if (value < 0) return value;
const auto& facet = std::use_facet<std::ctype<char>>(std::locale::classic());
const auto character = static_cast<char>(value);
return static_cast<unsigned char>(upper ? facet.toupper(character) : facet.tolower(character));
+8 -8
View File
@@ -416,9 +416,9 @@ int APS5_VABI __isfinitef_nid_postfix(float x) { return std::isfinite(x) ? 1 : 0
int APS5_VABI __isnormal_nid_postfix(double x) { return std::isnormal(x) ? 1 : 0; }
int APS5_VABI __isnormalf_nid_postfix(float x) { return std::isnormal(x) ? 1 : 0; }
int APS5_VABI __isinff_nid_postfix(float x) { return std::isinf(x) ? 1 : 0; }
float APS5_VABI nearbyintf_nid_postfix(float x) { return std::nearbyintf(x); }
float APS5_VABI nearbyintf_nid_postfix(float x) { return std::isnan(x) ? x + x : std::nearbyintf(x); }
long double APS5_VABI nearbyintl_nid_postfix(long double x) { return std::nearbyint(x); }
double APS5_VABI nearbyint_nid_postfix(double x) { return std::nearbyint(x); }
double APS5_VABI nearbyint_nid_postfix(double x) { return std::isnan(x) ? x + x : std::nearbyint(x); }
double APS5_VABI cbrt_nid_postfix(double x) { return std::cbrt(x); }
double APS5_VABI asin_nid_postfix(double x) {
@@ -441,12 +441,12 @@ float APS5_VABI copysignf_nid_postfix(float x, float y) {
return std::copysign(x, y);
}
int APS5_VABI abs_nid_postfix(int x) { return x < 0 ? static_cast<int>(0u - static_cast<unsigned>(x)) : x; }
double APS5_VABI trunc_nid_postfix(double x) { return std::trunc(x); }
double APS5_VABI ceil_nid_postfix(double x) { return std::ceil(x); }
double APS5_VABI floor_nid_postfix(double x) { return std::floor(x); }
float APS5_VABI truncf_nid_postfix(float x) { return std::trunc(x); }
float APS5_VABI ceilf_nid_postfix(float x) { return std::ceil(x); }
float APS5_VABI floorf_nid_postfix(float x) { return std::floor(x); }
double APS5_VABI trunc_nid_postfix(double x) { return std::isnan(x) ? x + x : std::trunc(x); }
double APS5_VABI ceil_nid_postfix(double x) { return std::isnan(x) ? x + x : std::ceil(x); }
double APS5_VABI floor_nid_postfix(double x) { return std::isnan(x) ? x + x : std::floor(x); }
float APS5_VABI truncf_nid_postfix(float x) { return std::isnan(x) ? x + x : std::trunc(x); }
float APS5_VABI ceilf_nid_postfix(float x) { return std::isnan(x) ? x + x : std::ceil(x); }
float APS5_VABI floorf_nid_postfix(float x) { return std::isnan(x) ? x + x : std::floor(x); }
double APS5_VABI fma_nid_postfix(double x, double y, double z) { return std::fma(x, y, z); }
float APS5_VABI fmaf_nid_postfix(float x, float y, float z) { return std::fma(x, y, z); }
double APS5_VABI fmin_nid_postfix(double x, double y) { return GuestFmin(x, y); }
+5
View File
@@ -2004,6 +2004,11 @@ if(BUILD_TESTING)
add_test(NAME guest_raise_in_system_call COMMAND guest_raise_in_system_call_tests)
set_tests_properties(guest_raise_in_system_call PROPERTIES TIMEOUT 30 ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
foreach(delay IN ITEMS raise return)
add_test(NAME guest_raise_in_system_call_delay_${delay} COMMAND guest_raise_in_system_call_tests --delay-${delay})
set_tests_properties(guest_raise_in_system_call_delay_${delay} PROPERTIES TIMEOUT 30 ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endforeach()
add_test_executable(guest_raise_in_host_code_tests ${LIBS_INCLUDE_DIR}/tests/GuestRaiseInHostCode.cpp)
target_include_directories(guest_raise_in_host_code_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_raise_in_host_code_tests PRIVATE libkernel libc)
+2
View File
@@ -99,5 +99,7 @@ inline constexpr int SCE_KERNEL_ERROR_ENOLINK = SceKernelError(91);
inline constexpr int SCE_KERNEL_ERROR_EPROTO = SceKernelError(92);
inline constexpr int SCE_KERNEL_ERROR_ENOTCAPABLE = SceKernelError(93);
inline constexpr int SCE_KERNEL_ERROR_ECAPMODE = SceKernelError(94);
inline constexpr int SCE_KERNEL_ERROR_ENOTRECOVERABLE = SceKernelError(95);
inline constexpr int SCE_KERNEL_ERROR_EOWNERDEAD = SceKernelError(96);
#endif
+52 -14
View File
@@ -1,31 +1,69 @@
#include "prx/libkernel/include/Api.hpp"
#include "prx/libc/include/Api.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <array>
#include <cstdint>
#include <cstring>
namespace {
constexpr int SetMask = 3;
constexpr int Eintr = 4;
constexpr int DescriptorLimit = 1024;
struct Timeval {
std::int64_t seconds;
std::int64_t microseconds;
};
using SavedSet = std::array<std::uint64_t, DescriptorLimit / 64>;
void Save(SavedSet& saved, const void* set, std::size_t bytes) {
if (set != nullptr) std::memcpy(saved.data(), set, bytes);
}
void Restore(void* set, const SavedSet& saved, std::size_t bytes) {
if (set != nullptr) std::memcpy(set, saved.data(), bytes);
}
int ScanAfterCaughtSignal(int nfds, void* readfds, void* writefds, void* exceptfds, const Timeval* wait) {
const bool validWait = wait == nullptr || (wait->seconds >= 0 && wait->microseconds >= 0 && wait->microseconds < 1000000);
if (nfds < 0 || nfds > DescriptorLimit || !validWait) return select_nid_postfix(nfds, readfds, writefds, exceptfds, wait);
const std::size_t bytes = static_cast<std::size_t>((nfds + 63) / 64) * sizeof(std::uint64_t);
SavedSet read{}, write{}, except{};
Save(read, readfds, bytes);
Save(write, writefds, bytes);
Save(except, exceptfds, bytes);
const Timeval zero{};
const int result = select_nid_postfix(nfds, readfds, writefds, exceptfds, &zero);
if (result != 0 || (wait != nullptr && wait->seconds == 0 && wait->microseconds == 0)) return result;
Restore(readfds, read, bytes);
Restore(writefds, write, bytes);
Restore(exceptfds, except, bytes);
*__error_nid_postfix() = Eintr;
return -1;
}
}
extern "C" {
int APS5_VABI pselect_nid_postfix(int nfds, void* readfds, void* writefds, void* exceptfds, const void* timeout, const void* sigmask) {
constexpr int SetMask = 3;
struct Timeval {
std::int64_t seconds;
std::int64_t microseconds;
} limit{};
Timeval limit{};
const Timeval* wait = nullptr;
if (timeout != nullptr) {
const auto* spec = static_cast<const std::int64_t*>(timeout);
limit = {spec[0], spec[1] / 1000};
wait = &limit;
}
std::uint32_t previous[4]{};
if (sigmask != nullptr && sigprocmask_nid_postfix(SetMask, sigmask, previous) != 0) return -1;
const int result = select_nid_postfix(nfds, readfds, writefds, exceptfds, wait);
if (sigmask != nullptr) {
const int error = *__error_nid_postfix();
sigprocmask_nid_postfix(SetMask, previous, nullptr);
*__error_nid_postfix() = error;
}
if (sigmask == nullptr) return select_nid_postfix(nfds, readfds, writefds, exceptfds, wait);
GuestSignalSet previous{};
const bool caught = SetSignalMaskCatching_nid_no_patch(static_cast<const GuestSignalSet*>(sigmask), &previous) != 0;
const int result = caught ? ScanAfterCaughtSignal(nfds, readfds, writefds, exceptfds, wait)
: select_nid_postfix(nfds, readfds, writefds, exceptfds, wait);
const int error = *__error_nid_postfix();
sigprocmask_nid_postfix(SetMask, &previous, nullptr);
*__error_nid_postfix() = error;
return result;
}
+4 -4
View File
@@ -36,15 +36,13 @@ const void* imageContaining(const void* address, bool reference, void*& handle,
#ifdef _WIN32
const DWORD flags = GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | (reference ? 0 : GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT);
HMODULE module = nullptr;
if (!GetModuleHandleExW(flags, reinterpret_cast<LPCWSTR>(address), &module))
throw std::runtime_error(std::string(caller) + ": address outside every loaded image");
if (!GetModuleHandleExW(flags, reinterpret_cast<LPCWSTR>(address), &module)) return nullptr;
if (reference) handle = module;
return module;
#else
Dl_info info{};
link_map* image = nullptr;
if (!dladdr1(address, &info, reinterpret_cast<void**>(&image), RTLD_DL_LINKMAP) || !image)
throw std::runtime_error(std::string(caller) + ": address outside every loaded image");
if (!dladdr1(address, &info, reinterpret_cast<void**>(&image), RTLD_DL_LINKMAP) || !image) return nullptr;
if (reference && image->l_name && *image->l_name) {
handle = dlopen(image->l_name, RTLD_LAZY | RTLD_NOLOAD);
if (!handle) throw std::runtime_error(std::string(caller) + ": cannot reference " + image->l_name);
@@ -56,6 +54,7 @@ const void* imageContaining(const void* address, bool reference, void*& handle,
int referenceThreadAtexitImage(const void* address, const char* caller) {
void* handle = nullptr;
const void* image = imageContaining(address, true, handle, caller);
if (!image) return -1;
std::lock_guard lock(threadAtexitMutex);
threadAtexitReferences.emplace(image, handle);
return 0;
@@ -64,6 +63,7 @@ int referenceThreadAtexitImage(const void* address, const char* caller) {
int releaseThreadAtexitImage(const void* address, const char* caller) {
void* handle = nullptr;
const void* image = imageContaining(address, false, handle, caller);
if (!image) return 0;
{
std::lock_guard lock(threadAtexitMutex);
const auto found = threadAtexitReferences.find(image);
+12 -4
View File
@@ -298,11 +298,13 @@ bool DeliverPendingSignal(int guest) {
return false;
}
void DeliverUnblockedPending() {
bool DeliverUnblockedPending() {
bool caught = false;
for (int guest = 1; guest <= MaxSignal; ++guest) {
if (SignalBlocked(guest) || !Deliverable(guest) || !TakePending(guest)) continue;
DeliverPendingSignal(guest);
if (DeliverPendingSignal(guest)) caught = true;
}
return caught;
}
int WaitForPendingInSet(const GuestSignalSet& set, bool waitForever, const KernelTimespec* timeout, int* guest) {
@@ -540,7 +542,7 @@ thread_local GuestStack alternateStack{nullptr, 0, SsDisable};
namespace {
int ChangeBlockedMask(int how, const GuestSignalSet* set, GuestSignalSet* previousSet, bool releaseHolds) {
int ChangeBlockedMask(int how, const GuestSignalSet* set, GuestSignalSet* previousSet, bool releaseHolds, bool* caught = nullptr) {
if (set != nullptr && (how < 1 || how > 3)) { *__error_nid_postfix() = 22; return -1; }
{
std::lock_guard lock(registration);
@@ -575,12 +577,18 @@ int ChangeBlockedMask(int how, const GuestSignalSet* set, GuestSignalSet* previo
}
}
std::atomic_thread_fence(std::memory_order_seq_cst);
DeliverUnblockedPending();
if (DeliverUnblockedPending() && caught != nullptr) *caught = true;
return 0;
}
}
extern "C" int APS5_VABI SetSignalMaskCatching_nid_no_patch(const GuestSignalSet* set, GuestSignalSet* previousSet) {
bool caught = false;
ChangeBlockedMask(GuestSigsetmask, set, previousSet, true, &caught);
return caught ? 1 : 0;
}
extern "C" {
GuestHandler APS5_VABI signal_nid_postfix(int guest, GuestHandler handler) {
const auto invalid = reinterpret_cast<GuestHandler>(static_cast<std::uintptr_t>(-1));
+1
View File
@@ -729,6 +729,7 @@ int APS5_VABI killpg_nid_postfix(int pgrp, int guest);
int APS5_VABI pthread_kill_nid_postfix(Pthread thread, int guest);
int APS5_VABI sigaltstack_nid_postfix(const GuestStack* stack, GuestStack* previous);
int APS5_VABI _sigprocmask_nid_postfix(int how, const GuestSignalSet* set, GuestSignalSet* previousSet);
int APS5_VABI SetSignalMaskCatching_nid_no_patch(const GuestSignalSet* set, GuestSignalSet* previousSet);
int APS5_VABI sigprocmask_nid_postfix(int how, const void* set, void* previousSet);
int APS5_VABI pthread_sigmask_nid_postfix(int how, const GuestSignalSet* set, GuestSignalSet* previousSet);
int APS5_VABI sigtimedwait_nid_postfix(const GuestSignalSet* set, void* info, const KernelTimespec* timeout);
@@ -69,11 +69,6 @@ int APS5_VABI futimesat_nid_postfix(void) {
return 0;
}
int APS5_VABI get_authinfo_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI get_module_info_list_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -979,11 +974,6 @@ int APS5_VABI sceKernelGetMemoryPstate(void) {
return 0;
}
int APS5_VABI sceKernelGetOpenPsIdForSystem(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceKernelGetPagingStatsOfAllObjects(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -1405,21 +1395,11 @@ int APS5_VABI sceKernelIsAuthenticNeo(void) {
return 1;
}
int APS5_VABI sceKernelIsCEX(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceKernelIsDebuggerAttached(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceKernelIsDevKit(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceKernelIsExperimentalBeta(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
+23 -3
View File
@@ -62,6 +62,7 @@ static int GuestFstat(int fd, bool* withoutVnode = nullptr) {
constexpr int GuestEnoent = 2;
constexpr int GuestEperm = 1;
constexpr int GuestEsrch = 3;
constexpr int GuestEio = 5;
constexpr int GuestEbadf = 9;
constexpr int GuestEfault = 14;
@@ -337,6 +338,27 @@ std::uint16_t APS5_VABI umask_nid_postfix(std::uint16_t mask) {
return File::ExchangeCreationMask_nid_no_patch(mask);
}
int APS5_VABI sceKernelGetOpenPsIdForSystem(void* id) {
if (id == nullptr) return GuestEinval;
return sceKernelGetOpenPsId(id);
}
int APS5_VABI sceKernelIsCEX(void) {
return 1;
}
int APS5_VABI sceKernelIsDevKit(void) {
return 0;
}
int APS5_VABI get_authinfo_nid_postfix(int pid, KernelAuthInfo* info) {
if (info == nullptr) return static_cast<int>(Fail(GuestEperm));
if (pid != 0 && pid != getpid_nid_postfix()) return static_cast<int>(Fail(GuestEsrch));
*info = {};
info->capabilities[0] = 0x2000000000000000;
return 0;
}
int APS5_VABI fstatfs_nid_postfix(int d, KernelStatfs* buf) {
return _fstatfs_nid_postfix(d, buf);
}
@@ -725,15 +747,13 @@ int APS5_VABI lchmod_nid_postfix(const char* path, std::uint16_t mode) {
}
int APS5_VABI fchown_nid_postfix(int fd, std::uint32_t uid, std::uint32_t gid) {
constexpr std::uint32_t Unchanged = 0xFFFFFFFFu;
if (uid != 0 && uid != Unchanged) NotImplemented_nid_no_patch("fchown: an owner other than the guest user");
if (gid != 0 && gid != Unchanged) NotImplemented_nid_no_patch("fchown: a group other than the guest group");
#ifdef _WIN32
if (GuestFstat(fd) != 0) return static_cast<int>(Fail(GuestEbadf));
#else
struct stat status{};
if (::fstat(fd, &status) != 0) return static_cast<int>(Fail(GuestEbadf));
#endif
RequireGuestOwner("fchown", uid, gid);
return 0;
}
+10 -2
View File
@@ -24,6 +24,14 @@ struct KernelTitleWorkaround {
std::uint64_t ids[2];
};
struct KernelAuthInfo {
std::uint64_t programAuthorityId;
std::uint64_t capabilities[4];
std::uint64_t attributes[4];
std::uint64_t credentials[8];
};
static_assert(sizeof(KernelAuthInfo) == 0x88);
extern "C" {
int APS5_VABI aio_read_nid_postfix(GuestAiocb* aiocb);
@@ -81,7 +89,7 @@ int APS5_VABI cpuset_getid_nid_postfix(void);
int APS5_VABI cpuset_setid_nid_postfix(void);
int APS5_VABI dynlib_get_obj_member_nid_postfix(void);
int APS5_VABI futimesat_nid_postfix(void);
int APS5_VABI get_authinfo_nid_postfix(void);
int APS5_VABI get_authinfo_nid_postfix(int pid, KernelAuthInfo* info);
int APS5_VABI get_module_info_list_nid_postfix(void);
int APS5_VABI get_page_table_stats_nid_postfix(void);
int APS5_VABI get_sdk_compiled_version_nid_postfix(void);
@@ -265,7 +273,7 @@ int APS5_VABI sceKernelGetIpcPath(void);
int APS5_VABI sceKernelGetKnobs(void);
int APS5_VABI sceKernelGetKqueueFromEqueue(void);
int APS5_VABI sceKernelGetMemoryPstate(void);
int APS5_VABI sceKernelGetOpenPsIdForSystem(void);
int APS5_VABI sceKernelGetOpenPsIdForSystem(void* id);
int APS5_VABI sceKernelGetPagingStatsOfAllObjects(void);
int APS5_VABI sceKernelGetPagingStatsOfAllThreads(void);
int APS5_VABI sceKernelGetParentApp(void);
+197 -1
View File
@@ -5,6 +5,7 @@
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <string_view>
#include <thread>
#include <xmmintrin.h>
#ifdef _WIN32
@@ -310,6 +311,195 @@ void RunInternalOptions() {
Require(sceFiberFinalize(fiber) == 0, "finalize fpu defaults fiber");
}
std::array<FiberStorage, 2> g_contextlessStorage;
std::array<unsigned, 2> g_contextlessEntries{};
alignas(16) std::array<unsigned char, 65536> g_transitionContext;
FiberStorage g_transitionStorage;
auto* const g_transition = reinterpret_cast<FiberObject*>(&g_transitionStorage);
std::uintptr_t g_callerStack = 0;
std::uint64_t g_contextlessFramePointer = 0;
unsigned g_contextlessHops = 0;
#ifdef _WIN32
void* g_callerBase = nullptr;
void* g_callerLimit = nullptr;
#endif
FiberObject* Contextless(unsigned index) {
return reinterpret_cast<FiberObject*>(&g_contextlessStorage[index]);
}
void APS5_VABI ContextlessEntry(std::uint64_t index, std::uint64_t run) {
++g_contextlessEntries[index];
auto* self = Contextless(index);
FiberObject* current = nullptr;
Require(sceFiberGetSelf(&current) == 0 && current == self, "contextless entry self");
FiberInfo info{};
info.size = sizeof(info);
Require(sceFiberGetInfo(self, &info) == 0, "contextless entry info");
if (info.addr_context) {
Require(run == 90, "attaching after contextless execution restarts entry");
for (;;) sceFiberReturnToThread(run + 1, &run);
}
Require(info.size_context == 0 && info.size_context_margin == ~0ull, "contextless info has no stack or margin");
std::uint64_t framePointer = 0;
Require(sceFiberGetThreadFramePointerAddress(&framePointer) == 0 && framePointer == g_contextlessFramePointer, "contextless entry preserves the caller frame pointer");
Require(sceFiberRun_nid_postfix(self, 0, nullptr) == FiberErrorPermission, "contextless entry cannot run a fiber");
Require(sceFiberSwitch(self, 0, nullptr) == FiberErrorState, "contextless entry cannot switch to itself");
Require(sceFiberFinalize(self) == FiberErrorState, "contextless entry cannot finalize itself");
volatile std::uint64_t marker = 0;
const auto address = reinterpret_cast<std::uintptr_t>(&marker);
Require(address < g_callerStack && g_callerStack - address < 32768, "contextless execution uses calling thread stack");
_mm_setcsr(_mm_getcsr() ^ 0x8000u);
#ifdef _WIN32
const auto* tib = reinterpret_cast<const NT_TIB*>(NtCurrentTeb());
Require(tib->StackBase == g_callerBase && tib->StackLimit == g_callerLimit, "contextless execution restores thread stack bounds");
#endif
if (run == 2) {
sceFiberSwitch(Contextless(1), 1, nullptr);
} else if (run == 3) {
_sceFiberAttachContextAndSwitch(g_transition, g_transitionContext.data(), g_transitionContext.size(), 50, nullptr);
} else if (run == 4) {
sceFiberSwitch(g_transition, 70, nullptr);
} else if (run == 5) {
if (++g_contextlessHops < 1024) sceFiberSwitch(Contextless(1 - index), 5, nullptr);
sceFiberReturnToThread(999, nullptr);
} else {
Require(run == 1, "contextless run argument");
sceFiberReturnToThread(200 + index, nullptr);
}
Require(false, "contextless fiber must restart rather than resume after yielding");
}
void APS5_VABI TransitionEntry(std::uint64_t, std::uint64_t run) {
Require(run == 50, "contextless source attaches target");
Require(sceFiberReturnToThread(777, &run) == 0 && run == 55, "attached target resumes after contextless source");
Require(_sceFiberAttachContextAndSwitch(Contextless(1), nullptr, 0, 1, &run) == 0 && run == 60, "attached source resumes after contextless target");
Require(sceFiberSwitch(Contextless(0), 4, &run) == 0 && run == 70, "contextless target switches back to suspended attached source");
for (;;) sceFiberReturnToThread(778, nullptr);
}
[[gnu::noinline]] std::int32_t RunContextless(FiberObject* fiber, std::uint64_t run, std::uint64_t* returned, bool attach = false) {
register std::uint64_t savedRbx asm("rbx") = 0x123456789abcdef0ull;
register std::uint64_t savedR12 asm("r12") = 0x23456789abcdef01ull;
register std::uint64_t savedR13 asm("r13") = 0x3456789abcdef012ull;
register std::uint64_t savedR14 asm("r14") = 0x456789abcdef0123ull;
register std::uint64_t savedR15 asm("r15") = 0x56789abcdef01234ull;
asm volatile("" : "+r"(savedRbx), "+r"(savedR12), "+r"(savedR13), "+r"(savedR14), "+r"(savedR15));
g_contextlessFramePointer = reinterpret_cast<std::uint64_t>(__builtin_frame_address(0));
const auto result = attach ? _sceFiberAttachContextAndRun(fiber, nullptr, 0, run, returned) : sceFiberRun_nid_postfix(fiber, run, returned);
asm volatile("" : "+r"(savedRbx), "+r"(savedR12), "+r"(savedR13), "+r"(savedR14), "+r"(savedR15) : : "memory");
Require(savedRbx == 0x123456789abcdef0ull && savedR12 == 0x23456789abcdef01ull && savedR13 == 0x3456789abcdef012ull && savedR14 == 0x456789abcdef0123ull && savedR15 == 0x56789abcdef01234ull, "contextless return restores caller nonvolatile registers");
return result;
}
void RunContextlessTransitions() {
volatile std::uint64_t marker = 0;
g_callerStack = reinterpret_cast<std::uintptr_t>(&marker);
#ifdef _WIN32
const auto* tib = reinterpret_cast<const NT_TIB*>(NtCurrentTeb());
g_callerBase = tib->StackBase;
g_callerLimit = tib->StackLimit;
#endif
for (unsigned index = 0; index < 2; ++index) {
Require(_sceFiberInitializeWithInternalOptionImpl(Contextless(index), "contextless", ContextlessEntry, index, nullptr, 0, nullptr, 0x10, 0) == 0, "initialize contextless transition fiber with size checking");
}
Require(_sceFiberInitializeImpl_nid_postfix(g_transition, "transition", TransitionEntry, 0, nullptr, 0, nullptr, 0) == 0, "initialize transition target");
std::uint64_t returned = 0;
const auto csr = _mm_getcsr();
Require(RunContextless(Contextless(0), 1, &returned) == 0 && returned == 200, "first contextless run");
Require(_mm_getcsr() == csr, "contextless return restores thread MXCSR");
Require(RunContextless(Contextless(0), 1, &returned) == 0 && returned == 200 && g_contextlessEntries[0] == 2, "contextless return discards continuation");
Require(RunContextless(Contextless(0), 2, &returned, true) == 0 && returned == 201, "contextless to contextless switch");
Require(RunContextless(Contextless(1), 1, &returned) == 0 && returned == 201 && g_contextlessEntries[1] == 2, "contextless switch target restarts");
Require(RunContextless(Contextless(0), 3, &returned) == 0 && returned == 777, "contextless to attached switch");
Require(RunContextless(g_transition, 55, &returned) == 0 && returned == 201, "attached to contextless switch");
Require(RunContextless(g_transition, 60, &returned) == 0 && returned == 778, "switch through contextless entry back to attached fiber");
Require(_sceFiberAttachContextAndRun(Contextless(0), g_firstContext.data(), g_firstContext.size(), 90, &returned) == 0 && returned == 91, "attach after abandoning contextless stack");
Require(sceFiberRun_nid_postfix(Contextless(0), 100, &returned) == 0 && returned == 101 && g_contextlessEntries[0] == 6, "attached continuation persists after contextless runs");
std::thread([&] {
volatile std::uint64_t workerMarker = 0;
g_callerStack = reinterpret_cast<std::uintptr_t>(&workerMarker);
#ifdef _WIN32
const auto* workerTib = reinterpret_cast<const NT_TIB*>(NtCurrentTeb());
g_callerBase = workerTib->StackBase;
g_callerLimit = workerTib->StackLimit;
#endif
Require(RunContextless(Contextless(1), 1, &returned) == 0 && returned == 201 && g_contextlessEntries[1] == 4, "contextless fiber restarts on another thread's stack");
}).join();
Require(sceFiberFinalize(Contextless(0)) == 0 && sceFiberFinalize(Contextless(1)) == 0 && sceFiberFinalize(g_transition) == 0, "finalize contextless transition fibers");
g_callerStack = reinterpret_cast<std::uintptr_t>(&marker);
#ifdef _WIN32
g_callerBase = tib->StackBase;
g_callerLimit = tib->StackLimit;
#endif
for (unsigned index = 0; index < 2; ++index) {
Require(_sceFiberInitializeImpl_nid_postfix(Contextless(index), "restart loop", ContextlessEntry, index, nullptr, 0, nullptr, 0) == 0, "initialize contextless restart loop");
}
Require(RunContextless(Contextless(0), 5, &returned) == 0 && returned == 999 && g_contextlessHops == 1024, "contextless switch loop reuses the caller stack");
Require(RunContextless(Contextless(1), 1, nullptr, true) == 0, "contextless run permits a null return output");
Require(sceFiberFinalize(Contextless(0)) == 0 && sceFiberFinalize(Contextless(1)) == 0, "finalize contextless restart loop");
}
std::uint16_t ReadFpuControl() {
std::uint16_t control = 0;
asm volatile("fnstcw %0" : "=m"(control));
return control;
}
void WriteFpuControl(std::uint16_t control) {
asm volatile("fldcw %0" : : "m"(control));
}
constexpr std::uint64_t ContextlessInitializeArgument = 0xfedcba9876543210ull;
constexpr std::uint64_t ContextlessRunArgument = 0x8123456789abcdefull;
constexpr std::uint32_t ContextlessCsr = 0x7f80;
constexpr std::uint16_t ContextlessControl = 0x0b7f;
unsigned g_contextlessOptionEntries = 0;
void APS5_VABI ContextlessOptionsEntry(std::uint64_t initialized, std::uint64_t run) {
Require(initialized == ContextlessInitializeArgument && run == ContextlessRunArgument, "contextless entry keeps 64-bit arguments");
++g_contextlessOptionEntries;
const auto result = (static_cast<std::uint64_t>(ReadFpuControl()) << 32) | _mm_getcsr();
_mm_setcsr(0x1f80);
WriteFpuControl(0x037f);
sceFiberReturnToThread(result, nullptr);
Require(false, "contextless options entry cannot resume after returning");
}
void APS5_VABI ContextlessFpuSource(std::uint64_t, std::uint64_t target) {
_mm_setcsr(ContextlessCsr);
WriteFpuControl(ContextlessControl);
sceFiberSwitch(reinterpret_cast<FiberObject*>(target), ContextlessRunArgument, nullptr);
Require(false, "contextless FPU source is not resumed");
}
void RunContextlessOptions() {
FiberStorage storage{};
FiberStorage sourceStorage{};
auto* fiber = reinterpret_cast<FiberObject*>(&storage);
auto* source = reinterpret_cast<FiberObject*>(&sourceStorage);
const auto callerCsr = _mm_getcsr();
const auto callerControl = ReadFpuControl();
for (const auto flags : {0u, 0x100u}) {
Require(_sceFiberInitializeWithInternalOptionImpl(fiber, "contextless options", ContextlessOptionsEntry, ContextlessInitializeArgument, nullptr, 0, nullptr, flags, 0) == 0, "initialize contextless FPU options");
_mm_setcsr(ContextlessCsr);
WriteFpuControl(ContextlessControl);
const auto expected = flags ? (0x037full << 32) | 0x9fc0u : (static_cast<std::uint64_t>(ContextlessControl) << 32) | ContextlessCsr;
std::uint64_t returned = 0;
for (unsigned restart = 0; restart < 2; ++restart) {
Require(sceFiberRun_nid_postfix(fiber, ContextlessRunArgument, &returned) == 0 && returned == expected, "contextless FPU policy applies on each run");
Require(_mm_getcsr() == ContextlessCsr && ReadFpuControl() == ContextlessControl, "contextless return restores caller FPU registers");
}
_mm_setcsr(callerCsr);
WriteFpuControl(callerControl);
Require(_sceFiberInitializeImpl_nid_postfix(source, "FPU source", ContextlessFpuSource, 0, g_transitionContext.data(), g_transitionContext.size(), nullptr, 0) == 0, "initialize attached FPU source");
Require(sceFiberRun_nid_postfix(source, reinterpret_cast<std::uint64_t>(fiber), &returned) == 0 && returned == expected, "contextless switch inherits or resets the source FPU registers");
Require(_mm_getcsr() == callerCsr && ReadFpuControl() == callerControl, "contextless switch restores the original caller FPU registers");
Require(sceFiberFinalize(fiber) == 0 && sceFiberFinalize(source) == 0, "finalize contextless FPU options");
}
Require(g_contextlessOptionEntries == 6, "contextless FPU entry restarts on runs and switches");
}
[[gnu::noinline]] std::int32_t RunFirst(std::uint64_t* returned) {
g_threadFramePointer = reinterpret_cast<std::uint64_t>(__builtin_frame_address(0));
const std::int32_t result = sceFiberRun_nid_postfix(g_first, 100, returned);
@@ -319,7 +509,13 @@ void RunInternalOptions() {
}
int main() {
int main(int argc, char** argv) {
if (argc == 2 && std::string_view(argv[1]) == "--contextless") {
RunContextlessTransitions();
RunContextlessOptions();
return 0;
}
Require(argc == 1, "known test arguments");
RunSmallContextFirst();
FiberObject* self = nullptr;
Require(sceFiberGetSelf(&self) == FiberErrorPermission, "no fiber on the thread");
+10
View File
@@ -37,8 +37,18 @@ static void CheckConsoleClassValues() {
Require(isprint_nid_postfix(c) == (bits & 0x3e) && isgraph_nid_postfix(c) == (bits & 0x3a) && ispunct_nid_postfix(c) == (bits & 0x08));
}
}
static void CheckSignedCharValues() {
for (int c = -128; c <= -2; ++c) {
Require(isupper_nid_postfix(c) == 0 && islower_nid_postfix(c) == 0 && isalpha_nid_postfix(c) == 0 && isdigit_nid_postfix(c) == 0);
Require(isalnum_nid_postfix(c) == 0 && isspace_nid_postfix(c) == 0 && isblank_nid_postfix(c) == 0 && iscntrl_nid_postfix(c) == 0);
Require(isprint_nid_postfix(c) == 0 && isgraph_nid_postfix(c) == 0 && ispunct_nid_postfix(c) == 0 && isxdigit_nid_postfix(c) == 0);
Require(toupper_nid_postfix(c) == c && tolower_nid_postfix(c) == c);
}
}
int main() {
CheckConsoleClassValues();
CheckSignedCharValues();
Require(std::setlocale(LC_CTYPE, "C") != nullptr);
for (int category = 0; category <= 6; ++category) {
const char* result = setlocale_nid_postfix(category, "C");
@@ -18,5 +18,11 @@ int main() {
std::memcpy(process.data() + 16, &sdkVersion, sizeof(sdkVersion));
if (!ProcessParametersFixture::Install(process.data(), process.size())) return 2;
std::uint32_t type = 0xFFFFFFFFu;
std::uint16_t virtualKey = 0;
char16_t character = 0;
if (sceConvertKeycodeGetVirtualKeycode(0, 0, nullptr) != static_cast<int>(0x80BC0031u)) return 1;
if (sceConvertKeycodeGetVirtualKeycode(0, 0, &virtualKey) != static_cast<int>(0x80BC001Du)) return 1;
if (sceConvertKeycodeGetCharacter(0, 0, 0, nullptr) != static_cast<int>(0x80BC0031u)) return 1;
if (sceConvertKeycodeGetCharacter(0, 0, 0, &character) != static_cast<int>(0x80BC001Du)) return 1;
return sceConvertKeycodeGetImeKeyboardType(0x10000000, &type) == 0 && type == 4 ? 0 : 1;
}
@@ -10,6 +10,50 @@ static void Require(bool value) { if (!value) std::abort(); }
constexpr int Relaxed = 0, Acquire = 2, Release = 3, AcqRel = 4, SeqCst = 5;
static void CheckRemainingTyped() {
std::uint8_t byte = 0;
Require(_Atomic_load_1_nid_postfix(&byte, SeqCst) == 0);
Require(_Atomic_exchange_1_nid_postfix(&byte, 0xa5, SeqCst) == 0 && byte == 0xa5);
std::uint8_t expectedByte = 0;
Require(_Atomic_compare_exchange_strong_1_nid_postfix(&byte, &expectedByte, 1, SeqCst, SeqCst) == 0 && expectedByte == 0xa5 && byte == 0xa5);
Require(_Atomic_compare_exchange_strong_1_nid_postfix(&byte, &expectedByte, 0x3c, SeqCst, SeqCst) == 1 && byte == 0x3c);
expectedByte = 0;
Require(_Atomic_compare_exchange_weak_1_nid_postfix(&byte, &expectedByte, 1, SeqCst, Relaxed) == 0 && expectedByte == 0x3c);
Require(_Atomic_compare_exchange_weak_1_nid_postfix(&byte, &expectedByte, 0x0f, SeqCst, Relaxed) == 1 && byte == 0x0f);
Require(_Atomic_fetch_sub_1_nid_postfix(&byte, 0x10, SeqCst) == 0x0f && byte == 0xff);
Require(_Atomic_fetch_and_1_nid_postfix(&byte, 0x3c, Relaxed) == 0xff && byte == 0x3c);
Require(_Atomic_fetch_xor_1_nid_postfix(&byte, 0xff, AcqRel) == 0x3c && byte == 0xc3);
std::uint16_t half = 0;
_Atomic_store_2_nid_postfix(&half, 0x1234, Release);
Require(half == 0x1234 && _Atomic_exchange_2_nid_postfix(&half, 0xfff0, SeqCst) == 0x1234 && half == 0xfff0);
Require(_Atomic_fetch_add_2_nid_postfix(&half, 0x20, SeqCst) == 0xfff0 && half == 0x0010);
Require(_Atomic_fetch_and_2_nid_postfix(&half, 0x0018, SeqCst) == 0x0010 && half == 0x0010);
Require(_Atomic_fetch_or_2_nid_postfix(&half, 0x0101, SeqCst) == 0x0010 && half == 0x0111);
Require(_Atomic_fetch_xor_2_nid_postfix(&half, 0x0110, SeqCst) == 0x0111 && half == 0x0001);
std::uint16_t expectedHalf = 2;
Require(_Atomic_compare_exchange_weak_2_nid_postfix(&half, &expectedHalf, 9, SeqCst, Relaxed) == 0 && expectedHalf == 1);
Require(_Atomic_compare_exchange_weak_2_nid_postfix(&half, &expectedHalf, 9, SeqCst, Relaxed) == 1 && half == 9);
std::uint32_t word = 0;
_Atomic_store_4_nid_postfix(&word, 0x00ff00ffu, SeqCst);
Require(word == 0x00ff00ffu && _Atomic_fetch_or_4_nid_postfix(&word, 0xff000000u, SeqCst) == 0x00ff00ffu && word == 0xffff00ffu);
Require(_Atomic_fetch_xor_4_nid_postfix(&word, 0xffffffffu, SeqCst) == 0xffff00ffu && word == 0x0000ff00u);
std::uint32_t expectedWord = 0;
Require(_Atomic_compare_exchange_strong_4_nid_postfix(&word, &expectedWord, 1, SeqCst, SeqCst) == 0 && expectedWord == 0x0000ff00u);
Require(_Atomic_compare_exchange_strong_4_nid_postfix(&word, &expectedWord, 1, SeqCst, SeqCst) == 1 && word == 1);
std::uint64_t wide = 0x8000000000000001ull;
Require(_Atomic_fetch_sub_8_nid_postfix(&wide, 2, SeqCst) == 0x8000000000000001ull && wide == 0x7fffffffffffffffull);
Require(_Atomic_fetch_and_8_nid_postfix(&wide, 0xffffffff00000000ull, SeqCst) == 0x7fffffffffffffffull && wide == 0x7fffffff00000000ull);
Require(_Atomic_fetch_or_8_nid_postfix(&wide, 0xffull, SeqCst) == 0x7fffffff00000000ull && wide == 0x7fffffff000000ffull);
std::uint64_t expectedWide = 0;
Require(_Atomic_compare_exchange_strong_8_nid_postfix(&wide, &expectedWide, 5, SeqCst, SeqCst) == 0 && expectedWide == 0x7fffffff000000ffull);
Require(_Atomic_compare_exchange_strong_8_nid_postfix(&wide, &expectedWide, 5, SeqCst, SeqCst) == 1 && wide == 5);
Require(_Atomic_is_lock_free_1_nid_postfix() == 1 && _Atomic_is_lock_free_2_nid_postfix() == 1 && _Atomic_is_lock_free_8_nid_postfix() == 1);
}
static void CheckTyped() {
std::uint8_t byte = 0;
_Atomic_store_1_nid_postfix(&byte, 7, SeqCst);
@@ -130,6 +174,7 @@ static void CheckSized() {
}
int main() {
CheckRemainingTyped();
CheckTyped();
CheckSized();
CheckFlagsAndFences();
+4 -2
View File
@@ -270,9 +270,11 @@ int main(int argc, char** argv) {
Require(_sceKernelRtldThreadAtexitDecrement_nid_postfix(reinterpret_cast<const void*>(add)) == 0);
Require(_sceKernelRtldThreadAtexitIncrement_nid_postfix(reinterpret_cast<const void*>(&ThrowsRuntimeError<void (*)()>)) == 0);
Require(_sceKernelRtldThreadAtexitDecrement_nid_postfix(reinterpret_cast<const void*>(&ThrowsRuntimeError<void (*)()>)) == 0);
Require(ThrowsRuntimeError([] { _sceKernelRtldThreadAtexitDecrement_nid_postfix(reinterpret_cast<const void*>(&Require)); }));
int local = 0;
Require(ThrowsRuntimeError([&] { _sceKernelRtldThreadAtexitIncrement_nid_postfix(&local); }));
Require(_sceKernelRtldThreadAtexitIncrement_nid_postfix(&local) == -1);
Require(_sceKernelRtldThreadAtexitIncrement_nid_postfix(nullptr) == -1);
Require(_sceKernelRtldThreadAtexitDecrement_nid_postfix(&local) == 0);
Require(_sceKernelRtldThreadAtexitDecrement_nid_postfix(nullptr) == 0);
#ifndef _WIN32
Require(dlsym_nid_postfix(nullptr, "malloc") == nullptr);
Require(dlsym_nid_postfix(nullptr, "dlopen") == nullptr);
@@ -0,0 +1,274 @@
#include "prx/libSceFreeType/include/Api.hpp"
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_STROKER_H
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <limits>
#include <stdexcept>
#include <string>
#include <thread>
#include <type_traits>
#include <vector>
#ifdef _WIN32
#include <windows.h>
#else
#include <dlfcn.h>
#include <sys/mman.h>
#include <unistd.h>
#endif
using FreeType::GuestOutline;
using FreeType::GuestVector;
using Query = decltype(&FT_Outline_GetInsideBorder_nid_postfix);
static_assert(std::is_same_v<Query, decltype(&FT_Outline_GetOutsideBorder_nid_postfix)>);
static_assert(sizeof(GuestOutline) == 40 && alignof(GuestOutline) == 8);
static_assert(offsetof(GuestOutline, points) == 8 && offsetof(GuestOutline, contours) == 24);
static_assert(offsetof(GuestOutline, flags) == 32 && sizeof(GuestVector) == 16);
static_assert(sizeof(FT_StrokerBorder) == 4);
namespace {
bool nativeInside;
void Require(bool result, const char* message) {
if (result) return;
std::fprintf(stderr, "FreeType outline border: %s\n", message);
std::exit(1);
}
int APS5_VABI Native(const GuestOutline* input) {
if (!input) return nativeInside ? FT_Outline_GetInsideBorder(nullptr) : FT_Outline_GetOutsideBorder(nullptr);
FT_Outline outline{};
outline.n_points = input->n_points;
outline.n_contours = input->n_contours;
outline.flags = input->flags;
std::vector<FT_Vector> points;
std::vector<unsigned short> contours;
if (outline.n_points > 0) {
for (int i = 0; i < outline.n_points; ++i) {
Require(input->points[i].x >= std::numeric_limits<FT_Pos>::min() && input->points[i].x <= std::numeric_limits<FT_Pos>::max(), "reference x range");
Require(input->points[i].y >= std::numeric_limits<FT_Pos>::min() && input->points[i].y <= std::numeric_limits<FT_Pos>::max(), "reference y range");
points.push_back({static_cast<FT_Pos>(input->points[i].x), static_cast<FT_Pos>(input->points[i].y)});
}
if (outline.n_contours > 0)
for (int i = 0; i < outline.n_contours; ++i) contours.push_back(input->contours[i]);
}
outline.points = points.data();
outline.contours = contours.data();
return nativeInside ? FT_Outline_GetInsideBorder(&outline) : FT_Outline_GetOutsideBorder(&outline);
}
int Expected(bool inside, bool clockwise) { return inside == clockwise ? 1 : 0; }
class ReadOnlyTail {
void* memory;
std::size_t page;
void* data;
public:
ReadOnlyTail(const void* source, std::size_t bytes) {
#ifdef _WIN32
SYSTEM_INFO info;
GetSystemInfo(&info);
page = info.dwPageSize;
memory = VirtualAlloc(nullptr, page * 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
Require(memory != nullptr, "allocate guard pages");
#else
page = static_cast<std::size_t>(sysconf(_SC_PAGESIZE));
memory = mmap(nullptr, page * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
Require(memory != MAP_FAILED, "allocate guard pages");
#endif
Require(bytes <= page, "guard fixture size");
data = static_cast<unsigned char*>(memory) + page - bytes;
std::memcpy(data, source, bytes);
#ifdef _WIN32
DWORD previous;
Require(VirtualProtect(memory, page, PAGE_READONLY, &previous) != 0, "read-only input");
Require(VirtualProtect(static_cast<unsigned char*>(memory) + page, page, PAGE_NOACCESS, &previous) != 0, "inaccessible neighbor");
#else
Require(mprotect(memory, page, PROT_READ) == 0, "read-only input");
Require(mprotect(static_cast<unsigned char*>(memory) + page, page, PROT_NONE) == 0, "inaccessible neighbor");
#endif
}
~ReadOnlyTail() {
#ifdef _WIN32
VirtualFree(memory, 0, MEM_RELEASE);
#else
munmap(memory, page * 2);
#endif
}
template<class T> T* As() const { return static_cast<T*>(data); }
void* End() const { return static_cast<unsigned char*>(memory) + page; }
};
void Cases(Query query, bool inside, bool guest) {
Require(query(nullptr) == Expected(inside, true), "null outline");
GuestOutline outline{};
outline.points = reinterpret_cast<GuestVector*>(std::uintptr_t{1});
outline.contours = reinterpret_cast<std::uint16_t*>(std::uintptr_t{1});
Require(query(&outline) == Expected(inside, true), "empty outline does not read points or contours");
std::array<GuestVector, 4> points{{{0, 0}, {0, 64}, {64, 64}, {64, 0}}};
std::uint16_t end = 3;
outline = {1, 4, points.data(), nullptr, &end, 0};
Require(query(&outline) == Expected(inside, true), "clockwise polygon");
const auto original = points;
for (const int flags : {0, 1, 2, -1}) {
outline.flags = flags;
Require(query(&outline) == Expected(inside, true), "orientation ignores outline flags");
}
Require(std::memcmp(points.data(), original.data(), sizeof(points)) == 0, "input points unchanged");
std::reverse(points.begin(), points.end());
Require(query(&outline) == Expected(inside, false), "counterclockwise polygon");
for (auto& point : points) point.y = 0;
Require(query(&outline) == Expected(inside, false), "collapsed polygon uses no-orientation fallback");
points = original;
outline.n_contours = 0;
Require(query(&outline) == Expected(inside, false), "no contours");
outline.n_contours = 1;
for (const std::int64_t edge : {std::int64_t{0x1000000}, std::int64_t{0x1000001}}) {
points = {{{-edge, -edge}, {-edge, edge}, {edge, edge}, {edge, -edge}}};
Require(query(&outline) == (edge == 0x1000000 ? Expected(inside, true) : Expected(inside, false)), "coordinate-limit boundary");
}
std::array<GuestVector, 8> mixed{};
std::copy(original.begin(), original.end(), mixed.begin());
std::copy(original.rbegin(), original.rend(), mixed.begin() + 4);
std::array<std::uint16_t, 2> mixedEnds{{3, 7}};
GuestOutline cancelled{2, 8, mixed.data(), nullptr, mixedEnds.data(), 0};
Require(query(&cancelled) == Expected(inside, false), "non-collapsed opposite contours cancel area");
std::vector<GuestVector> manyPoints(65535);
std::copy(original.rbegin(), original.rend(), manyPoints.begin());
std::uint16_t manyEnd = 65534;
GuestOutline many{1, 65535, manyPoints.data(), nullptr, &manyEnd, 0};
Require(query(&many) == Expected(inside, false), "unsigned point count above 32767");
std::fill(manyPoints.begin(), manyPoints.end(), GuestVector{});
std::copy(original.begin(), original.end(), manyPoints.end() - 4);
std::vector<std::uint16_t> manyEnds(65532);
for (unsigned i = 0; i < manyEnds.size() - 1; ++i) manyEnds[i] = static_cast<std::uint16_t>(i);
manyEnds.back() = 65534;
many.n_contours = 65532;
many.contours = manyEnds.data();
Require(query(&many) == Expected(inside, true), "unsigned contour count above 32767");
if (guest) {
for (const auto edge : {std::numeric_limits<std::int64_t>::min(), std::numeric_limits<std::int64_t>::max()}) {
points = original;
points[0].x = edge;
Require(query(&outline) == Expected(inside, false), "64-bit out-of-range coordinate");
}
points = original;
std::array<GuestVector, 20> repeated{};
std::array<std::uint16_t, 5> ends{};
const std::array<GuestVector, 4> large{{{0, 0}, {0, 16384}, {16384, 16384}, {16384, 0}}};
for (unsigned i = 0; i < 5; ++i) {
std::copy(large.begin(), large.end(), repeated.begin() + i * 4);
ends[i] = static_cast<std::uint16_t>(i * 4 + 3);
}
GuestOutline wide{5, 20, repeated.data(), nullptr, ends.data(), 0};
Require(query(&wide) == Expected(inside, true), "LP64 area must not wrap at 32 bits");
ReadOnlyTail guardedPoints(original.data(), sizeof(original));
ReadOnlyTail guardedEnds(&end, sizeof(end));
GuestOutline guarded{1, 4, guardedPoints.As<GuestVector>(), static_cast<unsigned char*>(guardedPoints.End()), guardedEnds.As<std::uint16_t>(), -1};
ReadOnlyTail guardedOutline(&guarded, sizeof(guarded));
Require(query(guardedOutline.As<GuestOutline>()) == Expected(inside, true), "read-only bounded inputs and unused inaccessible tags");
outline = {1, 4, nullptr, nullptr, &end, 0};
bool threw = false;
try { query(&outline); } catch (const std::invalid_argument&) { threw = true; }
Require(threw, "null positive-count points are explicitly unsupported");
outline.points = points.data();
outline.contours = nullptr;
threw = false;
try { query(&outline); } catch (const std::invalid_argument&) { threw = true; }
Require(threw, "null positive-count contours are explicitly unsupported");
for (const std::uint16_t badEnd : {std::uint16_t{4}, std::uint16_t{0xffff}}) {
auto invalidEnd = badEnd;
outline.contours = &invalidEnd;
threw = false;
try { query(&outline); } catch (const std::invalid_argument&) { threw = true; }
Require(threw, "invalid contour endpoint is explicitly unsupported");
}
for (const std::uint16_t nextEnd : {std::uint16_t{2}, std::uint16_t{3}}) {
std::array<std::uint16_t, 2> invalidEnds{{3, nextEnd}};
outline.n_contours = 2;
outline.contours = invalidEnds.data();
threw = false;
try { query(&outline); } catch (const std::invalid_argument&) { threw = true; }
Require(threw, "non-increasing contour endpoints are explicitly unsupported");
}
}
std::uint32_t state = 7;
for (unsigned iteration = 0; iteration < 512; ++iteration) {
for (auto& point : points) {
state = state * 1664525u + 1013904223u;
point.x = static_cast<int>((state >> 16) & 255) - 128;
state = state * 1664525u + 1013904223u;
point.y = static_cast<int>((state >> 16) & 255) - 128;
}
outline = {1, 4, points.data(), nullptr, &end, 0};
nativeInside = inside;
Require(query(&outline) == Native(&outline), "bounded polygon matches bundled FreeType");
}
if (guest) {
points = original;
outline = {1, 4, points.data(), nullptr, &end, 0};
std::array<std::thread, 4> threads;
for (auto& thread : threads) thread = std::thread([&] {
for (unsigned i = 0; i < 100; ++i) Require(query(&outline) == Expected(inside, true), "concurrent independent query");
});
for (auto& thread : threads) thread.join();
}
}
}
int main(int argc, char** argv) {
#ifdef _WIN32
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
#endif
if (argc == 2 && std::strcmp(argv[1], "reference") == 0) {
for (const bool inside : {false, true}) {
nativeInside = inside;
Cases(Native, inside, false);
}
std::puts("Bundled FreeType reference cases passed");
return 0;
}
Require(argc == 4, "expected library, border and export mode");
const bool inside = std::strcmp(argv[2], "inside") == 0;
const bool patched = std::strcmp(argv[3], "patched") == 0;
const char* symbol = patched ? (inside ? "Pz262plj20w" : "sKneJjUXWkQ") :
(inside ? "FT_Outline_GetInsideBorder_nid_postfix" : "FT_Outline_GetOutsideBorder_nid_postfix");
#ifdef _WIN32
const auto library = LoadLibraryA(argv[1]);
Require(library != nullptr, "load requested library");
const auto checkPath = [](HMODULE module, const std::filesystem::path& expected) {
Require(module != nullptr, "load matching runtime module");
std::array<wchar_t, 32768> path{};
const auto length = GetModuleFileNameW(module, path.data(), static_cast<DWORD>(path.size()));
Require(length != 0 && length < path.size(), "read loaded module path");
Require(std::filesystem::equivalent(expected, std::filesystem::path(path.data())), "loaded module variant path");
};
const auto requested = std::filesystem::absolute(argv[1]);
checkPath(library, requested);
checkPath(GetModuleHandleW(L"libc.prx"), requested.parent_path() / "libc.prx");
auto query = reinterpret_cast<Query>(GetProcAddress(library, symbol));
#else
const auto library = dlopen(argv[1], RTLD_NOW | RTLD_LOCAL);
if (!library) std::fprintf(stderr, "%s\n", dlerror());
Require(library != nullptr, "load requested library");
auto query = reinterpret_cast<Query>(dlsym(library, symbol));
#endif
if (!query) std::fprintf(stderr, "Missing %s\n", symbol);
Require(query != nullptr, "missing outline-border export");
Cases(query, inside, true);
std::puts("Guest outline-border cases passed");
#ifdef _WIN32
FreeLibrary(library);
#else
dlclose(library);
#endif
}
+5
View File
@@ -1107,7 +1107,12 @@ int main() {
#endif
Require(fchown_nid_postfix(base, 0, 0) == 0);
Require(fchown_nid_postfix(base, 0xFFFFFFFFu, 0xFFFFFFFFu) == 0);
Require(fchown_nid_postfix(base, 0, 0xFFFFFFFFu) == 0);
Require(fchown_nid_postfix(base, 0xFFFFFFFFu, 0) == 0);
Require(fchown_nid_postfix(-1, 0, 0) == -1 && Error() == 9);
Require(fchown_nid_postfix(-1, 1, 0) == -1 && Error() == 9);
Require(fchown_nid_postfix(-1, 0, 1) == -1 && Error() == 9);
Require(fchown_nid_postfix(-1, 1, 1) == -1 && Error() == 9);
bool fchownThrew = false;
try { fchown_nid_postfix(base, 1, 0); } catch (const std::exception&) { fchownThrew = true; }
Require(fchownThrew);
@@ -0,0 +1,71 @@
#include "prx/libkernel_sys/libkernel_sys.hpp"
#include "prx/libc/libc.hpp"
#include <array>
#include <bit>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
namespace {
constexpr int GuestEperm = 1;
constexpr int GuestEsrch = 3;
void Require(bool condition, const char* message) {
if (!condition) {
std::fprintf(stderr, "KERNEL SYS CONSOLE IDENTITY: %s\n", message);
std::abort();
}
}
void CheckConsoleKind() {
Require(sceKernelIsCEX() == 1, "the console must be reported as a retail unit");
Require(sceKernelIsDevKit() == 0, "the console must not be reported as a development kit");
}
void CheckAuthInfo() {
struct {
KernelAuthInfo value;
std::uint64_t guard;
} info;
*__error_nid_postfix() = 0;
Require(get_authinfo_nid_postfix(0, nullptr) == -1 && *__error_nid_postfix() == GuestEperm, "a null result must fail with EPERM");
std::memset(&info, 0x5a, sizeof(info));
*__error_nid_postfix() = 0;
Require(get_authinfo_nid_postfix(getpid_nid_postfix() + 1, &info.value) == -1 && *__error_nid_postfix() == GuestEsrch,
"another process must fail with ESRCH");
for (const auto byte : std::bit_cast<std::array<std::uint8_t, sizeof(info)>>(info))
Require(byte == 0x5a, "a refused call must not write the result");
for (const int pid : {0, getpid_nid_postfix()}) {
std::memset(&info, 0x5a, sizeof(info));
Require(get_authinfo_nid_postfix(pid, &info.value) == 0, "the guest process must be described");
Require(info.value.programAuthorityId == 0 && info.value.capabilities[0] == 0x2000000000000000, "the authority ID or first capability word is wrong");
const auto bytes = std::bit_cast<std::array<std::uint8_t, sizeof(KernelAuthInfo)>>(info.value);
for (std::size_t i = 16; i < bytes.size(); ++i) Require(bytes[i] == 0, "the rest of the result must be cleared");
Require(info.guard == 0x5a5a5a5a5a5a5a5a, "get_authinfo wrote past its 0x88-byte result");
}
}
void CheckOpenPsId() {
std::array<std::uint8_t, 16> plain{};
std::array<std::uint8_t, 24> system;
system.fill(0xa5);
*__error_nid_postfix() = GuestEsrch;
Require(sceKernelGetOpenPsIdForSystem(nullptr) == 22, "a null system OpenPSID must return positive EINVAL");
Require(*__error_nid_postfix() == GuestEsrch, "a null system OpenPSID must leave errno unchanged");
for (auto byte : system) Require(byte == 0xa5, "a null system OpenPSID must not write output");
Require(sceKernelGetOpenPsId(plain.data()) == 0, "the OpenPSID must be readable");
Require(sceKernelGetOpenPsIdForSystem(system.data()) == 0, "the system OpenPSID must be readable");
Require(std::memcmp(plain.data(), system.data(), plain.size()) == 0, "both OpenPSID queries must agree");
for (std::size_t i = plain.size(); i < system.size(); ++i) Require(system[i] == 0xa5, "the system OpenPSID wrote past 16 bytes");
}
}
int main() {
CheckConsoleKind();
CheckAuthInfo();
CheckOpenPsId();
return 0;
}
+66
View File
@@ -12,6 +12,7 @@
namespace {
constexpr int Eintr = 4;
constexpr int Einval = 22;
constexpr int SigBlock = 1;
constexpr int SigSetmask = 3;
@@ -52,6 +53,17 @@ bool Same(const SignalSet& left, const SignalSet& right) {
return std::memcmp(left.bits, right.bits, sizeof(left.bits)) == 0;
}
int caught = 0;
void APS5_VABI Count(int) {
++caught;
}
void RaiseBlocked(const SignalSet& blocked) {
Require(sigprocmask_nid_postfix(SigBlock, &blocked, nullptr) == 0);
Require(raise_nid_postfix(SigUsr1) == 0 && caught == 0);
}
}
int main() {
@@ -94,6 +106,60 @@ int main() {
const Timespec zero{0, 0};
Require(pselect_nid_postfix(-1, nullptr, nullptr, nullptr, &zero, nullptr) == -1 && Error() == Einval);
}
Require(signal_nid_postfix(SigUsr1, Count) != reinterpret_cast<GuestHandler>(static_cast<std::uintptr_t>(-1)));
const SignalSet empty{};
{
RaiseBlocked(blocked);
const SignalSet withUsr1 = CurrentMask();
DescriptorSet read{};
read.Add(writer);
const DescriptorSet original = read;
const Timespec limit{0, 300000000};
const auto start = std::chrono::steady_clock::now();
Require(pselect_nid_postfix(writer + 1, read.words.data(), nullptr, nullptr, &limit, &empty) == -1 && Error() == Eintr);
Require(std::chrono::steady_clock::now() - start < std::chrono::milliseconds(150));
Require(caught == 1);
Require(read.words == original.words);
Require(Same(CurrentMask(), withUsr1));
caught = 0;
}
{
RaiseBlocked(blocked);
Require(pselect_nid_postfix(0, nullptr, nullptr, nullptr, nullptr, &empty) == -1 && Error() == Eintr && caught == 1);
caught = 0;
}
{
RaiseBlocked(blocked);
DescriptorSet read{};
read.Add(reader);
const Timespec limit{0, 300000000};
Require(pselect_nid_postfix(reader + 1, read.words.data(), nullptr, nullptr, &limit, &empty) == 1 && caught == 1);
Require(read.Has(reader));
caught = 0;
}
{
RaiseBlocked(blocked);
const Timespec zero{0, 0};
DescriptorSet read{};
read.Add(writer);
Require(pselect_nid_postfix(writer + 1, read.words.data(), nullptr, nullptr, &zero, &empty) == 0 && caught == 1);
caught = 0;
}
{
RaiseBlocked(blocked);
const Timespec invalid{0, 1000000000};
Require(pselect_nid_postfix(0, nullptr, nullptr, nullptr, &invalid, &empty) == -1 && Error() == Einval && caught == 1);
caught = 0;
}
{
RaiseBlocked(blocked);
const Timespec limit{0, 200000000};
const auto start = std::chrono::steady_clock::now();
Require(pselect_nid_postfix(0, nullptr, nullptr, nullptr, &limit, &blocked) == 0 && caught == 0);
Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(150));
Require(sigprocmask_nid_postfix(SigSetmask, &empty, nullptr) == 0 && caught == 1);
caught = 0;
}
Require(sigprocmask_nid_postfix(SigSetmask, &before, nullptr) == 0);
Require(close_nid_postfix(reader) == 0 && close_nid_postfix(writer) == 0);
std::puts("pselect passed");
+78 -3
View File
@@ -5,15 +5,15 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <array>
#include <atomic>
#include <barrier>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <limits>
#include <stdexcept>
#include <thread>
extern "C" {
}
static void Require(bool value) { if (!value) std::abort(); }
@@ -275,6 +275,80 @@ static unsigned APS5_VABI TestPadGo(GuestPad* base) {
return 42;
}
static void RequireExit(bool value, const char* message) {
if (!value) {
std::fprintf(stderr, "CRT exit lifecycle: %s\n", message);
std::abort();
}
}
struct CrtExitState {
int code = 0;
int key = -1;
std::barrier<>* ready = nullptr;
void* parent = nullptr;
void* self = nullptr;
std::atomic<unsigned> destructors{0};
std::atomic<bool> returned{false};
};
static void APS5_VABI CrtExitDestructor(void* value) {
auto& state = *static_cast<CrtExitState*>(value);
RequireExit(_Thrd_current_nid_postfix() == state.self, "identity during destructor");
RequireExit(_Tss_get_nid_postfix(state.key) == nullptr, "value cleared before destructor");
if (state.destructors.fetch_add(1) == 0)
RequireExit(_Tss_set_nid_postfix(state.key, &state) == 0, "reinstall value for another cleanup pass");
}
static auto volatile crtExit = &_Thrd_exit_nid_postfix;
static void* APS5_VABI CrtExitEntry(void* value) {
auto& state = *static_cast<CrtExitState*>(value);
state.self = _Thrd_current_nid_postfix();
RequireExit(state.self && _Thrd_equal_nid_postfix(state.self, state.self) == 1, "current identity");
RequireExit(_Thrd_equal_nid_postfix(state.self, state.parent) == 0, "different parent identity");
RequireExit(_Tss_get_nid_postfix(state.key) == nullptr, "child starts with empty value");
RequireExit(_Tss_set_nid_postfix(state.key, &state) == 0, "set child value");
state.ready->arrive_and_wait();
RequireExit(_Tss_get_nid_postfix(state.key) == &state, "child value isolation");
crtExit(state.code);
state.returned = true;
return nullptr;
}
static void ThreadExitLifecycle() {
constexpr std::array<int, 5> codes{0, 42, -1, std::numeric_limits<int>::min(), std::numeric_limits<int>::max()};
constexpr std::array<std::uintptr_t, 5> expected{0, 42, 0xffffffffu, 0x80000000u, 0x7fffffffu};
std::barrier ready(6);
std::array<CrtExitState, 5> states;
std::array<Pthread, 5> threads{};
int key = -1, parentValue = 31;
void* parent = _Thrd_current_nid_postfix();
RequireExit(_Tss_create_nid_postfix(&key, CrtExitDestructor) == 0, "create destructor key");
RequireExit(_Tss_set_nid_postfix(key, &parentValue) == 0, "set parent value");
for (unsigned i = 0; i < states.size(); ++i) {
states[i].code = codes[i];
states[i].key = key;
states[i].ready = &ready;
states[i].parent = parent;
RequireExit(scePthreadCreate(&threads[i], nullptr, CrtExitEntry, &states[i], "CRT exit audit") == 0, "create child");
}
RequireExit(_Tss_get_nid_postfix(key) == &parentValue, "parent isolated before release");
ready.arrive_and_wait();
for (unsigned i = 0; i < states.size(); ++i) {
void* result = nullptr;
RequireExit(scePthreadJoin(threads[i], &result) == 0, "join exiting child");
RequireExit(reinterpret_cast<std::uintptr_t>(result) == expected[i], "full zero-extended exit result");
RequireExit(!states[i].returned, "exit must not return");
RequireExit(states[i].destructors == 2, "reinstalled destructor runs twice before join returns");
RequireExit(states[i].self == threads[i], "created and current identity match");
}
RequireExit(_Tss_get_nid_postfix(key) == &parentValue, "parent isolated after children exit");
RequireExit(_Tss_delete_nid_postfix(key) == 0, "delete key");
RequireExit(_Tss_get_nid_postfix(key) == nullptr, "deleted key value unavailable");
std::puts("PASS CRT lifecycle: five concurrent exit codes, full return width, identity, parent/child isolation, two destructor passes, key deletion");
}
static void PadLaunchAndJoin() {
unsigned (APS5_VABI* vtable[1])(GuestPad*) = {TestPadGo};
TestPad unregPad{};
@@ -508,6 +582,7 @@ static void ThreadHelpers() {
}
int main() {
ThreadExitLifecycle();
KernelKeys();
VersionedSync();
ForwardedCalls();
+21
View File
@@ -651,6 +651,26 @@ static void CheckTanhConsoleBits() {
}
}
static void CheckRoundingNaNConsoleBits() {
constexpr std::uint64_t cases[][2] = {
{0x7ff0000000000001ull, 0x7ff8000000000001ull}, {0xfff4000000000123ull, 0xfffc000000000123ull}, {0x7ff8000000000456ull, 0x7ff8000000000456ull},
{0x7ff0000000000000ull, 0x7ff0000000000000ull}, {0xfff0000000000000ull, 0xfff0000000000000ull},
};
constexpr std::uint32_t floatCases[][2] = {
{0x7f800001u, 0x7fc00001u}, {0xffa00123u, 0xffe00123u}, {0x7fc00456u, 0x7fc00456u}, {0x7f800000u, 0x7f800000u}, {0xff800000u, 0xff800000u},
};
for (const auto function : {trunc_nid_postfix, ceil_nid_postfix, floor_nid_postfix, nearbyint_nid_postfix}) {
for (const auto& [input, expected] : cases) {
Require(std::bit_cast<std::uint64_t>(function(std::bit_cast<double>(input))) == expected);
}
}
for (const auto function : {truncf_nid_postfix, ceilf_nid_postfix, floorf_nid_postfix, nearbyintf_nid_postfix}) {
for (const auto& [input, expected] : floatCases) {
Require(std::bit_cast<std::uint32_t>(function(std::bit_cast<float>(input))) == expected);
}
}
}
static void CheckAsinl() {
const struct { std::uint64_t inputMantissa; std::uint16_t inputSignExponent; std::uint64_t outputMantissa; std::uint16_t outputSignExponent; } cases[] = {
{0, 0x0000, 0, 0x0000}, {0, 0x8000, 0, 0x8000},
@@ -1744,6 +1764,7 @@ int main() {
CheckLog1pConsoleBits();
CheckNextafterConsoleBits();
CheckTanhConsoleBits();
CheckRoundingNaNConsoleBits();
CheckAsinl();
Require(scalbn_nid_postfix(0.75, -2) == 0.1875);
Require(scalbnf_nid_postfix(0.75f, 4) == 12.f);
+60 -4
View File
@@ -4,6 +4,7 @@
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <stdexcept>
static void Require(bool value, const char* what) {
@@ -23,6 +24,8 @@ static bool Throws(TCall call) {
}
int main() {
constexpr int kNotInitialized = -2141898750;
constexpr int kInvalidArgument = -2141898748;
constexpr int kIntentNotFound = -2141898746;
constexpr int kValueNotFound = -2141898745;
@@ -41,22 +44,75 @@ int main() {
Require(info.size == sizeof(NpGameIntentInfo), "info size mismatch");
Require(info.user_id == -1, "info user_id mismatch");
char preBuf[64]{};
Require(sceNpGameIntentGetPropertyValueString(nullptr, "key", preBuf, sizeof(preBuf)) == kNotInitialized,
"uninitialized get property string with null data must report not-initialized");
Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "key", preBuf, sizeof(preBuf)) == kNotInitialized,
"uninitialized get property string with valid data must report not-initialized");
Require(sceNpGameIntentReceiveIntent(nullptr) == kNotInitialized,
"uninitialized receive intent with null pointer must report not-initialized");
Require(sceNpGameIntentReceiveIntent(&info) == kNotInitialized,
"uninitialized receive intent with valid pointer must report not-initialized");
Require(sceNpGameIntentInitialize(&initParam) == 0, "initialization failed");
Require(sceNpGameIntentReceiveIntent(&info) == kIntentNotFound, "receive intent mismatch");
Require(sceNpGameIntentReceiveIntent(nullptr) == kInvalidArgument, "null receive intent did not fail");
Require(sceNpGameIntentReceiveIntent(&info) == kInvalidArgument, "mismatched struct size must report invalid argument");
info.size = 0;
Require(sceNpGameIntentReceiveIntent(&info) == kInvalidArgument, "zero size must report invalid argument");
info.size = 0x413f;
Require(sceNpGameIntentReceiveIntent(&info) == kInvalidArgument, "0x413f size must report invalid argument");
info.size = 0x4141;
Require(sceNpGameIntentReceiveIntent(&info) == kInvalidArgument, "0x4141 size must report invalid argument");
info.size = sizeof(NpGameIntentInfo);
char buf[64]{};
Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "key", buf, sizeof(buf)) == kValueNotFound,
Require(sceNpGameIntentGetPropertyValueString(nullptr, "key", buf, sizeof(buf)) == kInvalidArgument,
"null intent data did not fail");
Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, nullptr, buf, sizeof(buf)) == kInvalidArgument,
"null key did not fail");
Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "", buf, sizeof(buf)) == kInvalidArgument,
"empty key did not fail");
Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "key", nullptr, sizeof(buf)) == kInvalidArgument,
"null value buf did not fail");
Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "key", buf, 0) == kInvalidArgument,
"zero buf size did not fail");
char canaryBuf[64]{};
std::memset(canaryBuf, 'X', sizeof(canaryBuf));
Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "key", canaryBuf, 0) == kInvalidArgument,
"zero buf size canary call did not fail");
Require(canaryBuf[0] == 'X', "buffer was modified on zero size error");
Require(sceNpGameIntentGetPropertyValueString(nullptr, "key", canaryBuf, sizeof(canaryBuf)) == kInvalidArgument,
"null data canary call did not fail");
Require(canaryBuf[0] == 'X', "buffer was modified on null data error");
Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, nullptr, canaryBuf, sizeof(canaryBuf)) == kInvalidArgument,
"null key canary call did not fail");
Require(canaryBuf[0] == 'X', "buffer was modified on null key error");
Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "", canaryBuf, sizeof(canaryBuf)) == kInvalidArgument,
"empty key canary call did not fail");
Require(canaryBuf[0] == 'X', "buffer was modified on empty key error");
Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "key", canaryBuf, sizeof(canaryBuf)) == kValueNotFound,
"get property value mismatch");
Require(canaryBuf[0] == '\0', "valid call did not write terminator");
canaryBuf[0] = 'Y';
Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "key", canaryBuf, 1) == kValueNotFound,
"bufSize 1 did not succeed");
Require(canaryBuf[0] == '\0', "bufSize 1 did not write terminator");
Require(sceNpGameIntentVshInitialize() == 0, "vsh init failed");
Require(sceNpGameIntentVshTerminate() == 0, "vsh terminate failed");
Require(sceNpGameIntentTerminate() == 0, "terminate failed");
Require(sceNpGameIntentGetPropertyValueString(&info.intent_data, "key", buf, sizeof(buf)) == kNotInitialized,
"post-terminate get property value must report not-initialized");
Require(sceNpGameIntentReceiveIntent(&info) == kNotInitialized,
"post-terminate receive intent must report not-initialized");
unsigned char appLauncherParam[0x28]{};
*reinterpret_cast<std::uint64_t*>(appLauncherParam) = 0x28;
Require(sceNpAppLauncherInitialize(appLauncherParam) == 0, "app launcher init failed");
Require(Throws<std::runtime_error>([] { sceNpAppLauncherLaunchApp(); }),
"app launcher launch must throw until implemented");
Require(sceNpAppLauncherTerminate() == 0, "app launcher terminate failed");
*reinterpret_cast<std::uint64_t*>(appLauncherParam) = 0x10;
Require(sceNpAppLauncherInitialize(appLauncherParam) == 0,
+7
View File
@@ -14,6 +14,7 @@ namespace {
constexpr int InvalidArgument = static_cast<int>(0x80551604u);
constexpr int InsufficientBuffer = static_cast<int>(0x80551605u);
constexpr int InvalidHandle = static_cast<int>(0x80551608u);
constexpr int InvalidContext = static_cast<int>(0x80551609u);
constexpr int InvalidTrophyId = static_cast<int>(0x8055160au);
constexpr int AlreadyUnlocked = static_cast<int>(0x8055160cu);
@@ -97,6 +98,12 @@ int main() {
Require(sceNpTrophyCreateContext(&missing, 1, 1, 0) == 0, "context for a label without a package");
Require(sceNpTrophyRegisterContext(missing, handle, 0) == TitleConfNotInstalled, "register without a trophy package");
Require(sceNpTrophyCreateHandle(nullptr) == InvalidArgument, "null handle creation");
Require(sceNpTrophyCreateContext(nullptr, 1, 0, 0) == InvalidArgument, "null context creation");
Require(sceNpTrophyGetGameIcon(context, handle, icon, nullptr) == InvalidArgument, "null game icon size pointer");
Require(sceNpTrophyGetGroupIcon(context, handle, 0, icon, nullptr) == InvalidArgument, "null group icon size pointer");
Require(sceNpTrophyGetTrophyIcon(context, handle, 0, icon, nullptr) == InvalidArgument, "null trophy icon size pointer");
Require(sceNpTrophyDestroyHandle(-1) == InvalidHandle, "invalid handle destruction");
Require(sceNpTrophyDestroyContext(missing) == 0, "destroy the second context");
Require(sceNpTrophyDestroyHandle(handle) == 0 && sceNpTrophyDestroyContext(context) == 0, "destroy the handle and context");
Require(sceNpTrophyDestroyContext(context) == InvalidContext, "destroy the context twice");
+101
View File
@@ -3,6 +3,9 @@
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <atomic>
#include <chrono>
#include <thread>
namespace {
@@ -13,6 +16,7 @@ constexpr int Aborted = static_cast<int>(0x80550707u);
constexpr int InsufficientArgument = static_cast<int>(0x8055070Cu);
constexpr int InvalidId = static_cast<int>(0x8055070Eu);
constexpr int InvalidType = static_cast<int>(0x80550711u);
constexpr int LookupSynchronousRequest = static_cast<int>(0x80550307u);
constexpr int PollFinished = 0;
constexpr int PollRunning = 1;
constexpr int WordFilterTitleCtxBase = 0x01000000;
@@ -118,11 +122,108 @@ void CheckWordFilter() {
}
int WaitInThread(const char* name, int (APS5_VABI *wait)(int, int*), int reqId, int* result, void (*finish)(int)) {
int status = 7;
std::atomic<bool> entered{false};
std::atomic<bool> returned{false};
std::thread waiter([&] {
entered = true;
status = wait(reqId, result);
returned = true;
});
while (!entered) std::this_thread::yield();
std::this_thread::sleep_for(std::chrono::milliseconds(20));
Require(!returned, name, "the wait returned before the request finished");
finish(reqId);
waiter.join();
return status;
}
void StartLookup(int reqId) {
NpOnlineId onlineId{};
std::strcpy(onlineId.data, "player_2");
sceNpLookupNpId(reqId, &onlineId, npId, nullptr);
}
void AbortLookup(int reqId) {
sceNpLookupAbortRequest(reqId);
}
void DeleteLookup(int reqId) {
sceNpLookupDeleteRequest(reqId);
}
void CheckLookupAsync() {
const int context = sceNpLookupCreateTitleCtx(selfNpId);
Require(sceNpLookupCreateAsyncRequest(context, nullptr) == InvalidArgument, "sceNpLookupCreateAsyncRequest", "a null parameter was taken");
Require(sceNpLookupCreateAsyncRequest(context, wrongAsyncParam) == InvalidArgument, "sceNpLookupCreateAsyncRequest", "a parameter size other than 0x18 was taken");
Require(sceNpLookupCreateAsyncRequest(context + 100, asyncParam) == InvalidId, "sceNpLookupCreateAsyncRequest", "an unknown context was taken");
Require(sceNpLookupSetTimeout(context, -1, 0, 0, 0, 0) == InvalidArgument, "sceNpLookupSetTimeout", "a negative retry count was taken");
Require(sceNpLookupSetTimeout(context, 0, 0, 0, 0, 0) == InvalidArgument, "sceNpLookupSetTimeout", "settings without any effect were taken");
Require(sceNpLookupSetTimeout(context, 0, 999999, 0, 0, 0) == InvalidArgument, "sceNpLookupSetTimeout", "a resolve timeout under 1 s was taken");
Require(sceNpLookupSetTimeout(context, 0, 0, 9999999, 0, 0) == InvalidArgument, "sceNpLookupSetTimeout", "a connect timeout under 10 s was taken");
Require(sceNpLookupSetTimeout(context, 0, 0, 0, 9999999, 0) == InvalidArgument, "sceNpLookupSetTimeout", "a send timeout under 10 s was taken");
Require(sceNpLookupSetTimeout(context, 0, 0, 0, 0, 9999999) == InvalidArgument, "sceNpLookupSetTimeout", "a receive timeout under 10 s was taken");
Require(sceNpLookupSetTimeout(context + 100, 1, 0, 0, 0, 0) == InvalidId, "sceNpLookupSetTimeout", "an unknown id was taken");
Require(sceNpLookupSetTimeout(context, 1, 0, 0, 0, 0) == 0, "sceNpLookupSetTimeout", "a context timeout was refused");
const int async = sceNpLookupCreateAsyncRequest(context, asyncParam);
Require(async > 0, "sceNpLookupCreateAsyncRequest", "creating an asynchronous request failed");
Require(sceNpLookupSetTimeout(async, 0, 1000000, 10000000, 10000000, 10000000) == 0, "sceNpLookupSetTimeout", "a request timeout was refused");
int result = 7;
Require(sceNpLookupPollAsync(async + 100, &result) == InvalidId, "sceNpLookupPollAsync", "an unknown request was taken");
Require(sceNpLookupPollAsync(async, nullptr) == InvalidArgument, "sceNpLookupPollAsync", "a null result was taken");
Require(sceNpLookupPollAsync(async + 100, nullptr) == InvalidArgument, "sceNpLookupPollAsync", "an unknown request was checked before a null result");
Require(sceNpLookupWaitAsync(async, nullptr) == InvalidArgument, "sceNpLookupWaitAsync", "a null result was taken");
Require(sceNpLookupPollAsync(async, &result) == PollRunning && result == 7, "sceNpLookupPollAsync", "an unstarted request was not running");
Require(WaitInThread("sceNpLookupWaitAsync", sceNpLookupWaitAsync, async, &result, StartLookup) == 0 && result == SignedOut, "sceNpLookupWaitAsync", "waiting did not end with SIGNED_OUT when the lookup started");
Require(sceNpLookupPollAsync(async, nullptr) == InvalidArgument, "sceNpLookupPollAsync", "a null result was taken on a finished request");
result = 7;
Require(sceNpLookupPollAsync(async, &result) == PollFinished && result == SignedOut, "sceNpLookupPollAsync", "the request did not finish with SIGNED_OUT");
const int sync = sceNpLookupCreateRequest(context);
StartLookup(sync);
result = 7;
Require(sceNpLookupWaitAsync(sync, &result) == LookupSynchronousRequest && result == 7, "sceNpLookupWaitAsync", "a synchronous request was not refused with 0x80550307");
Require(sceNpLookupPollAsync(sync, &result) == LookupSynchronousRequest && result == 7, "sceNpLookupPollAsync", "a synchronous request was not refused with 0x80550307");
const int aborted = sceNpLookupCreateAsyncRequest(context, asyncParam);
Require(sceNpLookupAbortRequest(aborted + 100) == InvalidId, "sceNpLookupAbortRequest", "an unknown request was taken");
Require(WaitInThread("sceNpLookupWaitAsync", sceNpLookupWaitAsync, aborted, &result, AbortLookup) == 0 && result == Aborted, "sceNpLookupAbortRequest", "waiting did not end with ABORTED");
Require(sceNpLookupNpId(aborted, nullptr, npId, nullptr) == InsufficientArgument, "sceNpLookupNpId", "arguments were not checked before the abort");
const int deleted = sceNpLookupCreateAsyncRequest(context, asyncParam);
Require(WaitInThread("sceNpLookupWaitAsync", sceNpLookupWaitAsync, deleted, &result, DeleteLookup) == InvalidId, "sceNpLookupWaitAsync", "waiting on a deleted request did not return INVALID_ID");
Require(sceNpLookupWaitAsync(deleted, &result) == InvalidId, "sceNpLookupWaitAsync", "an unknown request was taken");
sceNpLookupDeleteTitleCtx(context);
}
void CheckWordFilterAsync() {
const int context = sceNpWordFilterCreateTitleCtx(selfNpId);
Require(sceNpWordFilterSetTimeout(context, 0, 0, 0, 0, 0) == InvalidArgument, "sceNpWordFilterSetTimeout", "settings without any effect were taken");
Require(sceNpWordFilterSetTimeout(context, 0, 0, 0, 0, 9999999) == InvalidArgument, "sceNpWordFilterSetTimeout", "a receive timeout under 10 s was taken");
Require(sceNpWordFilterSetTimeout(context, 1, 0, 0, 0, 0) == 0, "sceNpWordFilterSetTimeout", "a context timeout was refused");
Require(sceNpWordFilterSetTimeout(1, 1, 0, 0, 0, 0) == InvalidId, "sceNpWordFilterSetTimeout", "a lookup id was taken");
const int sync = sceNpWordFilterCreateRequest(context);
const int async = sceNpWordFilterCreateAsyncRequest(context, asyncParam);
Require(sceNpWordFilterSetTimeout(async, 2, 1000000, 0, 0, 0) == 0, "sceNpWordFilterSetTimeout", "a request timeout was refused");
int result = 7;
Require(sceNpWordFilterWaitAsync(async, nullptr) == InvalidArgument, "sceNpWordFilterWaitAsync", "a null result was taken");
Require(sceNpWordFilterWaitAsync(sync, &result) == InvalidId, "sceNpWordFilterWaitAsync", "a synchronous request was taken");
Require(sceNpWordFilterWaitAsync(async + 100, &result) == InvalidId, "sceNpWordFilterWaitAsync", "an unknown request was taken");
Require(sceNpWordFilterCensorComment(async, "word", nullptr) == 0, "sceNpWordFilterCensorComment", "the asynchronous request did not start");
Require(sceNpWordFilterWaitAsync(async, &result) == 0 && result == SignedOut, "sceNpWordFilterWaitAsync", "the request did not finish with SIGNED_OUT");
sceNpWordFilterDeleteTitleCtx(context);
}
int main() {
std::memset(selfNpId, 0, sizeof(selfNpId));
std::strcpy(reinterpret_cast<char*>(selfNpId), "player_1");
CheckLookup();
CheckWordFilter();
CheckLookupAsync();
CheckWordFilterAsync();
if (failures != 0) return 1;
std::puts("NpUtility offline tests passed");
return 0;
+16
View File
@@ -287,6 +287,22 @@ int main(int argc, char** argv) {
RunLayout(argv[0], "--package-chunk-table", {{"playgo-chunk.dat", ChunkTableFile()}});
RunLayout(argv[0], "--package-chunk-table", {{"playgo-chunk.dat", ChunkTableFile("plgo")}});
RunLayout(argv[0], "--scenario-with-chunk-table", {{"playgo-chunk.dat", ChunkTableFile()}, {"playgo-scenario.json", "{}"}});
std::int64_t eta = -1;
std::int32_t speed = -1;
PlayGoInitParams fixtureInit{};
int validHandle = -1;
Require(scePlayGoInitialize(&fixtureInit) == 0);
Require(scePlayGoOpen(&validHandle, nullptr) == 0);
constexpr int badHandleError = static_cast<int>(0x80B20009);
constexpr int badPointerError = static_cast<int>(0x80B2000A);
Require(scePlayGoGetEta(-1, nullptr, 0, &eta) == badHandleError);
Require(scePlayGoGetEta(validHandle, nullptr, 0, nullptr) == badPointerError);
Require(scePlayGoGetInstallSpeed(-1, &speed) == badHandleError);
Require(scePlayGoGetInstallSpeed(0x506C476F, &speed) == badHandleError && speed == -1);
Require(scePlayGoGetInstallSpeed(validHandle, nullptr) == badPointerError);
Require(scePlayGoGetInstallSpeed(validHandle, &speed) == 0 && speed == 2);
Require(scePlayGoPrefetch(-1, nullptr, 0, 0) == badHandleError);
Require(scePlayGoClose(validHandle) == 0);
RunLayout(argv[0], "--scenario-only", {{"playgo-scenario.json", "{}"}});
RunLayout(argv[0], "--scenario-only-installed", {{"playgo-scenario.json", "{}"}});
RunLayout(argv[0], "--scenario-only-absent", {{"playgo-scenario.json", "{}"}});
+59 -16
View File
@@ -5,75 +5,118 @@
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <io.h>
#include <string>
#include <string_view>
#include <thread>
#include <windows.h>
static constexpr int SigUsr1 = 30;
static constexpr int Raises = 10;
static constexpr DWORD PipeBytes = 4096;
static constexpr std::uint64_t BlockBytes = 4u * PipeBytes;
static constexpr std::size_t RipOffset = 0xe0;
static constexpr ULONG ThreadSuspendCount = 35;
static void Require(bool value) { if (!value) std::abort(); }
static std::FILE* shared = nullptr;
static std::atomic<int> deliveries{0};
static std::atomic<int> released{0};
static std::atomic<bool> stop{false};
static std::atomic<int> blocks{0};
static std::atomic<int> written{0};
static std::atomic<int> blocked{0};
static std::atomic<HANDLE> writerThread{nullptr};
static bool delayReturn = false;
static void APS5_VABI Handler(int, void*) {
static bool InTestImage(std::uint64_t rip) {
const auto base = reinterpret_cast<std::uint64_t>(GetModuleHandleW(nullptr));
const auto* dos = reinterpret_cast<const IMAGE_DOS_HEADER*>(base);
const auto* nt = reinterpret_cast<const IMAGE_NT_HEADERS*>(base + dos->e_lfanew);
return rip >= base && rip - base < nt->OptionalHeader.SizeOfImage;
}
static bool Suspended(HANDLE thread) {
using Query = LONG(NTAPI*)(HANDLE, ULONG, void*, ULONG, ULONG*);
static const auto query = reinterpret_cast<Query>(GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtQueryInformationThread"));
ULONG count = 0;
Require(query != nullptr && query(thread, ThreadSuspendCount, &count, sizeof(count), nullptr) >= 0);
return count > 0;
}
static void APS5_VABI Handler(int, void* context) {
const auto delivery = deliveries.fetch_add(1) + 1;
std::uint64_t rip = 0;
std::memcpy(&rip, static_cast<const std::byte*>(context) + RipOffset, sizeof(rip));
Require(InTestImage(rip));
while (released.load() < delivery) std::this_thread::yield();
}
static void* APS5_VABI Writer(void*) {
const std::string block(16u * PipeBytes, 'x');
while (!stop.load()) {
HANDLE self = nullptr;
Require(DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &self, THREAD_QUERY_INFORMATION, FALSE, 0) != 0);
writerThread.store(self);
const std::string block(BlockBytes, 'x');
for (int raise = 1; raise <= Raises; ++raise) {
Require(std::fwrite(block.data(), 1, block.size(), shared) == block.size());
blocks.fetch_add(1);
volatile std::uint64_t spin = 0;
for (std::uint64_t i = 0; i < 200000; ++i) spin = spin + i;
if (delayReturn) {
volatile std::uint64_t spin = 0;
for (std::uint64_t i = 0; i < 100000000; ++i) spin = spin + i;
}
written.store(raise);
while (deliveries.load() < raise) {
}
}
return nullptr;
}
int main() {
int main(int argc, char** argv) {
const std::string_view mode = argc > 1 ? argv[1] : "";
Require(mode.empty() || mode == "--delay-raise" || mode == "--delay-return");
delayReturn = mode == "--delay-return";
std::atomic<bool> done{false};
std::thread watchdog([&done] {
for (int i = 0; i < 25000 && !done.load(); ++i) std::this_thread::sleep_for(std::chrono::milliseconds(1));
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(25);
while (!done.load() && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(10));
if (!done.load()) std::abort();
});
HANDLE read = nullptr;
HANDLE write = nullptr;
Require(CreatePipe(&read, &write, nullptr, PipeBytes) != 0);
shared = _fdopen(_open_osfhandle(reinterpret_cast<intptr_t>(write), _O_WRONLY | _O_BINARY), "wb");
Require(shared != nullptr);
std::atomic<bool> draining{true};
Require(shared != nullptr && std::setvbuf(shared, nullptr, _IONBF, 0) == 0);
std::thread reader([&] {
char chunk[PipeBytes];
DWORD got = 0;
std::uint64_t total = 0;
int block = 0;
while (ReadFile(read, chunk, sizeof(chunk), &got, nullptr) != 0 && got != 0) {
if (draining.load()) std::this_thread::sleep_for(std::chrono::milliseconds(10));
total += got;
if (block == Raises || total <= static_cast<std::uint64_t>(block) * (BlockBytes + 1)) continue;
blocked.store(++block);
while (!Suspended(writerThread.load())) {
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
Require(written.load() < block);
}
});
Require(sceKernelInstallExceptionHandler(SigUsr1, reinterpret_cast<void*>(&Handler)) == 0);
Pthread writer = nullptr;
Require(scePthreadCreate(&writer, nullptr, Writer, nullptr, "pipe writer") == 0);
while (blocks.load() == 0) std::this_thread::yield();
for (int raise = 1; raise <= Raises; ++raise) {
while (blocked.load() < raise) std::this_thread::yield();
if (mode == "--delay-raise") std::this_thread::sleep_for(std::chrono::milliseconds(200));
Require(sceKernelRaiseException(writer, SigUsr1) == 0);
while (deliveries.load() < raise) std::this_thread::yield();
Require(std::fputc('y', shared) == 'y');
released.store(raise);
}
stop.store(true);
draining.store(false);
Require(scePthreadJoin(writer, nullptr) == 0);
Require(sceKernelRemoveExceptionHandler(SigUsr1) == 0);
std::fclose(shared);
reader.join();
CloseHandle(read);
CloseHandle(writerThread.load());
done.store(true);
watchdog.join();
}
@@ -241,6 +241,7 @@ int main() {
const auto previous = std::filesystem::current_path();
std::filesystem::current_path(root);
CheckUnmountedCalls();
Check(sceSaveDataSyncSaveDataMemory(nullptr) == static_cast<int>(0x809F0000), "null param rejected in memory sync");
Check(sceSaveDataInitialize3(nullptr) == 0, "SaveData initializes");
MountAndCheck("fresh", MountReadWrite | MountCreate2, 1u, "CREATE2 of a missing save");
MountAndCheck("fresh", MountReadWrite | MountCreate2, 0u, "CREATE2 of an existing save");
@@ -61,6 +61,7 @@ int main() {
for (const auto byte : vrr) Require(byte == std::byte{0xff});
Require(Rejects(handle, nullptr));
Require(Rejects(-1, nullptr));
Require(Rejects(0, &status));
Require(Rejects(-1, &status));
Require(Rejects(NEVER_OPENED_HANDLE, &status));
+5 -2
View File
@@ -12,14 +12,17 @@ static void Require(bool value) { if (!value) throw std::runtime_error("Kernel e
int main() {
*__error_nid_postfix() = 123;
const std::array<std::pair<std::int32_t, std::int32_t>, 6> cases{{
const std::array<std::pair<std::int32_t, std::int32_t>, 8> cases{{
{0, 0}, {1, SCE_KERNEL_ERROR_EPERM}, {2, SCE_KERNEL_ERROR_ENOENT},
{4, SCE_KERNEL_ERROR_EINTR}, {14, SCE_KERNEL_ERROR_EFAULT}, {22, SCE_KERNEL_ERROR_EINVAL}
{4, SCE_KERNEL_ERROR_EINTR}, {14, SCE_KERNEL_ERROR_EFAULT}, {22, SCE_KERNEL_ERROR_EINVAL},
{95, SCE_KERNEL_ERROR_ENOTRECOVERABLE}, {96, SCE_KERNEL_ERROR_EOWNERDEAD}
}};
for (const auto [input, expected] : cases) {
Require(sceKernelError(input) == expected);
Require(*__error_nid_postfix() == 123);
}
Require(SCE_KERNEL_ERROR_ENOTRECOVERABLE == static_cast<std::int32_t>(0x8002005fu));
Require(SCE_KERNEL_ERROR_EOWNERDEAD == static_cast<std::int32_t>(0x80020060u));
Require(sceKernelError(-1) == static_cast<std::int32_t>(0x8001ffffu));
Require(sceKernelError(std::numeric_limits<std::int32_t>::max()) == 0x1ffff);
Require(sceKernelError(std::numeric_limits<std::int32_t>::min()) == 0x20000);
+2
View File
@@ -574,6 +574,8 @@ if(BUILD_TESTING)
set_tests_properties(macos_tls_alu PROPERTIES TIMEOUT 60)
add_test(NAME linux_load_alignment COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_load_alignment.py $<TARGET_FILE:relinker>)
add_test(NAME linux_load_bounds COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_load_bounds.py $<TARGET_FILE:relinker>)
add_test(NAME linux_eh_frame_header COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_eh_frame_header.py $<TARGET_FILE:relinker>)
add_test(NAME linux_dynamic_strings COMMAND ${Python3_EXECUTABLE}
@@ -22,6 +22,8 @@ namespace Elfpatcher::Linux {
namespace {
constexpr std::uint64_t kLinuxUserAddressEnd = 0x800000000000;
constexpr std::uint64_t kLazyPltGotBytes = 24;
constexpr std::uint64_t kPltGotEntryBytes = 8;
std::string Hex(const std::uint64_t value) {
std::ostringstream text;
@@ -101,6 +103,19 @@ void RequireFileRange(const std::vector<std::uint8_t>& buf, const Domain::Progra
throw Domain::RelinkerException("PT_LOAD at " + Hex(load.MappedAddress) + " has file bytes outside the input");
}
void RequireLoaded(const std::vector<Domain::ProgramHeader>& headers, const std::uint64_t address, const std::uint64_t size, const std::uint32_t flags, const std::string& what) {
for (const auto& header : headers)
if (header.Type == PT_LOAD && (header.Flags == 0 || (header.Flags & flags) == flags) && address >= header.MappedAddress && address - header.MappedAddress < header.MemorySize && size <= header.MemorySize - (address - header.MappedAddress))
return;
const auto range = size > 1 ? " (" + std::to_string(size) + " bytes) does not fit inside " : std::string(" is not inside ");
throw Domain::RelinkerException(what + " " + Hex(address) + range + ((flags & PF_X) != 0 ? "an executable" : "a") + " PT_LOAD segment");
}
std::uint64_t RelocationWidth(const std::vector<std::uint8_t>& relocations, const std::size_t entry) {
const auto type = static_cast<std::uint32_t>(Io::ReadU64(relocations, entry + 8));
return type == R_X86_64_PC32 || type == R_X86_64_32 ? 4 : 8;
}
void SeparateSharedLoadPages(std::vector<std::uint8_t>& buf, std::vector<Domain::ProgramHeader>& headers) {
std::vector<std::size_t> loads;
for (std::size_t index = 0; index < headers.size(); ++index) {
@@ -363,6 +378,12 @@ std::vector<std::uint8_t> LinuxElfPatcher::Patch(
throw Domain::RelinkerException("--trace-imports requires libc.prx among the libraries");
if (_fixedBase)
RequireOnlyDependencies(dynSection.DynamicSegmentData);
RequireLoaded(originalHeaders, Io::ReadU64(sourceElf, kEhdrEntryOffset), 1, PF_X, "e_entry");
for (const auto& [relocationTable, name] : {std::pair{&dynSection.RelaData, "RELA r_offset"}, std::pair{&dynSection.RelaPltData, "JMPREL r_offset"}})
for (std::size_t entry = 0; entry < relocationTable->size(); entry += kRelaEntrySize)
RequireLoaded(originalHeaders, Io::ReadU64(*relocationTable, entry), RelocationWidth(*relocationTable, entry), 0, name);
if (!dynSection.RelaPltData.empty())
RequireLoaded(originalHeaders, originalPltGotVaddr, lazyBinding ? kLazyPltGotBytes : kPltGotEntryBytes, 0, "DT_PLTGOT");
const auto traced = _traceImports ? TracedImports(dynSection) : std::vector<TracedImport>{};
const std::uint64_t base = _fixedBase.value_or(0);
const auto address = [this, base](const std::uint64_t vaddr) {
@@ -18,7 +18,7 @@ def fixture(header_count, with_frame=True):
image = bytearray(0x8000)
image[:16] = b"\x7fELF\x02\x01\x01" + bytes(9)
struct.pack_into("<HHIQQQIHHHHHH", image, 16,
3, 62, 1, 0x4000, 64, 0, 0, 64, 56, header_count, 64, 0, 0)
3, 62, 1, 0, 64, 0, 0, 64, 56, header_count, 64, 0, 0)
image[0x4000:0x4006] = b"\xb8\x2a\x00\x00\x00\xc3"
tags = [(5, 0x600), (10, 1), (6, 0x620), (11, 24), (7, 0x700), (8, 0), (9, 24), (0, 0)]
struct.pack_into("<IIQQQQQQ", image, 64,
@@ -14,7 +14,7 @@ def fixture():
image = bytearray(0x8000)
image[:16] = b"\x7fELF\x02\x01\x01" + bytes(9)
struct.pack_into("<HHIQQQIHHHHHH", image, 16,
3, 62, 1, 0x4000, 64, 0, 0, 64, 56, 6, 64, 0, 0)
3, 62, 1, 0, 64, 0, 0, 64, 56, 6, 64, 0, 0)
image[0x4000:0x4006] = b"\xb8\x2a\x00\x00\x00\xc3"
tags = [(5, 0x600), (10, 1), (6, 0x620), (11, 24), (7, 0x700), (8, 0), (9, 24), (0, 0)]
struct.pack_into("<IIQQQQQQ", image, 64,
@@ -0,0 +1,122 @@
"""Check that a Linux relink rejects an entry point, relocation offset or DT_PLTGOT outside PT_LOAD memory."""
from pathlib import Path
import struct
import subprocess
import sys
import tempfile
sys.path.insert(0, str(Path(__file__).resolve().parent))
from test_jmprel_table_bounds import fixture
PT_LOAD = 1
PT_SCE_RELRO = 0x61000010
PF_X = 1
PF_W = 2
PF_R = 4
R_X86_64_PC32 = 2
R_X86_64_32 = 10
E_ENTRY = 24
FIRST_LOAD_FLAGS = 64 + 4
SPARE_HEADER = 64 + 3 * 56
PLTGOT_VALUE = 0x400 + 9 * 16 + 8
OUTSIDE = 0x2000
ENTRY = 0x200
FIXED_BASE = 0x400000
def with_bss(image):
struct.pack_into("<IIQQQQQQ", image, SPARE_HEADER, PT_LOAD, PF_R | PF_W, 0x1000, 0x1000, 0x1000, 0, 0x1000, 0x1000)
return image
def with_relro(image):
struct.pack_into("<IIQQQQQQ", image, SPARE_HEADER, PT_SCE_RELRO, PF_R, 0, 0x1000, 0x1000, 0, 0x1000, 1)
return with_pltgot(image, 0x1800)
def with_entry(image, entry):
struct.pack_into("<Q", image, E_ENTRY, entry)
return image
def with_pltgot(image, value):
struct.pack_into("<Q", image, PLTGOT_VALUE, value)
return image
def without_load_flags(image):
struct.pack_into("<I", image, FIRST_LOAD_FLAGS, 0)
return image
def load_flags_at(elf, address):
phoff, = struct.unpack_from("<Q", elf, 0x20)
phentsize, phnum = struct.unpack_from("<HH", elf, 0x36)
headers = [struct.unpack_from("<IIQQQQQQ", elf, phoff + index * phentsize) for index in range(phnum)]
return [header[1] for header in headers if header[0] == PT_LOAD and header[3] <= address < header[3] + header[6]]
def relink(relinker, work, name, image, options, error):
source = work / (name + ".elf")
output = work / (name + ".out")
source.write_bytes(image)
result = subprocess.run([str(relinker), "--skip-sce-module", *options, str(source), str(output)],
capture_output=True, text=True, timeout=20)
if error is None:
assert result.returncode == 0 and output.exists(), (name, options, result.returncode, result.stdout, result.stderr)
elf = output.read_bytes()
output.unlink()
return elf
assert result.returncode == 2 and error in result.stderr and not output.exists(), (
name, options, result.returncode, result.stdout, result.stderr)
return None
def main():
relinker = Path(sys.argv[1]).resolve()
cases = [
("plt-valid", fixture(linux=True), None),
("rela-valid", fixture(linux=True, data_relocations=True), None),
("rela-in-bss", with_bss(fixture(linux=True, data_relocations=True, target=0x1ff8)), None),
("rela-in-relro", with_relro(fixture(linux=True, data_relocations=True, target=0x1ff8)), None),
("jmprel-in-relro", with_relro(fixture(linux=True, target=0x1ff8)), None),
("pc32-load-tail", fixture(linux=True, data_relocations=True, relocation_type=R_X86_64_PC32, target=0xFFC), None),
("abs32-load-tail", fixture(linux=True, data_relocations=True, relocation_type=R_X86_64_32, target=0xFFC), None),
("entry-outside", with_entry(fixture(linux=True), 0x7FFFF000),
"e_entry 0x7ffff000 is not inside an executable PT_LOAD segment"),
("entry-not-executable", with_entry(with_bss(fixture(linux=True)), 0x1000),
"e_entry 0x1000 is not inside an executable PT_LOAD segment"),
("rela-outside", fixture(linux=True, data_relocations=True, target=OUTSIDE),
"RELA r_offset 0x2000 (8 bytes) does not fit inside a PT_LOAD segment"),
("rela-past-load-end", fixture(linux=True, data_relocations=True, target=0xFFC),
"RELA r_offset 0xffc (8 bytes) does not fit inside a PT_LOAD segment"),
("abs32-past-load-end", fixture(linux=True, data_relocations=True, relocation_type=R_X86_64_32, target=0xFFE),
"RELA r_offset 0xffe (4 bytes) does not fit inside a PT_LOAD segment"),
("jmprel-outside", fixture(linux=True, target=OUTSIDE),
"JMPREL r_offset 0x2000 (8 bytes) does not fit inside a PT_LOAD segment"),
]
with tempfile.TemporaryDirectory(prefix="anyps5-linux-load-bounds-") as directory:
work = Path(directory)
for name, image, error in cases:
for options in ([], ["--lazy-binding"]):
relink(relinker, work, name, image, options, error)
for options, base in (([], FIXED_BASE), (["--lazy-binding"], 0)):
elf = relink(relinker, work, "entry-in-flagless-load", without_load_flags(fixture(linux=True)), options, None)
flags = load_flags_at(elf, base + ENTRY)
assert flags == [PF_R | PF_W | PF_X], ("entry-in-flagless-load", options, flags)
pltgot_cases = [
("pltgot-outside", OUTSIDE, "DT_PLTGOT 0x2000 (8 bytes) does not fit inside a PT_LOAD segment",
"DT_PLTGOT 0x2000 (24 bytes) does not fit inside a PT_LOAD segment"),
("pltgot-load-tail", 0xFF0, None, "DT_PLTGOT 0xff0 (24 bytes) does not fit inside a PT_LOAD segment"),
("pltgot-lazy-fits", 0xFE8, None, None),
]
for name, pltgot, eager_error, lazy_error in pltgot_cases:
image = with_pltgot(fixture(linux=True), pltgot)
relink(relinker, work, name, image, [], eager_error)
relink(relinker, work, name, image, ["--lazy-binding"], lazy_error)
print("Linux load bounds tests passed")
if __name__ == "__main__":
main()
@@ -11,6 +11,8 @@ POINTER = 0x810
TABLE = 0x820
FUNCTION = CODE + 0x40
SECOND_FUNCTION = CODE + 0x60
REJECTED = {"got-slot-address-size-underflow":
"RELA r_offset 0xfffff008 (8 bytes) does not fit inside a PT_LOAD segment"}
def rel(next_address, target):
@@ -62,6 +64,11 @@ def run(relinker, work, name, image, level=1):
source.write_bytes(image)
result = subprocess.run([str(relinker), "--skip-sce-module", "--registry", f"unused-filter={level}",
str(source), str(output)], capture_output=True, text=True, timeout=20)
if name in REJECTED:
assert result.returncode == 2 and REJECTED[name] in result.stderr and not output.exists(), (
name, result.returncode, result.stdout, result.stderr)
assert "CFG/GOT filtering: 1 -> 1;" in result.stdout, (name, result.stdout)
return True
assert result.returncode == 0 and output.exists(), (name, result.returncode, result.stdout, result.stderr)
registry = json.loads(output.with_suffix(".registry.json").read_text())
kept = len(registry)
@@ -39,9 +39,11 @@ void IrValue::ReplaceArgument(std::size_t index, IrValue* argument) {
}
IrValue* previous = arguments[index];
if (previous != nullptr) {
const auto operandIt = std::find_if(previous->operandUses.begin(), previous->operandUses.end(), [this, index](const IrUse& use) {
return use.user == this && use.operand == index;
});
const auto operandIt = !previous->operandUses.empty() && previous->operandUses.back() == IrUse{this, index}
? previous->operandUses.end() - 1
: std::find_if(previous->operandUses.begin(), previous->operandUses.end(), [this, index](const IrUse& use) {
return use.user == this && use.operand == index;
});
if (operandIt != previous->operandUses.end()) {
previous->operandUses.erase(operandIt);
}
@@ -101,6 +101,23 @@ void CheckSimpleReplacement() {
Require(source->Opcode() == IrOpcode::IAdd32 && source->Parent() == &graph.block);
}
void CheckTailArgumentRemoval() {
Graph graph(4);
auto* source = graph.Value(0);
auto* first = graph.Value(1);
auto* last = graph.Value(2);
first->AddArgument(source);
last->AddArgument(source);
last->AddArgument(source);
last->ReplaceArgument(0, nullptr);
Require(source->OperandUses() == std::vector<IrUse>({{first, 0}, {last, 1}}));
last->ReplaceArgument(1, graph.Value(3));
Require(source->OperandUses() == std::vector<IrUse>({{first, 0}}));
Require(graph.Value(3)->OperandUses() == std::vector<IrUse>({{last, 1}}));
first->Invalidate();
Require(!source->HasUses() && first->Argument(0) == nullptr);
}
void CheckSelfAndEmpty() {
Graph graph(3);
graph.Value(2)->AddArgument(graph.Value(0));
@@ -223,6 +240,7 @@ int main() {
try {
const std::pair<const char*, void (*)()> cases[] = {
{"ordered duplicate operands", CheckSimpleReplacement},
{"tail argument removal", CheckTailArgumentRemoval},
{"self and empty replacement", CheckSelfAndEmpty},
{"phi and identity rewriting", CheckPhiAndIdentity},
{"allocation failure integrity", CheckAllocationFailure},
@@ -234,7 +252,7 @@ int main() {
current = name;
test();
}
std::puts("7 IR rewrite suites passed; 2000 differential use graphs and 48000 mutation comparisons");
std::puts("8 IR rewrite suites passed; 2000 differential use graphs and 48000 mutation comparisons");
return 0;
} catch (const std::exception& error) {
failureAfter = -1;
@@ -344,7 +344,6 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l
const auto first = PipelineSpecialization::BufferBase + index * PipelineSpecialization::BufferWords;
plan.specialization.push_back({first, descriptor.dwords[1] & 0xffff0000u});
plan.specialization.push_back({first + 1u, descriptor.dwords[3]});
plan.specialization.push_back({first + 2u, present ? 1u : 0u});
plan.specialization.push_back({first + 3u, misalignment});
}
if (buffer.formattedReadMask != 0u) {
@@ -107,7 +107,9 @@ RuntimeDescriptor descriptor(SpirvValueEmitContext& context, const IrValue& inst
}
if (memory.gpuDescriptor) faultIf(context, instruction, invalid, base, 0u, BdaAbi::FaultReason::InvalidDescriptor);
const auto stride = field(state, word1, 16u, 14u);
const auto present = runtime ? both(state, nonzero(state, context.Arg(*handle, 2u)), Binary(state, spv::OpLogicalOr, TypeBool(state), nonzero(state, context.Arg(*handle, 0u)), nonzero(state, field(state, word1, 0u, 16u)))) : nonzero(state, state.module.SpecializationConstant(u32, first + 2u, 0u));
auto baseHigh = runtime ? word1 : context.Arg(*handle, 1u);
if (!runtime && GcnGuestShaders()) baseHigh = Binary(state, spv::OpBitwiseAnd, u32, baseHigh, ConstantU32(state, GcnBufferWord1Mask));
const auto present = both(state, nonzero(state, context.Arg(*handle, 2u)), Binary(state, spv::OpLogicalOr, TypeBool(state), nonzero(state, context.Arg(*handle, 0u)), nonzero(state, field(state, baseHigh, 0u, 16u))));
const auto valid = memory.gpuDescriptor ? both(state, Unary(state, spv::OpLogicalNot, TypeBool(state), invalid), Binary(state, spv::OpINotEqual, TypeBool(state), base, BdaConstant(state, 0u))) : present;
return {base, context.Arg(*handle, 2u), stride, word3, both(state, nonzero(state, stride), nonzero(state, field(state, word1, 31u, 1u))), index, valid};
}
@@ -668,8 +670,7 @@ std::uint32_t formattedCall(SpirvValueEmitContext& context, const IrValue& instr
const auto word1 = state.module.SpecializationConstant(u32, first, 0u);
const auto stride = field(state, word1, 16u, 14u);
const auto swizzle = both(state, nonzero(state, stride), nonzero(state, field(state, word1, 31u, 1u)));
const auto valid = nonzero(state, state.module.SpecializationConstant(u32, first + 2u, 0u));
shared = {ConstantU32(state, 0u), parameters[3], stride, state.module.SpecializationConstant(u32, first + 1u, 0u), swizzle, parameters[0], valid, parameters[1], parameters[2]};
shared = {ConstantU32(state, 0u), parameters[3], stride, state.module.SpecializationConstant(u32, first + 1u, 0u), swizzle, parameters[0], nonzero(state, parameters[8]), parameters[1], parameters[2]};
}
const auto value = formatSwitch(context, instruction, shared, components, store, store ? parameters[9] : 0u);
if (store) state.module.AddFunction(spv::OpReturn);
+1 -1
View File
@@ -8,6 +8,6 @@
- [_sceFiberInitializeWithInternalOptionImpl](../../../core/libs/prx/libSceFiber/Export.cpp) (libSceFiber) - its signature and the flag values `0x1` (no ulobjmgr registration), `0x10` (context size check) and `0x100` (start with MXCSR `0x9fc0` and x87 control word `0x037f`) follow shadPS4's [initialize implementation](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/fiber/fiber.cpp#L150-L153) and [flags](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/fiber/fiber.h#L27-L32). Not measured on a console. `0x1` has no effect because no ulobjmgr exists; other flag bits throw. shadPS4 also sets `0x100` for `build_version` 3.50 and later in every initialize; `opt_param` and `build_version` are ignored.
- [_sceFiberAttachContextAndRun, _sceFiberAttachContextAndSwitch](../../../core/libs/prx/libSceFiber/Export.cpp) (libSceFiber) - signatures and behaviour follow shadPS4's [run](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/fiber/fiber.cpp#L260-L261), [switch](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/fiber/fiber.cpp#L336-L337) and [attach](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/fiber/fiber.cpp#L46-L79) implementations, not measured on a console. A context is attached only to an idle fiber initialized without one; a fiber that already has a context, or is not idle, returns `SCE_FIBER_ERROR_INVALID`. A size that is not a multiple of 16 returns `SCE_FIBER_ERROR_ALIGNMENT` as in initialize, where shadPS4 returns `SCE_FIBER_ERROR_INVALID`.
- [Fibers without a context](../../../core/libs/prx/libSceFiber/Export.cpp) (libSceFiber) - running or switching to a fiber initialized without a context, before one is attached, throws. On the console such a fiber runs on the stack of its caller, per shadPS4's [run implementation](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/fiber/fiber.cpp#L297-L313).
- [Fibers without a context](../../../core/libs/prx/libSceFiber/Export.cpp) (libSceFiber) - caller-stack execution and restart after yielding follow shadPS4 at commit `f68923fe807f657348e6719ce658bcbd0a2b1c72` ([run](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/fiber/fiber.cpp#L238-L307), [switch](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/fiber/fiber.cpp#L309-L360), [return](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/fiber/fiber.cpp#L376-L401)), not measurements on a PS5. The original calling thread stack is reused even when switching from an attached fiber; contextless continuations are discarded.
- [_sceFiberGetThreadFramePointerAddress](../../../core/libs/prx/libSceFiber/Export.cpp) (libSceFiber, NID `0dy4JtMUcMQ`) forwards to `sceFiberGetThreadFramePointerAddress`; that the argument list matches is inferred from the export without the underscore, as for the underscore Ult exports, not measured
- [Fiber stacks](../../../core/libs/prx/libSceFiber/Export.cpp) (libSceFiber) - a fiber whose context is smaller than 64 KiB runs on an mmap'd host stack of its context size plus 1 MiB, so host calls made from it (21 KiB deep when PPSA01341 reaches SPIRV-Tools from a 16 KiB context) do not overwrite the contexts packed next to it; a larger context is the stack, as on the console. PPSA05684 needs the second: its job fibers have 64 KiB and 512 KiB contexts, the 512 KiB ones at 1 MiB-aligned addresses, and find their job record by masking the stack pointer (`~0xfffff` for the 512 KiB ones), which a host stack breaks (the game waits forever in GameInit, or faults later in a 64 KiB fiber). The 64 KiB limit is chosen between these two titles, not measured: a title with small contexts that also derives state from the stack pointer, or one whose host calls exceed what is left of a large context, is not covered
+9
View File
@@ -0,0 +1,9 @@
# libSceFreeTypeOt technical debt
### Unknown function info
- [FT_Outline_GetInsideBorder, FT_Outline_GetOutsideBorder](../../../core/libs/prx/libSceFreeTypeOt/CMakeLists.txt) (libSceFreeTypeOl, libSceFreeTypeOt) - console prototypes, enum/layout equivalence, FreeType configuration and module/library versions are unverified. The existing LP64 outline layout and unsigned 16-bit counts follow bundled FreeType 2.13.3; border selection follows [ftstroke.c](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftstroke.c), with the shifted polygon area from [ftoutln.c](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftoutln.c) accumulated in 64 bits. Null positive-count arrays and out-of-range or non-increasing contour endpoints throw when reached; their console behavior is unknown. Tags and flags are not read, and input arrays remain caller-owned.
- The 140 `sceFreeTypeOtUnknown_*_nid_no_patch` exports in [Export.cpp](../../../core/libs/prx/libSceFreeTypeOt/Export.cpp) are present in the PS5 3.20 export inventory from [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs). Their prototypes and behavior are unverified. Each uses the no-parameter import-resolution convention and throws through `NotImplemented_nid_no_patch`; these declarations make no ABI or behavior claim.
- The FT_Outline_Get_Orientation, FT_Request_Size, FT_Sin, FT_Tan, FT_Vector_Unit, FT_New_Size, FT_Outline_Copy and FT_Outline_Decompose NIDs in [Export.cpp](../../../core/libs/prx/libSceFreeTypeOt/Export.cpp) are identified, but their provider-specific prototypes and behavior remain unverified. Their named helpers retain the explicit inventory aliases and throw; the separate libSceFreeType implementations do not supply this provider.
+4 -3
View File
@@ -2,11 +2,12 @@
### Silent stubs
- [sceNpGameIntentVshInitialize, sceNpGameIntentVshTerminate](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - VSH initialization and termination return 0 without performing shell operations, matching `sceNpGameIntentInitialize` and `sceNpGameIntentTerminate`
- [sceNpAppLauncherInitialize, sceNpAppLauncherTerminate](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - app-launcher lifecycle returns success without tracking launch state; `sceNpAppLauncherInitialize` accepts its init parameter without validation, following the silent-stub precedent of `sceNpGameIntentInitialize` — a title calls it early in boot, and returning success lets it continue
- [sceNpGameIntentVshInitialize, sceNpGameIntentVshTerminate](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - VSH initialization and termination return 0 without performing shell operations
- [sceNpAppLauncherInitialize, sceNpAppLauncherTerminate](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - app-launcher lifecycle returns success without tracking launch state; `sceNpAppLauncherInitialize` accepts its init parameter without validation — a title calls it early in boot, and returning success lets it continue
### Unknown function info
- [sceNpGameIntentReceiveIntent, sceNpGameIntentGetPropertyValueString](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - firmware 13.42 traces report `0x80553802` before `sceNpGameIntentInitialize` (precedence over argument validity), and `0x80553804` for invalid arguments (null pointer, empty key, or zero buffer size); `sceNpGameIntentTerminate` resetting initialized state is modeled lifecycle behavior rather than hardware-verified. `sceNpGameIntentReceiveIntent` reportedly requires `info->size == 0x4140` on firmware 13.42, while AnyPS5 `NpGameIntentInfo` is `0x560` bytes (matching fpPS4) and set by `sceNpGameIntentInfoInit`, so initialized objects are rejected by the receiver's measured size guard. Complete retail layout, output field offsets, and retail behavior of `sceNpGameIntentInfoInit` are unverified; expanding the type with unverified reserved bytes is not an evidence-backed solution.
- [sceNpAppLauncherLaunchApp, sceNpGameIntentGetUdsNpComId, sceNpGameIntentLaunchApp, sceNpGameIntentLaunchApp2, sceNpGameIntentNotifyAppLaunched, sceNpSessionServiceGetJoinedPartySession, sceNpSessionServiceGetRunningGamePlayerSession and sceNpSessionServiceSendPlayerSessionInvitations](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpGameIntent.c). Call sites in a title's executable give a partial prototype: `sceNpAppLauncherLaunchApp` takes `(user_id, uri struct, context)` and returns int — throws because no launcher-service semantics are derivable. The rest are declared without parameters solely to resolve known imports and make no ABI claim.
- [sceNpGameIntentInitParamInit](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - struct layout matches [fpPS4](https://github.com/red-prig/fpPS4/blob/master/src/np/ps4_libscenpgameintent.pas#L17) `SceNpGameIntentInitParam`; setting `size`, zeroing reserved bytes, and throwing on null pointer follow Sony SDK `*ParamInit` conventions but are unverified on retail hardware
- [sceNpGameIntentInfoInit](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - struct layout matches [fpPS4](https://github.com/red-prig/fpPS4/blob/master/src/np/ps4_libscenpgameintent.pas#L28) `SceNpGameIntentInfo`; setting `size`, `user_id = -1`, zeroing fields, and throwing on null pointer match `sceNpGameIntentReceiveIntent` defaults but are unverified on retail hardware
- [sceNpGameIntentInfoInit](../../../core/libs/prx/libSceNpGameIntent/Export.cpp) (libSceNpGameIntent) - struct layout matches [fpPS4](https://github.com/red-prig/fpPS4/blob/master/src/np/ps4_libscenpgameintent.pas#L28) `SceNpGameIntentInfo`; setting `size = 0x560`, `user_id = -1`, zeroing fields, and throwing on null pointer follow fpPS4 defaults, but reported firmware 13.42 behavior requires `0x4140` for `sceNpGameIntentReceiveIntent`, so the helper's `0x560` size does not match retail receiver defaults; the complete retail structure layout and retail behavior of `sceNpGameIntentInfoInit` remain unverified
+5 -5
View File
@@ -6,17 +6,17 @@
- [sceNpAppInfoIntCheckServiceAvailabilityA, sceNpAppInfoIntCheckServiceAvailabilityAll, sceNpAppInfoIntCheckServiceAvailabilityAllA, sceNpAppInfoIntCreateRequest, sceNpAppInfoIntDestroyRequest and sceNpAppInfoIntFinalize](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified.
- [sceNpAppInfoIntInitialize, sceNpAppLaunchLink2IntAbortRequest, sceNpAppLaunchLink2IntCreateRequest, sceNpAppLaunchLink2IntDestroyRequest, sceNpAppLaunchLink2IntFinalize and sceNpAppLaunchLink2IntGetCompatibleTitleIdList](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified.
- [sceNpAppLaunchLink2IntGetCompatibleTitleIdNum, sceNpAppLaunchLink2IntInitialize, sceNpAppLaunchLinkIntAbortRequest, sceNpAppLaunchLinkIntCreateRequest, sceNpAppLaunchLinkIntDestroyRequest and sceNpAppLaunchLinkIntFinalize](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified.
- [sceNpAppLaunchLinkIntGetCompatibleTitleIdList, sceNpAppLaunchLinkIntGetCompatibleTitleIdNum, sceNpAppLaunchLinkIntInitialize, sceNpBandwidthTestShutdownWithDetailedInfo and sceNpLookupAbortRequest](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified.
- [sceNpLookupCreateAsyncRequest, sceNpLookupCreateTitleCtxA, sceNpLookupNetAbortRequest and sceNpLookupNetCensorComment](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified.
- [sceNpAppLaunchLinkIntGetCompatibleTitleIdList, sceNpAppLaunchLinkIntGetCompatibleTitleIdNum, sceNpAppLaunchLinkIntInitialize and sceNpBandwidthTestShutdownWithDetailedInfo](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified.
- [sceNpLookupCreateTitleCtxA, sceNpLookupNetAbortRequest and sceNpLookupNetCensorComment](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified.
- [sceNpLookupNetConvertJidToNpId, sceNpLookupNetConvertNpIdToJid, sceNpLookupNetCreateRequest, sceNpLookupNetCreateTitleCtx, sceNpLookupNetDeleteRequest, sceNpLookupNetDeleteTitleCtx and sceNpLookupNetInit](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified.
- [sceNpLookupNetInitWithFunctionPointer, sceNpLookupNetInitWithMemoryPool, sceNpLookupNetIsInit, sceNpLookupNetNpId, sceNpLookupNetSanitizeComment, sceNpLookupNetSetTimeout, sceNpLookupNetTerm and sceNpLookupPollAsync](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified.
- [sceNpLookupSetTimeout, sceNpLookupWaitAsync, sceNpServiceChecker2IntAbortRequest, sceNpServiceChecker2IntCreateRequest, sceNpServiceChecker2IntDestroyRequest and sceNpServiceChecker2IntFinalize](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified.
- [sceNpLookupNetInitWithFunctionPointer, sceNpLookupNetInitWithMemoryPool, sceNpLookupNetIsInit, sceNpLookupNetNpId, sceNpLookupNetSanitizeComment, sceNpLookupNetSetTimeout and sceNpLookupNetTerm](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified.
- [sceNpServiceChecker2IntAbortRequest, sceNpServiceChecker2IntCreateRequest, sceNpServiceChecker2IntDestroyRequest and sceNpServiceChecker2IntFinalize](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified.
- [sceNpServiceChecker2IntGetServiceAvailability, sceNpServiceChecker2IntGetServiceAvailabilityA, sceNpServiceChecker2IntInitialize, sceNpServiceChecker2IntIsSetServiceType and sceNpServiceCheckerIntAbortRequest](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified.
- [sceNpServiceCheckerIntCreateRequest, sceNpServiceCheckerIntDestroyRequest, sceNpServiceCheckerIntFinalize, sceNpServiceCheckerIntGetAvailability, sceNpServiceCheckerIntGetAvailabilityList and sceNpServiceCheckerIntInitialize](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified.
- [sceNpServiceCheckerIntIsCached, sceNpTitleMetadataIntAbortRequest, sceNpTitleMetadataIntCreateRequest, sceNpTitleMetadataIntDeleteRequest, sceNpTitleMetadataIntGetInfo, sceNpTitleMetadataIntGetNpTitleId and sceNpUtilityInit](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified.
- [sceNpUtilityTerm and sceNpWordFilterCreateTitleCtxA](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified.
- [sceNpWordFilterSetTimeout and sceNpWordFilterWaitAsync](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - The list is the intersection of the export table of the console's libSceNpUtility (function symbols only), the pinned aerolib names and the functions not yet defined anywhere in the repo; names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpUtility.c) and the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim and callers must not call them until signatures are verified.
### Functional
- [NpLookup and NpWordFilter](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - offline only, not measured on a PS5: the argument checks and their order, the limit of 32 title contexts and 32 requests, the word filter id bases `0x01000001` and `0x02000001`, the 0x400-byte comment limit and single-use requests (`INVALID_TYPE`) follow shadPS4's [lookup](https://github.com/shadps4-emu/shadPS4/blob/91e6afa69b0a64b0afaa0281b80f8fbb0cd88136/src/core/libraries/np/np_utility/np_utility.cpp#L41-L222) and [word filter](https://github.com/shadps4-emu/shadPS4/blob/91e6afa69b0a64b0afaa0281b80f8fbb0cd88136/src/core/libraries/np/np_utility/np_utility.cpp#L705-L909) calls. `sceNpLookupNpId`, `sceNpWordFilterCensorComment` and `sceNpWordFilterSanitizeComment` then finish with `SCE_NP_ERROR_SIGNED_OUT` and write no NpId or comment; shadPS4's word filter accepts every comment as a stand-in for the server, so its result is not used
- [NpLookup and NpWordFilter waits and timeouts](../../../core/libs/prx/libSceNpUtility/Export.cpp) (libSceNpUtility) - IDs here carry no service: on the console, measured on firmware 13.42's module in an emulator (#10640), each ID holds its service in its high bits (Lookup requests `0x2xxxxxxx` and title contexts `0x4xxxxxxx`, word filter `0x02xxxxxx` and `0x04xxxxxx`), and `SetTimeout`, `AbortRequest`, `PollAsync` and `WaitAsync` find the object by those bits whichever service's call is made; here each call finds only its own service's IDs, which keep the ranges above. The timeouts are checked as measured and then have no effect, since no request reaches a server; the console stores them. A wait on a request deleted meanwhile returns `INVALID_ID`, unmeasured. The measured checks are followed: a null result first (`0x80550704`), a synchronous request (`0x80550307` for Lookup, `INVALID_ID` for the word filter), the timeout bounds and the 0x18-byte async parameter
+1
View File
@@ -9,6 +9,7 @@
### Unknown function info
- PPSA29866 imports declared without parameters, signatures unknown, added without an implementation because the title fails to load without them: [sceVideoOutSetFlipMaster and sceVideoOutClearFlipMaster](../../../core/libs/prx/libSceVideoOut/Unimplemented.cpp) (libSceVideoOut)
- [sceVideoOutColorSpaceConversionSettingsSetDefaultSdrToHdrConversion_, sceVideoOutColorSpaceConversionSettingsSetSdrToHdrConversion_, sceVideoOutAdjustColorSpaceConversion_, sceVideoOutSetGlobalBlendSpace](../../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - PPSA30630 imports these to configure color space conversion and global blend space. Color space conversion settings are accepted with size validation (32 bytes) but conversion blocks and blend space parameters are not applied to the presentation swapchain
- [sceVideoOutDeleteVrrActiveStatusEvent](../../../core/libs/prx/libSceVideoOut/src/Event.cpp) (libSceVideoOut, NID `-q-vbO859Tw`) - the name hashes to the NID PPSA02530 imports and is listed in [sce_symbols](https://github.com/zecoxao/sce_symbols) `aerolib.csv` at `fe2cc2948e`. The signature `(eq, handle)` and the behaviour, removing the handle's VRR status registration from the queue as the other `Delete*Event` calls do, follow sceVideoOutAddVrrActiveStatusEvent; no title has been observed calling it
- [sceVideoOutCursorEnable, sceVideoOutCursorDisable, sceVideoOutCursorSetImageAddress, sceVideoOutCursorSetPosition](../../../core/libs/prx/libSceVideoOut/Unimplemented.cpp) (libSceVideoOut, NIDs `UPZWCH8qTM4`, `fEPEwROdCwM`, `u-9bhWQApq8`, `N17AK88NdD0`) - the names hash to the NIDs Prospero Win (PPSA99995) imports, and the parameter lists `(handle, index, address)`, `(handle, index)`, `(handle, index, address)` and `(handle, index, x, y)` follow its [declarations](https://github.com/mpereiraesaa/prospero-win/blob/644e32e400c555e340d507383398c5d21b39b0b0/wine/ps5/pw_vulkan_radv.c#L65-L68). The cursor image's format and size and the console's return values are not known; the functions report that they are not implemented.
+4
View File
@@ -1,5 +1,9 @@
# libc technical debt
### Silent stubs
- [_Mtx_current_owns](../../../core/libs/prx/libc/src/Threads.cpp) (libSceLibcInternal, implemented in libc, NID `VYQwFs4CC4Y`) returns 1 and ignores its argument. This follows the PS5 12.00 body reading reported in [PR8859](https://github.com/boykopovar/AnyPS5/pull/8859), rather than inspecting host mutex ownership. The firmware observation is attributed to that contributor; other firmware versions and the argument's console type remain unverified.
### Unknown function info
- [strtonum](../../../core/libs/prx/libc/src/StringExtensions.cpp) (libc) is unverified on the console. Its signature, decimal parsing, inclusive bounds, error strings, error precedence and errno preservation follow [FreeBSD 11.4 strtonum.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdlib/strtonum.c). Parsing uses the host `strtoll` with base 10. It is not exported by `libSceLibcInternal.sprx` on a PS5 (firmware 13.60), so a payload process cannot call it there
+2 -2
View File
@@ -85,7 +85,7 @@
- [Map hints inside a reservation](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) (libkernel) - `sceKernelMapDirectMemory` and the flexible and anonymous maps place a mapping without `MAP_FIXED` at its hint address when the whole range lies in a range still reserved by `sceKernelReserveVirtualRange`, as if `MAP_FIXED` were set. Taken from PPSA01474, which reserves 64 GiB and then maps its heaps inside it with hints and no `MAP_FIXED`, and uses the hint addresses afterwards; not measured on the console. Hints outside a reservation, or over a range that is partly mapped, stay hints
- [Fixed anonymous mmap](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) (libkernel) - `MAP_PRIVATE | MAP_ANON | MAP_FIXED` replaces the requested range through the existing guest mapping machinery and zeroes the new memory. `MAP_EXCL` is checked under the allocation-registry mutation lock, including virtual reservations, and returns `ENOMEM` without changing an occupied range; without `MAP_FIXED` it is `EINVAL`. Lengths are rounded to 16 KiB and fixed addresses must be aligned. The complete rounded range must lie inside the guest arena or, for POSIX fixed anonymous `mmap` on Windows, one current or registered native thread stack; other mapping APIs do not opt into stack replacement. Native stack replacement keeps the reservation, decommits and recommits fresh pages, and rejects foreign allocations, ranges spanning reservations and host `PAGE_GUARD` pages before mutation. Current-stack ranges must end at least 64 KiB below the active host frame; callers replacing another thread's stack must coordinate with that thread. If recommitting fails, the call returns `ENOMEM` with the range still reserved; thread cleanup attempts to recommit it before restoring write access. This follows FreeBSD 11.4 [argument validation and rounding](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/vm/vm_mmap.c#L226-L299), [fixed replacement](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/vm/vm_map.c#L1455-L1464), and [collision errno](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/vm/vm_mmap.c#L1604-L1613). The console behaviour has not been measured. Linux and Windows accept addresses from 64 KiB that no host mapping occupies, such as the 0x400000 imported by PICO-8 PPSA99808; macOS keeps the 0x200000000 start. Non-fixed private anonymous mappings keep their hint; null hints still use the 8 GiB area. On Windows the host's `KUSER_SHARED_DATA` always occupies 0x7ffe0000, so Wine's shared user data page (Forever Launcher PPSA99030) cannot be placed there. The console address-space layout is unmeasured; executable placement outside the host arena is tracked in #5217. Static catalog disassembly finds `0x1012` in PPSA98001/PPSA99808 and `0x5012` in PPSA99808; these titles were not run.
- [Mapping hints with no room above them](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) (libkernel) - a mapping without `MAP_FIXED` whose hint lies above the guest address space, or has no free range at or above it, is placed from the bottom of the guest arena, as FreeBSD 11.4's [`vm_mmap_object`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/vm/vm_mmap.c#L1569) retries through [`vm_map_find_min`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/vm/vm_map.c#L1555-L1584) from its minimum address. FreeBSD's minimum is the end of the largest possible data segment rather than the start of the arena, and the console's address-space layout is not measured. PS5CEMU-HAR (PPSA99360) gives `sceKernelReserveVirtualRange` a hint near its own code, which is a host address here because the relinked executable lies outside the guest arena, so its JIT gets memory more than 2 GiB from its code and does not start
- [_sceKernelRtldThreadAtexitIncrement, _sceKernelRtldThreadAtexitDecrement](../../../core/libs/prx/libkernel/System/src/Rtld.cpp) (libkernel) - the argument is the `dso` address a title's `libc.prx` passes to `__cxa_thread_atexit` (PPSA14632), which fails when Increment returns nonzero. As in the [fpPS4](https://github.com/red-prig/fpPS4) reimplementation, Increment takes a reference on the loaded image containing the address and Decrement releases one; the variants without the leading underscore are assumed to match
- [_sceKernelRtldThreadAtexitIncrement, _sceKernelRtldThreadAtexitDecrement](../../../core/libs/prx/libkernel/System/src/Rtld.cpp) (libkernel) - the argument is the `dso` address a title's `libc.prx` passes to `__cxa_thread_atexit` (PPSA14632), which fails when Increment returns nonzero. As in a payload process on a PS5 (firmware 13.60), Increment returns -1 for an address outside every module (null, stack, heap) and 0 otherwise, and Decrement never fails on an address outside every module or after a matching Increment; the console's Decrement returns nothing, here 0. A Decrement on a module's address without a matching Increment was not measured and throws here. The module's `ref_count` did not change on the console. As in [fpPS4](https://github.com/red-prig/fpPS4), Increment here takes a reference on the image and Decrement releases one. The variants without the leading underscore are not exported by firmware 13.60's `libkernel_sys`; here they match
- [pthread_cleanup_push, pthread_cleanup_pop](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel, NIDs `4ZeZWcMsAV0`, `RVxb0Ssa5t0`) share the caller-record cleanup list with `__pthread_cleanup_push_imp` and `__pthread_cleanup_pop_imp`, following [FreeBSD 11.4 thr_clean.c](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/libthr/thread/thr_clean.c). The firmware body reading reported by [PR8428](https://github.com/boykopovar/AnyPS5/pull/8428) also sets the thread's unwind-disabled flag in the heap form; that flag is not modeled here, and runtime-created guest threads skip guest-frame unwinding in these exit paths. The firmware observation is attributed to that contributor, not independently reproduced. Heap records use C++ `new` here, whereas the FreeBSD reference leaves the cleanup list unchanged if `malloc` fails; console allocation-failure behavior and its interaction with unwinding remain unverified.
- [tcgetattr, tcsetattr](../../../core/libs/prx/libkernel/File/src/Terminal.cpp) (libkernel) - every file and pipe descriptor is taken as not a terminal (`ENOTTY`), and a socket gets `EADDRNOTAVAIL` for `AF_INET` and `EOPNOTSUPP` otherwise, as FreeBSD's `in_control` and `pru_control_notsupp` answer a termios ioctl; what descriptors 0 to 2 of a title are on the console, and whether any of them is a terminal there, is unknown
- [cfsetspeed](../../../core/libs/prx/libkernel/File/src/Terminal.cpp) (libkernel, NID `C1MQ6OaXRjo`) - console behavior and module/library versions are unverified. The prototype, uint32 speed and 44-byte termios layout (input speed at 36, output speed at 40) follow the public PS5 payload SDK [termios.h](https://github.com/ps5-payload-dev/sdk/blob/a7685ce8e97ce1f8a4f38253a41d54241c9124f1/include/freebsd/termios.h) and [sys/_termios.h](https://github.com/ps5-payload-dev/sdk/blob/a7685ce8e97ce1f8a4f38253a41d54241c9124f1/include/freebsd/sys/_termios.h). The module association follows its libkernel stub table and the NID rehashes to the aerolib name. Both speed fields accept every uint32 value and no flags or control characters change, as [FreeBSD 11.4 termios.c](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/libc/gen/termios.c) does. No terminal I/O is performed. Null pointers throw where the reference dereferences them.
@@ -172,7 +172,7 @@
- [sceKernelGetFsSandboxRandomWord](../../../core/libs/prx/libkernel/System/src/Sandbox.cpp) (libkernel) returns a fixed directory name, `gkbHtXc3Wq`. On the console the name is the process's randomized path (syscall 602, `randomized_path`), and the sandbox exposes system files under it, such as `common/lib` and `common/cert`. Nothing is mounted under the name here, so `/gkbHtXc3Wq/common/...` resolves under the guest root like any other path and does not exist, except `/gkbHtXc3Wq/common/cert/CA_LIST.cer`, which [SandboxCertificates.cpp](../../../core/libs/prx/libkernel/File/src/SandboxCertificates.cpp) serves from the host trust store
- [ktimer_create, ktimer_settime, ktimer_gettime, ktimer_getoverrun, ktimer_delete](../../../core/libs/prx/libkernel/Time/Ktimer.cpp) (libkernel) - a `SIGEV_SIGNAL` or `SIGEV_THREAD_ID` timer, and one created with a null `sigevent` (FreeBSD defaults it to `SIGEV_SIGNAL`), throws, because delivering a guest signal from a timer is not modelled. A non-zero value under one tick rounds up to 1 ms, as FreeBSD 11 does at hz 1000; the console's hz is not measured (`kern.clockrate` is unanswered). `SIGEV_NONE` timers on `CLOCK_REALTIME` and `CLOCK_MONOTONIC` follow `kern_time.c`: ids 3 to 31 (`EAGAIN` when full), `EINVAL` for another clock, a bad notify type or signal, an id that is not live or a bad `itimerspec` field, and `TIMER_ABSTIME` (1). Expiry is worked out from the guest clock when the timer is read, and `ktimer_getoverrun` returns 0, as FreeBSD only updates it when a signal is accepted
- [The sandbox CA list](../../../core/libs/prx/libkernel/File/src/SandboxCertificates.cpp) (libkernel), `/<sandbox word>/common/cert/CA_LIST.cer`, is a PEM bundle of the host's trust anchors, written once per process to `cert_cache/CA_LIST.cer` in the run directory with the owner write bit cleared: on Windows the current user's `ROOT` store, filtered through Disallowed and the combined EKU extension/property for TLS server authentication (Windows fetches missing roots on demand, so a root that was never fetched is absent), elsewhere `SSL_CERT_FILE` or the first of `/etc/ssl/certs/ca-certificates.crt`, `/etc/pki/tls/certs/ca-bundle.crt`, `/etc/ssl/ca-bundle.pem` and `/etc/ssl/cert.pem` that exists. On macOS, `/etc/ssl/cert.pem` is a static snapshot and does not reflect roots added to or distrusted in the Keychain. Existing guest files, explicit aliases and blocked mounts take precedence over the provider. The PEM snapshot does not carry Windows CTL restrictions beyond EKU, native revocation policy or later trust-store changes. Without any certificate the path does not exist. The console file's format is unverified: only that bundled OpenSSL accepts a PEM bundle there is shown (ProsperoRadio PPSA99001 and SwanStation PPSA98510, #5001). The other entries of the console's `common/cert` directory, the directory itself (it does not exist here) and the error a write open gets (`EACCES` from the host here) are unverified
- [pselect](../../../core/libs/prx/libkernel/Socket/src/Pselect.cpp) (libkernel) sets the signal mask with `sigprocmask`, calls `select` and restores the mask, as FreeBSD 11 `kern_pselect` does, but the swap is not atomic with the wait: a signal that becomes deliverable between the swap and the wait is not guaranteed to interrupt it
- [pselect](../../../core/libs/prx/libkernel/Socket/src/Pselect.cpp) (libkernel) sets the signal mask, calls `select` and restores the mask, as FreeBSD 11 `kern_pselect` does. A pending signal that the new mask unblocks has its handler run during the swap, and `pselect` then returns `EINTR` unless a descriptor is already ready, the timeout is zero or an argument is invalid, as FreeBSD's `kern_select` decides before it sleeps. A signal raised during the wait, including one sent by another thread, does not interrupt it, because `select` has no `EINTR` path; and the mask is the process-wide one, so it applies to every thread for the duration of the call
- [getcontext and setcontext](../../../core/libs/prx/libkernel/System/src/UserContext.cpp) (libkernel) and [makecontext](../../../core/libs/prx/libSceLibcInternal/UserContext.cpp) (libSceLibcInternal) use FreeBSD 11's amd64 `ucontext_t` (`mcontext` at 0x10, `mc_len` 800, `uc_link` at 0x330, `uc_stack` at 0x338, 0x370 bytes), the layout homebrew built against FreeBSD's headers passes: PPSA99039's OpenSSL `ASYNC` (`crypto/async/arch/async_posix.c`) captures into a 0x370-byte stack buffer. `getcontext` writes the 0x330 bytes before `uc_link`, as FreeBSD's `UC_COPY_SIZE`. The console's own layout for these functions is not verified; the exception handler's context is a different one (see `sceKernelRaiseException`)
- [getcontext and setcontext](../../../core/libs/prx/libkernel/System/src/UserContext.cpp) (libkernel) keep the 512-byte FXSAVE image in `mc_fpstate` (x87, MXCSR, XMM0-15), so the upper halves of YMM0-15 are neither saved nor restored, and `getcontext` leaves `mc_xfpustate` and `mc_xfpustate_len` 0. `getcontext` sets `mc_fpformat` and `mc_ownedfp` to FreeBSD's `_MC_FPFMT_XMM` and `_MC_FPOWNED_FPU`, `mc_cs` and `mc_ss` to the host selectors, and `mc_flags`, `mc_fsbase` and `mc_gsbase` to 0. `setcontext` loads `mc_fpstate` whatever `mc_fpformat` and `mc_ownedfp` say, and restores neither the segment selectors, `mc_fsbase` and `mc_gsbase` nor the extended state, which FreeBSD restores when `mc_flags` asks for them
- [setcontext](../../../core/libs/prx/libkernel/System/src/UserContext.cpp) (libkernel) and [longjmp](../../../core/libs/prx/libc/src/JumpBuffer.cpp) (libc) on Windows set the thread's stack bounds in the TEB to the stack they switch to: a `makecontext` stack, or else the committed region around the target stack pointer. A switch to any other guest stack, such as a context built by hand, gets the bounds of that region only
+1
View File
@@ -28,6 +28,7 @@
- [fork](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) throws: creating a child process is not supported. PPSA19111 imports it and fails to load without it. Its engine calls `fork` in `Posix_Daemonize`, only with `-daemonize`, and in `Platform_ShellExecute` before `execvp("xdg-open")`, which the main menu's Previews button reaches, so pressing Previews ends in this throw. [setsid](../../../core/libs/prx/libkernel_sys/Export.cpp) fails with `EPERM`, as FreeBSD's does for a process group leader, since the guest is taken to lead its own process group (see `kill`). [umask](../../../core/libs/prx/libkernel_sys/Export.cpp) keeps the guest's mask (initially 022, masked to 07777) and returns the previous one, Linux named shared-memory creation applies it, but ordinary files are still created with the host process's mask
- [sceKernelGetModuleInfoInternal and sceKernelGetModuleListInternal](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) answer for every loaded module, system modules included. `sceKernelGetModuleInfoInternal` fills the 0x1A8-byte `SceKernelModuleInfoEx` of a module handle the way `sceKernelGetModuleInfoFromAddr` does for an address, with [shadPS4's checks](https://github.com/shadps4-emu/shadPS4/blob/a808e76/src/core/libraries/kernel/process.cpp#L243-L258) in their order: `EFAULT` for a null pointer, `EINVAL` when `st_size` is not 0x1A8, `ESRCH` for an unknown handle. `sceKernelGetModuleListInternal` returns the same list as `sceKernelGetModuleList`, as [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd4/kernel/ps4_libkernel.pas#L852-L858) does; fpPS4 marks that as a guess, and neither is checked on a PS5
- [chroot](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) follows FreeBSD 11.4's [`sys_chroot`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/vfs_syscalls.c#L776-L807) and [`pwd_chroot`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L3059-L3085) for a root caller: `EFAULT` for a null path, `ENOENT` for an empty or missing one, `ENAMETOOLONG` for a path of 1024 bytes or more or a component over 255, and `ENOTDIR` for a path that is not a directory or passes through a file. A directory that is the guest root (`/`, `/..`, or `.` after `fchdir` to a descriptor of `/`) succeeds and changes nothing, as the root stays the same directory; the guest is taken never to have changed its root, so open directory descriptors do not make it fail with `EPERM` (`kern.chroot_allow_open_directories` 1). Any other directory throws, naming the path: the guest path resolver has a fixed root and mount aliases (`/app0`, `/savedata0`) that do not move with it. On the console a title starts in a sandbox root, where `pwd_chroot` refuses with `EPERM` while a directory is open; whether that holds for titles, and whether a title holds `PRIV_VFS_CHROOT`, are unverified. Porpoise (PPSA99764) weak-imports it and calls it only as `fchdir` to its saved descriptor of `/` then `chroot(".")`, to put its sandbox root back after a jailbreak daemon replaced it ([`put_root_back`](https://github.com/elripalda/Porpoise-Dolphin-Emulator-for-PS5/blob/2ed1ab5a06995aa198f7bccd4da0f90c3fc29d39/src/porpoise_jailbreak.cpp#L303-L331))
- [Console identity](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - `get_authinfo` errors and capability bits remain unverified on PS5; they follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/396c2a09a38d22eaa02aab32c1967263dceab95f/src/core/libraries/kernel/kernel.cpp), leaving the authority ID and other capability, attribute and credential words zero. The host models a retail unit (`IsCEX` 1, `IsDevKit` 0). The system OpenPSID reuses the ordinary ID; firmware 13.42 reads a separate `machdep.openpsid_for_sys`, whose value is unknown. Null returns positive 22 per the [module report](https://github.com/boykopovar/AnyPS5/pull/10524#issuecomment-6393566914).
- [fchown](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - ownership is not modelled: the guest's own owner and group (0) and the unchanged value (-1) are accepted, a descriptor that is not open fails with `EBADF`, and any other owner or group throws, naming it. The check reads the descriptor with `fstat`, ignoring the CRT's invalid-parameter handler on Windows as `DescriptorControl` does. The console's ownership rules are unverified. Morrowind (`mshivam019/morrowind-ps5`) imports it
- [chown, lchown and fchownat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) - ownership is not modelled, as for `fchown`: owner and group 0 and the unchanged value (-1) are accepted, and any other owner or group throws, naming it. The path is looked up first, as in FreeBSD 11's `kern_fchownat`, with `stat`, with `lstat` for `lchown`, and with `fstatat` for `fchownat`, so a lookup error is returned before the owner is checked and nothing is changed on the host. `fchownat` flags other than `AT_SYMLINK_NOFOLLOW` (0x200) return `EINVAL`, as in FreeBSD 11. The console's ownership rules are unverified
- [linkat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) resolves `AT_FDCWD` (-100) and absolute paths as `link` does, and flags other than `AT_SYMLINK_FOLLOW` (0x400) return `EINVAL`, as in FreeBSD 11. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. A symbolic link as the source without `AT_SYMLINK_FOLLOW` throws, where FreeBSD links the symbolic link itself. Console behaviour is unverified