mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-05 13:20:11 +00:00
Merge remote-tracking branch 'origin/main' into feat/ime-320-exports
This commit is contained in:
commit
e2d8b453fb
59 files changed
+2140
-106
No files matched your search
@@ -357,6 +357,8 @@ if(BUILD_TESTING)
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap_pages.py)
|
||||
add_test(NAME pr_overlap_export_cache COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap_export_cache.py)
|
||||
add_test(NAME pr_overlap_fail_closed COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap_fail_closed.py)
|
||||
add_test(NAME nid_database_cache COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_nid_names.py)
|
||||
add_test(NAME release_package COMMAND ${Python3_EXECUTABLE}
|
||||
|
||||
@@ -398,7 +398,12 @@ int APS5_VABI sceAcm_Panner(AcmBatchInfo* batch, std::uint32_t sourceCount, cons
|
||||
}
|
||||
|
||||
int APS5_VABI sceAcmBatchJobPriority(AcmBatchInfo* batch, std::uint32_t priority) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
if (!batch || priority >= 4) return SCE_ACM_ERROR_BAD_POINTER;
|
||||
const auto offset = batch->offset;
|
||||
if (offset > batch->buffer_size || batch->buffer_size - offset < 8) return static_cast<int>(0x81940009);
|
||||
const std::uint64_t job = 0x10 | (static_cast<std::uint64_t>(priority) << 8);
|
||||
std::memcpy(static_cast<std::byte*>(batch->buffer) + offset, &job, sizeof(job));
|
||||
batch->offset = offset + sizeof(job);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Dispatch/IndirectDispatch.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Memory/WriteEvidence.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Packets/PacketTiming.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/LabelTakes.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/WaitMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Pm4.hpp"
|
||||
@@ -220,6 +221,7 @@ private:
|
||||
void noteLabelStore(std::uint64_t address, std::span<const std::byte> bytes, std::uint64_t stamp, std::uint32_t queue);
|
||||
void noteLabelPass(std::span<const std::uint32_t> packet, std::uint32_t queue);
|
||||
void noteLabelIssue(std::span<const std::uint32_t> packet, std::uint32_t queue);
|
||||
void retireLabelTakes(std::uint32_t queue, std::uint64_t received);
|
||||
bool storedSince(std::span<const std::uint32_t> packet, std::uint64_t address, std::size_t bytes, std::uint64_t received, std::uint32_t queue, std::uint32_t* writer = nullptr);
|
||||
static void traceLabel(std::span<const std::uint32_t> packet, std::uint32_t queue);
|
||||
static std::array<WriteRecord, 16384>& writeHistory();
|
||||
@@ -461,8 +463,7 @@ private:
|
||||
|
||||
AgcDriver::Mutex labelStoresMutex;
|
||||
std::unordered_map<std::uint64_t, std::array<LabelStore, LabelStoreHistory>> labelStores;
|
||||
std::unordered_map<std::uint64_t, std::uint64_t> labelPasses;
|
||||
std::unordered_map<std::uint64_t, std::uint64_t> labelTaken;
|
||||
LabelTakes labelTakes;
|
||||
|
||||
std::vector<std::shared_ptr<VulkanDevice>> replacedDevices;
|
||||
std::stop_token shutdownToken = LibcShutdownToken_nid_postfix();
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_DRIVER_SYNCHRONIZATION_LABELTAKES_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_DRIVER_SYNCHRONIZATION_LABELTAKES_HPP
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
namespace AgcDriver::DriverDetail {
|
||||
|
||||
class LabelTakes {
|
||||
public:
|
||||
void Pass(std::uint32_t queue, std::uint64_t dword, std::uint64_t serial) {
|
||||
const auto key = queueDword(queue, dword);
|
||||
auto& entry = entries[key];
|
||||
if (entry.passed == 0) passCounts[slot(key)].fetch_add(1, std::memory_order_relaxed);
|
||||
entry.passed = serial;
|
||||
passes[queue].emplace_back(serial, key);
|
||||
}
|
||||
|
||||
bool MayHavePass(std::uint32_t queue, std::uint64_t dword) const {
|
||||
return passCounts[slot(queueDword(queue, dword))].load(std::memory_order_relaxed) != 0;
|
||||
}
|
||||
|
||||
void Take(std::uint32_t queue, std::uint64_t dword) {
|
||||
const auto key = queueDword(queue, dword);
|
||||
const auto found = entries.find(key);
|
||||
if (found == entries.end() || found->second.passed == 0) return;
|
||||
found->second.taken = found->second.passed;
|
||||
found->second.passed = 0;
|
||||
passCounts[slot(key)].fetch_sub(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
std::uint64_t Taken(std::uint32_t queue, std::uint64_t dword) const {
|
||||
const auto found = entries.find(queueDword(queue, dword));
|
||||
return found == entries.end() ? 0 : found->second.taken;
|
||||
}
|
||||
|
||||
void Retire(std::uint32_t queue, std::uint64_t received) {
|
||||
const auto queued = passes.find(queue);
|
||||
if (queued == passes.end()) return;
|
||||
auto& order = queued->second;
|
||||
while (!order.empty() && order.front().first <= received) {
|
||||
const auto [serial, key] = order.front();
|
||||
order.pop_front();
|
||||
const auto found = entries.find(key);
|
||||
if (found == entries.end()) continue;
|
||||
if (found->second.passed == serial) {
|
||||
found->second.passed = 0;
|
||||
passCounts[slot(key)].fetch_sub(1, std::memory_order_relaxed);
|
||||
}
|
||||
if (found->second.taken == serial) found->second.taken = 0;
|
||||
if (found->second.passed == 0 && found->second.taken == 0) entries.erase(found);
|
||||
}
|
||||
if (order.empty()) passes.erase(queued);
|
||||
}
|
||||
|
||||
bool Empty() const {
|
||||
return entries.empty() && passes.empty();
|
||||
}
|
||||
|
||||
private:
|
||||
struct Entry {
|
||||
std::uint64_t passed = 0;
|
||||
std::uint64_t taken = 0;
|
||||
};
|
||||
|
||||
static constexpr std::size_t PassSlotBits = 12;
|
||||
|
||||
static std::uint64_t queueDword(std::uint32_t queue, std::uint64_t dword) {
|
||||
return dword | (static_cast<std::uint64_t>(queue) << 56u);
|
||||
}
|
||||
|
||||
static std::size_t slot(std::uint64_t key) {
|
||||
return static_cast<std::size_t>(((key >> 2u) * 0x9e3779b97f4a7c15ull) >> (64u - PassSlotBits));
|
||||
}
|
||||
|
||||
std::unordered_map<std::uint64_t, Entry> entries;
|
||||
std::unordered_map<std::uint32_t, std::deque<std::pair<std::uint64_t, std::uint64_t>>> passes;
|
||||
std::array<std::atomic<std::uint32_t>, std::size_t{1} << PassSlotBits> passCounts{};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -278,6 +278,7 @@ void Driver::run(std::uint32_t id) noexcept {
|
||||
running.Start();
|
||||
if (profile && submission.enqueuedAt != std::chrono::steady_clock::time_point{}) costs.dequeueNs += static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - submission.enqueuedAt).count());
|
||||
}
|
||||
retireLabelTakes(id, submission.received);
|
||||
execute(submission);
|
||||
if (submission.progress && submission.rewindTail == nullptr && !completionDeferred()) submitOpenWork(submission.progress);
|
||||
if (traceGpu) std::fprintf(stderr, "[gpu] %.1f done serial=%llu queue=0x%x\n", TraceMs(), static_cast<unsigned long long>(submission.serial), id);
|
||||
|
||||
@@ -362,6 +362,7 @@ void Driver::executeRewindTail(const Submission& stalled) {
|
||||
if (APS5_ENABLE_TIMING_LOG) tail.roomReadyAt = std::chrono::steady_clock::now();
|
||||
auto* const capture = SubmissionCapture::Active();
|
||||
auto captured = capture != nullptr ? SubmissionCapture::Prepare(tail.queue, SubmissionKind::Rewind, tail.commands, tail.conditionalEnds) : CapturedSubmission{};
|
||||
std::uint64_t retiredTakes = 0;
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
rethrowFailure();
|
||||
@@ -370,12 +371,15 @@ void Driver::executeRewindTail(const Submission& stalled) {
|
||||
tail.shaders = shaders;
|
||||
tail.serial = stalled.serial;
|
||||
tail.received = ++eventSerial;
|
||||
const auto& waiting = workers[tail.queue].pending;
|
||||
retiredTakes = waiting.empty() ? tail.received : std::min(tail.received, waiting.front().received);
|
||||
if (capture != nullptr) capture->Take(captured, tail.serial, captureTime(), tail.heldAtSubmit);
|
||||
if (ring) RecordSubmission(tail.queue, tail.serial, tail.commands, stalled.rewindTail, true);
|
||||
}
|
||||
if (capture != nullptr) writeCaptured(*this, *capture, captured);
|
||||
if (APS5_ENABLE_TIMING_LOG) tail.enqueuedAt = tail.dequeuedAt = tail.orderedAt = std::chrono::steady_clock::now();
|
||||
timing.Finish();
|
||||
retireLabelTakes(tail.queue, retiredTakes);
|
||||
execute(tail);
|
||||
}
|
||||
|
||||
|
||||
+15
-17
@@ -20,14 +20,6 @@ void Driver::noteLabelStore(std::uint64_t address, std::span<const std::byte> by
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::uint64_t queueDword(std::uint32_t queue, std::uint64_t dword) {
|
||||
return dword | (static_cast<std::uint64_t>(queue) << 56u);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Driver::noteLabelPass(std::span<const std::uint32_t> packet, std::uint32_t queue) {
|
||||
const auto opcode = (packet[0] >> 8u) & 0xffu;
|
||||
if ((opcode != 0x3c && opcode != 0x93) || packet.size() < 7 || ((packet[1] >> 4u) & 3u) != 1u) return;
|
||||
@@ -35,20 +27,28 @@ void Driver::noteLabelPass(std::span<const std::uint32_t> packet, std::uint32_t
|
||||
const std::uint64_t address = (packet[2] & (bytes == 8 ? ~7u : ~3u)) | (static_cast<std::uint64_t>(packet[3]) << 32u);
|
||||
const auto passed = eventSerial.load(std::memory_order_acquire);
|
||||
std::lock_guard lock(labelStoresMutex);
|
||||
for (std::size_t offset = 0; offset < bytes; offset += 4) labelPasses[queueDword(queue, address + offset)] = passed;
|
||||
for (std::size_t offset = 0; offset < bytes; offset += 4) labelTakes.Pass(queue, address + offset, passed);
|
||||
}
|
||||
|
||||
void Driver::retireLabelTakes(std::uint32_t queue, std::uint64_t received) {
|
||||
std::lock_guard lock(labelStoresMutex);
|
||||
labelTakes.Retire(queue, received);
|
||||
}
|
||||
|
||||
void Driver::noteLabelIssue(std::span<const std::uint32_t> packet, std::uint32_t queue) {
|
||||
const auto take = [&](std::uint64_t address, std::uint64_t bytes) {
|
||||
const auto mayTake = [&](std::uint64_t address, std::uint64_t bytes) {
|
||||
for (std::uint64_t offset = 0; offset < bytes; offset += 4) {
|
||||
const auto key = queueDword(queue, address + offset);
|
||||
if (const auto pass = labelPasses.find(key); pass != labelPasses.end()) labelTaken[key] = pass->second;
|
||||
if (labelTakes.MayHavePass(queue, address + offset)) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const auto take = [&](std::uint64_t address, std::uint64_t bytes) {
|
||||
for (std::uint64_t offset = 0; offset < bytes; offset += 4) labelTakes.Take(queue, address + offset);
|
||||
};
|
||||
switch ((packet[0] >> 8u) & 0xffu) {
|
||||
case 0x37: case 0x49: {
|
||||
const auto label = Pm4::DecodeLabelWrite(packet);
|
||||
if (!label.has_value() || !KeepsLabelStore(label->address, label->Bytes().size())) return;
|
||||
if (!label.has_value() || !KeepsLabelStore(label->address, label->Bytes().size()) || !mayTake(label->address, label->Bytes().size())) return;
|
||||
std::lock_guard lock(labelStoresMutex);
|
||||
take(label->address, label->Bytes().size());
|
||||
return;
|
||||
@@ -57,7 +57,7 @@ void Driver::noteLabelIssue(std::span<const std::uint32_t> packet, std::uint32_t
|
||||
default: return;
|
||||
}
|
||||
auto stores = Pm4::DecodeStores(packet);
|
||||
std::erase_if(stores, [](const Pm4::PacketStore& store) { return store.conditional || !KeepsLabelStore(store.address, static_cast<std::size_t>(store.bytes)); });
|
||||
std::erase_if(stores, [&](const Pm4::PacketStore& store) { return store.conditional || !KeepsLabelStore(store.address, static_cast<std::size_t>(store.bytes)) || !mayTake(store.address, store.bytes); });
|
||||
if (stores.empty()) return;
|
||||
std::lock_guard lock(labelStoresMutex);
|
||||
for (const auto& store : stores) take(store.address, store.bytes);
|
||||
@@ -70,9 +70,7 @@ bool Driver::storedSince(std::span<const std::uint32_t> packet, std::uint64_t ad
|
||||
const auto high = bytes == 8 ? labelStores.find(address + 4) : labelStores.end();
|
||||
if (bytes == 8 && high == labelStores.end()) return false;
|
||||
std::uint64_t taken = 0;
|
||||
for (std::size_t offset = 0; offset < bytes; offset += 4) {
|
||||
if (const auto found = labelTaken.find(queueDword(queue, address + offset)); found != labelTaken.end()) taken = std::max(taken, found->second);
|
||||
}
|
||||
for (std::size_t offset = 0; offset < bytes; offset += 4) taken = std::max(taken, labelTakes.Taken(queue, address + offset));
|
||||
for (const auto& store : low->second) {
|
||||
if (store.stamp <= received || (store.queue != queue && store.stamp <= taken)) continue;
|
||||
std::uint64_t value = store.value;
|
||||
|
||||
@@ -37,8 +37,6 @@ public:
|
||||
std::uint64_t hits = 0;
|
||||
std::uint64_t misses = 0;
|
||||
std::size_t held = 0;
|
||||
// Hits served by the cached address space's serial alone (no hash, no compare; see
|
||||
// GuestBufferMemory::CachedAddressTable), counted in `hits` too.
|
||||
std::uint64_t spaceTables = 0;
|
||||
// Why the most recently used table did not serve a build ([bda-table] first-differing entry
|
||||
// class): its owners were gone (expired weak reference), or its word hash differed, split by
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Context.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/SampleOrder.hpp"
|
||||
#include "BdaAbi.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/State.hpp"
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
@@ -81,6 +82,9 @@ bool MultisampledColorFillAbsorbs(std::uint64_t address, std::size_t bytes, std:
|
||||
void NoteColorMetadataFill(std::uint64_t address, std::size_t bytes, std::uint32_t pattern);
|
||||
std::shared_ptr<Texture> MultisampleTargetFmaskTexture_nid_no_patch(const Context& context, const GuestTextureResource& resource, VkComponentMapping components);
|
||||
void ClearMultisampleTargets(VkDevice device);
|
||||
std::uint64_t MultisampleTargetRangeGeneration();
|
||||
std::optional<std::vector<ShaderRecompiler::BdaAbi::Range>> ExcludeMultisampleTargetRanges(
|
||||
VkDevice device, std::span<const ShaderRecompiler::BdaAbi::Range> ranges, std::uint64_t& generation);
|
||||
void InstallResidentImageReadGuard_nid_no_patch();
|
||||
void UpdateResidentImageUnmapObserver_nid_no_patch();
|
||||
void ClaimMultisampleTargetMemory(std::uint64_t address, std::uint64_t bytes);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "prx/libSceAgcDriver/Graphics/include/BdaResources.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp"
|
||||
#include "ImageTableAbi.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include <algorithm>
|
||||
@@ -18,29 +19,11 @@ namespace AgcDriver::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
// The page tables built per device, reused by a build whose ranges (guest ranges, device
|
||||
// addresses, permissions) equal one of them: consecutive address-based builds map the same registered
|
||||
// set through the same imports and mirrors, so the ~1200-entry table repeats. The shader only reads
|
||||
// the table (its lookup keeps its state in function variables), and a batch in flight keeps its
|
||||
// BdaResources, so sharing the buffer is safe. The cache holds the tables weakly: they never outlive
|
||||
// the builds that own them (a static owner would destroy the buffer after its device, at exit or on
|
||||
// device replacement, and would fail the tests' leak check), and with deferred lease release the
|
||||
// previous builds are still in flight when the next one asks, which is what makes the hit rate.
|
||||
// One table was enough while every build repeated the last one exactly; now the misaligned
|
||||
// descriptor sub-ranges the GPU copies out of imports sit in pool staging buffers whose device
|
||||
// addresses cycle through the few buffers the batches in flight leave free, and the three
|
||||
// address-based programs of one queue interleave with the lease draws, so a table differs from the
|
||||
// previous one and comes back a few builds later (69% misses in the wave-8 run, 4% before the GPU
|
||||
// copies). Hence several recent tables, most recently used first, matched by a word hash before
|
||||
// the byte compare. APS5_BDA_TABLE_CACHE_ENTRIES sets how many (default 8; 1 is the previous
|
||||
// behaviour); APS5_NO_BDA_TABLE_CACHE=1 builds every table.
|
||||
// A table built for (or found equal to) the cached address space's ranges carries the space's
|
||||
// serial: a build served by that space alone binds it by the serial, with no hash and no compare.
|
||||
struct TableEntry {
|
||||
std::uint64_t hash;
|
||||
std::vector<ShaderRecompiler::BdaAbi::Range> ranges;
|
||||
std::weak_ptr<Buffer> buffer;
|
||||
std::uint64_t spaceSerial = 0;
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> spaceKeys;
|
||||
};
|
||||
|
||||
struct TableCache {
|
||||
@@ -54,8 +37,6 @@ struct TableCache {
|
||||
|
||||
bool sameRanges(const std::vector<ShaderRecompiler::BdaAbi::Range>& left, const std::vector<ShaderRecompiler::BdaAbi::Range>& right);
|
||||
|
||||
// APS5_PROFILE_DRAW: why the most recently used entry did not serve this build (see
|
||||
// TableCacheStats). Under the cache mutex.
|
||||
void classifyFirstEntry(TableCache& cache, std::uint64_t hash, const std::vector<ShaderRecompiler::BdaAbi::Range>& ranges) {
|
||||
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
|
||||
if (!profile) return;
|
||||
@@ -135,8 +116,33 @@ BdaResources::BdaResources(const Context& context, const GuestBufferMemory& memo
|
||||
const auto cached = memory.CachedAddressTable();
|
||||
std::vector<ShaderRecompiler::BdaAbi::Range> built;
|
||||
if (!cached.has_value()) built = memory.AddressRanges();
|
||||
const auto& ranges = cached.has_value() ? *cached->ranges : built;
|
||||
const auto& addressRanges = cached.has_value() ? *cached->ranges : built;
|
||||
const auto serial = cached.has_value() ? cached->serial : 0;
|
||||
auto guardSerial = MultisampleTargetRangeGeneration();
|
||||
if (tableCacheEnabled() && serial != 0) {
|
||||
auto& cache = Tables();
|
||||
std::lock_guard lock(cache.mutex);
|
||||
if (cache.device != context.device) {
|
||||
cache.entries.clear();
|
||||
cache.device = context.device;
|
||||
}
|
||||
for (auto it = cache.entries.begin(); it != cache.entries.end(); ++it) {
|
||||
const auto key = std::pair{serial, guardSerial};
|
||||
if (std::find(it->spaceKeys.begin(), it->spaceKeys.end(), key) == it->spaceKeys.end()) continue;
|
||||
if (auto shared = it->buffer.lock(); shared != nullptr) {
|
||||
tableBytes = shared->Bytes().size();
|
||||
++cache.hits;
|
||||
++cache.classes.spaceTables;
|
||||
table = std::move(shared);
|
||||
cache.entries.splice(cache.entries.begin(), cache.entries, it);
|
||||
return;
|
||||
}
|
||||
cache.entries.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto filtered = ExcludeMultisampleTargetRanges(context.device, addressRanges, guardSerial);
|
||||
const auto& ranges = filtered.has_value() ? *filtered : addressRanges;
|
||||
Require(ranges.size() <= std::numeric_limits<std::uint32_t>::max(), "BDA table range count overflow");
|
||||
Require(ranges.size() <= (std::numeric_limits<std::size_t>::max() - sizeof(ShaderRecompiler::BdaAbi::Header)) / sizeof(ShaderRecompiler::BdaAbi::Range), "BDA table size overflow");
|
||||
tableBytes = sizeof(ShaderRecompiler::BdaAbi::Header) + ranges.size() * sizeof(ShaderRecompiler::BdaAbi::Range);
|
||||
@@ -150,20 +156,6 @@ BdaResources::BdaResources(const Context& context, const GuestBufferMemory& memo
|
||||
cache.entries.clear();
|
||||
cache.device = context.device;
|
||||
}
|
||||
if (serial != 0) {
|
||||
for (auto it = cache.entries.begin(); it != cache.entries.end(); ++it) {
|
||||
if (it->spaceSerial != serial) continue;
|
||||
if (auto shared = it->buffer.lock(); shared != nullptr) {
|
||||
++cache.hits;
|
||||
++cache.classes.spaceTables;
|
||||
table = std::move(shared);
|
||||
cache.entries.splice(cache.entries.begin(), cache.entries, it);
|
||||
return;
|
||||
}
|
||||
cache.entries.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
hash = hashRanges(ranges);
|
||||
classifyFirstEntry(cache, hash, ranges);
|
||||
for (auto it = cache.entries.begin(); it != cache.entries.end();) {
|
||||
@@ -186,9 +178,13 @@ BdaResources::BdaResources(const Context& context, const GuestBufferMemory& memo
|
||||
}
|
||||
if (sameRanges(it->ranges, ranges)) {
|
||||
++cache.hits;
|
||||
// An equal table of an earlier space (a rebuild that mapped the same ranges the
|
||||
// same way): the next build of this space binds it by the serial.
|
||||
if (serial != 0) it->spaceSerial = serial;
|
||||
if (serial != 0) {
|
||||
const auto key = std::pair{serial, guardSerial};
|
||||
auto found = std::find(it->spaceKeys.begin(), it->spaceKeys.end(), key);
|
||||
if (found != it->spaceKeys.end()) it->spaceKeys.erase(found);
|
||||
it->spaceKeys.push_back(key);
|
||||
if (it->spaceKeys.size() > tableCacheEntries()) it->spaceKeys.erase(it->spaceKeys.begin());
|
||||
}
|
||||
table = std::move(shared);
|
||||
cache.entries.splice(cache.entries.begin(), cache.entries, it);
|
||||
return;
|
||||
@@ -209,7 +205,9 @@ BdaResources::BdaResources(const Context& context, const GuestBufferMemory& memo
|
||||
cache.entries.clear();
|
||||
cache.device = context.device;
|
||||
}
|
||||
cache.entries.push_front({hash, cached.has_value() ? ranges : std::move(built), table, serial});
|
||||
auto cachedRanges = filtered.has_value() ? std::move(*filtered) : cached.has_value() ? *cached->ranges : std::move(built);
|
||||
cache.entries.push_front({hash, std::move(cachedRanges), table, {}});
|
||||
if (serial != 0) cache.entries.front().spaceKeys.emplace_back(serial, guardSerial);
|
||||
while (cache.entries.size() > tableCacheEntries()) cache.entries.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -796,6 +796,11 @@ std::atomic<bool>& anyGuarded() {
|
||||
return any;
|
||||
}
|
||||
|
||||
std::atomic<std::uint64_t>& guardedGeneration() {
|
||||
static std::atomic<std::uint64_t> generation{0};
|
||||
return generation;
|
||||
}
|
||||
|
||||
void guardReads(std::uint64_t address, std::size_t bytes) {
|
||||
RequireNoMultisampleTarget(address, bytes, "a CPU read");
|
||||
}
|
||||
@@ -834,13 +839,18 @@ void retireUnmapped(std::uintptr_t address, std::size_t bytes) {
|
||||
forgetSingleClears(address, bytes);
|
||||
RetireUnmappedDepth_nid_no_patch(address, bytes);
|
||||
std::unique_lock lock(guardedMutex());
|
||||
std::erase_if(guarded(), [&](const auto& range) {
|
||||
if (range.metadataValid && address < range.address + range.bytes && range.address < address + bytes) range.metadataValid->store(false, std::memory_order_release);
|
||||
bool metadataChanged = false;
|
||||
const auto invalidated = std::erase_if(guarded(), [&](const auto& range) {
|
||||
if (range.metadataValid && address < range.address + range.bytes && range.address < address + bytes) {
|
||||
range.metadataValid->store(false, std::memory_order_release);
|
||||
metadataChanged = true;
|
||||
}
|
||||
if (range.address < address || range.address - address > bytes || range.bytes > bytes - (range.address - address)) return false;
|
||||
if (!range.metadataValid) range.active->store(false, std::memory_order_release);
|
||||
return true;
|
||||
});
|
||||
std::erase_if(guarded(), [](const auto& range) { return range.metadataValid && !range.active->load(std::memory_order_acquire); });
|
||||
const auto staleMetadata = std::erase_if(guarded(), [](const auto& range) { return range.metadataValid && !range.active->load(std::memory_order_acquire); });
|
||||
if (metadataChanged || invalidated != 0 || staleMetadata != 0) guardedGeneration().fetch_add(1, std::memory_order_release);
|
||||
anyGuarded().store(!guarded().empty(), std::memory_order_release);
|
||||
}
|
||||
|
||||
@@ -882,6 +892,7 @@ void guard(const Context& context, const ColorTarget& target, const std::shared_
|
||||
if (target.cmaskAddress != 0) guarded().push_back({context.device, target.cmaskAddress, target.cmaskBytes, target.samples, active, metadataValid, use});
|
||||
if (target.samples == 8u && target.fmaskAddress != 0u && target.cmaskAddress != 0u) guarded().push_back({context.device, target.fmaskAddress, DescribeSurface(fmaskResource(target)).guestBytes, target.samples, active, metadataValid, use});
|
||||
}
|
||||
guardedGeneration().fetch_add(1, std::memory_order_release);
|
||||
anyGuarded().store(true, std::memory_order_release);
|
||||
InstallResidentImageReadGuard_nid_no_patch();
|
||||
}
|
||||
@@ -1477,7 +1488,8 @@ void ClearMultisampleTargets(VkDevice device) {
|
||||
std::lock_guard lock(targetsMutex());
|
||||
std::erase_if(targets(), [&](const auto& target) { return target->context.device == device; });
|
||||
std::unique_lock guardLock(guardedMutex());
|
||||
std::erase_if(guarded(), [&](const auto& range) { return range.device == device; });
|
||||
const auto removed = std::erase_if(guarded(), [&](const auto& range) { return range.device == device; });
|
||||
if (removed != 0) guardedGeneration().fetch_add(1, std::memory_order_release);
|
||||
anyGuarded().store(!guarded().empty(), std::memory_order_release);
|
||||
UpdateResidentImageUnmapObserver_nid_no_patch();
|
||||
}
|
||||
@@ -1499,6 +1511,71 @@ void GuardOneFragmentFmask(const Context& context, const ColorTarget& target) {
|
||||
InstallResidentImageReadGuard_nid_no_patch();
|
||||
}
|
||||
|
||||
std::uint64_t MultisampleTargetRangeGeneration() {
|
||||
std::shared_lock lock(guardedMutex());
|
||||
return guardedGeneration().load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
std::optional<std::vector<ShaderRecompiler::BdaAbi::Range>> ExcludeMultisampleTargetRanges(
|
||||
VkDevice device, std::span<const ShaderRecompiler::BdaAbi::Range> ranges, std::uint64_t& generation) {
|
||||
if (ranges.empty()) {
|
||||
generation = MultisampleTargetRangeGeneration();
|
||||
return std::nullopt;
|
||||
}
|
||||
if (!anyGuarded().load(std::memory_order_acquire)) {
|
||||
generation = MultisampleTargetRangeGeneration();
|
||||
return std::nullopt;
|
||||
}
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> excluded;
|
||||
{
|
||||
std::shared_lock lock(guardedMutex());
|
||||
generation = guardedGeneration().load(std::memory_order_relaxed);
|
||||
for (const auto& range : guarded()) {
|
||||
if (range.device != device || !range.active->load(std::memory_order_acquire)) continue;
|
||||
excluded.emplace_back(range.address, range.address + range.bytes);
|
||||
}
|
||||
}
|
||||
if (excluded.empty()) return std::nullopt;
|
||||
std::sort(excluded.begin(), excluded.end());
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> merged;
|
||||
merged.reserve(excluded.size());
|
||||
for (const auto& range : excluded) {
|
||||
if (!merged.empty() && range.first <= merged.back().second) merged.back().second = std::max(merged.back().second, range.second);
|
||||
else merged.push_back(range);
|
||||
}
|
||||
std::vector<ShaderRecompiler::BdaAbi::Range> filtered;
|
||||
filtered.reserve(ranges.size() + merged.size());
|
||||
bool changed = false;
|
||||
for (const auto& range : ranges) {
|
||||
auto cursor = range.begin;
|
||||
for (const auto& [begin, end] : merged) {
|
||||
if (end <= cursor) continue;
|
||||
if (begin >= range.end) break;
|
||||
const auto cutBegin = std::max(cursor, begin);
|
||||
const auto cutEnd = std::min(range.end, end);
|
||||
if (cutBegin > cursor) {
|
||||
auto part = range;
|
||||
part.begin = cursor;
|
||||
part.end = cutBegin;
|
||||
part.deviceAddress += cursor - range.begin;
|
||||
filtered.push_back(part);
|
||||
}
|
||||
if (cutBegin < cutEnd) {
|
||||
changed = true;
|
||||
cursor = cutEnd;
|
||||
}
|
||||
if (cursor == range.end) break;
|
||||
}
|
||||
if (cursor < range.end) {
|
||||
auto part = range;
|
||||
part.begin = cursor;
|
||||
part.deviceAddress += cursor - range.begin;
|
||||
filtered.push_back(part);
|
||||
}
|
||||
}
|
||||
return changed ? std::optional(std::move(filtered)) : std::nullopt;
|
||||
}
|
||||
|
||||
void RequireNoMultisampleTarget(std::uint64_t address, std::size_t bytes, const char* access) {
|
||||
RequireNoResidentDepthRead_nid_no_patch(address, bytes);
|
||||
RequireNoMultisampleColorTarget(address, bytes, access);
|
||||
|
||||
@@ -116,6 +116,7 @@ void snapshotTableTests(const Context& context, const BdaTestAccess& access) {
|
||||
auto a = build(first, false, true, snapshotAddress);
|
||||
auto plain = build(first, false, false, snapshotAddress);
|
||||
auto d = build(first, true, true, snapshotAddress);
|
||||
const auto spaceTableHitsBefore = BdaResources::TableCacheCounters().spaceTables;
|
||||
auto b = build(first, false, true, snapshotAddress);
|
||||
auto c = build(second, false, true, snapshotAddress);
|
||||
Require(a.snapshot == b.snapshot && a.view == b.view, "two builds of one snapshot with the same bytes do not share its device copy");
|
||||
@@ -124,6 +125,8 @@ void snapshotTableTests(const Context& context, const BdaTestAccess& access) {
|
||||
if (spaces) {
|
||||
Require(a.memory->CachedAddressTable().has_value() && c.memory->CachedAddressTable().has_value() && d.memory->CachedAddressTable().has_value(), "a build with its own regions over the address space has no cached BDA table");
|
||||
Require(a.table == b.table, "two builds of the same space and snapshot do not share their BDA table");
|
||||
Require(BdaResources::TableCacheCounters().spaceTables == spaceTableHitsBefore + 1, "a repeated build with a stable address-space and MSAA guard generation missed the serial-keyed BDA table");
|
||||
Require(b.bda->Table().range == a.bda->Table().range && b.bda->Table().range != 0, "a cached BDA descriptor lost its byte range: " + std::to_string(b.bda->Table().range) + " instead of " + std::to_string(a.bda->Table().range));
|
||||
Require(c.table != a.table, "a build with other snapshot bytes took another build's BDA table");
|
||||
Require(a.memory->CachedAddressTable()->serial == b.memory->CachedAddressTable()->serial && c.memory->CachedAddressTable()->serial != a.memory->CachedAddressTable()->serial, "cached BDA table serials do not follow the build's ranges");
|
||||
}
|
||||
|
||||
@@ -6280,7 +6280,15 @@ void descriptorPoolResetFailureTest() {
|
||||
const auto previous = _ZSt13set_terminatePFvvE_nid_postfix([] {
|
||||
std::_Exit(mock.descriptorResetResult == VK_SUCCESS ? 0 : 1);
|
||||
});
|
||||
#ifdef __APPLE__
|
||||
const auto previousHost = std::set_terminate([] {
|
||||
std::_Exit(mock.descriptorResetResult == VK_SUCCESS ? 0 : 1);
|
||||
});
|
||||
#endif
|
||||
cache.Free(allocation);
|
||||
#ifdef __APPLE__
|
||||
std::set_terminate(previousHost);
|
||||
#endif
|
||||
_ZSt13set_terminatePFvvE_nid_postfix(previous);
|
||||
|
||||
}
|
||||
|
||||
@@ -39,6 +39,18 @@ static_assert(offsetof(Packet, addr) == 0);
|
||||
static_assert(offsetof(Packet, dw_num) == 8);
|
||||
static_assert(offsetof(Packet, flags) == 12);
|
||||
|
||||
namespace AgcDriver::DriverDetail {
|
||||
|
||||
struct DriverTestAccess {
|
||||
static std::uint64_t LabelTaken(std::uint32_t queue, const volatile std::uint32_t* address) {
|
||||
auto& driver = Driver::Get();
|
||||
std::lock_guard lock(driver.labelStoresMutex);
|
||||
return driver.labelTakes.Taken(queue, reinterpret_cast<std::uintptr_t>(address));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void check(bool condition, const char* message) {
|
||||
@@ -451,6 +463,56 @@ void testLabelTakenAndResetByEarlierSubmission() {
|
||||
AgcDriverWaitIdle_nid_postfix();
|
||||
}
|
||||
|
||||
void testLabelTakesRetire() {
|
||||
using AgcDriver::DriverDetail::LabelTakes;
|
||||
auto takes = std::make_unique<LabelTakes>();
|
||||
constexpr std::uint64_t label = 0x1000, other = 0x2000;
|
||||
takes->Pass(0x20, label, 10);
|
||||
check(takes->MayHavePass(0x20, label) && takes->Taken(0x20, label) == 0, "a passed wait was not kept for the store that takes it");
|
||||
takes->Take(0x20, label);
|
||||
check(takes->Taken(0x20, label) == 10 && !takes->MayHavePass(0x20, label), "the queue's own store did not turn its pass into a take");
|
||||
takes->Retire(0x20, 9);
|
||||
check(takes->Taken(0x20, label) == 10, "a take was dropped while a submission of the queue entered before it could still wait");
|
||||
takes->Retire(0x28, 20);
|
||||
check(takes->Taken(0x20, label) == 10, "another queue's submission retired a take");
|
||||
takes->Retire(0x20, 10);
|
||||
check(takes->Taken(0x20, label) == 0 && takes->Empty(), "a take was kept after every submission of the queue that could wait entered after it");
|
||||
takes->Pass(0x20, label, 30);
|
||||
takes->Pass(0x20, label, 40);
|
||||
takes->Retire(0x20, 30);
|
||||
check(takes->MayHavePass(0x20, label), "an older pass on the dword retired its newer pass");
|
||||
takes->Take(0x20, label);
|
||||
takes->Retire(0x20, 39);
|
||||
check(takes->Taken(0x20, label) == 40, "a take was retired by a submission entered before it");
|
||||
takes->Pass(0x20, other, 50);
|
||||
takes->Pass(0x28, label, 50);
|
||||
takes->Retire(0x20, 50);
|
||||
check(takes->Taken(0x20, label) == 0 && !takes->MayHavePass(0x20, other), "a submission of the queue entered after its passes and take did not retire them");
|
||||
check(takes->MayHavePass(0x28, label) && !takes->Empty(), "a submission of one queue retired another queue's pass");
|
||||
takes->Retire(0x28, 50);
|
||||
check(takes->Empty(), "a pass was kept after every submission of its queue entered after it");
|
||||
}
|
||||
|
||||
void testLabelTakeRetiredByLaterSubmission() {
|
||||
using AgcDriver::DriverDetail::DriverTestAccess;
|
||||
alignas(64) static volatile std::uint32_t gate = 0, label = 0, first = 0, second = 0, third = 0;
|
||||
submit(0x20, commands(waitEqual(&gate, 1), waitEqual(&label, 1), writeData(&label, 0), writeData(&first, 1)));
|
||||
submit(0x20, commands(waitEqual(&label, 1), writeData(&second, 1)));
|
||||
submit(0, commands(writeData(&label, 1)));
|
||||
waitFor(&label, 1, "producer label never landed");
|
||||
gate = 1;
|
||||
waitFor(&first, 1, "consumer never passed its first label wait");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1200));
|
||||
check(second == 0, "a label store an earlier submission of the queue had taken and reset satisfied a later submission's wait, or the wait expired");
|
||||
check(DriverTestAccess::LabelTaken(0x20, &label) != 0, "a take was dropped while a submission of the queue entered before it was waiting");
|
||||
submit(0, commands(writeData(&label, 1)));
|
||||
waitFor(&second, 1, "the later submission did not resume after a new producer label");
|
||||
submit(0x20, commands(writeData(&third, 1)));
|
||||
waitFor(&third, 1, "a submission entered after the take never ran");
|
||||
check(DriverTestAccess::LabelTaken(0x20, &label) == 0, "a take was kept after a submission of the queue entered after it started");
|
||||
AgcDriverWaitIdle_nid_postfix();
|
||||
}
|
||||
|
||||
void testWaitFreeSubmissionAfterEarlierQueue0Work() {
|
||||
alignas(64) static volatile std::uint32_t first = 0, second = 0;
|
||||
constexpr std::uint32_t writes = 20000;
|
||||
@@ -1103,6 +1165,8 @@ int main(int argc, char** argv) {
|
||||
testLabelHeldAtSubmission();
|
||||
testLabelTakenAndResetInOneSubmission();
|
||||
testLabelTakenAndResetByEarlierSubmission();
|
||||
testLabelTakesRetire();
|
||||
testLabelTakeRetiredByLaterSubmission();
|
||||
testWideLabelStoredSinceSubmission();
|
||||
testWaitFreeSubmissionAfterEarlierQueue0Work();
|
||||
testWaitFreeSubmissionQueue0WaitsOn();
|
||||
|
||||
@@ -24,9 +24,9 @@ constexpr std::uint32_t Instructions = 5;
|
||||
alignas(256) std::array<std::uint32_t, Threads * Inputs> Input{};
|
||||
alignas(256) std::array<std::uint32_t, Threads * Results> Output{};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 18> Code{
|
||||
alignas(256) constexpr std::array<std::uint32_t, 19> Code{
|
||||
0x34020084u, 0x34040086u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0x1e140b04u, 0x20160b04u, 0xd551000cu,
|
||||
0x041a0b04u, 0xd554000du, 0x041a0b04u, 0xd557000eu, 0x041a0b04u, 0xe0781000u, 0x80010a02u, 0xe0701010u,
|
||||
0x041a0b04u, 0xd554000du, 0x041a0b04u, 0xd557000eu, 0x041a0b04u, 0x201e0880u, 0xe0781000u, 0x80010a02u, 0xe0781010u,
|
||||
0x80010e02u, 0xbf810000u,
|
||||
};
|
||||
|
||||
@@ -155,6 +155,10 @@ void Check(std::uint32_t denormals, bool ieee) {
|
||||
Hex(vector.operands[0]) + ", " + Hex(vector.operands[1]) + ", " + Hex(vector.operands[2]) + " is " + Hex(actual) +
|
||||
", expected " + Hex(expected));
|
||||
}
|
||||
const auto value = vector.operands[0];
|
||||
const bool tiny = denormals != 1u && (value & 0x7f800000u) == 0u;
|
||||
const auto zeroMax = value <= 0x7f800000u && !tiny ? value : 0u;
|
||||
Require(Output[lane * Results + 5u] == zeroMax, "v_max_f32 v15, 0, v4 denormal result for " + Hex(value));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,10 +23,10 @@ constexpr std::uint32_t Results = 16;
|
||||
alignas(256) std::array<std::uint32_t, Threads * Inputs> Input{};
|
||||
alignas(256) std::array<std::uint32_t, Threads * Results> Output{};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 22> Code{
|
||||
alignas(256) constexpr std::array<std::uint32_t, 23> Code{
|
||||
0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x1e140b04, 0x20160b04, 0xd551000c,
|
||||
0x041a0b04, 0xd554000d, 0x041a0b04, 0xd557000e, 0x041a0b04, 0xd50f000f, 0x02020b04, 0xd5100010,
|
||||
0x02020b04, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xbf810000,
|
||||
0x02020b04, 0x20220880, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xbf810000,
|
||||
};
|
||||
|
||||
constexpr std::uint32_t Rows[128][4] = {
|
||||
@@ -482,6 +482,10 @@ void Check() {
|
||||
for (std::uint32_t i = 0; i < 7; ++i) {
|
||||
Expect(Chunk * Threads + tid, out[i], (Ieee ? ExpectedIeee : Expected)[Chunk * Threads + tid][i], Names[i]);
|
||||
}
|
||||
const auto magnitude = in[0] & 0x7fffffffu;
|
||||
const bool signaling = magnitude > 0x7f800000u && (in[0] & 0x00400000u) == 0u;
|
||||
const auto zeroMax = Ieee && signaling ? in[0] | 0x00400000u : in[0] <= 0x7f800000u ? in[0] : 0u;
|
||||
Expect(Chunk * Threads + tid, out[7], zeroMax, "v_max_f32 v17, 0, v4");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,11 @@
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace AgcDriver {
|
||||
|
||||
@@ -82,6 +87,9 @@ constexpr std::uint32_t TileD64KBX = 0x1a;
|
||||
constexpr std::uint32_t Threads = 32;
|
||||
constexpr std::uint32_t Words = 32;
|
||||
constexpr std::uint32_t LoadResult = 12;
|
||||
constexpr std::uint32_t BdaThreads = 64;
|
||||
constexpr std::uint32_t BdaLoadBase = 0x100;
|
||||
constexpr std::uint32_t BdaOutputBase = 0x8000;
|
||||
constexpr std::array<float, 4> Drawn{0.25f, 0.5f, 0.125f, 1.0f};
|
||||
constexpr double EdgeMargin = 1.0 / 32.0;
|
||||
constexpr std::uint32_t FillOnes = 0x3c003c00u;
|
||||
@@ -150,6 +158,11 @@ struct SampleWrite {
|
||||
std::uint32_t sample;
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 17> BdaSpanCode{
|
||||
0x34040084, 0x4a0404ff, BdaLoadBase, 0x34080081, 0x36080882, 0x4a040504, 0x34060083, 0x4a0606ff,
|
||||
BdaOutputBase, 0xdc388ff8, 0x04000002, 0xbf8c3f70, 0xdc708000, 0x00000503, 0xdc708004, 0x00000703, 0xbf810000,
|
||||
};
|
||||
|
||||
constexpr std::array<std::array<float, 4>, 3> Triangle{{
|
||||
{-1.0f, -1.0f, 0.5f, 1.0f}, {0.83f, -1.0f, 0.5f, 1.0f}, {-1.0f, 0.71f, 0.5f, 1.0f}
|
||||
}};
|
||||
@@ -158,6 +171,43 @@ alignas(256) std::array<std::uint32_t, Threads * Words> Buffer{};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 7> BufferLoadCode{0x34020082u, 0xe8001000u | (20u << 19u), 0x80000201u, 0xbf8c0000u, 0xe0701000u, 0x80010201u, 0xbf810000u};
|
||||
|
||||
class StderrCapture {
|
||||
public:
|
||||
StderrCapture() {
|
||||
std::fflush(stderr);
|
||||
file = std::tmpfile();
|
||||
Require(file != nullptr, "cannot open a temporary file for stderr");
|
||||
#ifdef _WIN32
|
||||
saved = _dup(_fileno(stderr));
|
||||
_dup2(_fileno(file), _fileno(stderr));
|
||||
#else
|
||||
saved = dup(fileno(stderr));
|
||||
dup2(fileno(file), fileno(stderr));
|
||||
#endif
|
||||
}
|
||||
std::string Finish() {
|
||||
std::fflush(stderr);
|
||||
#ifdef _WIN32
|
||||
_dup2(saved, _fileno(stderr));
|
||||
_close(saved);
|
||||
#else
|
||||
dup2(saved, fileno(stderr));
|
||||
close(saved);
|
||||
#endif
|
||||
std::rewind(file);
|
||||
std::string text;
|
||||
char buffer[4096];
|
||||
for (std::size_t read = 0; (read = std::fread(buffer, 1, sizeof(buffer), file)) != 0;) text.append(buffer, read);
|
||||
std::fclose(file);
|
||||
std::fwrite(text.data(), 1, text.size(), stderr);
|
||||
return text;
|
||||
}
|
||||
|
||||
private:
|
||||
std::FILE* file = nullptr;
|
||||
int saved = -1;
|
||||
};
|
||||
|
||||
struct Block {
|
||||
explicit Block(std::size_t bytes, bool watched = false) : bytes(bytes), watched(watched) {
|
||||
#ifdef _WIN32
|
||||
@@ -821,6 +871,55 @@ void StoreWords(const Block& block, std::uint64_t bytes, std::uint32_t word) {
|
||||
for (std::uint64_t offset = 0; offset < bytes; offset += sizeof(word)) std::memcpy(block.data + offset, &word, sizeof(word));
|
||||
}
|
||||
|
||||
ShaderRecompiler::RecompileResult CompileBdaSpan(AgcDriver::VulkanDevice& device, std::uint64_t address) {
|
||||
const std::span<const std::uint32_t> code(BdaSpanCode);
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
|
||||
const std::array<std::uint32_t, 2> userData{static_cast<std::uint32_t>(address), static_cast<std::uint32_t>(address >> 32u)};
|
||||
const ShaderRecompiler::ShaderComputeStageInfo compute{{BdaThreads, 1, 1}, 0, {false, false, false}, false, 1};
|
||||
ShaderRecompiler::RecompileRequest request{
|
||||
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
|
||||
{32, 0, userData, compute, std::nullopt, std::nullopt, memory},
|
||||
device.Target(),
|
||||
{0, 0, 0, 128}
|
||||
};
|
||||
request.useCache = false;
|
||||
const auto result = ShaderRecompiler::Recompile(request);
|
||||
Require(std::ranges::any_of(result.bindings, [](const auto& binding) { return binding.role == ShaderRecompiler::DescriptorRole::BdaPagetable; }), "the native multisample BDA test has no address-table binding");
|
||||
return result;
|
||||
}
|
||||
|
||||
void CheckBdaSpan(AgcDriver::VulkanDevice& device, const Block& block, std::size_t offset, bool refused) {
|
||||
std::vector<std::uint8_t> before(block.bytes);
|
||||
for (std::size_t index = 0; index < before.size(); ++index) before[index] = static_cast<std::uint8_t>(index * 37u + 11u);
|
||||
const auto output = offset + 0x8000u;
|
||||
std::fill(before.begin() + static_cast<std::ptrdiff_t>(output), before.begin() + static_cast<std::ptrdiff_t>(output + BdaThreads * 8u), 0xcdu);
|
||||
std::memcpy(block.data, before.data(), before.size());
|
||||
auto expected = before;
|
||||
for (std::uint32_t tid = 0; tid < BdaThreads; ++tid) {
|
||||
const auto load = offset + BdaLoadBase + tid * 16u + (tid & 1u) * 2u - 8u;
|
||||
std::memcpy(expected.data() + output + tid * 8u, before.data() + load + 4u, sizeof(std::uint32_t));
|
||||
std::memcpy(expected.data() + output + tid * 8u + 4u, before.data() + load + 12u, sizeof(std::uint32_t));
|
||||
}
|
||||
std::string failure;
|
||||
StderrCapture log;
|
||||
try {
|
||||
const auto address = block.Address() + offset;
|
||||
device.Dispatch(CompileBdaSpan(device, address), 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(BdaSpanCode.data()));
|
||||
device.WaitIdle();
|
||||
} catch (const std::exception& error) {
|
||||
failure = error.what();
|
||||
}
|
||||
const auto deferred = log.Finish();
|
||||
if (failure.empty()) failure = deferred;
|
||||
if (refused) {
|
||||
Require(failure.find("BDA access failed:") != std::string::npos, "BDA access to a resident native multisample range was not refused: " + failure);
|
||||
Require(std::equal(before.begin(), before.end(), block.data), "a refused BDA access changed guest memory");
|
||||
} else {
|
||||
Require(failure.empty(), "BDA access to an ordinary neighboring range failed: " + failure);
|
||||
Require(std::equal(expected.begin(), expected.end(), block.data), "the ordinary BDA span did not preserve its reads, writes or boundaries");
|
||||
}
|
||||
}
|
||||
|
||||
void RequireRefused(AgcDriver::VulkanDevice& device, const std::array<std::uint32_t, 8>& texture, const std::string& expected, const std::string& what) {
|
||||
std::string refusal;
|
||||
try {
|
||||
@@ -1260,6 +1359,18 @@ int main(int argc, char** argv) {
|
||||
return VulkanTestSkipped;
|
||||
}
|
||||
const auto colorBytes = AgcDriver::Graphics::MultisampledColorBytes(color);
|
||||
Require(colorBytes == BlockBytes, "the native color target does not occupy exactly one BDA block");
|
||||
Block bda(4u * BlockBytes);
|
||||
const auto bdaState = TargetState(DecodeTarget(bda));
|
||||
CheckBdaSpan(*device, bda, 0, false);
|
||||
CheckBdaSpan(*device, bda, BlockBytes, false);
|
||||
Draw(*device, bdaState);
|
||||
CheckBdaSpan(*device, bda, 0, true);
|
||||
CheckBdaSpan(*device, bda, BlockBytes, false);
|
||||
Draw(*device, state);
|
||||
CheckBdaSpan(*device, block, 0, true);
|
||||
CheckBdaSpan(*device, block, BlockBytes, false);
|
||||
CheckSamples(state, LoadSamples(*device, block), {});
|
||||
Require(colorBytes != 0 && colorBytes <= block.bytes, "the 4x target's color memory does not fit its block");
|
||||
AgcDriver::Graphics::NoteColorMetadataFill(block.Address(), static_cast<std::size_t>(colorBytes), FillOnes);
|
||||
CheckUniform(LoadSamples(*device, block), Ones, "after a fill of the target's color memory with half-float ones");
|
||||
@@ -1289,6 +1400,7 @@ int main(int argc, char** argv) {
|
||||
device->WaitIdle();
|
||||
}, "recipe dispatch", missing);
|
||||
}
|
||||
std::memset(block.data + colorBytes, 0, BlockBytes);
|
||||
CheckBufferLoad(*device, CompileBufferLoad(*device, block.Address() + colorBytes, output.Address()), output);
|
||||
CheckUniform(LoadSamples(*device, block), Ones, "after the native buffer access refusals and adjacent buffer control");
|
||||
std::cout << "native buffer controls: adjacent=passed deferred=" << (prepared != nullptr) << " recipe=" << recipeReused << std::endl;
|
||||
@@ -1385,6 +1497,10 @@ int main(int argc, char** argv) {
|
||||
Block standard(BlockBytes);
|
||||
StoreWords(standard, colorBytes, FillOnes);
|
||||
RequireRefused(*device, TargetDescriptor(standard, SampleShift, Format16161616Float, TileS64KBX), "has no modeled multisampled color footprint", "loading an S swizzle multisampled texture no draw rendered to");
|
||||
device->WaitIdle();
|
||||
AgcDriver::Graphics::ClearMultisampleTargets(device->Device());
|
||||
CheckBdaSpan(*device, block, 0, false);
|
||||
Draw(*device, TargetState(DecodeTarget(block)));
|
||||
std::puts("every sample of a 4x color target loaded through its multisampled texture descriptor matches the triangle drawn into it, then the fill of its color memory; a surface no draw rendered to loads the value its color memory repeats and keeps it under a later draw");
|
||||
struct ReleaseIndexDevice {
|
||||
std::unique_ptr<AgcDriver::VulkanDevice>& device;
|
||||
|
||||
@@ -512,6 +512,10 @@ int main() {
|
||||
std::puts("skipped, the device lacks fragmentShaderPixelInterlock");
|
||||
return VulkanTestSkipped;
|
||||
}
|
||||
if (!TargetHasCapability(device->Target(), spv::CapabilityStorageImageReadWithoutFormat)) {
|
||||
std::puts("skipped, the device lacks shaderStorageImageReadWithoutFormat");
|
||||
return VulkanTestSkipped;
|
||||
}
|
||||
const ColorTargetLayout layout(Width, Height, ColorTileMode::RenderTarget, 4);
|
||||
Block block(layout.Bytes());
|
||||
const auto unorm = DecodeTarget(block, Unorm8888Info);
|
||||
|
||||
@@ -58,6 +58,10 @@ double AudioOut2TraceSeconds() {
|
||||
return std::chrono::duration<double>(Clock::now() - start).count();
|
||||
}
|
||||
|
||||
static Clock::time_point Now(const AudioOut2Context& context) {
|
||||
return context.clockForTest ? *context.clockForTest : Clock::now();
|
||||
}
|
||||
|
||||
static std::mutex g_contextsLock;
|
||||
static std::unordered_set<AudioOut2Context*> g_contexts;
|
||||
|
||||
@@ -285,6 +289,18 @@ static void TraceSummary(AudioOut2Context& context, Clock::time_point now) {
|
||||
context.summaryPeak = 0.0f;
|
||||
}
|
||||
|
||||
static Clock::time_point WaitForRoom(AudioOut2Context& context, std::unique_lock<std::mutex>& lock) {
|
||||
auto now = Now(context);
|
||||
const auto waitStart = now;
|
||||
while (QueueLevel(context, now) >= context.queueDepth && now - waitStart <= FULL_WAIT_TIMEOUT) {
|
||||
lock.unlock();
|
||||
PreciseSleepUs(std::chrono::microseconds(FULL_WAIT_STEP).count());
|
||||
lock.lock();
|
||||
now = Now(context);
|
||||
}
|
||||
return now;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI sceAudioOut2ContextAdvance(AudioOut2ContextHandle ctx) try {
|
||||
@@ -368,7 +384,7 @@ int APS5_VABI sceAudioOut2ContextGetQueueLevel(AudioOut2ContextHandle ctx, uint3
|
||||
return 0;
|
||||
}
|
||||
std::lock_guard lock(context->lock);
|
||||
const auto level = std::min(context->queueDepth, QueueLevel(*context, Clock::now()) + static_cast<std::uint32_t>(context->advancedGrains.size()));
|
||||
const auto level = std::min(context->queueDepth, QueueLevel(*context, Now(*context)) + static_cast<std::uint32_t>(context->advancedGrains.size()));
|
||||
if (queue_level) *queue_level = level;
|
||||
if (available_queue) *available_queue = context->queueDepth - level;
|
||||
context->queueLevelPolls++;
|
||||
@@ -383,24 +399,17 @@ int APS5_VABI sceAudioOut2ContextPush(AudioOut2ContextHandle ctx, uint32_t block
|
||||
if (!context) return SCE_AUDIO_OUT2_ERROR_INVALID_HANDLE;
|
||||
std::unique_lock lock(context->lock);
|
||||
const QueueLevelPublisher publication{*context, lock};
|
||||
auto now = Clock::now();
|
||||
const auto waitStart = now;
|
||||
while (QueueLevel(*context, now) >= context->queueDepth) {
|
||||
if (!blocking) {
|
||||
context->fullRejects++;
|
||||
if (context->fullRejects <= CALL_TRACE_FULL) AUDIOOUT2_TRACE("t=%.3f Push ctx %p non-blocking on a full queue (%u): rejected\n", AudioOut2TraceSeconds(), static_cast<void*>(context), context->queueDepth);
|
||||
return SCE_AUDIO_OUT2_ERROR_QUEUE_FULL;
|
||||
}
|
||||
if (now - waitStart > FULL_WAIT_TIMEOUT) break;
|
||||
lock.unlock();
|
||||
PreciseSleepUs(std::chrono::microseconds(FULL_WAIT_STEP).count());
|
||||
lock.lock();
|
||||
now = Clock::now();
|
||||
auto now = Now(*context);
|
||||
if (!blocking && QueueLevel(*context, now) >= context->queueDepth) {
|
||||
context->fullRejects++;
|
||||
if (context->fullRejects <= CALL_TRACE_FULL) AUDIOOUT2_TRACE("t=%.3f Push ctx %p non-blocking on a full queue (%u): rejected\n", AudioOut2TraceSeconds(), static_cast<void*>(context), context->queueDepth);
|
||||
return SCE_AUDIO_OUT2_ERROR_QUEUE_FULL;
|
||||
}
|
||||
if (blocking) now = WaitForRoom(*context, lock);
|
||||
if (context->advancedMode && context->advancedGrains.empty()) {
|
||||
if (!blocking) throw std::runtime_error("AudioOut2: a non-blocking push without a rendered grain is not implemented");
|
||||
if (!context->grainReady.wait(lock, LibcShutdownToken_nid_postfix(), [&] { return !context->advancedGrains.empty(); })) throw ProcessShutdown{};
|
||||
now = Clock::now();
|
||||
now = Now(*context);
|
||||
Drain(*context, now);
|
||||
}
|
||||
context->queued++;
|
||||
@@ -437,6 +446,7 @@ int APS5_VABI sceAudioOut2ContextPush(AudioOut2ContextHandle ctx, uint32_t block
|
||||
context->output.insert(context->output.end(), context->mix.begin(), context->mix.end());
|
||||
}
|
||||
}
|
||||
if (blocking) now = WaitForRoom(*context, lock);
|
||||
context->pushes++;
|
||||
context->summaryPushes++;
|
||||
if (blocking) context->blockingPushes++;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <cstdio>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <stop_token>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -129,6 +130,7 @@ struct AudioOut2Context {
|
||||
std::chrono::steady_clock::time_point summaryStart;
|
||||
bool deviceBacked = false;
|
||||
std::atomic<std::uint32_t> publishedQueueLevel{0};
|
||||
std::optional<std::chrono::steady_clock::time_point> clockForTest;
|
||||
};
|
||||
|
||||
AudioOut2Context* AudioOut2FindContext(AudioOut2ContextHandle ctx);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <fstream>
|
||||
#include <future>
|
||||
#include <iterator>
|
||||
#include <mutex>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
@@ -213,6 +214,104 @@ void TestLevelWithoutADeviceUsesThePlaybackClock() {
|
||||
session.Close();
|
||||
}
|
||||
|
||||
void TestBlockingPushWaitsForRoomWithoutADevice() {
|
||||
SetEnvironment("SDL_DISKAUDIOFILE", std::filesystem::temp_directory_path().string());
|
||||
constexpr std::uint32_t paced = 1024;
|
||||
AudioOut2ContextParam params{};
|
||||
Require(sceAudioOut2ContextResetParam(¶ms) == 0);
|
||||
params.num_grains = paced;
|
||||
params.queue_depth = queueDepth;
|
||||
AudioOut2ContextHandle handle = 0;
|
||||
Require(sceAudioOut2ContextCreate(¶ms, nullptr, 0, &handle) == 0);
|
||||
Require(reinterpret_cast<AudioOut2Context*>(handle)->device == 0);
|
||||
std::uint32_t level = 99;
|
||||
std::uint32_t available = 99;
|
||||
for (std::uint32_t round = 0; round < 4; round++) {
|
||||
Require(sceAudioOut2ContextPush(handle, 1) == 0);
|
||||
Require(sceAudioOut2ContextGetQueueLevel(handle, &level, &available) == 0);
|
||||
Require(level < queueDepth && available > 0);
|
||||
}
|
||||
Require(sceAudioOut2ContextDestroy(handle) == 0);
|
||||
}
|
||||
|
||||
AudioOut2ContextHandle CreateContextOnATestClock() {
|
||||
SetEnvironment("SDL_DISKAUDIOFILE", std::filesystem::temp_directory_path().string());
|
||||
AudioOut2ContextParam params{};
|
||||
Require(sceAudioOut2ContextResetParam(¶ms) == 0);
|
||||
params.num_grains = grain;
|
||||
params.queue_depth = queueDepth;
|
||||
AudioOut2ContextHandle handle = 0;
|
||||
Require(sceAudioOut2ContextCreate(¶ms, nullptr, 0, &handle) == 0);
|
||||
auto& context = *reinterpret_cast<AudioOut2Context*>(handle);
|
||||
std::lock_guard lock(context.lock);
|
||||
Require(context.device == 0);
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
context.clockForTest = start;
|
||||
context.playHead = start;
|
||||
return handle;
|
||||
}
|
||||
|
||||
void PlayOneGrainOnTheTestClock(AudioOut2ContextHandle handle) {
|
||||
auto& context = *reinterpret_cast<AudioOut2Context*>(handle);
|
||||
std::lock_guard lock(context.lock);
|
||||
*context.clockForTest += std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::duration<double>(static_cast<double>(grain) / frequency)) + std::chrono::microseconds(1);
|
||||
}
|
||||
|
||||
template<class Predicate>
|
||||
bool ContextReaches(AudioOut2ContextHandle handle, std::chrono::milliseconds limit, Predicate predicate) {
|
||||
auto& context = *reinterpret_cast<AudioOut2Context*>(handle);
|
||||
const auto deadline = std::chrono::steady_clock::now() + limit;
|
||||
for (;;) {
|
||||
{
|
||||
std::lock_guard lock(context.lock);
|
||||
if (predicate(context)) return true;
|
||||
}
|
||||
if (std::chrono::steady_clock::now() >= deadline) return false;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
std::uint32_t QueueLevelNow(AudioOut2ContextHandle handle) {
|
||||
std::uint32_t level = 99;
|
||||
std::uint32_t available = 99;
|
||||
Require(sceAudioOut2ContextGetQueueLevel(handle, &level, &available) == 0);
|
||||
Require(level + available == queueDepth);
|
||||
return level;
|
||||
}
|
||||
|
||||
void TestBlockingPushStaysBlockedUntilPlaybackFreesASlot() {
|
||||
const auto handle = CreateContextOnATestClock();
|
||||
for (std::uint32_t push = 0; push + 1 < queueDepth; push++) Require(sceAudioOut2ContextPush(handle, 1) == 0);
|
||||
Require(QueueLevelNow(handle) == queueDepth - 1);
|
||||
auto push = std::async(std::launch::async, [handle] { return sceAudioOut2ContextPush(handle, 1); });
|
||||
std::uint64_t pushesOnceQueued = 0;
|
||||
Require(ContextReaches(handle, std::chrono::seconds(10), [&](const AudioOut2Context& context) {
|
||||
pushesOnceQueued = context.pushes;
|
||||
return context.queued == queueDepth;
|
||||
}));
|
||||
Require(pushesOnceQueued == queueDepth - 1);
|
||||
Require(push.wait_for(std::chrono::seconds(0)) == std::future_status::timeout);
|
||||
Require(QueueLevelNow(handle) == queueDepth);
|
||||
PlayOneGrainOnTheTestClock(handle);
|
||||
Require(push.get() == 0);
|
||||
Require(QueueLevelNow(handle) == queueDepth - 1);
|
||||
Require(sceAudioOut2ContextDestroy(handle) == 0);
|
||||
}
|
||||
|
||||
void TestBlockingPushOnAFullQueueQueuesOnlyOnceASlotIsFree() {
|
||||
const auto handle = CreateContextOnATestClock();
|
||||
for (std::uint32_t push = 0; push < queueDepth; push++) Require(sceAudioOut2ContextPush(handle, 0) == 0);
|
||||
Require(sceAudioOut2ContextPush(handle, 0) == queueFull);
|
||||
auto push = std::async(std::launch::async, [handle] { return sceAudioOut2ContextPush(handle, 1); });
|
||||
Require(!ContextReaches(handle, std::chrono::seconds(1), [](const AudioOut2Context& context) { return context.queued > queueDepth; }));
|
||||
Require(push.wait_for(std::chrono::seconds(0)) == std::future_status::timeout);
|
||||
PlayOneGrainOnTheTestClock(handle);
|
||||
PlayOneGrainOnTheTestClock(handle);
|
||||
Require(push.get() == 0);
|
||||
Require(QueueLevelNow(handle) == queueDepth - 1);
|
||||
Require(sceAudioOut2ContextDestroy(handle) == 0);
|
||||
}
|
||||
|
||||
void TestPrimingAfterRunningDry() {
|
||||
Session session("anyps5_audio_out2_priming.raw");
|
||||
session.Fill(0.5f);
|
||||
@@ -372,6 +471,9 @@ int main() {
|
||||
TestDeviceLevelDoesNotWaitForTheContext(false);
|
||||
TestDeviceLevelDoesNotWaitForTheContext(true);
|
||||
TestLevelWithoutADeviceUsesThePlaybackClock();
|
||||
TestBlockingPushStaysBlockedUntilPlaybackFreesASlot();
|
||||
TestBlockingPushOnAFullQueueQueuesOnlyOnceASlotIsFree();
|
||||
TestBlockingPushWaitsForRoomWithoutADevice();
|
||||
TestPrimingAfterRunningDry();
|
||||
TestLowLatencyCallsRejectBadArguments();
|
||||
TestLowLatencyReadsAtNextPush();
|
||||
|
||||
@@ -22,10 +22,13 @@ if(MINGW OR CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
endif()
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/3rdparty/nghttp2/COPYING DESTINATION licenses/nghttp2)
|
||||
|
||||
add_library(anyps5_http2_transport STATIC EXCLUDE_FROM_ALL Transport.cpp)
|
||||
target_include_directories(anyps5_http2_transport PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
set(http2ZlibDir ${CMAKE_SOURCE_DIR}/3rdparty/libjpeg-turbo/src/spng/zlib)
|
||||
add_library(anyps5_http2_transport STATIC EXCLUDE_FROM_ALL Transport.cpp
|
||||
${http2ZlibDir}/adler32.c ${http2ZlibDir}/crc32.c ${http2ZlibDir}/inffast.c
|
||||
${http2ZlibDir}/inflate.c ${http2ZlibDir}/inftrees.c ${http2ZlibDir}/zutil.c)
|
||||
target_include_directories(anyps5_http2_transport PRIVATE ${LIBS_INCLUDE_DIR} ${http2ZlibDir})
|
||||
target_link_libraries(anyps5_http2_transport PRIVATE libSceNet libSceSsl libkernel libc nghttp2_static)
|
||||
set_target_properties(anyps5_http2_transport PROPERTIES POSITION_INDEPENDENT_CODE ON CXX_VISIBILITY_PRESET hidden)
|
||||
set_target_properties(anyps5_http2_transport PROPERTIES POSITION_INDEPENDENT_CODE ON CXX_VISIBILITY_PRESET hidden C_VISIBILITY_PRESET hidden)
|
||||
if(MINGW OR CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
target_link_options(anyps5_http2_transport INTERFACE LINKER:--exclude-libs,libanyps5_http2_transport.a)
|
||||
endif()
|
||||
|
||||
@@ -254,7 +254,7 @@ int APS5_VABI sceHttp2SetConnectTimeOut(int id, uint32_t usec) {
|
||||
}
|
||||
|
||||
int APS5_VABI sceHttp2SetInflateGZIPEnabled(int id, int enable) {
|
||||
return Update(id, __func__, [&](auto&) { if (enable != 0) NotImplemented_nid_no_patch("sceHttp2SetInflateGZIPEnabled"); });
|
||||
return Update(id, __func__, [&](auto& settings) { settings.inflateGzip = enable != 0; });
|
||||
}
|
||||
|
||||
int APS5_VABI sceHttp2SetRecvTimeOut(int id, uint32_t usec) {
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#endif
|
||||
|
||||
#include "Transport.hpp"
|
||||
|
||||
#include "zlib.h"
|
||||
#include "prx/libSceNet/include/Net.hpp"
|
||||
#include "prx/libSceSsl/include/Ssl.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
@@ -36,6 +38,7 @@ using Clock = std::chrono::steady_clock;
|
||||
constexpr int WantRead = static_cast<int>(0x8095f013);
|
||||
constexpr int WantWrite = static_cast<int>(0x8095f014);
|
||||
constexpr int InvalidValue = static_cast<int>(0x817B11FE);
|
||||
constexpr std::size_t MaxGzipBytes = 64 * 1024 * 1024;
|
||||
|
||||
[[noreturn]] void Unknown(const char* message) {
|
||||
NotImplemented_nid_no_patch(message);
|
||||
@@ -254,6 +257,8 @@ struct Request::State {
|
||||
bool ready = false;
|
||||
bool ended = false;
|
||||
int status = 0;
|
||||
std::string contentEncoding;
|
||||
bool inflatedGzip = false;
|
||||
int blockStatus = 0;
|
||||
int networkResult = 0;
|
||||
int socket = -1;
|
||||
@@ -318,6 +323,60 @@ struct Request::State {
|
||||
}
|
||||
}
|
||||
|
||||
bool Inflating() const { return settings.inflateGzip && contentEncoding == "gzip"; }
|
||||
|
||||
void InflateGzip() {
|
||||
if (inflatedGzip || !Inflating() || body.empty()) return;
|
||||
inflatedGzip = true;
|
||||
const std::uint8_t* data = body.data();
|
||||
const std::size_t size = body.size();
|
||||
Require(size >= 18 && data[0] == 0x1f && data[1] == 0x8b && data[2] == 8, "sceHttp2: malformed gzip header");
|
||||
const std::uint8_t flags = data[3];
|
||||
Require((flags & 0xe0) == 0, "sceHttp2: unknown gzip header flags");
|
||||
std::size_t offset = 10;
|
||||
const auto skip = [&](std::size_t count) { Require(offset + count <= size - 8, "sceHttp2: truncated gzip header"); offset += count; };
|
||||
if (flags & 4) {
|
||||
Require(offset + 2 <= size - 8, "sceHttp2: truncated gzip extra field");
|
||||
const std::size_t extra = static_cast<std::size_t>(data[offset]) | (static_cast<std::size_t>(data[offset + 1]) << 8);
|
||||
skip(2 + extra);
|
||||
}
|
||||
if (flags & 8) while (skip(1), data[offset - 1] != 0) {}
|
||||
if (flags & 16) while (skip(1), data[offset - 1] != 0) {}
|
||||
if (flags & 2) skip(2);
|
||||
z_stream inflater{};
|
||||
Require(inflateInit2(&inflater, -15) == Z_OK, "sceHttp2: gzip inflate initialization failed");
|
||||
struct End {
|
||||
z_stream* stream;
|
||||
~End() { inflateEnd(stream); }
|
||||
} end{&inflater};
|
||||
inflater.next_in = const_cast<std::uint8_t*>(data + offset);
|
||||
inflater.avail_in = static_cast<uInt>(size - offset - 8);
|
||||
std::vector<std::uint8_t> output;
|
||||
std::uint8_t chunk[16384];
|
||||
int result = Z_OK;
|
||||
while (result != Z_STREAM_END) {
|
||||
inflater.next_out = chunk;
|
||||
inflater.avail_out = sizeof(chunk);
|
||||
result = inflate(&inflater, Z_NO_FLUSH);
|
||||
Require(result == Z_OK || result == Z_STREAM_END, "sceHttp2: gzip inflate failed");
|
||||
output.insert(output.end(), chunk, chunk + (sizeof(chunk) - inflater.avail_out));
|
||||
Require(result == Z_STREAM_END || inflater.avail_in != 0 || inflater.avail_out == 0, "sceHttp2: truncated gzip stream");
|
||||
Require(output.size() <= MaxGzipBytes, "sceHttp2: inflated response exceeds the host limit");
|
||||
}
|
||||
Require(inflater.avail_in == 0, "sceHttp2: concatenated gzip members are not implemented");
|
||||
const auto readLe32 = [&](std::size_t position) {
|
||||
return static_cast<std::uint32_t>(data[position]) | (static_cast<std::uint32_t>(data[position + 1]) << 8) |
|
||||
(static_cast<std::uint32_t>(data[position + 2]) << 16) | (static_cast<std::uint32_t>(data[position + 3]) << 24);
|
||||
};
|
||||
Require(crc32(0, output.data(), static_cast<uInt>(output.size())) == readLe32(size - 8), "sceHttp2: gzip checksum mismatch");
|
||||
Require(static_cast<std::uint32_t>(output.size()) == readLe32(size - 4), "sceHttp2: gzip size mismatch");
|
||||
body = std::move(output);
|
||||
bodyOffset = 0;
|
||||
contentLength = body.size();
|
||||
hasLength = true;
|
||||
contentEncoding.clear();
|
||||
}
|
||||
|
||||
int Connect() {
|
||||
#ifdef _WIN32
|
||||
const int startup = sceNetSocket("http2-resolver", 2, 1, 0);
|
||||
@@ -412,6 +471,7 @@ struct Request::State {
|
||||
state.contentLength = Number(text);
|
||||
state.hasLength = true;
|
||||
}
|
||||
if (key == "content-encoding") state.contentEncoding = Lower(std::string(text));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -433,15 +493,21 @@ struct Request::State {
|
||||
(frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) {
|
||||
Require(state.ready, "sceHttp2: response ended without final headers");
|
||||
state.ended = true;
|
||||
state.InflateGzip();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static int Data(nghttp2_session*, std::uint8_t, std::int32_t streamId, const std::uint8_t* bytes, std::size_t size, void* data) {
|
||||
static int Data(nghttp2_session* session, std::uint8_t, std::int32_t streamId, const std::uint8_t* bytes, std::size_t size, void* data) {
|
||||
auto& state = *static_cast<State*>(data);
|
||||
return Callback(state, [&] {
|
||||
if (streamId != state.stream) return;
|
||||
Require(state.body.size() + size <= 65535, "sceHttp2: receive flow-control limit exceeded");
|
||||
if (state.Inflating()) {
|
||||
Require(state.body.size() + size <= MaxGzipBytes, "sceHttp2: encoded gzip response exceeds the host limit");
|
||||
CheckNg(nghttp2_session_consume(session, streamId, size));
|
||||
} else {
|
||||
Require(state.body.size() + size <= 65535, "sceHttp2: receive flow-control limit exceeded");
|
||||
}
|
||||
state.body.insert(state.body.end(), bytes, bytes + size);
|
||||
});
|
||||
}
|
||||
@@ -598,7 +664,7 @@ int Request::Read(void* data, std::size_t size) {
|
||||
try {
|
||||
const auto deadline = state->Deadline(state->settings.recvTimeout);
|
||||
state->Check(deadline);
|
||||
while (state->bodyOffset == state->body.size() && !state->ended) state->Pump(deadline);
|
||||
while (!state->ended && (state->bodyOffset == state->body.size() || state->Inflating())) state->Pump(deadline);
|
||||
const auto count = std::min({size, state->body.size() - state->bodyOffset, static_cast<std::size_t>(std::numeric_limits<int>::max())});
|
||||
state->Check(deadline);
|
||||
if (count) std::memcpy(data, state->body.data() + state->bodyOffset, count);
|
||||
@@ -622,6 +688,12 @@ int Request::ContentLength(int* kind, std::uint64_t* length) {
|
||||
auto lock = state->Lock();
|
||||
if (state->networkResult) return state->networkResult;
|
||||
Require(kind && length && state->ready, "sceHttp2GetResponseContentLength: null output or response unavailable");
|
||||
if (state->Inflating()) {
|
||||
try {
|
||||
const auto deadline = state->Deadline(state->settings.recvTimeout);
|
||||
while (!state->ended) state->Pump(deadline);
|
||||
} catch (...) { state->Fail(); throw; }
|
||||
}
|
||||
*kind = state->hasLength ? 0 : 1;
|
||||
*length = state->hasLength ? state->contentLength : 0;
|
||||
return 0;
|
||||
|
||||
@@ -22,6 +22,7 @@ struct Settings {
|
||||
std::vector<std::pair<std::string, std::string>> headers;
|
||||
std::uint64_t contentLength = 0;
|
||||
bool noContentLength = false;
|
||||
bool inflateGzip = false;
|
||||
bool auth = true;
|
||||
int redirect = 1;
|
||||
std::uint32_t verifyFlags = 0x3d;
|
||||
|
||||
@@ -2,6 +2,18 @@ add_library(libSceNpCppWebApi SHARED EXCLUDE_FROM_ALL Export.cpp)
|
||||
target_include_directories(libSceNpCppWebApi PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
configure_windows_unwind(libSceNpCppWebApi)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(guest_np_cpp_web_api_matchmaking_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpCppWebApiMatchmaking.cpp)
|
||||
target_include_directories(guest_np_cpp_web_api_matchmaking_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_np_cpp_web_api_matchmaking_tests PRIVATE libSceNpCppWebApi libc)
|
||||
configure_windows_unwind(guest_np_cpp_web_api_matchmaking_tests)
|
||||
add_test(NAME guest_np_cpp_web_api_matchmaking COMMAND guest_np_cpp_web_api_matchmaking_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_np_cpp_web_api_matchmaking
|
||||
PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(MINGW)
|
||||
foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static)
|
||||
string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}")
|
||||
|
||||
@@ -260,6 +260,518 @@ static int InitializeParameterWithBody(void* self, const std::uint64_t* context,
|
||||
return ParameterBodyMissing;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V113PlayerForRead19getPlayerAttributesEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V115LocationFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_8LocationEEE(void* self) {
|
||||
(void)self;
|
||||
return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V116AttributeFactory6createEPNS1_6Common10LibContextEPKcNS3_13AttributeTypeES9_PNS5_12IntrusivePtrINS3_9AttributeEEE(void* self) {
|
||||
(void)self;
|
||||
return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody10getPlayersEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody10getPlayersEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody19getTicketAttributesEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V121PlayerForTicketCreate10setNatTypeERKi(void* self) {
|
||||
(void)self;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V121PlayerForTicketCreate11setTeamNameEPKc(void* self) {
|
||||
(void)self;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V121PlayerForTicketCreate19setPlayerAttributesERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_9AttributeEEEEE(void* self) {
|
||||
(void)self;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V123SubmitTicketRequestBody11setLocationERKNS1_6Common12IntrusivePtrINS3_8LocationEEE(void* self) {
|
||||
(void)self;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V123SubmitTicketRequestBody19setTicketAttributesERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_9AttributeEEEEE(void* self) {
|
||||
(void)self;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V128PlayerForTicketCreateFactory6createEPNS1_6Common10LibContextERKmNS3_8PlatformEPNS5_12IntrusivePtrINS3_21PlayerForTicketCreateEEE(void* self) {
|
||||
(void)self;
|
||||
return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V130SubmitTicketRequestBodyFactory6createEPNS1_6Common10LibContextEPKcRKNS5_6VectorINS5_12IntrusivePtrINS3_21PlayerForTicketCreateEEEEEPNSB_INS3_23SubmitTicketRequestBodyEEE(void* self) {
|
||||
(void)self;
|
||||
return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api12cancelTicketEiRKNS4_23ParameterToCancelTicketERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE(void* self) {
|
||||
(void)self;
|
||||
return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api12submitTicketEiRKNS4_23ParameterToSubmitTicketERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_24SubmitTicketResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE(void* self) {
|
||||
(void)self;
|
||||
return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api19ParameterToGetOffer10initializeEPNS1_6Common10LibContextEPKc(void* self, const std::uint64_t* context, const char* id) {
|
||||
return InitializeParameter(self, context, id != nullptr);
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api19ParameterToGetOffer9terminateEv(void* self) {
|
||||
return TerminateParameter(self);
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api19ParameterToGetOfferC1Ev(void* self) {
|
||||
ConstructParameter(self);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api19ParameterToGetOfferD1Ev(void* self) {
|
||||
(void)self;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api20ParameterToGetTicket10initializeEPNS1_6Common10LibContextEPKc(void* self, const std::uint64_t* context, const char* id) {
|
||||
return InitializeParameter(self, context, id != nullptr);
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api20ParameterToGetTicket7setviewEPKc(void* self, const char* view) {
|
||||
return SetParameterText(self, view);
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api20ParameterToGetTicket9terminateEv(void* self) {
|
||||
return TerminateParameter(self);
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api20ParameterToGetTicketC1Ev(void* self) {
|
||||
ConstructParameter(self);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api20ParameterToGetTicketD1Ev(void* self) {
|
||||
(void)self;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api23ParameterToCancelTicket10initializeEPNS1_6Common10LibContextEPKc(void* self, const std::uint64_t* context, const char* id) {
|
||||
return InitializeParameter(self, context, id != nullptr);
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api23ParameterToCancelTicket9terminateEv(void* self) {
|
||||
return TerminateParameter(self);
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api23ParameterToCancelTicketC1Ev(void* self) {
|
||||
ConstructParameter(self);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api23ParameterToCancelTicketD1Ev(void* self) {
|
||||
(void)self;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api23ParameterToSubmitTicket10initializeEPNS1_6Common10LibContextENS6_12IntrusivePtrINS3_23SubmitTicketRequestBodyEEE(void* self, const std::uint64_t* context, const IntrusivePtr* body) {
|
||||
return InitializeParameterWithBody(self, context, true, body);
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api23ParameterToSubmitTicket9terminateEv(void* self) {
|
||||
return TerminateParameter(self);
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api23ParameterToSubmitTicketC1Ev(void* self) {
|
||||
ConstructParameter(self);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api23ParameterToSubmitTicketD1Ev(void* self) {
|
||||
(void)self;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api8getOfferEiRKNS4_19ParameterToGetOfferERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_20GetOfferResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE(void* self) {
|
||||
(void)self;
|
||||
return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api9getTicketEiRKNS4_20ParameterToGetTicketERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_21GetTicketResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE(void* self) {
|
||||
(void)self;
|
||||
return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V18Location16setGameSessionIdEPKc(void* self) {
|
||||
(void)self;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V113PlayerForRead11getOnlineIdEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V113PlayerForRead11getPlatformEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V113PlayerForRead11getTeamNameEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V113PlayerForRead12getAccountIdEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V113PlayerForRead13onlineIdIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V113PlayerForRead13platformIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V113PlayerForRead13teamNameIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V113PlayerForRead14accountIdIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V113PlayerForRead21playerAttributesIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V118PlayerForOfferRead11getOnlineIdEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V118PlayerForOfferRead11getPlatformEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V118PlayerForOfferRead11getTeamNameEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V118PlayerForOfferRead11getTicketIdEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V118PlayerForOfferRead12getAccountIdEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V118PlayerForOfferRead13onlineIdIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V118PlayerForOfferRead13platformIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V118PlayerForOfferRead13teamNameIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V118PlayerForOfferRead13ticketIdIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V118PlayerForOfferRead14accountIdIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody10getOfferIdEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody11getLocationEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody11statusIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody12offerIdIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody12playersIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody13locationIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody14getRulesetNameEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody16rulesetNameIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody18getCreatedDateTimeEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody18getUpdatedDateTimeEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody20createdDateTimeIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody20updatedDateTimeIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody9getStatusEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody11getTicketIdEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody11statusIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody12getSubmitterEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody12playersIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody13ticketIdIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody14getRulesetNameEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody14submitterIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody16rulesetNameIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody18getCreatedDateTimeEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody18getUpdatedDateTimeEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody20createdDateTimeIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody20updatedDateTimeIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody21ticketAttributesIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody9getStatusEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V124SubmitTicketResponseBody11getTicketIdEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V124SubmitTicketResponseBody13ticketIdIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V18Location16getGameSessionIdEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V18Location18gameSessionIdIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V19Attribute7getNameEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V19Attribute7getTypeEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V19Attribute8getValueEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V19Submitter11getPlatformEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V19Submitter12getAccountIdEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V19Submitter13platformIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V19Submitter14accountIdIsSetEv(void* self) {
|
||||
(void)self;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi17TitleCloudStorage2V17DataApi23ParameterToDownloadData10initializeEPNS1_6Common10LibContextEPKci(void* self, const std::uint64_t* context, const char* text, int value) {
|
||||
(void)value;
|
||||
return InitializeParameter(self, context, text != nullptr);
|
||||
|
||||
@@ -18,6 +18,99 @@ struct IntrusivePtr {
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V111UserFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_4UserEEE();
|
||||
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V113PlayerForRead19getPlayerAttributesEv(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V115LocationFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_8LocationEEE(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V116AttributeFactory6createEPNS1_6Common10LibContextEPKcNS3_13AttributeTypeES9_PNS5_12IntrusivePtrINS3_9AttributeEEE(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody10getPlayersEv(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody10getPlayersEv(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody19getTicketAttributesEv(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V121PlayerForTicketCreate10setNatTypeERKi(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V121PlayerForTicketCreate11setTeamNameEPKc(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V121PlayerForTicketCreate19setPlayerAttributesERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_9AttributeEEEEE(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V123SubmitTicketRequestBody11setLocationERKNS1_6Common12IntrusivePtrINS3_8LocationEEE(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V123SubmitTicketRequestBody19setTicketAttributesERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_9AttributeEEEEE(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V128PlayerForTicketCreateFactory6createEPNS1_6Common10LibContextERKmNS3_8PlatformEPNS5_12IntrusivePtrINS3_21PlayerForTicketCreateEEE(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V130SubmitTicketRequestBodyFactory6createEPNS1_6Common10LibContextEPKcRKNS5_6VectorINS5_12IntrusivePtrINS3_21PlayerForTicketCreateEEEEEPNSB_INS3_23SubmitTicketRequestBodyEEE(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api12cancelTicketEiRKNS4_23ParameterToCancelTicketERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api12submitTicketEiRKNS4_23ParameterToSubmitTicketERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_24SubmitTicketResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api19ParameterToGetOffer10initializeEPNS1_6Common10LibContextEPKc(void* self, const std::uint64_t* context, const char* id);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api19ParameterToGetOffer9terminateEv(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api19ParameterToGetOfferC1Ev(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api19ParameterToGetOfferD1Ev(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api20ParameterToGetTicket10initializeEPNS1_6Common10LibContextEPKc(void* self, const std::uint64_t* context, const char* id);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api20ParameterToGetTicket7setviewEPKc(void* self, const char* view);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api20ParameterToGetTicket9terminateEv(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api20ParameterToGetTicketC1Ev(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api20ParameterToGetTicketD1Ev(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api23ParameterToCancelTicket10initializeEPNS1_6Common10LibContextEPKc(void* self, const std::uint64_t* context, const char* id);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api23ParameterToCancelTicket9terminateEv(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api23ParameterToCancelTicketC1Ev(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api23ParameterToCancelTicketD1Ev(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api23ParameterToSubmitTicket10initializeEPNS1_6Common10LibContextENS6_12IntrusivePtrINS3_23SubmitTicketRequestBodyEEE(void* self, const std::uint64_t* context, const IntrusivePtr* body);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api23ParameterToSubmitTicket9terminateEv(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api23ParameterToSubmitTicketC1Ev(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api23ParameterToSubmitTicketD1Ev(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api8getOfferEiRKNS4_19ParameterToGetOfferERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_20GetOfferResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V13Api9getTicketEiRKNS4_20ParameterToGetTicketERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_21GetTicketResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi11Matchmaking2V18Location16setGameSessionIdEPKc(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V113PlayerForRead11getOnlineIdEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V113PlayerForRead11getPlatformEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V113PlayerForRead11getTeamNameEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V113PlayerForRead12getAccountIdEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V113PlayerForRead13onlineIdIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V113PlayerForRead13platformIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V113PlayerForRead13teamNameIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V113PlayerForRead14accountIdIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V113PlayerForRead21playerAttributesIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V118PlayerForOfferRead11getOnlineIdEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V118PlayerForOfferRead11getPlatformEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V118PlayerForOfferRead11getTeamNameEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V118PlayerForOfferRead11getTicketIdEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V118PlayerForOfferRead12getAccountIdEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V118PlayerForOfferRead13onlineIdIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V118PlayerForOfferRead13platformIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V118PlayerForOfferRead13teamNameIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V118PlayerForOfferRead13ticketIdIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V118PlayerForOfferRead14accountIdIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody10getOfferIdEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody11getLocationEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody11statusIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody12offerIdIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody12playersIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody13locationIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody14getRulesetNameEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody16rulesetNameIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody18getCreatedDateTimeEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody18getUpdatedDateTimeEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody20createdDateTimeIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody20updatedDateTimeIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V120GetOfferResponseBody9getStatusEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody11getTicketIdEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody11statusIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody12getSubmitterEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody12playersIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody13ticketIdIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody14getRulesetNameEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody14submitterIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody16rulesetNameIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody18getCreatedDateTimeEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody18getUpdatedDateTimeEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody20createdDateTimeIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody20updatedDateTimeIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody21ticketAttributesIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V121GetTicketResponseBody9getStatusEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V124SubmitTicketResponseBody11getTicketIdEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V124SubmitTicketResponseBody13ticketIdIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V18Location16getGameSessionIdEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V18Location18gameSessionIdIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V19Attribute7getNameEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V19Attribute7getTypeEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V19Attribute8getValueEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V19Submitter11getPlatformEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V19Submitter12getAccountIdEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V19Submitter13platformIsSetEv(void* self);
|
||||
int APS5_VABI _ZNK3sce2Np9CppWebApi11Matchmaking2V19Submitter14accountIdIsSetEv(void* self);
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V121GetRankingRequestBody18setStartSerialRankERKi();
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V121GetRankingRequestBody8setGroupERKNS3_5GroupE();
|
||||
int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V121GetRankingRequestBody8setLimitERKi();
|
||||
|
||||
@@ -80,6 +80,7 @@ bool IsConnected(int port);
|
||||
|
||||
extern "C" void PadPublishInput_nid_postfix(const PadInputState& input);
|
||||
extern "C" void PadPublishInputForPort_nid_postfix(int port, const PadInputState& input);
|
||||
extern "C" void PadApplyInjectedInput_nid_postfix(PadInputState* state);
|
||||
extern "C" void PadReportInputFailure_nid_postfix(std::exception_ptr error);
|
||||
extern "C" bool PadFetchOutput_nid_postfix(std::uint32_t* seenSequence, PadOutputState* out);
|
||||
extern "C" bool PadFetchOutputForPort_nid_postfix(int port, std::uint32_t* seenSequence, PadOutputState* out);
|
||||
|
||||
@@ -505,6 +505,14 @@ extern "C" void PadPublishInput_nid_postfix(const PadInputState& input) {
|
||||
PadPublishInputForPort_nid_postfix(0, input);
|
||||
}
|
||||
|
||||
extern "C" void PadApplyInjectedInput_nid_postfix(PadInputState* state) {
|
||||
if (state == nullptr) return;
|
||||
std::lock_guard lock(stateMutex);
|
||||
const auto injected = InjectedInput(sceKernelGetProcessTime());
|
||||
state->buttons |= injected.buttons;
|
||||
if (injected.hasSticks) state->sticks = injected.sticks;
|
||||
}
|
||||
|
||||
extern "C" void PadReportInputFailure_nid_postfix(std::exception_ptr error) {
|
||||
if (!error) throw std::invalid_argument("Pad: missing input failure");
|
||||
std::lock_guard lock(stateMutex);
|
||||
|
||||
@@ -62,6 +62,7 @@ private:
|
||||
std::array<ControllerSlot, PAD_MAX_CONTROLLERS> slots{};
|
||||
VideoOutTelemetry::PadChangeFilter telemetryFilter;
|
||||
AgcDriver::PadStatusProvider statusProvider;
|
||||
bool padCaptured = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -357,7 +357,9 @@ void PadInput::Update(const DisplayWindow& window) {
|
||||
return;
|
||||
}
|
||||
const bool changed = keyboardMouse.Advance(std::chrono::steady_clock::now());
|
||||
if (slots[0].controller != nullptr || keyboardMouse.MouseMode() || changed) {
|
||||
const bool captured = ImeCapturesPad_nid_postfix() || ModalDialogCapturesInput_nid_postfix();
|
||||
const bool captureChanged = std::exchange(padCaptured, captured) != captured;
|
||||
if (slots[0].controller != nullptr || keyboardMouse.MouseMode() || changed || captured || captureChanged) {
|
||||
publish(window.FrameNumber(), 0);
|
||||
}
|
||||
for (int slot = 1; slot < PAD_MAX_CONTROLLERS; ++slot) {
|
||||
@@ -377,6 +379,7 @@ PadInputState PadInput::handOff(int slot, PadInputState state) {
|
||||
auto& held = slots[slot].heldThroughCapture;
|
||||
const bool imeCaptures = ImeCapturesPad_nid_postfix();
|
||||
if (imeCaptures || ModalDialogCapturesInput_nid_postfix()) {
|
||||
if (slot == 0) PadApplyInjectedInput_nid_postfix(&state);
|
||||
if (slot == 0 && imeCaptures) ImePublishPad_nid_postfix({state.buttons, state.sticks[0], state.sticks[1], state.analogButtonsL2});
|
||||
else if (slot == 0) ModalDialogPublishPad_nid_postfix({state.buttons, state.sticks[0], state.sticks[1], state.analogButtonsL2});
|
||||
held = state.buttons;
|
||||
|
||||
@@ -7,8 +7,13 @@
|
||||
#include "prx/libScePad/include/PadState.hpp"
|
||||
#include "prx/libSceIme/include/ImeInput.hpp"
|
||||
#include "prx/libSceIme/include/ModalDialog.hpp"
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#undef main
|
||||
|
||||
@@ -113,6 +118,40 @@ void HandOff(PadInput& padInput, const DisplayWindow& window, int device) {
|
||||
ModalDialogSetPresenter_nid_postfix(false);
|
||||
}
|
||||
|
||||
void InjectedAnswer(PadInput& padInput, const DisplayWindow& window, const std::filesystem::path& commandPath) {
|
||||
constexpr std::uint32_t Cross = 0x4000;
|
||||
constexpr std::uint32_t Intercepted = 0x80000000;
|
||||
ModalDialogSetPresenter_nid_postfix(true);
|
||||
ModalDialogRequest request;
|
||||
request.message = u"Create new data?";
|
||||
request.buttons = {u"OK"};
|
||||
Require(ModalDialogOpen_nid_postfix(request));
|
||||
padInput.Update(window);
|
||||
Require(Pad::ReadState(0).buttons == Intercepted);
|
||||
std::ofstream(commandPath) << "1 16384 400\n";
|
||||
std::size_t chosen = 9;
|
||||
ModalDialogState state = ModalDialogState::Showing;
|
||||
for (int frame = 0; frame < 200 && state == ModalDialogState::Showing; ++frame) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
padInput.Update(window);
|
||||
Require(Pad::ReadState(0).buttons == Intercepted);
|
||||
state = ModalDialogPoll_nid_postfix(chosen);
|
||||
}
|
||||
Require(state == ModalDialogState::Answered && chosen == 0);
|
||||
padInput.Update(window);
|
||||
Require((Pad::ReadState(0).buttons & Intercepted) == 0);
|
||||
std::ofstream(commandPath) << "2 16384 400\n";
|
||||
bool reached = false;
|
||||
for (int frame = 0; frame < 200 && !reached; ++frame) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
padInput.Update(window);
|
||||
reached = Pad::ReadState(0).buttons == Cross;
|
||||
}
|
||||
Require(reached);
|
||||
ModalDialogReset_nid_postfix();
|
||||
ModalDialogSetPresenter_nid_postfix(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
@@ -123,6 +162,17 @@ int main() {
|
||||
return 1;
|
||||
#endif
|
||||
|
||||
const auto commandPath = std::filesystem::temp_directory_path() / ("anyps5-pad-sdl-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
|
||||
struct Cleanup {
|
||||
std::filesystem::path path;
|
||||
~Cleanup() { std::error_code error; std::filesystem::remove(path, error); }
|
||||
} cleanup{commandPath};
|
||||
#ifdef _WIN32
|
||||
Require(_wputenv_s(L"APS5_PAD_INPUT_FILE", commandPath.c_str()) == 0);
|
||||
#else
|
||||
Require(setenv("APS5_PAD_INPUT_FILE", commandPath.string().c_str(), 1) == 0);
|
||||
#endif
|
||||
|
||||
Require(SDL_Init(SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK) == 0);
|
||||
|
||||
const int hostJoysticks = SDL_NumJoysticks();
|
||||
@@ -199,6 +249,7 @@ int main() {
|
||||
padInput.HandleEvent(rem0, window);
|
||||
Require(!padInput.SlotOccupied(0));
|
||||
Require(!padInput.SlotOccupied(1));
|
||||
InjectedAnswer(padInput, window, commandPath);
|
||||
|
||||
Require(SDL_JoystickDetachVirtual(dev1) == 0);
|
||||
Require(SDL_JoystickDetachVirtual(dev0) == 0);
|
||||
|
||||
@@ -262,6 +262,16 @@ if(BUILD_TESTING)
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
|
||||
add_test_executable(guest_memccpy_bounds_tests ${LIBS_INCLUDE_DIR}/tests/GuestMemccpyBounds.cpp)
|
||||
target_include_directories(guest_memccpy_bounds_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_memccpy_bounds_tests PRIVATE libc)
|
||||
configure_windows_unwind(guest_memccpy_bounds_tests)
|
||||
add_test(NAME guest_memccpy_bounds COMMAND guest_memccpy_bounds_tests)
|
||||
set_tests_properties(guest_memccpy_bounds PROPERTIES TIMEOUT 20)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_memccpy_bounds PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test_executable(guest_allocating_strings_tests ${LIBS_INCLUDE_DIR}/tests/GuestAllocatingStrings.cpp)
|
||||
target_include_directories(guest_allocating_strings_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_allocating_strings_tests PRIVATE libc libkernel)
|
||||
|
||||
@@ -102,6 +102,7 @@ const char* CutAtQuadScanDirective(const char* format, std::string& head) {
|
||||
#endif
|
||||
|
||||
std::string HostLength(const std::string& length, char conversion) {
|
||||
if (std::strchr("sc[", conversion) && length != "l") return {};
|
||||
#ifdef _WIN32
|
||||
if (length == "l" && std::strchr("diouxXn", conversion) != nullptr) return "ll";
|
||||
#else
|
||||
@@ -110,7 +111,46 @@ std::string HostLength(const std::string& length, char conversion) {
|
||||
return length;
|
||||
}
|
||||
|
||||
const char* ScanLengthEnd(const char* cursor) {
|
||||
if (*cursor == 'h' || *cursor == 'l') {
|
||||
const char first = *cursor++;
|
||||
if (*cursor == first) ++cursor;
|
||||
} else if (*cursor != '\0' && std::strchr("jztL", *cursor)) ++cursor;
|
||||
return cursor;
|
||||
}
|
||||
|
||||
const char* HostScanFormat(const char* format, std::string& normalized) {
|
||||
if (format == nullptr) return format;
|
||||
const char* copied = format;
|
||||
const char* cursor = format;
|
||||
while ((cursor = std::strchr(cursor, '%')) != nullptr) {
|
||||
++cursor;
|
||||
if (*cursor == '%') { ++cursor; continue; }
|
||||
while (*cursor && std::strchr("-+ #0'0123456789.*$", *cursor)) ++cursor;
|
||||
const char* const length = cursor;
|
||||
cursor = ScanLengthEnd(cursor);
|
||||
const bool wide = cursor == length + 1 && *length == 'l';
|
||||
if (cursor != length && !wide && *cursor && std::strchr("sc[", *cursor)) {
|
||||
normalized.append(copied, length);
|
||||
copied = cursor;
|
||||
}
|
||||
if (*cursor == '[') {
|
||||
++cursor;
|
||||
if (*cursor == '^') ++cursor;
|
||||
if (*cursor == ']') ++cursor;
|
||||
while (*cursor && *cursor != ']') ++cursor;
|
||||
}
|
||||
if (*cursor) ++cursor;
|
||||
else break;
|
||||
}
|
||||
if (copied == format) return format;
|
||||
normalized.append(copied);
|
||||
return normalized.c_str();
|
||||
}
|
||||
|
||||
int HostVsscanf(const char* input, const char* format, std::va_list args) {
|
||||
std::string normalized;
|
||||
format = HostScanFormat(format, normalized);
|
||||
#if defined(__GLIBC__)
|
||||
const int savedError = errno;
|
||||
const int result = __isoc99_vsscanf(input, format, args);
|
||||
@@ -131,8 +171,13 @@ int HostSscanf(const char* input, const char* format, ...) {
|
||||
|
||||
#ifndef _WIN32
|
||||
int HostVfscanf(std::FILE* stream, const char* format, std::va_list args) {
|
||||
std::string normalized;
|
||||
format = HostScanFormat(format, normalized);
|
||||
#if defined(__GLIBC__)
|
||||
return __isoc99_vfscanf(stream, format, args);
|
||||
const int savedError = errno;
|
||||
const int result = __isoc99_vfscanf(stream, format, args);
|
||||
if (result == EOF && errno == 0) errno = savedError;
|
||||
return result;
|
||||
#else
|
||||
return std::vfscanf(stream, format, args);
|
||||
#endif
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#include "prx/libc/include/FileStream.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <array>
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
#include <barrier>
|
||||
#include <cerrno>
|
||||
#include <cstdarg>
|
||||
@@ -217,6 +219,53 @@ static int APS5_VABI ScanList(FileStream* stream, const char* format, ...) {
|
||||
return result;
|
||||
}
|
||||
|
||||
static void CheckScanInputFailureErrno(const std::string& path) {
|
||||
const int savedError = errno;
|
||||
for (const bool variadicList : {false, true}) {
|
||||
for (const int seed : {0, 22, 97}) {
|
||||
for (const char* input : {"", " "}) {
|
||||
for (const char* format : {"%d", "%*d"}) {
|
||||
auto* native = std::tmpfile();
|
||||
Require(native != nullptr);
|
||||
Require(std::fwrite(input, 1, std::strlen(input), native) == std::strlen(input));
|
||||
std::rewind(native);
|
||||
FileStream stream(native);
|
||||
int value = 123;
|
||||
errno = seed;
|
||||
const int result = variadicList ? ScanList(&stream, format, &value) : fscanf_nid_postfix(&stream, format, &value);
|
||||
Require(result == EOF && errno == seed, "Stream scan input exhaustion changed errno");
|
||||
Require(value == 123 && feof_nid_postfix(&stream) != 0 && ferror_nid_postfix(&stream) == 0);
|
||||
Require(std::fclose(native) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
#if defined(__GLIBC__)
|
||||
auto* errorNative = std::tmpfile();
|
||||
Require(errorNative != nullptr);
|
||||
const int writeOnly = ::open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0600);
|
||||
Require(writeOnly >= 0);
|
||||
Require(::dup2(writeOnly, ::fileno(errorNative)) >= 0 && ::close(writeOnly) == 0);
|
||||
FileStream readError(errorNative);
|
||||
int unchanged = 123;
|
||||
errno = 97;
|
||||
const int failed = variadicList ? ScanList(&readError, "%d", &unchanged) : fscanf_nid_postfix(&readError, "%d", &unchanged);
|
||||
Require(failed == EOF && errno != 97, "Stream scan lost its read error");
|
||||
Require(unchanged == 123 && ferror_nid_postfix(&readError) != 0, "Stream scan did not keep its read error indicator");
|
||||
Require(std::fclose(errorNative) == 0 && std::remove(path.c_str()) == 0);
|
||||
#endif
|
||||
auto* native = std::tmpfile();
|
||||
Require(native != nullptr && std::fputs("1e99999", native) >= 0);
|
||||
std::rewind(native);
|
||||
FileStream stream(native);
|
||||
float value = 0;
|
||||
errno = 97;
|
||||
const int result = variadicList ? ScanList(&stream, "%f", &value) : fscanf_nid_postfix(&stream, "%f", &value);
|
||||
Require(result == 1 && errno == ERANGE, "Stream scan lost its conversion range error");
|
||||
Require(std::fclose(native) == 0);
|
||||
}
|
||||
errno = savedError;
|
||||
}
|
||||
|
||||
static std::string ReadWhole(const std::string& path) {
|
||||
auto* stream = fopen_nid_postfix(path.c_str(), "rb");
|
||||
Require(stream != nullptr);
|
||||
@@ -226,6 +275,43 @@ static std::string ReadWhole(const std::string& path) {
|
||||
return std::string(buffer.data(), size);
|
||||
}
|
||||
|
||||
static int APS5_VABI ScanSafeList(FileStream* stream, const char* format, ...) {
|
||||
__builtin_sysv_va_list args;
|
||||
__builtin_sysv_va_start(args, format);
|
||||
const int result = vfscanf_s_nid_postfix(stream, format, reinterpret_cast<VaList*>(args));
|
||||
__builtin_sysv_va_end(args);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void CheckCharacterScanLengths() {
|
||||
using Scanner = int (APS5_VABI*)(FileStream*, const char*, ...);
|
||||
const Scanner scanners[]{fscanf_nid_postfix, ScanList, fscanf_s_nid_postfix, ScanSafeList};
|
||||
for (std::size_t api = 0; api < std::size(scanners); ++api) {
|
||||
#ifdef _WIN32
|
||||
if (api < 2) continue;
|
||||
#endif
|
||||
for (const char* length : {"hh", "h", "ll", "j", "z", "t", "L"}) {
|
||||
for (const char* conversion : {"s", "c", "[a-z]"}) {
|
||||
auto* native = std::tmpfile();
|
||||
Require(native != nullptr && std::fputs("ab", native) >= 0);
|
||||
std::rewind(native);
|
||||
FileStream stream(native);
|
||||
std::array<char, 32> output;
|
||||
output.fill('Z');
|
||||
int consumed = -1;
|
||||
const std::string format = std::string("%2") + length + conversion + "%n";
|
||||
const auto scan = scanners[api];
|
||||
const int result = api >= 2 ? scan(&stream, format.c_str(), output.data(), 32u, &consumed) : scan(&stream, format.c_str(), output.data(), &consumed);
|
||||
Require(result == 1 && consumed == 2, "Stream character scan ignored-length consumption");
|
||||
const bool single = conversion[0] == 'c';
|
||||
Require(output[0] == 'a' && output[1] == 'b' && (single || output[2] == 0), "Stream character scan ignored length stored wide data");
|
||||
Require(std::all_of(output.begin() + (single ? 2 : 3), output.end(), [](char value) { return value == 'Z'; }));
|
||||
Require(std::fclose(native) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TRead>
|
||||
static void CheckReadAfterWrite(const std::string& path, const char* mode, const char* expected, TRead read) {
|
||||
auto* stream = fopen_nid_postfix(path.c_str(), "wb");
|
||||
@@ -239,6 +325,7 @@ static void CheckReadAfterWrite(const std::string& path, const char* mode, const
|
||||
}
|
||||
|
||||
static void CheckUpdateStreamReadsAfterWrites(const std::string& base) {
|
||||
CheckScanInputFailureErrno(base + ".scan-error");
|
||||
const std::string path = base + ".update";
|
||||
constexpr char overwritten[] = "AB23456789";
|
||||
CheckReadAfterWrite(path, "r+b", overwritten, [](FileStream* stream) {
|
||||
@@ -307,6 +394,7 @@ static void CheckWriteAfterRead(const std::string& path, const char* mode, const
|
||||
}
|
||||
|
||||
static void CheckStreamWritesAfterReads(const std::string& base) {
|
||||
CheckCharacterScanLengths();
|
||||
const std::string path = base + ".written";
|
||||
constexpr char appended[] = "0123456789XY";
|
||||
#if defined(__GLIBC__)
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include "prx/libc/include/ScanQuadLength.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <utility>
|
||||
#include <iterator>
|
||||
#include <climits>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
@@ -67,6 +69,41 @@ static int APS5_VABI ScanListS(const char* buffer, const char* format, ...) {
|
||||
return result;
|
||||
}
|
||||
|
||||
static void CheckCharacterScanLengths() {
|
||||
const auto check = [](bool condition, const char* message = "Character scan length check failed") {
|
||||
if (!condition) throw std::runtime_error(message);
|
||||
};
|
||||
using Scanner = int (APS5_VABI*)(const char*, const char*, ...);
|
||||
const Scanner scanners[]{sscanf_nid_postfix, ScanList, sscanf_s_nid_postfix, ScanListS};
|
||||
for (std::size_t api = 0; api < std::size(scanners); ++api) {
|
||||
const auto scan = scanners[api];
|
||||
const bool secure = api >= 2;
|
||||
#ifdef _WIN32
|
||||
if (!secure) continue;
|
||||
#endif
|
||||
for (const char* length : {"hh", "h", "ll", "j", "z", "t", "L"}) {
|
||||
for (const char* conversion : {"s", "c", "[a-z]"}) {
|
||||
const std::string format = std::string("%2") + length + conversion + "%n";
|
||||
std::array<char, 32> output;
|
||||
output.fill('Z');
|
||||
int consumed = -1;
|
||||
const int result = secure ? scan("ab", format.c_str(), output.data(), 32u, &consumed) : scan("ab", format.c_str(), output.data(), &consumed);
|
||||
check(result == 1 && consumed == 2, "Character scan ignored-length consumption");
|
||||
const bool single = conversion[0] == 'c';
|
||||
check(output[0] == 'a' && output[1] == 'b' && (single || output[2] == '\0'), "Character scan ignored length stored wide data");
|
||||
check(std::all_of(output.begin() + (single ? 2 : 3), output.end(), [](char value) { return value == 'Z'; }), "Character scan changed its destination guard");
|
||||
}
|
||||
}
|
||||
for (const auto& example : {std::pair{"%lls", "%5[%lls]%n"}, std::pair{"% ab", "%% %2lls%n"}}) {
|
||||
std::array<char, 32> output{};
|
||||
int consumed = -1;
|
||||
const int result = secure ? scan(example.first, example.second, output.data(), 32u, &consumed) : scan(example.first, example.second, output.data(), &consumed);
|
||||
check(result == 1 && consumed == 4);
|
||||
check(std::strcmp(output.data(), example.first[1] == ' ' ? "ab" : "%lls") == 0, "Character scan normalization changed a literal or scanset");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int APS5_VABI PrintList(const char* format, ...) {
|
||||
__builtin_sysv_va_list args;
|
||||
__builtin_sysv_va_start(args, format);
|
||||
@@ -78,6 +115,7 @@ static int APS5_VABI PrintList(const char* format, ...) {
|
||||
}
|
||||
|
||||
static void CheckIgnoredLengths() {
|
||||
CheckCharacterScanLengths();
|
||||
char output[96];
|
||||
Require(snprintf_nid_postfix(output, sizeof(output), "%Ld|%Lx|%Lu|%Lo", -1985229329, 0x89abcdefu, 0x89abcdefu, 0x89abcdefu) == 43 &&
|
||||
std::strcmp(output, "-1985229329|89abcdef|2309737967|21152746757") == 0);
|
||||
|
||||
@@ -288,6 +288,25 @@ int main() {
|
||||
outputA.channelCount = 3;
|
||||
Require(reverb(1, reverbFilters, reverbOutputs) == BadPointer);
|
||||
Require(reverbBatch.offset == 0 && reverbBatch.buffer_size == sizeof(reverbCommands));
|
||||
commandBatch = {commands, 0, sizeof(commands)};
|
||||
std::memset(commands, 0xCC, sizeof(commands));
|
||||
Require(sceAcmBatchJobPriority(nullptr, 0) == BadPointer);
|
||||
Require(sceAcmBatchJobPriority(&commandBatch, 4) == BadPointer && commandBatch.offset == 0);
|
||||
Require(sceAcmBatchJobPriority(&commandBatch, 0xFFFFFFFFu) == BadPointer && commandBatch.offset == 0);
|
||||
Require(sceAcmBatchJobPriority(&commandBatch, 3) == 0 && commandBatch.offset == 8);
|
||||
Require(sceAcmBatchJobPriority(&commandBatch, 0) == 0 && commandBatch.offset == 16 && commandBatch.buffer_size == sizeof(commands));
|
||||
std::uint64_t priorityWords[3];
|
||||
std::memcpy(priorityWords, commands, sizeof(priorityWords));
|
||||
Require(priorityWords[0] == 0x310 && priorityWords[1] == 0x10 && priorityWords[2] == 0xCCCCCCCCCCCCCCCCull);
|
||||
commandBatch = {commands, sizeof(commands) - 7, sizeof(commands)};
|
||||
Require(sceAcmBatchJobPriority(&commandBatch, 1) == static_cast<int>(0x81940009) && commandBatch.offset == sizeof(commands) - 7);
|
||||
Require(sceAcmBatchJobPriority(&commandBatch, 4) == BadPointer && commandBatch.offset == sizeof(commands) - 7);
|
||||
commandBatch = {commands, sizeof(commands) + 8, sizeof(commands)};
|
||||
Require(sceAcmBatchJobPriority(&commandBatch, 1) == static_cast<int>(0x81940009) && commandBatch.offset == sizeof(commands) + 8);
|
||||
commandBatch = {commands, sizeof(commands) - 8, sizeof(commands)};
|
||||
Require(sceAcmBatchJobPriority(&commandBatch, 1) == 0 && commandBatch.offset == sizeof(commands));
|
||||
std::memcpy(priorityWords, commands + sizeof(commands) - 8, sizeof(std::uint64_t));
|
||||
Require(priorityWords[0] == 0x110);
|
||||
|
||||
Require(sceAcmContextDestroy(context1) == 0);
|
||||
Require(sceAcmContextDestroy(context1) == BadContext);
|
||||
|
||||
@@ -52,9 +52,41 @@ bool Throws(TCall call) {
|
||||
return false;
|
||||
}
|
||||
|
||||
enum class Mode { Normal, Large, NoLength, Head, NotFound, Redirect, Auth, Reset, Truncate, StallHandshake, StallHeaders, StallBody, NoAlpn, Informational, StallUpload, EarlyFinal, EarlyReset, EarlyEmpty, EarlyLarge, EarlyResetIncomplete };
|
||||
enum class Mode { Normal, Large, NoLength, Head, NotFound, Redirect, Auth, Reset, Truncate, Gzip, GzipSplit, GzipTruncated, GzipChecksum, GzipConcat, GzipLarge, StallHandshake, StallHeaders, StallBody, NoAlpn, Informational, StallUpload, EarlyFinal, EarlyReset, EarlyEmpty, EarlyLarge, EarlyResetIncomplete };
|
||||
const char* Protocols[] = {"h2", nullptr};
|
||||
|
||||
std::string GzipPayload() {
|
||||
std::string payload(70000, '\0');
|
||||
for (std::size_t i = 0; i < payload.size(); ++i) payload[i] = static_cast<char>((i * 31 + 7) & 0xff);
|
||||
return payload;
|
||||
}
|
||||
|
||||
std::string StoredGzip(const std::string& payload) {
|
||||
static const unsigned char header[] = {0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff};
|
||||
std::string body(reinterpret_cast<const char*>(header), sizeof(header));
|
||||
std::size_t offset = 0;
|
||||
while (offset < payload.size()) {
|
||||
const std::size_t count = std::min<std::size_t>(payload.size() - offset, 65535);
|
||||
body.push_back(static_cast<char>(offset + count == payload.size() ? 1 : 0));
|
||||
body.push_back(static_cast<char>(count & 0xff));
|
||||
body.push_back(static_cast<char>((count >> 8) & 0xff));
|
||||
body.push_back(static_cast<char>(~count & 0xff));
|
||||
body.push_back(static_cast<char>((~count >> 8) & 0xff));
|
||||
body.append(payload, offset, count);
|
||||
offset += count;
|
||||
}
|
||||
std::uint32_t crc = 0xffffffffu;
|
||||
for (const unsigned char byte : payload) {
|
||||
crc ^= byte;
|
||||
for (int bit = 0; bit < 8; ++bit) crc = (crc >> 1) ^ (0xedb88320u & (0u - (crc & 1u)));
|
||||
}
|
||||
crc = ~crc;
|
||||
const std::uint32_t size = static_cast<std::uint32_t>(payload.size());
|
||||
for (int shift = 0; shift < 32; shift += 8) body.push_back(static_cast<char>((crc >> shift) & 0xff));
|
||||
for (int shift = 0; shift < 32; shift += 8) body.push_back(static_cast<char>((size >> shift) & 0xff));
|
||||
return body;
|
||||
}
|
||||
|
||||
class Server {
|
||||
public:
|
||||
explicit Server(Mode mode) : mode(mode) {
|
||||
@@ -137,6 +169,8 @@ private:
|
||||
std::size_t offset = 0;
|
||||
bool responded = false;
|
||||
bool ended = false;
|
||||
bool splitSent = false;
|
||||
bool splitResumed = false;
|
||||
|
||||
void Stage(int value) {
|
||||
{ std::lock_guard lock(mutex); stage = value; }
|
||||
@@ -176,7 +210,12 @@ private:
|
||||
std::uint32_t* flags, nghttp2_data_source*, void* data) {
|
||||
auto& server = *static_cast<Server*>(data);
|
||||
if (server.mode == Mode::StallBody || server.mode == Mode::EarlyResetIncomplete) return NGHTTP2_ERR_DEFERRED;
|
||||
if (server.mode == Mode::GzipSplit && server.splitSent && !server.splitResumed) return NGHTTP2_ERR_DEFERRED;
|
||||
size = std::min(size, server.body.size() - server.offset);
|
||||
if (server.mode == Mode::GzipSplit && !server.splitSent) {
|
||||
size = std::min<std::size_t>(size, 8);
|
||||
server.splitSent = true;
|
||||
}
|
||||
if (size) std::memcpy(bytes, server.body.data() + server.offset, size);
|
||||
server.offset += size;
|
||||
if (server.offset == server.body.size()) *flags |= NGHTTP2_DATA_FLAG_EOF;
|
||||
@@ -194,6 +233,21 @@ private:
|
||||
auto length = std::to_string(body.size());
|
||||
if (mode == Mode::Head) body.clear();
|
||||
if (mode == Mode::Truncate) body = "short";
|
||||
if (mode == Mode::Gzip || mode == Mode::GzipSplit || mode == Mode::GzipTruncated || mode == Mode::GzipChecksum || mode == Mode::GzipConcat) {
|
||||
static const unsigned char compressed[] = {
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x2b, 0x4a, 0x2d, 0x2e, 0xc8, 0xcf,
|
||||
0x2b, 0x4e, 0x55, 0x48, 0xca, 0x4f, 0xa9, 0x04, 0x00, 0x36, 0x76, 0x8b, 0x0c, 0x0d, 0x00, 0x00, 0x00,
|
||||
};
|
||||
body.assign(reinterpret_cast<const char*>(compressed), sizeof(compressed));
|
||||
if (mode == Mode::GzipTruncated) body.resize(body.size() - 4);
|
||||
if (mode == Mode::GzipChecksum) body[body.size() - 8] ^= 0xff;
|
||||
if (mode == Mode::GzipConcat) { const std::string member = body; body += member; }
|
||||
length = std::to_string(body.size());
|
||||
}
|
||||
if (mode == Mode::GzipLarge) {
|
||||
body = StoredGzip(GzipPayload());
|
||||
length = std::to_string(body.size());
|
||||
}
|
||||
if (mode == Mode::Informational) {
|
||||
nghttp2_nv early{reinterpret_cast<std::uint8_t*>(const_cast<char*>(":status")), reinterpret_cast<std::uint8_t*>(const_cast<char*>("103")), 7, 3, 0};
|
||||
Require(nghttp2_submit_headers(session, 0, stream, nullptr, &early, 1, nullptr) == 0, "server informational response");
|
||||
@@ -201,6 +255,7 @@ private:
|
||||
std::vector<std::pair<std::string, std::string>> fields{{":status", status}, {"x-loopback", "yes"}};
|
||||
if (mode != Mode::NoLength) fields.emplace_back("content-length", length);
|
||||
if (mode == Mode::Redirect) fields.emplace_back("location", "/another");
|
||||
if (mode == Mode::Gzip || mode == Mode::GzipSplit || mode == Mode::GzipTruncated || mode == Mode::GzipChecksum || mode == Mode::GzipConcat || mode == Mode::GzipLarge) fields.emplace_back("content-encoding", "gzip");
|
||||
std::vector<nghttp2_nv> response;
|
||||
for (auto& [name, value] : fields) response.push_back({reinterpret_cast<std::uint8_t*>(name.data()), reinterpret_cast<std::uint8_t*>(value.data()), name.size(), value.size(), 0});
|
||||
nghttp2_data_provider2 provider{};
|
||||
@@ -259,6 +314,12 @@ private:
|
||||
}
|
||||
if (!sent) break;
|
||||
}
|
||||
if (mode == Mode::GzipSplit && splitSent && !splitResumed) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
Require(nghttp2_session_resume_data(session, 1) == 0, "resume split gzip body");
|
||||
splitResumed = true;
|
||||
continue;
|
||||
}
|
||||
if (IsEarly() && responded && sent) {
|
||||
Stage(2);
|
||||
if (!resetSent && (mode == Mode::EarlyReset || mode == Mode::EarlyResetIncomplete)) {
|
||||
@@ -355,6 +416,94 @@ void Success(int ssl, Mode mode, bool disableCa = false) {
|
||||
Require(Throws([&] { sceHttp2SendRequest(client.request, nullptr, 0); }), "unsupported reuse throws");
|
||||
}
|
||||
|
||||
void Gzip(int ssl) {
|
||||
std::fprintf(stderr, "HTTP2 gzip scenario\n");
|
||||
for (const int enabled : {1, 0}) {
|
||||
Server server(Mode::Gzip);
|
||||
Client client(ssl);
|
||||
Require(sceHttp2SetInflateGZIPEnabled(client.tmpl, enabled) == 0, "gzip option");
|
||||
client.Create(server.Url());
|
||||
Require(sceHttp2SendRequest(client.request, nullptr, 0) == 0, "gzip send");
|
||||
int kind = -1;
|
||||
std::uint64_t length = 0;
|
||||
Require(sceHttp2GetResponseContentLength(client.request, &kind, &length) == 0, "gzip length");
|
||||
std::array<char, 64> buffer{};
|
||||
const int count = sceHttp2ReadData(client.request, buffer.data(), buffer.size());
|
||||
if (enabled) {
|
||||
Require(kind == 0 && length == 13 && count == 13 && std::string(buffer.data(), 13) == "response body", "gzip inflated body");
|
||||
} else {
|
||||
Require(kind == 0 && length == 33 && count == 33 && static_cast<unsigned char>(buffer[0]) == 0x1f &&
|
||||
static_cast<unsigned char>(buffer[1]) == 0x8b, "gzip passthrough body");
|
||||
}
|
||||
server.Join();
|
||||
}
|
||||
}
|
||||
|
||||
void GzipSplit(int ssl) {
|
||||
std::fprintf(stderr, "HTTP2 gzip split scenario\n");
|
||||
Server server(Mode::GzipSplit);
|
||||
Client client(ssl);
|
||||
Require(sceHttp2SetInflateGZIPEnabled(client.tmpl, 1) == 0, "gzip split option");
|
||||
client.Create(server.Url());
|
||||
Require(sceHttp2SendRequest(client.request, nullptr, 0) == 0, "gzip split send");
|
||||
std::string body;
|
||||
std::array<char, 3> buffer;
|
||||
while (body.size() < 12) {
|
||||
const int count = sceHttp2ReadData(client.request, buffer.data(), buffer.size());
|
||||
Require(count > 0 && count <= static_cast<int>(buffer.size()), "gzip split read count");
|
||||
body.append(buffer.data(), static_cast<std::size_t>(count));
|
||||
}
|
||||
int kind = -1;
|
||||
std::uint64_t length = 0;
|
||||
Require(sceHttp2GetResponseContentLength(client.request, &kind, &length) == 0, "gzip split length");
|
||||
Require(kind == 0 && length == 13, "gzip split inflated length");
|
||||
for (;;) {
|
||||
const int count = sceHttp2ReadData(client.request, buffer.data(), buffer.size());
|
||||
Require(count >= 0 && count <= static_cast<int>(buffer.size()), "gzip split read count");
|
||||
if (!count) break;
|
||||
body.append(buffer.data(), static_cast<std::size_t>(count));
|
||||
}
|
||||
Require(body == "response body", "gzip split inflated body");
|
||||
server.Join();
|
||||
}
|
||||
|
||||
void GzipLarge(int ssl) {
|
||||
std::fprintf(stderr, "HTTP2 gzip large scenario\n");
|
||||
Server server(Mode::GzipLarge);
|
||||
Client client(ssl);
|
||||
Require(sceHttp2SetInflateGZIPEnabled(client.tmpl, 1) == 0, "gzip large option");
|
||||
client.Create(server.Url());
|
||||
Require(sceHttp2SendRequest(client.request, nullptr, 0) == 0, "gzip large send");
|
||||
int kind = -1;
|
||||
std::uint64_t length = 0;
|
||||
Require(sceHttp2GetResponseContentLength(client.request, &kind, &length) == 0, "gzip large length");
|
||||
Require(kind == 0 && length == 70000, "gzip large inflated length");
|
||||
std::string body;
|
||||
std::array<char, 317> buffer;
|
||||
for (;;) {
|
||||
const int count = sceHttp2ReadData(client.request, buffer.data(), buffer.size());
|
||||
Require(count >= 0 && count <= static_cast<int>(buffer.size()), "gzip large read count");
|
||||
if (!count) break;
|
||||
body.append(buffer.data(), static_cast<std::size_t>(count));
|
||||
}
|
||||
Require(body == GzipPayload(), "gzip large inflated body");
|
||||
server.Join();
|
||||
}
|
||||
|
||||
void GzipFailure(int ssl, Mode mode, const char* message) {
|
||||
std::fprintf(stderr, "HTTP2 gzip failure scenario %d\n", static_cast<int>(mode));
|
||||
Server server(mode);
|
||||
Client client(ssl);
|
||||
Require(sceHttp2SetInflateGZIPEnabled(client.tmpl, 1) == 0, "gzip failure option");
|
||||
client.Create(server.Url());
|
||||
Require(Throws([&] {
|
||||
sceHttp2SendRequest(client.request, nullptr, 0);
|
||||
std::array<char, 64> buffer;
|
||||
sceHttp2ReadData(client.request, buffer.data(), buffer.size());
|
||||
}), message);
|
||||
server.Join();
|
||||
}
|
||||
|
||||
void Failure(int ssl, Mode mode, bool verifyName = false) {
|
||||
std::fprintf(stderr, "HTTP2 failure scenario %d\n", static_cast<int>(mode));
|
||||
Server server(mode);
|
||||
@@ -586,6 +735,12 @@ int main(int argc, char** argv) {
|
||||
} else {
|
||||
Require(argc == 1, "test arguments");
|
||||
for (const auto mode : {Mode::Normal, Mode::Large, Mode::NoLength, Mode::Head, Mode::NotFound, Mode::Redirect, Mode::Auth, Mode::Informational}) Success(ssl, mode);
|
||||
Gzip(ssl);
|
||||
GzipSplit(ssl);
|
||||
GzipLarge(ssl);
|
||||
GzipFailure(ssl, Mode::GzipTruncated, "truncated gzip throws");
|
||||
GzipFailure(ssl, Mode::GzipChecksum, "gzip checksum mismatch throws");
|
||||
GzipFailure(ssl, Mode::GzipConcat, "concatenated gzip members throw");
|
||||
for (const auto mode : {Mode::NoAlpn, Mode::Reset, Mode::Truncate, Mode::Redirect, Mode::Auth}) Failure(ssl, mode);
|
||||
Failure(ssl, Mode::Normal, true);
|
||||
for (const auto mode : {Mode::StallHandshake, Mode::StallHeaders, Mode::StallBody, Mode::StallUpload}) {
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
#include "prx/libc/include/Api.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
static void Check(bool condition, int line) {
|
||||
if (!condition) {
|
||||
std::fprintf(stderr, "memccpy check failed at line %d\n", line);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
#define Require(condition) Check((condition), __LINE__)
|
||||
|
||||
class GuardedPage {
|
||||
public:
|
||||
std::size_t size;
|
||||
unsigned char* data;
|
||||
|
||||
GuardedPage() {
|
||||
#ifdef _WIN32
|
||||
SYSTEM_INFO info{};
|
||||
GetSystemInfo(&info);
|
||||
size = info.dwPageSize;
|
||||
data = static_cast<unsigned char*>(VirtualAlloc(nullptr, size * 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
|
||||
Require(data != nullptr);
|
||||
DWORD previous = 0;
|
||||
Require(VirtualProtect(data + size, size, PAGE_NOACCESS, &previous) != 0);
|
||||
#else
|
||||
const long pageSize = sysconf(_SC_PAGESIZE);
|
||||
Require(pageSize > 0);
|
||||
size = static_cast<std::size_t>(pageSize);
|
||||
data = static_cast<unsigned char*>(mmap(nullptr, size * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
|
||||
Require(data != MAP_FAILED);
|
||||
Require(mprotect(data + size, size, PROT_NONE) == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
~GuardedPage() {
|
||||
#ifdef _WIN32
|
||||
VirtualFree(data, 0, MEM_RELEASE);
|
||||
#else
|
||||
munmap(data, size * 2);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
int main() {
|
||||
GuardedPage source;
|
||||
GuardedPage destination;
|
||||
for (std::size_t count = 0; count <= 256; ++count) {
|
||||
for (const std::size_t stop : {count, count / 2, std::size_t{0}}) {
|
||||
std::memset(source.data, 0x35, source.size);
|
||||
std::memset(destination.data, 0xA7, destination.size);
|
||||
auto* input = source.data + source.size - std::max(count, std::size_t{1});
|
||||
auto* output = destination.data + destination.size - std::max(count, std::size_t{1});
|
||||
if (stop < count) input[stop] = 0xFF;
|
||||
const std::vector<unsigned char> before(source.data, source.data + source.size);
|
||||
std::vector<unsigned char> expected(destination.data, destination.data + destination.size);
|
||||
const std::size_t copied = stop < count ? stop + 1 : count;
|
||||
std::copy_n(input, copied, expected.data() + (output - destination.data));
|
||||
void* result = memccpy_nid_postfix(output, input, 0x1FF, count);
|
||||
Require(result == (stop < count ? output + copied : nullptr));
|
||||
Require(std::memcmp(destination.data, expected.data(), destination.size) == 0);
|
||||
Require(std::memcmp(source.data, before.data(), source.size) == 0);
|
||||
}
|
||||
}
|
||||
for (const int character : {-1, 0xFF, 0x1FF}) {
|
||||
source.data[source.size - 1] = 0xFF;
|
||||
destination.data[destination.size - 1] = 0;
|
||||
Require(memccpy_nid_postfix(destination.data + destination.size - 1, source.data + source.size - 1,
|
||||
character, 256) == destination.data + destination.size);
|
||||
Require(destination.data[destination.size - 1] == 0xFF);
|
||||
}
|
||||
std::puts("memccpy return values, stop bytes, guards and unsigned conversion passed");
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
#include "prx/libSceNpCppWebApi/include/Api.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <source_location>
|
||||
|
||||
namespace {
|
||||
|
||||
void Require(bool value, std::source_location at = std::source_location::current()) {
|
||||
if (!value) {
|
||||
std::fprintf(stderr, "requirement failed at %s:%u\n", at.file_name(), at.line());
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
constexpr int kNotSignedIn = static_cast<int>(0x80552907);
|
||||
constexpr int kParameterNotInitialized = static_cast<int>(0x80553502);
|
||||
constexpr int kInvalidArgument = static_cast<int>(0x80553503);
|
||||
constexpr int kAlreadyInitialized = static_cast<int>(0x8055350f);
|
||||
constexpr int kNotInitialized = static_cast<int>(0x80553510);
|
||||
constexpr int kBodyMissing = static_cast<int>(0x80553512);
|
||||
constexpr std::size_t kParameterSize = 0x10;
|
||||
constexpr std::size_t kGuardSize = 0x10;
|
||||
constexpr unsigned char kFill = 0xA5;
|
||||
|
||||
struct Storage {
|
||||
alignas(8) unsigned char bytes[kParameterSize + kGuardSize];
|
||||
};
|
||||
|
||||
bool GuardIntact(const Storage& storage) {
|
||||
for (std::size_t index = kParameterSize; index < sizeof(storage.bytes); ++index) {
|
||||
if (storage.bytes[index] != kFill) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
using SimpleFn = int (APS5_VABI*)(void*);
|
||||
using TextFn = int (APS5_VABI*)(void*, const char*);
|
||||
using StringInitFn = int (APS5_VABI*)(void*, const std::uint64_t*, const char*);
|
||||
using BodyInitFn = int (APS5_VABI*)(void*, const std::uint64_t*, const IntrusivePtr*);
|
||||
|
||||
const SimpleFn kSignedOut[] = {
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V115LocationFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_8LocationEEE,
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V116AttributeFactory6createEPNS1_6Common10LibContextEPKcNS3_13AttributeTypeES9_PNS5_12IntrusivePtrINS3_9AttributeEEE,
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V128PlayerForTicketCreateFactory6createEPNS1_6Common10LibContextERKmNS3_8PlatformEPNS5_12IntrusivePtrINS3_21PlayerForTicketCreateEEE,
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V130SubmitTicketRequestBodyFactory6createEPNS1_6Common10LibContextEPKcRKNS5_6VectorINS5_12IntrusivePtrINS3_21PlayerForTicketCreateEEEEEPNSB_INS3_23SubmitTicketRequestBodyEEE,
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V13Api12cancelTicketEiRKNS4_23ParameterToCancelTicketERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE,
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V13Api12submitTicketEiRKNS4_23ParameterToSubmitTicketERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_24SubmitTicketResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE,
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V13Api8getOfferEiRKNS4_19ParameterToGetOfferERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_20GetOfferResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE,
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V13Api9getTicketEiRKNS4_20ParameterToGetTicketERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_21GetTicketResponseBodyEEENSA_INS8_18ResponseHeaderBaseEEEEE,
|
||||
};
|
||||
const SimpleFn kSetters[] = {
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V121PlayerForTicketCreate10setNatTypeERKi,
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V121PlayerForTicketCreate11setTeamNameEPKc,
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V121PlayerForTicketCreate19setPlayerAttributesERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_9AttributeEEEEE,
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V123SubmitTicketRequestBody11setLocationERKNS1_6Common12IntrusivePtrINS3_8LocationEEE,
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V123SubmitTicketRequestBody19setTicketAttributesERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_9AttributeEEEEE,
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V18Location16setGameSessionIdEPKc,
|
||||
};
|
||||
const SimpleFn kConstructors[] = {
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V13Api19ParameterToGetOfferC1Ev,
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V13Api20ParameterToGetTicketC1Ev,
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V13Api23ParameterToCancelTicketC1Ev,
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V13Api23ParameterToSubmitTicketC1Ev,
|
||||
};
|
||||
const SimpleFn kDestructors[] = {
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V13Api19ParameterToGetOfferD1Ev,
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V13Api20ParameterToGetTicketD1Ev,
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V13Api23ParameterToCancelTicketD1Ev,
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V13Api23ParameterToSubmitTicketD1Ev,
|
||||
};
|
||||
const SimpleFn kTerminators[] = {
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V13Api19ParameterToGetOffer9terminateEv,
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V13Api20ParameterToGetTicket9terminateEv,
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V13Api23ParameterToCancelTicket9terminateEv,
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V13Api23ParameterToSubmitTicket9terminateEv,
|
||||
};
|
||||
const StringInitFn kStringInitializers[] = {
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V13Api19ParameterToGetOffer10initializeEPNS1_6Common10LibContextEPKc,
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V13Api20ParameterToGetTicket10initializeEPNS1_6Common10LibContextEPKc,
|
||||
_ZN3sce2Np9CppWebApi11Matchmaking2V13Api23ParameterToCancelTicket10initializeEPNS1_6Common10LibContextEPKc,
|
||||
};
|
||||
const BodyInitFn kBodyInitializer = _ZN3sce2Np9CppWebApi11Matchmaking2V13Api23ParameterToSubmitTicket10initializeEPNS1_6Common10LibContextENS6_12IntrusivePtrINS3_23SubmitTicketRequestBodyEEE;
|
||||
const TextFn kSetView = _ZN3sce2Np9CppWebApi11Matchmaking2V13Api20ParameterToGetTicket7setviewEPKc;
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
for (const auto fn : kSignedOut) Require(fn(nullptr) == kNotSignedIn);
|
||||
for (const auto fn : kSetters) Require(fn(nullptr) == 0);
|
||||
|
||||
for (const auto fn : kConstructors) {
|
||||
Storage storage;
|
||||
std::memset(storage.bytes, kFill, sizeof(storage.bytes));
|
||||
Require(fn(storage.bytes) == 0);
|
||||
Require(GuardIntact(storage));
|
||||
}
|
||||
for (const auto fn : kDestructors) {
|
||||
Storage storage;
|
||||
std::memset(storage.bytes, kFill, sizeof(storage.bytes));
|
||||
Require(fn(storage.bytes) == 0);
|
||||
Require(GuardIntact(storage));
|
||||
}
|
||||
for (const auto fn : kTerminators) {
|
||||
Storage storage;
|
||||
std::memset(storage.bytes, 0, sizeof(storage.bytes));
|
||||
Require(fn(storage.bytes) == kNotInitialized);
|
||||
}
|
||||
for (std::size_t index = 0; index < sizeof(kStringInitializers) / sizeof(kStringInitializers[0]); ++index) {
|
||||
Storage storage;
|
||||
std::memset(storage.bytes, kFill, sizeof(storage.bytes));
|
||||
std::memset(storage.bytes, 0, kParameterSize);
|
||||
std::uint64_t context = 1;
|
||||
Require(kStringInitializers[index](storage.bytes, &context, nullptr) == kInvalidArgument);
|
||||
Require(kStringInitializers[index](storage.bytes, &context, "id") == 0);
|
||||
Require(kStringInitializers[index](storage.bytes, &context, "id") == kAlreadyInitialized);
|
||||
Require(kTerminators[index](storage.bytes) == 0);
|
||||
Require(kTerminators[index](storage.bytes) == kNotInitialized);
|
||||
Require(GuardIntact(storage));
|
||||
}
|
||||
{
|
||||
Storage storage;
|
||||
std::memset(storage.bytes, kFill, sizeof(storage.bytes));
|
||||
std::memset(storage.bytes, 0, kParameterSize);
|
||||
std::uint64_t context = 1;
|
||||
IntrusivePtr empty{};
|
||||
Require(kBodyInitializer(storage.bytes, &context, &empty) == kBodyMissing);
|
||||
IntrusivePtr body{};
|
||||
body.object = storage.bytes;
|
||||
Require(kBodyInitializer(storage.bytes, &context, &body) == 0);
|
||||
Require(kBodyInitializer(storage.bytes, &context, &body) == kAlreadyInitialized);
|
||||
Require(kTerminators[3](storage.bytes) == 0);
|
||||
Require(kTerminators[3](storage.bytes) == kNotInitialized);
|
||||
Require(GuardIntact(storage));
|
||||
}
|
||||
{
|
||||
Storage storage;
|
||||
std::memset(storage.bytes, kFill, sizeof(storage.bytes));
|
||||
std::memset(storage.bytes, 0, kParameterSize);
|
||||
std::uint64_t context = 1;
|
||||
Require(kSetView(storage.bytes, "view") == kParameterNotInitialized);
|
||||
Require(kStringInitializers[1](storage.bytes, &context, "id") == 0);
|
||||
Require(kSetView(storage.bytes, "view") == 0);
|
||||
Require(kSetView(storage.bytes, nullptr) == kInvalidArgument);
|
||||
Require(kTerminators[1](storage.bytes) == 0);
|
||||
Require(kSetView(storage.bytes, "view") == kParameterNotInitialized);
|
||||
Require(GuardIntact(storage));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -160,4 +160,5 @@ int main() {
|
||||
Require(sceNpSessionSignalingInitialize(reinterpret_cast<const SessionSignalingInitParam*>(initializeParam)) == 0, "reinitialize");
|
||||
Require(sceNpSessionSignalingRequestPrepare(contextId, &requestId) == ContextNotFound, "reinitialize retained a context");
|
||||
Require(sceNpSessionSignalingTerminate() == 0, "final terminate");
|
||||
Require(sceNpSessionSignalingCreateContext(reinterpret_cast<const SessionSignalingContextParam1*>(1), &contextId) == NotInitialized, "create context post-terminate");
|
||||
}
|
||||
@@ -102,6 +102,12 @@ void checkGenericPictureInfo(const OutputInfo& output, const TInfo& typed) {
|
||||
shorter.thisSize = sizeof(OutputInfo) - 8;
|
||||
auto actual = typed;
|
||||
check(sceVdecswGetPictureInfo(&shorter, &actual, nullptr) == 0 && std::memcmp(&actual, &typed, sizeof(actual)) == 0, "generic picture info rejected the shorter output descriptor");
|
||||
auto doubled = output;
|
||||
doubled.pictureCount = 2;
|
||||
auto doubledFirst = typed;
|
||||
const auto doubledBefore = doubledFirst;
|
||||
auto doubledSecond = typed;
|
||||
check(throws([&] { sceVdecswGetPictureInfo(&doubled, &doubledFirst, &doubledSecond); }) && std::memcmp(&doubledFirst, &doubledBefore, sizeof(doubledFirst)) == 0 && std::memcmp(&doubledSecond, &doubledBefore, sizeof(doubledSecond)) == 0, "generic picture info accepted two pictures");
|
||||
}
|
||||
|
||||
void testGenericPictureInfoValidation() {
|
||||
|
||||
@@ -20,6 +20,7 @@ inline constexpr std::uint8_t PrefixAddressSize = 0x67;
|
||||
inline constexpr std::uint8_t RexMin = 0x40;
|
||||
inline constexpr std::uint8_t RexMax = 0x4F;
|
||||
inline constexpr std::uint8_t RexWBit = 0x08;
|
||||
inline constexpr std::uint8_t RexBBit = 0x01;
|
||||
|
||||
inline constexpr std::uint8_t TwoByteOpcodeEscape = 0x0F;
|
||||
|
||||
@@ -108,6 +109,8 @@ inline constexpr std::uint8_t OneByteLoopMax = 0xE2;
|
||||
inline constexpr std::uint8_t OneByteInOutImm8Min = 0xE4;
|
||||
inline constexpr std::uint8_t OneByteInOutImm8Max = 0xE7;
|
||||
inline constexpr std::uint8_t OneByteInt = 0xCD;
|
||||
inline constexpr std::uint8_t OneByteInt3 = 0xCC;
|
||||
inline constexpr std::uint8_t OneByteNop = 0x90;
|
||||
inline constexpr std::uint8_t OneByteRetImm16 = 0xC2;
|
||||
inline constexpr std::uint8_t OneByteRetFarImm16 = 0xCA;
|
||||
inline constexpr std::uint8_t OneByteEnter = 0xC8;
|
||||
|
||||
@@ -112,6 +112,41 @@ private:
|
||||
return hit != _branchTargets.end() && *hit < end;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool _int3(const std::size_t index) const {
|
||||
const auto bytes = _bytes(index);
|
||||
return bytes.size() == 1 && bytes[0] == X64OpcodeConstants::OneByteInt3;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool _padding(const std::size_t index) const {
|
||||
const auto info = _info(index);
|
||||
if (info.IsTwoByteOpcode)
|
||||
return info.Opcode == X64OpcodeConstants::TwoByteNopModRm;
|
||||
if (info.Opcode != X64OpcodeConstants::OneByteNop)
|
||||
return _int3(index);
|
||||
const auto prefixes = _bytes(index).first(info.OpcodeOffset);
|
||||
return (info.RexPrefix & X64OpcodeConstants::RexBBit) == 0 && std::find(prefixes.begin(), prefixes.end(), X64OpcodeConstants::PrefixRep) == prefixes.end();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool _mayBeEntered(const std::size_t index) const {
|
||||
for (auto previous = index; previous > 0; --previous) {
|
||||
if (_end(previous - 1) != _address(previous) || _blocksFollowing(previous - 1))
|
||||
return true;
|
||||
if (!_padding(previous - 1))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool _enteredWithin(const std::size_t first, const std::size_t last) const {
|
||||
if (_entered(_address(first), _end(last)))
|
||||
return true;
|
||||
for (auto index = first + 1; index <= last; ++index) {
|
||||
if (!_int3(index) && _mayBeEntered(index))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool _blocksFollowing(const std::size_t index) const {
|
||||
const auto kind = _info(index).FlowKind;
|
||||
return kind != ControlFlowKind::Sequential && kind != ControlFlowKind::ConditionalBranch;
|
||||
@@ -135,7 +170,7 @@ private:
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<Placement> _relocate(const std::size_t index, const std::size_t first, const std::size_t last, const bool allowInPlace = false) const {
|
||||
if (_entered(_address(first), _end(last)))
|
||||
if (_enteredWithin(first, last))
|
||||
return std::nullopt;
|
||||
StubBodyBuilder body;
|
||||
Placement placement;
|
||||
@@ -162,7 +197,7 @@ private:
|
||||
using Amd64OnlySubstitutionTable::kJmpRel32;
|
||||
std::size_t head = 0;
|
||||
for (auto last = start; last < _matches.size(); ++last) {
|
||||
if (!_movable(last, true) || _entered(_address(start), _end(last)))
|
||||
if (!_movable(last, true) || _enteredWithin(start, last))
|
||||
return std::nullopt;
|
||||
if (head == 0 && _end(last) - _address(start) >= kJmpRel32.Size)
|
||||
head = static_cast<std::size_t>(_end(last) - _address(start));
|
||||
|
||||
@@ -1058,6 +1058,39 @@ void converterWideStoreWindows() {
|
||||
requireFailure([&] { (void)convert(withStore(far, tail)); }, "A 256-bit store whose movable neighbours are all beyond a short jump was kept");
|
||||
}
|
||||
|
||||
void converterIslandIndirectEntries() {
|
||||
const auto converter = Codegen::MakeAmd64OnlyConverter(Codegen::Amd64OnlyTarget::Rosetta);
|
||||
const Bytes prologue = {0x55, 0x48, 0x89, 0xE5, 0x41, 0x57, 0x41, 0x56, 0x41, 0x55, 0x41, 0x54, 0x53, 0xC3};
|
||||
for (const auto& padding : {Bytes{0xCC, 0xCC}, Bytes{0x66, 0x90}, Bytes{0x0F, 0x1F, 0x00}, Bytes{}}) {
|
||||
Bytes text = {0xE8, 0x40, 0x00, 0x00, 0x00, 0xC5, 0xFC, 0x11, 0x07, 0xC3, 0xEB, 0xFD};
|
||||
text.insert(text.end(), padding.begin(), padding.end());
|
||||
const auto entry = static_cast<Domain::FileByteOffset>(0x200 + text.size());
|
||||
text.insert(text.end(), prologue.begin(), prologue.end());
|
||||
Bytes file(0x300 + text.size(), 0xCC);
|
||||
std::copy(text.begin(), text.end(), file.begin() + 0x200);
|
||||
const auto result = converter->Convert(file, {segmentHeader(text.size())});
|
||||
require(result.Trampolines.size() == 2 && result.KeptCount == 0, "A 256-bit store before a function that only a pointer reaches was not lowered through an island");
|
||||
for (const auto& site : result.Trampolines)
|
||||
require(site.Offset >= entry || site.Offset + site.Length <= entry, "An island covered the entry of a function that follows a jump and its padding");
|
||||
require(result.Trampolines[0].Offset == entry, "An island next to a function that follows a jump and its padding did not start at its entry");
|
||||
}
|
||||
}
|
||||
|
||||
void converterIslandInTrapPadding() {
|
||||
const auto converter = Codegen::MakeAmd64OnlyConverter(Codegen::Amd64OnlyTarget::Rosetta);
|
||||
Bytes text = {0xE8, 0x40, 0x00, 0x00, 0x00, 0xC5, 0xFC, 0x11, 0x07, 0xC3, 0xEB, 0xFD};
|
||||
const Bytes padding(15, 0xCC);
|
||||
text.insert(text.end(), padding.begin(), padding.end());
|
||||
const auto entry = static_cast<Domain::FileByteOffset>(0x200 + text.size());
|
||||
text.insert(text.end(), {0xE9, 0x00, 0x01, 0x00, 0x00});
|
||||
Bytes file(0x300 + text.size(), 0xCC);
|
||||
std::copy(text.begin(), text.end(), file.begin() + 0x200);
|
||||
const auto result = converter->Convert(file, {segmentHeader(text.size())});
|
||||
require(result.Trampolines.size() == 2 && result.KeptCount == 0, "A 256-bit store before int3 padding and a function that cannot move was not lowered through an island");
|
||||
for (const auto& site : result.Trampolines)
|
||||
require(site.Offset + site.Length <= entry, "An island in the int3 padding after a jump ran into the next function");
|
||||
}
|
||||
|
||||
void converterClaimedFollower() {
|
||||
const auto converter = Codegen::MakeAmd64OnlyConverter(Codegen::Amd64OnlyTarget::Rosetta);
|
||||
for (const auto& [shortInstruction, name] : {std::pair{Bytes{0x0F, 0x53, 0xCA}, "RCPPS"}, std::pair{Bytes{0xF3, 0x0F, 0xC7, 0xF8}, "RDPID"}}) {
|
||||
@@ -2413,6 +2446,8 @@ int main() {
|
||||
wideStoreSplit();
|
||||
converterWideStoreWindows();
|
||||
converterWideStoreIslandAcrossInt64Max();
|
||||
converterIslandIndirectEntries();
|
||||
converterIslandInTrapPadding();
|
||||
converterClaimedFollower();
|
||||
converterJumpTableTargets();
|
||||
converterReciprocal();
|
||||
|
||||
@@ -482,6 +482,8 @@ bool canReachBefore(const ControlFlowGraph& graph, std::uint32_t start, std::uin
|
||||
}
|
||||
|
||||
bool reachesWithinIteration(const ControlFlowGraph& graph, const NaturalLoop& loop, std::uint32_t start, std::uint32_t target) {
|
||||
if (start == target) return true;
|
||||
if (start == loop.continueBlock || start == loop.mergeBlock) return false;
|
||||
std::vector<std::uint32_t> pending = {start};
|
||||
std::vector<bool> visited(graph.blocks.size(), false);
|
||||
while (!pending.empty()) {
|
||||
|
||||
@@ -1660,6 +1660,10 @@ std::uint32_t EmitClassMaskF32(SpirvEmitterState& state, std::uint32_t value, st
|
||||
}
|
||||
|
||||
std::uint32_t EmitMinMaxF32Value(SpirvEmitterState& state, std::uint32_t lhs, std::uint32_t rhs, bool maxValue) {
|
||||
if (maxValue && lhs == ConstantF32(state, 0u)) {
|
||||
const auto positive = Binary(state, spv::OpFOrdGreaterThan, TypeBool(state), rhs, lhs);
|
||||
return Select(state, TypeF32(state), positive, rhs, lhs);
|
||||
}
|
||||
const auto lhsClass = EmitClassifyF32(state, lhs);
|
||||
const auto rhsClass = EmitClassifyF32(state, rhs);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
### Silent stubs
|
||||
|
||||
- [libSceAcm](../../../core/libs/prx/libSceAcm/Export.cpp) batches execute as silent no-ops, and do not execute encoded panner, FFT and reverb commands.
|
||||
- [libSceAcm](../../../core/libs/prx/libSceAcm/Export.cpp) batches execute as silent no-ops, and do not execute encoded panner, FFT, reverb and priority commands (the priority job layout and checks are read from `libSceAcm.sprx`, firmware 13.40, not measured on a console).
|
||||
|
||||
### Unknown function info
|
||||
|
||||
|
||||
@@ -285,7 +285,7 @@
|
||||
- [Host import refresh](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) (libSceAgcDriver): after a change to the guest allocation registry, an import is checked again only when its import window, or its own range when larger, touches a range that changed since the last check, or when it was made since then; every import is checked when the window is 0 or the window or the budget changed. An unchanged import whose run could now be larger, because a remembered refusal no longer holds, is kept until its window changes or a request for the larger run retires it; before, any registry change anywhere retired it. Remembered failures and refusals are dropped the same way and stay checked where they are used.
|
||||
|
||||
- [Sampled MSAA surface geometry](../../../core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp) supplies the fragment-aware linear size and one mip for a tiled 2D surface at two, four or eight fragments. Unsupported arrays, mip chains, extents and block sizes throw. Linear descriptors retain the existing geometry path, including its use when sampling an existing native target; a linear MSAA guest layout is not modeled. The range calculation follows AMD Addrlib's thin-block dimensions. Console layout equivalence and title effects of the additional mip metadata remain unmeasured. This does not add sample upload or readback.
|
||||
- [Unclamped normalized color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - CB_COLOR_INFO targets with BLEND_CLAMP clear (bit 15) on UNORM and SNORM formats are accepted. Without blending Vulkan's conversion clamps the stored value. With blending the pixel shader reads the target through its storage image under pixel interlock, evaluates the blend equation on the unclamped export, and stores the clamped result, because Vulkan clamps blend inputs to the normalized range. The result is not measured on a console. The final float to normalized rounding is Vulkan's. The path needs fragmentShaderPixelInterlock, recorded draws and a single-sample resident target, and refuses dual-source blending, multisampled, packed, reversed and alternate component order targets, a signed target whose write mask skips a component (the shader rewrites it through a float, so the code -128 would become -127), and a packed target's partial write in the same draw.
|
||||
- [Unclamped normalized color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - CB_COLOR_INFO targets with BLEND_CLAMP clear (bit 15) on UNORM and SNORM formats are accepted. With blending the pixel shader reads the target through its storage image under pixel interlock, evaluates the blend equation on the unclamped export, and stores the clamped result, because Vulkan clamps blend inputs to the normalized range. The result is not measured on a console. The final float to normalized rounding is Vulkan's. The path needs fragmentShaderPixelInterlock and shaderStorageImageReadWithoutFormat (refused without either), recorded draws and a single-sample resident target, and refuses dual-source blending, multisampled, packed, reversed and alternate component order targets, a signed target whose write mask skips a component (the shader rewrites it through a float, so the code -128 would become -127), and a packed target's partial write in the same draw.
|
||||
|
||||
- [Multisample image readback](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleReadback.cpp) fetches unsigned R8/R16/R32/R32G32/R32G32B32A32 or D32 depth samples through a 2D array view in GENERAL, in layer/y/x/fragment order. The caller owns producer completion, image transitions and resource lifetimes. It implements Vulkan per-sample fetches, not guest-memory publication, metadata expansion or PS5 layout equivalence; D32 exceptional-value preservation has not been measured. Other formats, sample counts and invalid ranges throw.
|
||||
- [Multisample image upload](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleUpload.cpp) does not model guest layouts, native ownership or CMASK/FMASK/DCC publication; console equivalence and large-title initialization costs remain unmeasured. It stores 1/2/4/8/16-byte elements from a storage buffer into an unsigned 2D multisample array view in layer/y/x/fragment order. It requires enabled multisample storage images and formatless image writes, 2/4/8 fragments and valid aligned buffer and dispatch ranges. The caller supplies the matching storage view in GENERAL and owns barriers, completion and resource lifetimes.
|
||||
|
||||
@@ -134,6 +134,7 @@
|
||||
|
||||
### Functional
|
||||
|
||||
- [sceAudioOut2ContextPush](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Context.cpp) (libSceAudioOut) - a blocking Push waits for room before it queues its grain and again after, so it returns once fewer grains than the queue depth wait to be played (each wait ends after at most 200 ms, so on a queue that never drains a blocking Push returns after up to 400 ms). Inferred from PPSA02530, whose sound engine pushes one grain per call from a feeder thread and wakes its renderer after each push: while Push returned as soon as the grain was queued, the renderer found the queue full and slept until the next rendered frame. When the console's blocking Push returns is unmeasured. Both waits release the context lock, so destroying a context while a push is waiting on it is not supported.
|
||||
- [sceAudioOutMasteringInit, sceAudioOutMasteringTerm and sceAudioOutMasteringSetParam](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) (libSceAudioOut) - non-zero flags, a null pointer and a params id other than 1 throw; a repeated Init, a Term without Init and a SetParam or GetState before Init are unknown. There is no mastering stage, so Init and Term keep no state and SetParam reads only the id. The signatures and the default params id 1 follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/AudioOut.cs#L312-L344), not a console measurement. SharpProspero documents flag bit 0 as `SwapEqCompressor`, and shadPS4's PS4 stub [rejects any non-zero Init flags](https://github.com/shadps4-emu/shadPS4/blob/8d50471b7dc141dd700b51276b8edd1f293bce26/src/core/libraries/audio/audioout.cpp#L956-L962) with 0x80260201
|
||||
- [sceAudioOutMasteringGetState](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) (libSceAudioOut) - the states id 1 and the meter offsets 32, 128, 240, 272 and 304 in a 336-byte struct follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Audio/AudioOutMastering.cs#L97-L110), not a console measurement. With no mastering stage it zeroes the compressor input RMS and gain, and the limiter input, output and gain peaks, and leaves the other bytes, including the descriptors the AudioOut2 call writes, untouched; zero is not known to be what the console reports for an idle chain. A null pointer and a states id other than 1 throw; the console error codes are unknown
|
||||
- [sceAudioOut2EnableChat and sceAudioOut2DisableChat](../../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) - both PS5 exports take no arguments, return 0x80268006 before AudioOut2 initialization, and invoke the system chat service with selector 3. A negative service result maps to 0x80268000, otherwise they return zero. The host only records a local enabled flag, without a chat service; repeated-call and no-session behavior therefore still depends on the missing service.
|
||||
|
||||
@@ -30,9 +30,10 @@
|
||||
|
||||
- [libSceHttp2 transport](../../../core/libs/prx/libSceHttp2/Transport.cpp) - only synchronous HTTPS requests with version 3 and ALPN `h2` are supported. HTTP/1.x, cleartext HTTP/2, negotiation fallback, connection reuse, multiplexing across requests, CONNECT, response trailers, automatic redirects and authentication challenges throw. Version values, method strings, header overwrite/append modes, status/read/content-length signatures and timeout units follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Http2.cs); wire framing and negotiation follow [RFC 9113 sections 3, 5, 6 and 8](https://www.rfc-editor.org/rfc/rfc9113.html). [prosper](https://github.com/mattias800/prosper/blob/b1bec57186a0/prosper/src/hle/net/hle_http2.cpp) instead records the method argument as an integer; the string signature follows SharpProspero. These are public compatibility contracts, not console measurements.
|
||||
- [libSceHttp2 error paths](../../../core/libs/prx/libSceHttp2/Transport.cpp) - cancellation, deadlines, DNS/TLS/protocol failures, malformed inputs, concurrent calls on one request and exhausted connection slots throw because their console error mappings are unknown. Socket connect errors propagate the libSceNet result, following the module-analysis report in [prosper #2894](https://github.com/mattias800/prosper/issues/2894); the report does not establish every exported error path. A request uses one connection; a context's concurrency cap counts connections until EOF, failure or deletion. Native pooling, scheduling, validation order and teardown semantics remain unverified. DNS uses two host workers and a queue of eight; cancellation/deadlines abandon the caller's wait, not libc's lookup. Library unload can wait for a host lookup to return. A resolve retry resubmits a failed lookup with a new timeout; a timed-out lookup isn't retried. The console's retry semantics are unverified.
|
||||
- [libSceHttp2 TLS settings](../../../core/libs/prx/libSceHttp2/Export.cpp) - minimum SSL versions outside 2 to 5, nonnull SSL callbacks and flags outside `0x3d` throw. Supported certificate-check bits follow SharpProspero and use libSceSsl's TLS 1.2 backend and CA context; native defaults and error translation are unverified. Settings on object kinds other than templates/requests throw, except for the request options below; native object-kind restrictions are otherwise unverified. Settings become immutable when transport is created. Zero timeout values, negative resolve retries, gzip inflation and connection-slot wait timeouts throw; a failed lookup is retried up to the set count. Host HTTP allocations are not charged to the context pool, so `sceHttp2GetMemoryPoolStats` reports a live context's pool size with zero peak and current use. PPSA01494, PPSA30839 and PPSA31162 read the three sizes for memory logging without checking the result, and stopped at startup while it threw.
|
||||
- [libSceHttp2 TLS settings](../../../core/libs/prx/libSceHttp2/Export.cpp) - minimum SSL versions outside 2 to 5, nonnull SSL callbacks and flags outside `0x3d` throw. Supported certificate-check bits follow SharpProspero and use libSceSsl's TLS 1.2 backend and CA context; native defaults and error translation are unverified. Settings on object kinds other than templates/requests throw, except for the request options below; native object-kind restrictions are otherwise unverified. Settings become immutable when transport is created. Zero timeout values, negative resolve retries and connection-slot wait timeouts throw; a failed lookup is retried up to the set count. Host HTTP allocations are not charged to the context pool, so `sceHttp2GetMemoryPoolStats` reports a live context's pool size with zero peak and current use. PPSA01494, PPSA30839 and PPSA31162 read the three sizes for memory logging without checking the result, and stopped at startup while it threw.
|
||||
- [sceHttp2SetMinSslVersion](../../../core/libs/prx/libSceHttp2/Export.cpp) - libSceHttp2's own range and validation order are not measured: versions 2 (SSL 3.0) to 5 (TLS 1.2) are accepted, the range `sceSslSetMinSslVersion` takes on a 13.40 console ([libSceSsl](libSceSsl.md)). The floor is not kept: libSceSsl negotiates TLS 1.2 only, which meets every accepted floor, so a server limited to versions older than TLS 1.2 fails the handshake whatever the floor; what a lower floor does with such a server on a console is not measured.
|
||||
- [libSceHttp2 request options](../../../core/libs/prx/libSceHttp2/Export.cpp) - extends the TLS settings entry: the auth and redirect flags, headers and the auth-info and pre-send callbacks live in `Http2::Settings`, are copied into each new request and become immutable once transport exists; the getters still read them then. Accepted kinds, defaults, copying, header modes, error codes and their order follow the 13.42 module run in an emulator ([#10536](https://github.com/boykopovar/AnyPS5/pull/10536)), not a console. A null out-pointer to `sceHttp2GetAuthEnabled` is written through on that module; here it throws. The pre-send callback type is unknown. The transport never calls either callback, and the auth cache flush only validates the context
|
||||
- [libSceHttp2 gzip responses](../../../core/libs/prx/libSceHttp2/Transport.cpp) - `sceHttp2SetInflateGZIPEnabled` stores the flag. When it is set and the response carries `content-encoding: gzip`, the transport inflates the body with the vendored zlib before `sceHttp2ReadData` and `sceHttp2GetResponseContentLength` see it, and reports the inflated length; both calls wait for END_STREAM while the enabled response is gzip. The transport neither adds nor inspects `Accept-Encoding`, so the guest supplies that request header and an unsolicited gzip response is inflated too. Whether a console rewrites the header block (Content-Encoding and Content-Length) is unverified, so the serialized headers keep the fields the server sent; other encodings are not inflated, a malformed or truncated stream, a bad checksum or size and concatenated members throw, and the optional gzip header CRC is not verified.
|
||||
- [sceHttp2GetAllResponseHeaders](../../../core/libs/prx/libSceHttp2/Export.cpp) throws: the serialization, size and lifetime of a decoded HTTP/2 header block have not been established. Status and Content-Length fields are decoded; absent Content-Length reports `NotFound`, following SharpProspero.
|
||||
- [libSceHttp2 asynchronous requests](../../../core/libs/prx/libSceHttp2/Export.cpp) - send/read submission and `sceHttp2WaitAsync` throw. The previous offline implementation queued a fabricated network failure and used event type zero; public sources do not establish the completion event types, wait timeout updates, cancellation results or ownership of async buffers. PPSA17221's kqueue layout and PPSA15508's 24-byte wait result establish caller ABI only. No completion or equeue event is manufactured.
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
- [sce::Np::CppWebApi::Common::String](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the 0x20-byte layout (32-bit reference count, buffer at 0x08, buffer size at 0x10, `LibContext*` at 0x18) comes from the module bundled with one title (PPSA21564); other SDK versions are not checked. `append` grows the buffer with the host allocator and the destructor frees it, instead of going through the `LibContext` allocator. `pushBack` into `Vector<String>` stores a deep copy, so a destroyed temporary cannot invalidate the stored element; a title that byte-copies a `String` with a live buffer would still double-free, as nothing increments the reference count
|
||||
- [sce::Np::CppWebApi::Common::Vector](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by PPSA03671, whose dump ships no `libSceNpCppWebApi.prx`. A `Vector` handle is an `IntrusivePtr` over a shared body; bodies created by `pushBack` use a host-side layout with the reference count at 0x30, matching the adopted-body convention measured on PPSA21564. The pointer/deleter constructor adopts a title-provided body released through the stored deleter; `pushBack` on such a body throws, since its element layout is unknown. `begin`, `end`, iterators and `copyFrom` throw: the only `Vector`s the title reads back come from failed transactions, which never produce one. `empty`, `size` and `operator[]` read the host-created body honestly and throw on title-provided bodies
|
||||
- [sce::Np::CppWebApi::SessionManager2V1](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by PPSA03671. Offline, the factories, `PlayerSessionsApi` calls and transaction `start`/`getResponse`/`readData` fail with `SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN`; parameter setters, `initialize`, `terminate`, constructors and destructors succeed without keeping state, as no request is ever sent. Response-body accessors (`getSessionId`, `getPlayerSessions`, `getInvitations`, the `IsSet` queries) throw: a failed request never produces a response object
|
||||
- [sce::Np::CppWebApi::Matchmaking::V1](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by PPSA21564 (86 of 92) and PPSA05510, PPSA25256, PPSA17226 and PPSA08395 for the other six; behaviour comes from the module bundled with PPSA21564/PPSA23732 (builds J03093737/J03341606), not from a console. Offline, the four `Api` calls and four factories return `SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN`. The four `ParameterTo*` objects keep only an initialized flag: `initialize` returns 0x80553503 for a null context or string, 0x80553502 for an unset context, 0x8055350f when already initialized and 0x80553512 for an empty `SubmitTicketRequestBody`; `terminate` returns 0x80553510. `ParameterToGetTicket::setview` returns 0x80553502 uninitialized and 0x80553503 for a null string. Other setters succeed without keeping state. Response accessors throw: a failed request produces no response. 0x80553504 is never returned; the stub stores a static pointer.
|
||||
- [sce::Np::CppWebApi::UserProfile::V1, ProfanityFilter::V2, CommunicationRestrictionStatus::V3](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - imported by an Epic Online Services module whose dump ships no `libSceNpCppWebApi.prx`. The reference-count offset 0x00 of `ResponseHeaderBase` and `WebApiFilterRequest` is assumed from the other objects, not measured. Offline, the five API calls, `WebApiFilterRequestFactory::create` and the transactions' `start`/`getResponseHeaders` fail with `SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN`. Response accessors, `ResponseHeaderBase::getHeaderValue` and the `Vector`/`Iterator` members over `IntrusivePtr<UserProfile::V1::BasicPresence>` and `unsigned long` throw: a failed request never produces a response
|
||||
- [libSceNpCppWebApi](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - unimplemented stubs have their parameter lists, const qualifiers and return types aligned with the demangled symbols confirmed by NID matching in [#8143](https://github.com/boykopovar/AnyPS5/issues/8143). They cover response-body accessors, iterators, and uninstantiated transactions in Leaderboards::V1, SessionManager::V1, InGameCatalog::V5, TitleCloudStorage::V1, and Common templates: each mangled name gives the method and parameter types, but the runtime layout of response objects and errors are unverified on console, so each call continues to throw NotImplemented_nid_no_patch. No call site is recorded for them
|
||||
- [sce::Np::CppWebApi::SessionManager2V1 and sce::Np::CppWebApi::InGameCatalog2V5](../../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - 198 further exports imported by PPSA20397, named from aerolib. API calls, factories and transaction `start`/`getResponse*` fail `SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN`; parameter bookkeeping succeeds without state; `IntrusivePtr`/`Vector` helpers follow the layouts above, with `Vector::empty`/`size`/`operator[]` reading host-created bodies and throwing on title-provided ones. Response accessors, iterators, `Vector::begin`/`end`/`copyFrom` and transaction `operator=` throw: a failed request produces no response object. As `start` never succeeds here, `TransactionBase::finish` and `abort` return 0x80553508 and `getId` returns 0, which is what the module bundled with PPSA21564 returns while the two words its `Transaction` constructor zeroes are still unset
|
||||
|
||||
@@ -116,6 +116,7 @@
|
||||
- [sscanf_s and vsscanf_s](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) return `EOF` when a reached conversion has a null destination, stopping before further assignments, following C11 K.3.5.3.7 and K.3.5.3.14. Earlier assignments are retained. The constraint handler is not invoked, and console behaviour is unverified.
|
||||
- [memmem and memccpy](../../../core/libs/prx/libc/src/Strings.cpp) (libc) - `memmem` searches for needle occurrences in memory buffers and `memccpy` copies buffers until a character match, following FreeBSD and POSIX specifications; behavior is unverified on console. Neither is exported by `libSceLibcInternal.sprx` on a PS5 (firmware 13.60), so a payload process cannot call them there.
|
||||
- [strtof and wcstof](../../../core/libs/prx/libc/src/Strings.cpp) (libc) round to the nearest float, where the console's `_Stof` in `libSceLibcInternal.sprx` (0x734a0) does not always: about a third of decimal inputs within a relative 2^-51 of a point halfway between two floats, and some hexadecimal inputs with more than 24 significant bits, come out one unit apart in the last place (`1e-40` is `0x1.16c3p-133` there and `0x1.16c2p-133` here). The console's own rounding is not reproduced
|
||||
- [Short narrow character fields](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) - on glibc-backed scan paths, `%2c` with one available input byte can return `EOF` after storing that byte, while the bundled modules in PPSA03766 and PPSA07923 count one assignment. This is a CPU-only module comparison, not a console run.
|
||||
- [strtof and wcstof](../../../core/libs/prx/libc/src/Strings.cpp) (libc) - `ERANGE` follows the `libc.prx` bundled with PPSA08427 (J03547016) and PPSA03671 (J03848509), both run side by side with this libc on a Linux host: a decimal input whose result is a nonzero subnormal sets it only when inexact, a hexadecimal one only when it lies below the smallest subnormal and rounds up to it, and underflow to zero and overflow set it. Both builds also take a `NAN(...)` payload in `strtof` from the sequence's leading digits (`nan(0x123)` is `0x7fc00123`, `-nan(5)` is `0xffc00005`); the sign is kept here, the payload is not. `libSceLibcInternal`'s `_Stof` differs: no `ERANGE` for hexadecimal input, a positive `nan`, and after a minus sign a failed parse is `-0.0` for a lone sign and `+0.0` before an `inf`/`nan` spelling, the reverse of `libc.prx`. Decimal `strtod` sets `ERANGE` for a nonzero subnormal in J03547016 but not in J03848509 or `libSceLibcInternal`, whose result is kept.
|
||||
|
||||
- [operator new](../../../core/libs/prx/libc/src/StdioExtras.cpp) (libc) - the `nothrow` forms return null only for `std::bad_alloc`, where the `libc.prx` bundled with PPSA21564 (build `J03093718`) catches every exception there, so a new handler that throws anything else ends the process instead of returning null. The aligned forms throw `std::bad_alloc` for an alignment of zero or one that is not a power of two, where that binary calls `abort`.
|
||||
|
||||
@@ -586,7 +586,10 @@ def unconditional_calls(body):
|
||||
|
||||
def always_stub_calls(text):
|
||||
helpers = {}
|
||||
possible = progress.stub_calls(text)
|
||||
for match in progress.STUB_WRAPPER.finditer(text):
|
||||
if match.group(1) not in possible:
|
||||
continue
|
||||
body = text[match.end() - 1:progress.body_end(text, match.end() - 1) + 1]
|
||||
helpers.setdefault(match.group(1), []).extend(variant_calls(body))
|
||||
calls = {progress.STUB}
|
||||
|
||||
@@ -529,6 +529,10 @@ def write_reports(out, rows, blob, main_exports=None):
|
||||
(out / f"{number}.md").write_text(report(found, blob, main_exports.get(number)), encoding="utf-8")
|
||||
|
||||
|
||||
def unverified_overlaps(rows):
|
||||
return {number: set(found["uncompared"]) for number, found in rows.items() if found.get("uncompared")}
|
||||
|
||||
|
||||
def main_export_findings(prs, current_exports):
|
||||
findings = {}
|
||||
for number, pr in prs.items():
|
||||
@@ -591,6 +595,12 @@ def main(argv=None):
|
||||
raise SystemExit(f"pr_overlap: {error}")
|
||||
if args.branch:
|
||||
print(report(rows[0], blob, main_exports.get(0)) or "No overlap with open pull requests.", end="")
|
||||
unresolved = unverified_overlaps(rows[0])
|
||||
if unresolved:
|
||||
for number, files in sorted(unresolved.items()):
|
||||
progress(f"cannot confirm overlap with PR {number}: {', '.join(sorted(files))}")
|
||||
progress("overlap check failed closed; manually compare every reported PR branch before opening this one")
|
||||
return 2
|
||||
return 0
|
||||
write_reports(args.out, rows, blob, main_exports)
|
||||
return 0
|
||||
|
||||
@@ -47,7 +47,7 @@ class ConfigureSeedPathTests(unittest.TestCase):
|
||||
"resource.setrlimit(resource.RLIMIT_FSIZE, (64, 64)); "
|
||||
"sys.path.insert(0, sys.argv[1]); import configure_seed; configure_seed.main(sys.argv[2:])")
|
||||
result = subprocess.run([sys.executable, "-c", program, str(TOOL.parent), str(self.cache), str(seed)],
|
||||
capture_output=True, text=True, timeout=5)
|
||||
capture_output=True, encoding="utf-8", timeout=5)
|
||||
self.assertEqual(result.returncode, 2, result.stderr)
|
||||
self.assertNotIn("Traceback", result.stderr)
|
||||
self.assertEqual(result.stdout, "")
|
||||
@@ -62,11 +62,11 @@ class ConfigureSeedPathTests(unittest.TestCase):
|
||||
seed = self.root / "seed.cmake"
|
||||
program = "import os,sys; os.umask(0o027); sys.path.insert(0,sys.argv[1]); import configure_seed; configure_seed.main(sys.argv[2:])"
|
||||
arguments = [sys.executable, "-c", program, str(TOOL.parent), str(self.cache), str(seed)]
|
||||
result = subprocess.run(arguments, capture_output=True, text=True, timeout=5)
|
||||
result = subprocess.run(arguments, capture_output=True, encoding="utf-8", timeout=5)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertEqual(seed.stat().st_mode & 0o777, 0o640)
|
||||
seed.chmod(0o600)
|
||||
result = subprocess.run(arguments, capture_output=True, text=True, timeout=5)
|
||||
result = subprocess.run(arguments, capture_output=True, encoding="utf-8", timeout=5)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertEqual(seed.stat().st_mode & 0o777, 0o600)
|
||||
|
||||
@@ -110,7 +110,7 @@ class ConfigureSeedPathTests(unittest.TestCase):
|
||||
|
||||
def run_seed(self, seed, *options):
|
||||
return subprocess.run([sys.executable, str(TOOL), str(self.cache), str(seed), *options],
|
||||
capture_output=True, text=True)
|
||||
capture_output=True, encoding="utf-8")
|
||||
|
||||
def assert_rejected(self, seed, *options):
|
||||
result = self.run_seed(seed, *options)
|
||||
@@ -124,7 +124,7 @@ class ConfigureSeedPathTests(unittest.TestCase):
|
||||
def test_unreadable_cache_is_reported_without_a_traceback(self):
|
||||
missing = self.root / "no cache dir" / "CMakeCache.txt"
|
||||
result = subprocess.run([sys.executable, str(TOOL), str(missing), str(self.root / "seed.cmake")],
|
||||
capture_output=True, text=True)
|
||||
capture_output=True, encoding="utf-8")
|
||||
self.assertEqual(result.returncode, 2, result.stderr)
|
||||
self.assertIn(str(missing), result.stderr)
|
||||
self.assertNotIn("Traceback", result.stderr)
|
||||
@@ -373,7 +373,7 @@ class ChecksParsingTests(unittest.TestCase):
|
||||
("--include", "ARCH_SIZE"), ("--exclude", "ARCH_SIZE")):
|
||||
with self.subTest(options=options):
|
||||
result = subprocess.run([sys.executable, str(TOOL), str(cache), str(seed), *options],
|
||||
capture_output=True, text=True)
|
||||
capture_output=True, encoding="utf-8")
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
text = seed.read_text(encoding="utf-8")
|
||||
self.assertNotIn("ARCH_SIZE", text)
|
||||
|
||||
@@ -1679,6 +1679,42 @@ class ImportAuditTests(unittest.TestCase):
|
||||
with self.assertRaisesRegex(import_audit.AuditError, "more than 256 preprocessor branch combinations"):
|
||||
import_audit.source_stub_classes(source)
|
||||
|
||||
def test_unrelated_helper_host_branches_do_not_abort_source_classification(self):
|
||||
blocks = "".join(f"#ifdef HOST_{index}\n value += {index};\n#endif\n" for index in range(10))
|
||||
source = self.work.source
|
||||
write_source(source, "libHosts",
|
||||
f"int PollDescriptors(int value) {{\n{blocks} return value;\n}}\n"
|
||||
"int PollAgain(int value) { return PollDescriptors(value); }\n"
|
||||
"void FirstFail() { LastFail(); }\n"
|
||||
"void LastFail() { NotImplemented_nid_no_patch(__func__); }\n"
|
||||
"int APS5_VABI scePoll(int value) { return PollAgain(value); }\n"
|
||||
"int APS5_VABI sceStub(int value) { FirstFail(); return value; }\n"
|
||||
"int APS5_VABI scePartial(int value) { if (value) FirstFail(); return value; }\n")
|
||||
stubs, partial = import_audit.source_stub_classes(source)
|
||||
stub = ("libHosts.prx", nid_names.compute_nid("sceStub"))
|
||||
guarded = ("libHosts.prx", nid_names.compute_nid("scePartial"))
|
||||
self.assertEqual(set(stubs), {stub, guarded})
|
||||
self.assertEqual(partial, {guarded})
|
||||
|
||||
def test_relevant_helper_at_the_variant_limit_remains_a_stub(self):
|
||||
blocks = "".join(f"#ifdef HOST_{index}\n value += {index};\n#endif\n" for index in range(8))
|
||||
source = self.work.source
|
||||
write_source(source, "libHosts",
|
||||
f"void Fail(int value) {{\n{blocks} NotImplemented_nid_no_patch(__func__);\n}}\n"
|
||||
"int APS5_VABI sceStub(int value) { Fail(value); return value; }\n")
|
||||
stub = ("libHosts.prx", nid_names.compute_nid("sceStub"))
|
||||
self.assertEqual(import_audit.source_stub_classes(source), ({stub: "sceStub"}, set()))
|
||||
|
||||
def test_relevant_helper_host_branches_keep_the_variant_limit(self):
|
||||
blocks = "".join(f"#ifdef HOST_{index}\n value += {index};\n#endif\n" for index in range(9))
|
||||
source = self.work.source
|
||||
write_source(source, "libHosts",
|
||||
f"void LastFail(int value) {{\n{blocks} NotImplemented_nid_no_patch(__func__);\n}}\n"
|
||||
"void FirstFail(int value) { LastFail(value); }\n"
|
||||
"int APS5_VABI sceStub(int value) { FirstFail(value); return value; }\n")
|
||||
with self.assertRaisesRegex(import_audit.AuditError, "more than 256 preprocessor branch combinations"):
|
||||
import_audit.source_stub_classes(source)
|
||||
|
||||
def test_partial_import_is_its_own_class(self):
|
||||
direct, guarded = nid_names.compute_nid("sceDirect"), nid_names.compute_nid("sceGuarded")
|
||||
stubs = {("libA.prx", direct): "sceDirect", ("libA.prx", guarded): "sceGuarded", ("libB.prx", guarded): "sceGuarded"}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import sys
|
||||
import unittest
|
||||
from contextlib import ExitStack
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
import pr_overlap
|
||||
|
||||
|
||||
class FailClosedTests(unittest.TestCase):
|
||||
def run_branch_check(self, found):
|
||||
argv = ["pr_overlap.py", "--repo", "owner/project", "--branch", "topic"]
|
||||
patches = (
|
||||
patch.object(sys, "argv", argv),
|
||||
patch.object(pr_overlap, "open_prs", return_value={7: {"ref": "refs/pr/7"}}),
|
||||
patch.object(pr_overlap, "fetch"),
|
||||
patch.object(pr_overlap, "own", return_value={7: {"ref": "refs/pr/7"}}),
|
||||
patch.object(pr_overlap, "git", return_value="a" * 40),
|
||||
patch.object(pr_overlap, "scan_all"),
|
||||
patch.object(pr_overlap, "base_exports", return_value={}),
|
||||
patch.object(pr_overlap, "main_export_findings", return_value={}),
|
||||
patch.object(pr_overlap, "overlaps", return_value=found),
|
||||
patch.object(pr_overlap, "report", return_value="review result\n"),
|
||||
patch.object(pr_overlap, "progress"),
|
||||
patch("builtins.print"),
|
||||
)
|
||||
with ExitStack() as stack:
|
||||
for item in patches:
|
||||
stack.enter_context(item)
|
||||
return pr_overlap.main()
|
||||
|
||||
def test_uncompared_shared_files_fail_the_local_branch_check(self):
|
||||
result = self.run_branch_check({"added": set(), "uncompared": {"VectorAlgorithms.cpp"}})
|
||||
self.assertEqual(result, 2)
|
||||
|
||||
def test_confirmed_comparisons_keep_the_local_branch_check_green(self):
|
||||
result = self.run_branch_check({"added": set(), "conflicts": {}, "exports": set()})
|
||||
self.assertEqual(result, 0)
|
||||
|
||||
def test_reports_every_uncompared_path(self):
|
||||
self.assertEqual(
|
||||
pr_overlap.unverified_overlaps({
|
||||
7: {"uncompared": {"b.cpp", "a.cpp"}},
|
||||
8: {"conflicts": {"c.cpp": [(1, 2)]}},
|
||||
}),
|
||||
{7: {"a.cpp", "b.cpp"}},
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user