mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-05 13:20:11 +00:00
chore(ngs2): update Gain prerequisite to current main
This commit is contained in:
commit
154ca3754e
34 files changed
+654
-129
No files matched your search
@@ -4804,11 +4804,14 @@ void idlePublishWaitTests(const Device& device, Recorder& recorder) {
|
||||
recorder.Sync();
|
||||
Require(words[2 * unit] == 0x44 && words[4 * unit - 1] == 0x45, "a CPU store made after the idle flush returned was overwritten by its publish");
|
||||
Require(words[2 * unit + 1] == 0x22 && words[3 * unit - 1] == 0x22 && words[3 * unit] == 0x33 && words[1 * unit] == 0x11 && words[4 * unit] == 0x11, "the idle publish's bytes are wrong");
|
||||
words[5 * unit] = 0x11;
|
||||
CollectWritesUncached(address, bytes);
|
||||
retile(address + 5 * unit, 0x55);
|
||||
StorageTexture::FlushAllPending("label");
|
||||
Require(recorder.PendingWriteOverlaps(address + 5 * unit, unit), "a publish of a flush other than the idle one was waited");
|
||||
const bool pending = recorder.PendingWriteOverlaps(address + 5 * unit, unit);
|
||||
recorder.Sync();
|
||||
Require(words[5 * unit] == 0x55 && words[6 * unit - 1] == 0x55, "the label flush's publish did not land");
|
||||
Require(words[5 * unit] == 0x55 && words[6 * unit - 1] == 0x55, "the label flush's publish did not land: the unit read as written after its retile, so the publish dropped it as stale");
|
||||
Require(pending, "a publish of a flush other than the idle one was waited");
|
||||
std::cout << "idle publish wait verified\n";
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_SAVEDEXECMASKLOOP_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_SAVEDEXECMASKLOOP_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace AgcTest {
|
||||
|
||||
inline std::vector<std::uint32_t> SavedExecMaskLoop(std::uint32_t rounds) {
|
||||
std::vector<std::uint32_t> code{0xbea2047eu, 0xbea40380u};
|
||||
for (std::uint32_t step = 0; step < rounds; ++step) code.insert(code.end(), {0xbefe0422u, 0x7c080080u, 0x8aa26a7eu});
|
||||
const auto branch = 0xbf850000u | static_cast<std::uint16_t>(2 - static_cast<int>(code.size() + 3));
|
||||
code.insert(code.end(), {0x80248124u, 0xbf0a8424u, branch, 0xbefe0422u});
|
||||
return code;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "IntermediateRepresentation/IrBuilder.hpp"
|
||||
#include "IntermediateRepresentation/IrProgram.hpp"
|
||||
#include "SpirvBackend/SpirvAnalysis.hpp"
|
||||
#include "SavedExecMaskLoop.hpp"
|
||||
#if ANYPS5_ENABLE_SPIRV_TOOLS
|
||||
#include "SpirvBackend/SpirvOptimizer.hpp"
|
||||
#endif
|
||||
@@ -4088,6 +4089,18 @@ void verifyHalfWaveReduction() {
|
||||
{0u, 0x38000000u, 0x88000000u, spv::OpGroupNonUniformBitwiseOr, "half-wave reduction: the wave64 OR scan did not read lane 31 as a subgroup OR on 32 lanes"},
|
||||
}};
|
||||
for (const auto& reduction : scans) require(reductions(pixel(scan(reduction.identity, reduction.vector, reduction.scalar), 32u, true), reduction.reduce) == 1u, reduction.message);
|
||||
auto loopScan = scan(0xffffffffu, 0x26000000u, 0x83800000u);
|
||||
const auto savedMaskLoop = AgcTest::SavedExecMaskLoop(8u);
|
||||
loopScan.insert(loopScan.begin(), savedMaskLoop.begin(), savedMaskLoop.end());
|
||||
auto helperLoop = loopScan;
|
||||
helperLoop[0] = 0xbea204c1u;
|
||||
expectFailure([&] { static_cast<void>(pixel(helperLoop, 32u, true)); }, "v_readlane_b32 of lane 63 is outside the 32-lane host subgroup", "half-wave reduction: a loop initialized with helper lanes was proven to exclude them");
|
||||
auto upperHelperLoop = loopScan;
|
||||
upperHelperLoop[0] = 0xbea2037eu;
|
||||
upperHelperLoop.insert(upperHelperLoop.begin() + 1, 0xbea303c1u);
|
||||
expectFailure([&] { static_cast<void>(pixel(upperHelperLoop, 32u, true)); }, "v_readlane_b32 of lane 63 is outside the 32-lane host subgroup", "half-wave reduction: the low-word loop proof was reused for helper-inclusive upper lanes");
|
||||
require(reductions(pixel(loopScan, 32u, true), spv::OpGroupNonUniformUMin) == 1u, "half-wave reduction: a loop-carried saved EXEC mask exhausted the entry-lane proof on 32 lanes");
|
||||
require(halves(pixel(loopScan, 64u, true), spv::OpGroupNonUniformUMin, 0xffffffffu), "half-wave reduction: a loop-carried saved EXEC mask did not preserve the two half-wave UMin reductions");
|
||||
auto comparedScan = scan(0u, 0x38000000u, 0x88000000u);
|
||||
*std::find(comparedScan.begin(), comparedScan.end(), 0xbe98047eu) = 0x8a986a7eu;
|
||||
comparedScan.insert(comparedScan.begin(), 0x7c080080u);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <cstdlib>
|
||||
#include <future>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
@@ -75,16 +76,29 @@ void AppendLabel(std::vector<std::uint32_t>& words, std::uint32_t* target, std::
|
||||
}
|
||||
|
||||
struct Blocker {
|
||||
std::atomic<bool> release{false};
|
||||
struct State {
|
||||
std::atomic<bool> release{false};
|
||||
std::atomic<bool> done{false};
|
||||
};
|
||||
std::shared_ptr<State> state = std::make_shared<State>();
|
||||
std::uint32_t* target = nullptr;
|
||||
~Blocker() { release = true; }
|
||||
~Blocker() { Release(); }
|
||||
void Release() { state->release = true; }
|
||||
void Enqueue(std::vector<DrawPipeline::Range> writes = {}) {
|
||||
DrawPipeline::Queue0().Enqueue([this] {
|
||||
while (!release.load()) std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
DrawPipeline::Queue0().Enqueue([state = state, target = target] {
|
||||
while (!state->release.load()) std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
if (target != nullptr) std::atomic_ref<std::uint32_t>(*target).store(1);
|
||||
std::lock_guard gpuLock(AgcDriver::GuestMemory::GpuMutex());
|
||||
{ std::lock_guard gpuLock(AgcDriver::GuestMemory::GpuMutex()); }
|
||||
state->done = true;
|
||||
}, std::move(writes));
|
||||
}
|
||||
void Join() const {
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
while (!state->done.load()) {
|
||||
Require(std::chrono::steady_clock::now() - start < std::chrono::seconds(20), "the blocker never ran");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void SubmitBehind(Blocker& blocker, std::vector<std::uint32_t>& words) {
|
||||
@@ -107,7 +121,7 @@ void ReleaseWhen(Blocker& blocker, std::size_t reached, std::size_t passed) {
|
||||
Require(now - start < std::chrono::seconds(20), "the queue worker never reached the fill");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
blocker.release = true;
|
||||
blocker.Release();
|
||||
}
|
||||
|
||||
void Finish() {
|
||||
@@ -118,6 +132,11 @@ void Finish() {
|
||||
std::_Exit(1);
|
||||
}
|
||||
idle.get();
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
while (DrawPipeline::Queue0().Outstanding() != 0) {
|
||||
Require(std::chrono::steady_clock::now() - start < std::chrono::seconds(20), "the draw pipeline kept an item after the queue went idle");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast<std::uintptr_t>(Output.data()), sizeof(Output));
|
||||
AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast<std::uintptr_t>(Scratch.data()), sizeof(Scratch));
|
||||
}
|
||||
@@ -135,6 +154,7 @@ void InputsAfterQueuedLabels() {
|
||||
SubmitBehind(blocker, words);
|
||||
ReleaseWhen(blocker, 4, 5);
|
||||
Finish();
|
||||
blocker.Join();
|
||||
for (std::size_t i = 0; i < Output.size(); ++i) {
|
||||
const auto expected = i < 128 ? 0xababababu : 0xcafebabeu;
|
||||
Require(Output[i] == expected, "a fill read its control or pattern before the queued labels that write them, at " + std::to_string(i));
|
||||
@@ -156,6 +176,7 @@ void FillIntoFullPipeline() {
|
||||
SubmitBehind(blocker, words);
|
||||
ReleaseWhen(blocker, depth, depth + 1);
|
||||
Finish();
|
||||
blocker.Join();
|
||||
for (std::size_t i = 0; i + 2 < depth; ++i) Require(Labels[i] == i + 1, "a queued label was not stored");
|
||||
for (std::size_t i = 0; i < Output.size(); ++i) Require(Output[i] == (i < 256 ? 0x5a5a5a5au : 0xcafebabeu), "a fill behind a full draw pipeline did not land at " + std::to_string(i));
|
||||
}
|
||||
@@ -180,8 +201,9 @@ void SatisfiedWaitKeepsUnrelatedQueuedWork() {
|
||||
while (DrawPipeline::Queue0().Outstanding() < 2 && std::chrono::steady_clock::now() < deadline)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
const bool keptQueued = DrawPipeline::Queue0().Outstanding() >= 2;
|
||||
blocker.release = true;
|
||||
blocker.Release();
|
||||
Finish();
|
||||
blocker.Join();
|
||||
Require(keptQueued, "a satisfied WAIT_REG_MEM drained unrelated queued work");
|
||||
Require(marker == 1, "a label after a satisfied WAIT_REG_MEM did not execute");
|
||||
}
|
||||
@@ -211,11 +233,12 @@ void SatisfiedWaitDrainsOverlappingQueuedWork() {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
const bool drained = DrawPipeline::DrainRequestsByOpcode(0x3c) > drainsBefore;
|
||||
const bool labelQueuedEarly = DrawPipeline::Queue0().Outstanding() >= 2;
|
||||
blocker.release = true;
|
||||
blocker.Release();
|
||||
Require(submitted.wait_for(std::chrono::seconds(20)) == std::future_status::ready,
|
||||
"WAIT_REG_MEM submission did not finish after the overlapping write");
|
||||
submitted.get();
|
||||
Finish();
|
||||
blocker.Join();
|
||||
Require(drained && !labelQueuedEarly, "a satisfied WAIT_REG_MEM skipped an overlapping queued write");
|
||||
Require(marker == 1, "a label after an overlapping WAIT_REG_MEM did not execute");
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ int APS5_VABI sceAudioOut2LoInit(void) {
|
||||
return sceAudioOut2Initialize();
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioOut2LoTerminate(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioOut2LoContextQueryMemory(const AudioOut2ContextParam* params, size_t* memory_size) {
|
||||
return sceAudioOut2ContextQueryMemory(params, memory_size);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,11 @@ int APS5_VABI sceAudioOut2Initialize(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioOut2InitializeEx(std::uint32_t param) {
|
||||
(void)param;
|
||||
return sceAudioOut2Initialize();
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioOut2Set3DLatency(int userId, std::uint32_t output) {
|
||||
if (output > MAX_SUPPORTED_3D_OUTPUT) return SCE_AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
if (userId != 0 && (userId != 0xFF || output == 0)) return SCE_AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
|
||||
@@ -380,11 +380,6 @@ int APS5_VABI sceAudioOut2DebugStateCtrl(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioOut2LoTerminate(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioOut2ContextBedWrite(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
@@ -395,11 +390,6 @@ int APS5_VABI sceAudioOut2PortCreateEx(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioOut2InitializeEx(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioOut2Terminate(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
extern "C" {
|
||||
int APS5_VABI sceAudioOut2Initialize();
|
||||
int APS5_VABI sceAudioOut2InitializeEx(std::uint32_t);
|
||||
int APS5_VABI sceAudioOut2LoInit();
|
||||
int APS5_VABI sceAudioOut2LoTerminate();
|
||||
int APS5_VABI sceAudioOut2Set3DLatency(int, std::uint32_t);
|
||||
@@ -125,7 +126,9 @@ void TestEnableChat() {
|
||||
void TestLowLatencyService() {
|
||||
Require(sceAudioOut2LoInit() == 0, "starting the low-latency service must be accepted");
|
||||
Require(sceAudioOut2LoInit() == 0, "starting the low-latency service again must be accepted");
|
||||
Require(ThrowsRuntimeError([] { sceAudioOut2LoTerminate(); }), "stopping the low-latency service must throw");
|
||||
Require(sceAudioOut2LoTerminate() == 0, "stopping the low-latency service returns 0");
|
||||
Require(sceAudioOut2LoTerminate() == 0, "stopping a stopped low-latency service returns 0");
|
||||
Require(sceAudioOut2InitializeEx(0) == 0 && sceAudioOut2InitializeEx(1) == 0, "the extended initialization succeeds");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -619,15 +619,6 @@ std::int64_t message_length(const NetMsghdr* message) {
|
||||
return static_cast<std::int64_t>(total);
|
||||
}
|
||||
|
||||
int receive_error(const Sock& socket) {
|
||||
#ifdef _WIN32
|
||||
if (WSAGetLastError() == WSAETIMEDOUT && socket.type != NET_SOCK_STREAM && socket.native->receiveTimeout > 0) return NET_EAGAIN;
|
||||
#else
|
||||
(void)socket;
|
||||
#endif
|
||||
return native_error();
|
||||
}
|
||||
|
||||
bool message_flags_supported(int flags, int accepted) {
|
||||
return (flags & ~accepted) == 0;
|
||||
}
|
||||
@@ -655,7 +646,7 @@ auto native_transfer(const NativeSocketHandle& socket, int flags, bool send, TOp
|
||||
if (timeout > 0) {
|
||||
const auto remaining = deadline - std::chrono::steady_clock::now();
|
||||
if (remaining <= std::chrono::steady_clock::duration::zero()) {
|
||||
WSASetLastError(completeLength > 0 ? WSAEWOULDBLOCK : WSAETIMEDOUT);
|
||||
WSASetLastError(send && completeLength == 0 ? WSAETIMEDOUT : WSAEWOULDBLOCK);
|
||||
return transferred > 0 ? static_cast<Result>(transferred) : static_cast<Result>(-1);
|
||||
}
|
||||
milliseconds = static_cast<int>(std::min<std::int64_t>(std::chrono::ceil<std::chrono::milliseconds>(remaining).count(), milliseconds));
|
||||
@@ -1268,7 +1259,7 @@ int64_t APS5_VABI sceNetRecv(int s, void* buf, size_t len, int flags) {
|
||||
#ifdef _WIN32
|
||||
if (result < 0 && socket.type == NET_SOCK_DGRAM && WSAGetLastError() == WSAEMSGSIZE) return static_cast<int64_t>(len);
|
||||
#endif
|
||||
return result >= 0 ? result : fail(receive_error(socket));
|
||||
return result >= 0 ? result : fail(native_error());
|
||||
}
|
||||
|
||||
int64_t APS5_VABI sceNetRecvfrom(int s, void* buf, size_t len, int flags, void* from, uint32_t* fromlen) {
|
||||
@@ -1309,7 +1300,7 @@ int64_t APS5_VABI sceNetRecvfrom(int s, void* buf, size_t len, int flags, void*
|
||||
#ifdef _WIN32
|
||||
if (result < 0 && socket.type == NET_SOCK_DGRAM && WSAGetLastError() == WSAEMSGSIZE) result = static_cast<int>(len);
|
||||
#endif
|
||||
if (result < 0) return fail(receive_error(socket));
|
||||
if (result < 0) return fail(native_error());
|
||||
if (from && !native_to_guest_address(peer, from, fromlen)) return fail(NET_EAFNOSUPPORT);
|
||||
return result;
|
||||
}
|
||||
@@ -1427,7 +1418,7 @@ int64_t APS5_VABI sceNetRecvmsg(int s, NetMsghdr* msg, int flags) {
|
||||
});
|
||||
if (received < 0) {
|
||||
#ifdef _WIN32
|
||||
if (!datagram || WSAGetLastError() != WSAEMSGSIZE) return fail(receive_error(socket));
|
||||
if (!datagram || WSAGetLastError() != WSAEMSGSIZE) return fail(native_error());
|
||||
received = static_cast<std::int64_t>(buffer.size());
|
||||
#else
|
||||
return fail(native_error());
|
||||
@@ -2482,12 +2473,11 @@ int APS5_VABI sceNetIoctl(int socketId, std::uint32_t command, void* argument) {
|
||||
|
||||
void* APS5_VABI sceNetMemoryAllocate(std::int32_t size, int flags) {
|
||||
constexpr int zeroFill = 2;
|
||||
constexpr std::size_t blockAlignment = 64;
|
||||
if (size <= 0) return nullptr;
|
||||
if ((flags & 4) != 0)
|
||||
throw std::runtime_error("sceNetMemoryAllocate: pool reserve allocation is not implemented");
|
||||
void* memory = nullptr;
|
||||
try {
|
||||
memory = GuestHeap::GuestHeapAllocate_nid_postfix(static_cast<std::size_t>(size));
|
||||
memory = GuestHeap::GuestHeapAlign_nid_postfix(blockAlignment, static_cast<std::size_t>(size));
|
||||
} catch (const std::bad_alloc&) {
|
||||
return nullptr;
|
||||
} catch (const std::length_error&) {
|
||||
|
||||
@@ -212,6 +212,7 @@ static Ngs2SystemOption DefaultSystemOption() {
|
||||
option.max_grain_samples = 512;
|
||||
option.num_grain_samples = 256;
|
||||
option.sample_rate = 48000;
|
||||
option.max_voice_channels = 8;
|
||||
return option;
|
||||
}
|
||||
|
||||
@@ -422,7 +423,7 @@ int APS5_VABI sceNgs2SystemRender(uintptr_t system_handle, const Ngs2RenderBuffe
|
||||
}
|
||||
|
||||
int APS5_VABI sceNgs2SystemResetOption(Ngs2SystemOption* option) {
|
||||
if (option == nullptr) APS5_INVALID_ARG_EX;
|
||||
if (option == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS;
|
||||
*option = DefaultSystemOption();
|
||||
return SCE_NGS2_OK;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "Ngs2Test.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
@@ -7,6 +9,28 @@ static void Status(int actual, int expected, const char* message) {
|
||||
if (actual != expected) throw std::runtime_error(message);
|
||||
}
|
||||
|
||||
static void CheckResetOption() {
|
||||
Status(sceNgs2SystemResetOption(nullptr), static_cast<int>(0x804A8010u), "null system option did not return the invalid out address error");
|
||||
constexpr std::uint64_t size = 0x90;
|
||||
constexpr std::uint32_t maxGrainSamples = 512;
|
||||
constexpr std::uint32_t numGrainSamples = 256;
|
||||
constexpr std::uint32_t sampleRate = 48000;
|
||||
constexpr std::uint32_t maxVoiceChannels = 8;
|
||||
unsigned char expected[0x90] = {};
|
||||
std::memcpy(expected + 0x00, &size, sizeof(size));
|
||||
std::memcpy(expected + 0x6c, &maxGrainSamples, sizeof(maxGrainSamples));
|
||||
std::memcpy(expected + 0x70, &numGrainSamples, sizeof(numGrainSamples));
|
||||
std::memcpy(expected + 0x74, &sampleRate, sizeof(sampleRate));
|
||||
std::memcpy(expected + 0x78, &maxVoiceChannels, sizeof(maxVoiceChannels));
|
||||
static_assert(sizeof(Ngs2SystemOption) == sizeof(expected));
|
||||
Ngs2SystemOption option;
|
||||
std::memset(&option, 0xAB, sizeof(option));
|
||||
Status(sceNgs2SystemResetOption(&option), SCE_NGS2_OK, "system option reset failed");
|
||||
Require(std::memcmp(&option, expected, sizeof(expected)) == 0);
|
||||
Ngs2ContextBufferInfo query{};
|
||||
Status(sceNgs2SystemQueryBufferSize(&option, &query), SCE_NGS2_OK, "reset system option was rejected");
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
constexpr int systemError = static_cast<int>(0x804A8201u);
|
||||
@@ -39,6 +63,7 @@ int main() {
|
||||
Status(sceNgs2SystemDestroy(system, nullptr), SCE_NGS2_OK, "valid system destroy failed");
|
||||
Status(sceNgs2SystemGetUserData(system, &value), systemError, "retired system returned the wrong error");
|
||||
Require(value == sentinel);
|
||||
CheckResetOption();
|
||||
std::cout << "NGS2 PS5 handle errors and validation precedence checked\n";
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <cstdint>
|
||||
|
||||
static constexpr int SCE_NP_TROPHY2_OK = 0;
|
||||
static constexpr int SCE_NP_TROPHY2_ERROR_INVALID_TROPHY_ID = -2141898486;
|
||||
static constexpr int SCE_NP_TROPHY2_ERROR_ICON_FILE_NOT_FOUND = -2141898479;
|
||||
|
||||
static constexpr int NP_TROPHY2_CONTEXT_DEFAULT = 1;
|
||||
|
||||
@@ -82,9 +82,7 @@ int APS5_VABI sceNpTrophy2GetTrophyInfo(int context, int handle, int trophy_id,
|
||||
return SCE_NP_TROPHY2_OK;
|
||||
}
|
||||
const auto trophy = package->trophies.find(trophy_id);
|
||||
if (trophy == package->trophies.end()) {
|
||||
throw std::invalid_argument(std::string(__func__) + ": trophy " + std::to_string(trophy_id) + " is not in " + NpTrophy2::PackagePath(owner->serviceLabel));
|
||||
}
|
||||
if (trophy == package->trophies.end()) return SCE_NP_TROPHY2_ERROR_INVALID_TROPHY_ID;
|
||||
if (details != nullptr) fillDetails(trophy->second, details);
|
||||
if (data != nullptr) fillData(*owner, trophy->second, data);
|
||||
return SCE_NP_TROPHY2_OK;
|
||||
|
||||
@@ -62,6 +62,7 @@ target_sources(libc PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/HostEntropy.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Backtrace.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/DinkumwareAtomics.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/DinkumwareInternals.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Time.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/TimeExtras.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/AtomicBuiltins.cpp
|
||||
@@ -526,6 +527,15 @@ if(BUILD_TESTING)
|
||||
set_tests_properties(guest_dinkumware_atomics PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test_executable(guest_dinkumware_internals_tests ${LIBS_INCLUDE_DIR}/tests/GuestDinkumwareInternals.cpp)
|
||||
target_include_directories(guest_dinkumware_internals_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_dinkumware_internals_tests PRIVATE libc)
|
||||
configure_windows_unwind(guest_dinkumware_internals_tests)
|
||||
add_test(NAME guest_dinkumware_internals COMMAND guest_dinkumware_internals_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_dinkumware_internals PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test_executable(guest_hostname_tests ${LIBS_INCLUDE_DIR}/tests/GuestHostname.cpp)
|
||||
target_include_directories(guest_hostname_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_hostname_tests PRIVATE libc)
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
|
||||
#include "prx/libc/include/General.hpp"
|
||||
|
||||
extern "C" {
|
||||
int* APS5_VABI __error_nid_postfix();
|
||||
double APS5_VABI strtod_nid_postfix(const char* str, char** endptr);
|
||||
long long APS5_VABI strtoll_nid_postfix(const char* str, char** endptr, int base);
|
||||
double APS5_VABI sin_nid_postfix(double x);
|
||||
double APS5_VABI cos_nid_postfix(double x);
|
||||
float APS5_VABI sinf_nid_postfix(float x);
|
||||
float APS5_VABI cosf_nid_postfix(float x);
|
||||
long double APS5_VABI sinl_nid_postfix(long double x);
|
||||
long double APS5_VABI cosl_nid_postfix(long double x);
|
||||
double APS5_VABI log_nid_postfix(double x);
|
||||
double APS5_VABI log10_nid_postfix(double x);
|
||||
double APS5_VABI log2_nid_postfix(double x);
|
||||
float APS5_VABI logf_nid_postfix(float x);
|
||||
float APS5_VABI log10f_nid_postfix(float x);
|
||||
float APS5_VABI log2f_nid_postfix(float x);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr long long ExponentLimit = 1000000000;
|
||||
constexpr int RangeError = 34;
|
||||
|
||||
bool Hexadecimal(const char* text) {
|
||||
while (std::isspace(static_cast<unsigned char>(*text))) ++text;
|
||||
if (*text == '+' || *text == '-') ++text;
|
||||
return text[0] == '0' && (text[1] == 'x' || text[1] == 'X');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
double APS5_VABI _Stod_nid_postfix(const char* text, char** end, std::int64_t pten) {
|
||||
int& guestErrno = *__error_nid_postfix();
|
||||
const int saved = guestErrno;
|
||||
guestErrno = 0;
|
||||
char* stop = nullptr;
|
||||
const double value = strtod_nid_postfix(text, &stop);
|
||||
if (end != nullptr) *end = stop;
|
||||
const bool overflowed = std::isinf(value) && guestErrno == RangeError;
|
||||
if (pten == 0 || stop == text || !(std::isfinite(value) || overflowed)) {
|
||||
if (guestErrno == 0) guestErrno = saved;
|
||||
return value;
|
||||
}
|
||||
if (Hexadecimal(text)) NotImplemented_nid_no_patch("_Stod (hexadecimal text with a power-of-ten scale)");
|
||||
std::string number(text, static_cast<const char*>(stop));
|
||||
long long exponent = 0;
|
||||
const auto marker = number.find_first_of("eE");
|
||||
if (marker != std::string::npos) {
|
||||
exponent = std::strtoll(number.c_str() + marker + 1, nullptr, 10);
|
||||
number.resize(marker);
|
||||
}
|
||||
exponent = std::clamp(exponent, -ExponentLimit, ExponentLimit) + std::clamp<std::int64_t>(pten, -ExponentLimit, ExponentLimit);
|
||||
number += 'e';
|
||||
number += std::to_string(exponent);
|
||||
guestErrno = saved;
|
||||
return strtod_nid_postfix(number.c_str(), nullptr);
|
||||
}
|
||||
|
||||
long long APS5_VABI _Stoll_nid_postfix(const char* text, char** end, int base) {
|
||||
return strtoll_nid_postfix(text, end, base);
|
||||
}
|
||||
|
||||
double APS5_VABI _Sin_nid_postfix(double x, unsigned int quadrant) {
|
||||
return quadrant != 0 ? cos_nid_postfix(x) : sin_nid_postfix(x);
|
||||
}
|
||||
|
||||
float APS5_VABI _FSin_nid_postfix(float x, unsigned int quadrant) {
|
||||
return quadrant != 0 ? cosf_nid_postfix(x) : sinf_nid_postfix(x);
|
||||
}
|
||||
|
||||
long double APS5_VABI _LSin_nid_postfix(long double x, unsigned int quadrant) {
|
||||
return quadrant != 0 ? cosl_nid_postfix(x) : sinl_nid_postfix(x);
|
||||
}
|
||||
|
||||
double APS5_VABI _Log_nid_postfix(double x, int base) {
|
||||
if (base > 0) return log10_nid_postfix(x);
|
||||
return base == 0 ? log_nid_postfix(x) : log2_nid_postfix(x);
|
||||
}
|
||||
|
||||
float APS5_VABI _FLog_nid_postfix(float x, int base) {
|
||||
if (base > 0) return log10f_nid_postfix(x);
|
||||
return base == 0 ? logf_nid_postfix(x) : log2f_nid_postfix(x);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -68,7 +68,7 @@ int APS5_VABI sceCoredumpAttachMemoryRegionAsUserFile(std::uint32_t userValue, c
|
||||
}
|
||||
|
||||
int APS5_VABI sceCoredumpSetUserDataType(std::int32_t type) {
|
||||
NotImplemented_nid_no_patch("Uxqkdta7wEg");
|
||||
(void)type;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ int64_t APS5_VABI sceCoredumpWriteUserData(const void* data, size_t size);
|
||||
void APS5_VABI sceCoredumpDebugTextOut(const char* str, int len);
|
||||
int APS5_VABI sceCoredumpAttachUserFile(uint32_t userValue, const char* path);
|
||||
int APS5_VABI sceCoredumpAttachMemoryRegionAsUserFile(uint32_t userValue, const void* mem, std::int32_t size, const char* name);
|
||||
int APS5_VABI sceCoredumpSetUserDataType(std::int32_t type);
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -77,6 +78,7 @@ int main() {
|
||||
Require(sceCoredumpAttachUserFile(0x40000000u, "") == NotInCoredumpHandler);
|
||||
|
||||
Require(NotInCoredumpHandler < 0);
|
||||
Require(sceCoredumpSetUserDataType(0) == 0);
|
||||
RequireWritesRefused(region);
|
||||
RequireAttachesRefused(region);
|
||||
sceCoredumpDebugTextOut(text, static_cast<int>(sizeof(text) - 1));
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
extern "C" {
|
||||
int* APS5_VABI __error_nid_postfix();
|
||||
double APS5_VABI _Stod_nid_postfix(const char* text, char** end, std::int64_t pten);
|
||||
long long APS5_VABI _Stoll_nid_postfix(const char* text, char** end, int base);
|
||||
double APS5_VABI _Sin_nid_postfix(double x, unsigned int quadrant);
|
||||
float APS5_VABI _FSin_nid_postfix(float x, unsigned int quadrant);
|
||||
long double APS5_VABI _LSin_nid_postfix(long double x, unsigned int quadrant);
|
||||
double APS5_VABI _Log_nid_postfix(double x, int base);
|
||||
float APS5_VABI _FLog_nid_postfix(float x, int base);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int RangeError = 34;
|
||||
|
||||
void Require(bool condition, const char* message) {
|
||||
if (!condition) {
|
||||
std::fprintf(stderr, "Dinkumware internals: %s\n", message);
|
||||
std::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void CheckStod(const char* text, std::int64_t pten, double expected, long consumed, int error, const char* message) {
|
||||
*__error_nid_postfix() = 0;
|
||||
char* end = nullptr;
|
||||
const double value = _Stod_nid_postfix(text, &end, pten);
|
||||
const bool same = std::isnan(expected) ? std::isnan(value) : value == expected && std::signbit(value) == std::signbit(expected);
|
||||
Require(same && end - text == consumed && *__error_nid_postfix() == error, message);
|
||||
}
|
||||
|
||||
void TestStod() {
|
||||
CheckStod("1.5", 0, 1.5, 3, 0, "unscaled decimal");
|
||||
CheckStod("1.5", 1, 15.0, 3, 0, "decimal scaled up");
|
||||
CheckStod("12", -2, 0.12, 2, 0, "decimal scaled down");
|
||||
CheckStod(" -2.25e3xyz", 300, -0x1.a3f7f806a8135p+1007, 9, 0, "the scale is applied before rounding");
|
||||
CheckStod("1e-400", 300, 1e-100, 6, 0, "an underflowing text scaled back into range");
|
||||
CheckStod("1e-400", 0, 0.0, 6, RangeError, "an unscaled underflow");
|
||||
CheckStod("1e300", 100, HUGE_VAL, 5, RangeError, "a scale that overflows");
|
||||
CheckStod("1e300", 8, 1e308, 5, 0, "a scale up to the largest decades");
|
||||
CheckStod("5e-324", -1, 0.0, 6, RangeError, "a scale that underflows to zero");
|
||||
CheckStod("1", 2147483647, HUGE_VAL, 1, RangeError, "the largest scale");
|
||||
CheckStod("5", std::int64_t{1} << 40, HUGE_VAL, 1, RangeError, "a scale above 32 bits");
|
||||
CheckStod("1e400", -100, 1e300, 5, 0, "an overflowing text scaled back into range");
|
||||
CheckStod("0.01e41", -2, 1e37, 7, 0, "a negative scale from leading fraction zeros");
|
||||
CheckStod("1e-2147483648", -1, 0.0, 13, RangeError, "the smallest exponent and scale");
|
||||
CheckStod("-0", 5, -0.0, 2, 0, "negative zero keeps its sign");
|
||||
CheckStod("infinity", 3, HUGE_VAL, 8, 0, "infinity is not scaled");
|
||||
CheckStod("nan", 3, NAN, 3, 0, "NaN is not scaled");
|
||||
CheckStod("1e", 2, 100.0, 1, 0, "an exponent marker without digits");
|
||||
CheckStod(".5", 2, 50.0, 2, 0, "a fraction without integer digits");
|
||||
CheckStod("0.3", -1, 0.03, 3, 0, "0.3 scaled down rounds as 0.03");
|
||||
CheckStod("123456789012345678901234567890", -20, 1234567890.1234567, 30, 0, "a long digit string");
|
||||
CheckStod("abc", 4, 0.0, 0, 0, "no number");
|
||||
CheckStod("0x1.8p0", 0, 1.5, 7, 0, "unscaled hexadecimal");
|
||||
}
|
||||
|
||||
void TestStoll() {
|
||||
char* end = nullptr;
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(_Stoll_nid_postfix("-0x1F", &end, 0) == -31 && std::strcmp(end, "") == 0, "_Stoll base detection");
|
||||
Require(_Stoll_nid_postfix("+42abc", &end, 10) == 42 && std::strcmp(end, "abc") == 0, "_Stoll stops at the first non-digit");
|
||||
Require(_Stoll_nid_postfix(" z", &end, 36) == 35, "_Stoll base 36");
|
||||
Require(*__error_nid_postfix() == 0, "_Stoll set errno without an overflow");
|
||||
Require(_Stoll_nid_postfix("9223372036854775808", &end, 10) == 9223372036854775807ll && *__error_nid_postfix() == RangeError, "_Stoll saturates");
|
||||
}
|
||||
|
||||
void TestDispatch() {
|
||||
Require(_Sin_nid_postfix(0.5, 0) == std::sin(0.5) && _Sin_nid_postfix(0.5, 1) == std::cos(0.5) && _Sin_nid_postfix(0.5, 7) == std::cos(0.5), "_Sin");
|
||||
Require(_FSin_nid_postfix(0.5f, 0) == std::sin(0.5f) && _FSin_nid_postfix(0.5f, 2) == std::cos(0.5f), "_FSin");
|
||||
Require(_LSin_nid_postfix(0.5L, 0) == std::sin(0.5L) && _LSin_nid_postfix(0.5L, 3) == std::cos(0.5L), "_LSin");
|
||||
Require(_Log_nid_postfix(8.0, 0) == std::log(8.0) && _Log_nid_postfix(1000.0, 1) == 3.0 && _Log_nid_postfix(1000.0, 5) == 3.0 && _Log_nid_postfix(8.0, -1) == 3.0, "_Log");
|
||||
Require(_FLog_nid_postfix(8.0f, 0) == std::log(8.0f) && _FLog_nid_postfix(100.0f, 1) == 2.0f && _FLog_nid_postfix(8.0f, -3) == 3.0f, "_FLog");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
TestStod();
|
||||
TestStoll();
|
||||
TestDispatch();
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "prx/libSceNpTrophy2/include/NpTrophy2Types.hpp"
|
||||
#include "prx/libSceNpTrophy2/include/TrophyStore.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
@@ -36,6 +37,7 @@ int APS5_VABI sceNpTrophy2GetTrophyInfoArray(int, int, uint32_t, uint32_t, NpTro
|
||||
}
|
||||
|
||||
static constexpr int InitialUser = 0x10000000;
|
||||
static constexpr int InvalidTrophyId = static_cast<int>(0x8055390Au);
|
||||
|
||||
static const char TropConf[] = R"({"defaultLanguage":"en-US","trophies":[
|
||||
{"id":"0","grade":"P"},
|
||||
@@ -168,8 +170,18 @@ static void ReportTrophyData(int context) {
|
||||
Require(details.trophy_grade == 1 && std::strcmp(details.name, "All of them") == 0, "the platinum is not reported");
|
||||
const auto steps = Trophy(context, 5, &details);
|
||||
Require(details.target.type == 1 && details.target.value == 5 && !steps.unlocked && steps.progress.type == 1 && steps.progress.value == 0, "the progressive trophy does not report its target");
|
||||
NpTrophy2Data unknown{};
|
||||
Require(ThrowsInvalidArgument([context, &unknown] { sceNpTrophy2GetTrophyInfo(context, 1, 99, nullptr, &unknown); }), "a trophy that is not in the package was reported");
|
||||
const auto untouched = [](const void* bytes, std::size_t size) {
|
||||
const auto* first = static_cast<const unsigned char*>(bytes);
|
||||
return std::all_of(first, first + size, [](unsigned char byte) { return byte == 0x5a; });
|
||||
};
|
||||
for (const int unknownId : {8, 99, -1}) {
|
||||
NpTrophy2Details unknownDetails{};
|
||||
NpTrophy2Data unknown{};
|
||||
std::memset(&unknownDetails, 0x5a, sizeof(unknownDetails));
|
||||
std::memset(&unknown, 0x5a, sizeof(unknown));
|
||||
Require(sceNpTrophy2GetTrophyInfo(context, 1, unknownId, &unknownDetails, &unknown) == InvalidTrophyId, "a trophy that is not in the package did not return the invalid trophy id error");
|
||||
Require(untouched(&unknownDetails, sizeof(unknownDetails)) && untouched(&unknown, sizeof(unknown)), "a trophy that is not in the package wrote its details or data");
|
||||
}
|
||||
|
||||
std::vector<NpTrophy2Details> list(10);
|
||||
std::vector<NpTrophy2Data> listed(10);
|
||||
|
||||
@@ -67,6 +67,8 @@ int APS5_VABI sceRtcSetCurrentTick(const RtcTick*);
|
||||
int APS5_VABI sceRtcSetCurrentNetworkTick(const RtcTick*);
|
||||
int APS5_VABI sceRtcSetCurrentAdNetworkTick(const RtcTick*);
|
||||
int APS5_VABI sceRtcSetCurrentDebugNetworkTick(const RtcTick*);
|
||||
int APS5_VABI sceRtcInit(void);
|
||||
int APS5_VABI sceRtcEnd(void);
|
||||
}
|
||||
|
||||
static void Require(bool value) { if (!value) std::abort(); }
|
||||
@@ -907,4 +909,6 @@ int main() {
|
||||
Require(sceRtcGetCurrentDebugNetworkTick(¤t) == 0 && current.tick >= debugClock.tick && current.tick < debugClock.tick + 1000000);
|
||||
Require(sceRtcGetCurrentRawNetworkTick(¤t) == 0 && current.tick > unixEpochTick);
|
||||
}
|
||||
Require(sceRtcInit() == 0);
|
||||
Require(sceRtcEnd() == 0);
|
||||
}
|
||||
@@ -322,14 +322,20 @@ static void CheckRandom() {
|
||||
Require(Failed(sceNetGetRandom(nullptr), 22));
|
||||
std::array<std::uint32_t, 8> values{};
|
||||
for (auto& value : values) Require(sceNetGetRandom(&value) == 0);
|
||||
std::array<std::uint32_t, 2> guarded{0x5a5a5a5au, 0x5a5a5a5au};
|
||||
Require(sceNetGetRandom(guarded.data()) == 0 && guarded[1] == 0x5a5a5a5au && *sceNetErrnoLoc() == 22);
|
||||
Require(std::any_of(values.begin() + 1, values.end(), [&](std::uint32_t value) { return value != values[0]; }));
|
||||
}
|
||||
|
||||
static void CheckNetMemory() {
|
||||
Require(sceNetMemoryAllocate(0, 0) == nullptr);
|
||||
Require(sceNetMemoryAllocate(-1, 2) == nullptr);
|
||||
Require(sceNetMemoryAllocate(0, 4) == nullptr);
|
||||
Require(sceNetMemoryAllocate(INT32_MIN, -1) == nullptr);
|
||||
*sceNetErrnoLoc() = 33;
|
||||
for (const std::int32_t size : {0, -1, INT32_MIN}) {
|
||||
Require(sceNetMemoryAllocate(size, 0) == nullptr);
|
||||
Require(sceNetMemoryAllocate(size, 2) == nullptr);
|
||||
Require(sceNetMemoryAllocate(size, 4) == nullptr);
|
||||
Require(sceNetMemoryAllocate(size, -1) == nullptr);
|
||||
}
|
||||
Require(*sceNetErrnoLoc() == 33);
|
||||
auto* zeroed = static_cast<std::uint8_t*>(sceNetMemoryAllocate(4096, 2));
|
||||
Require(zeroed != nullptr);
|
||||
Require(std::all_of(zeroed, zeroed + 4096, [](std::uint8_t byte) { return byte == 0; }));
|
||||
@@ -340,20 +346,20 @@ static void CheckNetMemory() {
|
||||
Require(zeroed[4095] == 0xa5);
|
||||
sceNetMemoryFree(plain);
|
||||
sceNetMemoryFree(zeroed);
|
||||
for (const int flags : {1, 3, 4, 6, 8, 0x10, 0x100, 0x40000001, -5, INT32_MIN}) {
|
||||
auto* block = static_cast<std::uint8_t*>(sceNetMemoryAllocate(16, flags));
|
||||
Require(block != nullptr && reinterpret_cast<std::uintptr_t>(block) % 64 == 0);
|
||||
if ((flags & 2) != 0) Require(std::all_of(block, block + 16, [](std::uint8_t byte) { return byte == 0; }));
|
||||
std::memset(block, 0x5a, 16);
|
||||
sceNetMemoryFree(block);
|
||||
}
|
||||
for (const std::int32_t size : {1, 7, 256, 65536}) {
|
||||
void* block = sceNetMemoryAllocate(size, 0);
|
||||
Require(block != nullptr && reinterpret_cast<std::uintptr_t>(block) % 64 == 0);
|
||||
sceNetMemoryFree(block);
|
||||
}
|
||||
sceNetMemoryFree(nullptr);
|
||||
for (const int flags : {1, 0x40000001, -5}) {
|
||||
auto* allocation = static_cast<std::uint8_t*>(sceNetMemoryAllocate(16, flags));
|
||||
Require(allocation != nullptr);
|
||||
if ((flags & 2) != 0) Require(std::all_of(allocation, allocation + 16, [](std::uint8_t byte) { return byte == 0; }));
|
||||
sceNetMemoryFree(allocation);
|
||||
}
|
||||
bool threw = false;
|
||||
try {
|
||||
sceNetMemoryAllocate(16, 4);
|
||||
} catch (const std::runtime_error&) {
|
||||
threw = true;
|
||||
}
|
||||
Require(threw);
|
||||
Require(*sceNetErrnoLoc() == 33);
|
||||
}
|
||||
|
||||
static void CheckUnboundSocketName() {
|
||||
@@ -613,6 +619,36 @@ static void CheckReceiveTimeout(int receiver, int sender, const std::array<std::
|
||||
Require(sceNetSetsockopt(receiver, 0xffff, 0x1006, &disabled, sizeof(disabled)) == 0);
|
||||
}
|
||||
|
||||
static void CheckStreamReceiveTimeout() {
|
||||
const int listener = sceNetSocket("stream-timeout", 2, 1, 6);
|
||||
Require(listener >= 0);
|
||||
std::array<std::uint8_t, 16> address{16, 2, 0, 0, 127, 0, 0, 1};
|
||||
Require(sceNetBind_nid_postfix(listener, address.data(), address.size()) == 0 && sceNetListen(listener, 1) == 0);
|
||||
std::uint32_t size = address.size();
|
||||
Require(sceNetGetsockname(listener, address.data(), &size) == 0);
|
||||
const int client = sceNetSocket("stream-timeout-client", 2, 1, 6);
|
||||
Require(client >= 0 && sceNetConnect(client, address.data(), address.size()) == 0);
|
||||
const int accepted = sceNetAccept(listener, nullptr, nullptr);
|
||||
Require(accepted >= 0);
|
||||
const KernelTimeval timeout{0, 50000};
|
||||
Require(sceNetSetsockopt(client, 0xffff, 0x1006, &timeout, sizeof(timeout)) == 0);
|
||||
char buffer[8]{};
|
||||
NetIovec in[] = {{buffer, sizeof(buffer)}};
|
||||
NetMsghdr receive{nullptr, 0, in, 1, nullptr, 0, 0};
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
Require(Failed(sceNetRecv(client, buffer, sizeof(buffer), 0), 35));
|
||||
Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(40));
|
||||
Require(Failed(sceNetRecvfrom(client, buffer, sizeof(buffer), 0, nullptr, nullptr), 35));
|
||||
Require(Failed(sceNetRecvmsg(client, &receive, 0), 35));
|
||||
const char payload[] = "late";
|
||||
Require(sceNetSend(accepted, payload, sizeof(payload), 0) == sizeof(payload));
|
||||
Require(sceNetRecv(client, buffer, sizeof(buffer), 0) == sizeof(payload) && std::strcmp(buffer, payload) == 0);
|
||||
Require(sceNetSend(client, payload, sizeof(payload), 0) == sizeof(payload));
|
||||
std::memset(buffer, 0, sizeof(buffer));
|
||||
Require(sceNetRecv(accepted, buffer, sizeof(buffer), 0) == sizeof(payload) && std::strcmp(buffer, payload) == 0);
|
||||
for (const int descriptor : {client, accepted, listener}) Require(sceNetSocketClose(descriptor) == 0);
|
||||
}
|
||||
|
||||
static void CheckMicrosecondTimeouts(int receiver) {
|
||||
const int receiveTimeout = 50000;
|
||||
const int sendTimeout = 70000;
|
||||
@@ -1019,6 +1055,8 @@ static void CheckUsleep() {
|
||||
Require(std::chrono::steady_clock::now() - start >= std::chrono::microseconds(2000));
|
||||
*sceNetErrnoLoc() = 0;
|
||||
Require(Failed(sceNetUsleep(-1), 22));
|
||||
*sceNetErrnoLoc() = 0;
|
||||
Require(Failed(sceNetUsleep(INT32_MIN), 22));
|
||||
}
|
||||
|
||||
static void CheckEpollIdent() {
|
||||
@@ -1784,6 +1822,7 @@ int main() {
|
||||
stdio_readable[0] = 1;
|
||||
Require(select_nid_postfix(1, stdio_readable, nullptr, nullptr, poll_now) == 0 && stdio_readable[0] == 0);
|
||||
CheckReceiveTimeout(udp_receiver, udp_sender, address);
|
||||
CheckStreamReceiveTimeout();
|
||||
CheckMicrosecondTimeouts(udp_receiver);
|
||||
CheckMessages(udp_receiver, udp_sender, address);
|
||||
Require(sceNetSocketClose(udp_sender) == 0);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <initializer_list>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
@@ -322,74 +323,126 @@ public:
|
||||
explicit EntryLaneWalk(IrShaderStage stage) : everyInvocationEnters(EveryInvocationEnters(stage)) {}
|
||||
|
||||
bool ZeroBit(const IrValue* bit) {
|
||||
bit = Resolved(bit);
|
||||
if (bit == nullptr || !Spend()) return false;
|
||||
if (bit->HasImmediate()) return bit->Type() == IrType::Bool && !bit->ImmediateBool();
|
||||
if (Is(bit, IrOpcode::LogicalAnd)) return HelperOrFalseConjunct(bit) || ZeroBit(bit->Argument(0)) || ZeroBit(bit->Argument(1));
|
||||
if (Is(bit, IrOpcode::SelectU1)) return ZeroBit(bit->Argument(1)) && ZeroBit(bit->Argument(2));
|
||||
if (Is(bit, IrOpcode::Phi)) return Incoming(bit, [this](const IrValue* incoming) { return ZeroBit(incoming); });
|
||||
if (Is(bit, IrOpcode::IEqual32)) return HelperTest(bit);
|
||||
const auto words = LaneBitWords(bit);
|
||||
return words && Word((*words)[0], 0u) && Word((*words)[1], 1u);
|
||||
return Prove(bit, Proof::ZeroBit, [this](const IrValue* value) {
|
||||
if (value->HasImmediate()) return value->Type() == IrType::Bool && !value->ImmediateBool();
|
||||
if (Is(value, IrOpcode::LogicalAnd)) return HelperOrFalseConjunct(value) || ZeroBit(value->Argument(0)) || ZeroBit(value->Argument(1));
|
||||
if (Is(value, IrOpcode::SelectU1)) return ZeroBit(value->Argument(1)) && ZeroBit(value->Argument(2));
|
||||
if (Is(value, IrOpcode::Phi)) return Incoming(value, [this](const IrValue* incoming) { return ZeroBit(incoming); });
|
||||
if (Is(value, IrOpcode::IEqual32)) return HelperTest(value);
|
||||
const auto words = LaneBitWords(value);
|
||||
return words && Word((*words)[0], 0u) && Word((*words)[1], 1u);
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
enum class Proof : std::uint8_t { ZeroBit, NotHelper, LowWord, HighWord, LowWord64, HighWord64 };
|
||||
using Key = std::pair<const IrValue*, Proof>;
|
||||
struct Assumption {
|
||||
std::size_t depth;
|
||||
std::uint64_t order;
|
||||
};
|
||||
static constexpr std::size_t Unbounded = static_cast<std::size_t>(-1);
|
||||
|
||||
bool Spend() {
|
||||
if (budget == 0u) return false;
|
||||
--budget;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NotHelper(const IrValue* predicate) {
|
||||
predicate = Resolved(predicate);
|
||||
if (predicate == nullptr || !Spend()) return false;
|
||||
if (predicate->HasImmediate()) return everyInvocationEnters && predicate->Type() == IrType::Bool && predicate->ImmediateBool();
|
||||
switch (predicate->Opcode()) {
|
||||
case IrOpcode::LogicalAnd: return NotHelper(predicate->Argument(0)) || NotHelper(predicate->Argument(1));
|
||||
case IrOpcode::IEqual32: return HelperTest(predicate);
|
||||
default: return ZeroBit(predicate);
|
||||
template<typename TCheck>
|
||||
bool Prove(const IrValue* value, Proof proof, TCheck&& check) {
|
||||
value = Resolved(value);
|
||||
if (value == nullptr) return false;
|
||||
const Key key{value, proof};
|
||||
if (const auto found = settled.find(key); found != settled.end()) return found->second;
|
||||
if (const auto found = active.find(key); found != active.end()) return Assume(found->second);
|
||||
if (const auto found = assumed.find(key); found != assumed.end()) return Assume(found->second.depth);
|
||||
if (!Spend()) return false;
|
||||
const auto outer = dependency;
|
||||
const auto depth = active.size();
|
||||
const auto mark = created;
|
||||
dependency = Unbounded;
|
||||
active.emplace(key, depth);
|
||||
const bool result = check(value);
|
||||
active.erase(key);
|
||||
const auto inner = dependency;
|
||||
const bool holds = result && inner >= depth;
|
||||
for (auto entry = assumed.begin(); entry != assumed.end();) {
|
||||
if (entry->second.order < mark) {
|
||||
++entry;
|
||||
continue;
|
||||
}
|
||||
if (result && !holds) {
|
||||
entry->second.depth = std::min(entry->second.depth, inner);
|
||||
++entry;
|
||||
continue;
|
||||
}
|
||||
if (holds) settled.emplace(entry->first, true);
|
||||
entry = assumed.erase(entry);
|
||||
}
|
||||
if (result && !holds) assumed.emplace(key, Assumption{inner, created++});
|
||||
else settled.emplace(key, result);
|
||||
dependency = result && !holds ? std::min(outer, inner) : outer;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Assume(std::size_t depth) {
|
||||
dependency = std::min(dependency, depth);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NotHelper(const IrValue* predicate) {
|
||||
return Prove(predicate, Proof::NotHelper, [this](const IrValue* value) {
|
||||
if (value->HasImmediate()) return everyInvocationEnters && value->Type() == IrType::Bool && value->ImmediateBool();
|
||||
switch (value->Opcode()) {
|
||||
case IrOpcode::LogicalAnd: return NotHelper(value->Argument(0)) || NotHelper(value->Argument(1));
|
||||
case IrOpcode::IEqual32: return HelperTest(value);
|
||||
default: return ZeroBit(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool Word(const IrValue* word, std::uint32_t half) {
|
||||
word = Resolved(word);
|
||||
if (word == nullptr || !Spend()) return false;
|
||||
if (word->HasImmediate()) return IsU32(word, 0u);
|
||||
switch (word->Opcode()) {
|
||||
case IrOpcode::CompositeExtractU32x4: {
|
||||
const auto* ballot = Resolved(word->Argument(0));
|
||||
return IsU32(word->Argument(1), half) && Is(ballot, IrOpcode::Ballot) && NotHelper(ballot->Argument(0));
|
||||
return Prove(word, half == 0u ? Proof::LowWord : Proof::HighWord, [this, half](const IrValue* value) {
|
||||
if (value->HasImmediate()) return IsU32(value, 0u);
|
||||
switch (value->Opcode()) {
|
||||
case IrOpcode::CompositeExtractU32x4: {
|
||||
const auto* ballot = Resolved(value->Argument(0));
|
||||
return IsU32(value->Argument(1), half) && Is(ballot, IrOpcode::Ballot) && NotHelper(ballot->Argument(0));
|
||||
}
|
||||
case IrOpcode::CompositeExtractU64: return IsU32(value->Argument(1), half) && Word64(value->Argument(0), half);
|
||||
case IrOpcode::BitwiseAnd32: return Word(value->Argument(0), half) || Word(value->Argument(1), half);
|
||||
case IrOpcode::BitwiseOr32: return Word(value->Argument(0), half) && Word(value->Argument(1), half);
|
||||
case IrOpcode::Phi: return Incoming(value, [this, half](const IrValue* incoming) { return Word(incoming, half); });
|
||||
default: return false;
|
||||
}
|
||||
case IrOpcode::CompositeExtractU64: return IsU32(word->Argument(1), half) && Word64(word->Argument(0), half);
|
||||
case IrOpcode::BitwiseAnd32: return Word(word->Argument(0), half) || Word(word->Argument(1), half);
|
||||
case IrOpcode::BitwiseOr32: return Word(word->Argument(0), half) && Word(word->Argument(1), half);
|
||||
case IrOpcode::Phi: return Incoming(word, [this, half](const IrValue* incoming) { return Word(incoming, half); });
|
||||
default: return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool Word64(const IrValue* mask, std::uint32_t half) {
|
||||
mask = Resolved(mask);
|
||||
if (mask == nullptr || !Spend()) return false;
|
||||
if (mask->HasImmediate()) return mask->Type() == IrType::U64 && static_cast<std::uint32_t>(mask->ImmediateU64() >> (32u * half)) == 0u;
|
||||
switch (mask->Opcode()) {
|
||||
case IrOpcode::CompositeConstructU64: return Word(mask->Argument(half), half);
|
||||
case IrOpcode::Phi: return Incoming(mask, [this, half](const IrValue* incoming) { return Word64(incoming, half); });
|
||||
default: return false;
|
||||
}
|
||||
return Prove(mask, half == 0u ? Proof::LowWord64 : Proof::HighWord64, [this, half](const IrValue* value) {
|
||||
if (value->HasImmediate()) return value->Type() == IrType::U64 && static_cast<std::uint32_t>(value->ImmediateU64() >> (32u * half)) == 0u;
|
||||
switch (value->Opcode()) {
|
||||
case IrOpcode::CompositeConstructU64: return Word(value->Argument(half), half);
|
||||
case IrOpcode::Phi: return Incoming(value, [this, half](const IrValue* incoming) { return Word64(incoming, half); });
|
||||
default: return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template<typename TCheck>
|
||||
bool Incoming(const IrValue* phi, TCheck&& check) {
|
||||
if (!phis.insert(phi).second) return true;
|
||||
static bool Incoming(const IrValue* phi, TCheck&& check) {
|
||||
bool result = phi->ArgumentCount() != 0u;
|
||||
for (std::size_t index = 0; result && index < phi->ArgumentCount(); ++index) result = check(phi->Argument(index));
|
||||
phis.erase(phi);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool everyInvocationEnters;
|
||||
std::unordered_set<const IrValue*> phis;
|
||||
std::map<Key, std::size_t> active;
|
||||
std::map<Key, Assumption> assumed;
|
||||
std::map<Key, bool> settled;
|
||||
std::size_t dependency = Unbounded;
|
||||
std::uint64_t created = 0u;
|
||||
std::uint32_t budget = 2048u;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# libSceAudioOut technical debt
|
||||
|
||||
### Silent stubs
|
||||
|
||||
- [sceAudioOut2LoTerminate](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Low.cpp) (libSceAudioOut) - returns 0, as the PS5 12.00 module (0x248d0) does whether or not the low-latency service runs. The module disconnects from the audio server and clears the service tables; here `sceAudioOut2LoInit` starts no service and open contexts and ports are left as they are
|
||||
|
||||
### Unknown function info
|
||||
|
||||
- [sceAudioOut2ContextAdvance and sceAudioOut2ContextPush](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Context.cpp) (libSceAudioOut) - with an SDL device, Advance snapshots each port's PCM and gain; Push consumes completed grains in order and a blocking Push waits for Advance when none is ready. Inferred from PPSA26358, which rewrites the same 512-frame, 12-channel float buffer on a producer thread and pushes on another thread. Console buffer ownership, snapshot timing and empty-queue errors are unverified. Completed grains remain queued until Push consumes them, so memory use grows with an unconsumed PCM backlog. The console backlog limit is unverified. A nonblocking Push without a completed grain throws; this is a runtime restriction, not a console measurement. Context destruction concurrent with active calls remains unsupported. Without an SDL device the existing wall-clock path remains.
|
||||
@@ -90,7 +94,6 @@
|
||||
- [sceAudioOut2MbusSetSparkParam](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature
|
||||
- [sceAudioOut2AllAudioMainToHeadphoneForDiag](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature
|
||||
- [sceAudioOut2LoPortGetStateEx](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature
|
||||
- [sceAudioOut2LoTerminate](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) throws. It takes no arguments and returns an int, and stops the low-latency service ([SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/AudioOut2.cs#L497-L498)), but what it does to open contexts and ports and what it returns when the service is not started are unknown, and its counterpart `sceAudioOut2Terminate` is unimplemented.
|
||||
- [sceAudioOut2ReportRegisterHandler](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature
|
||||
- [sceAudioOut2ReportUnregisterHandler](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature
|
||||
- [sceAudioOut2GetTvCorrectionInfo](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature
|
||||
@@ -101,7 +104,7 @@
|
||||
- [sceAudioOut2DebugStateCtrl](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature
|
||||
- [sceAudioOut2ContextBedWrite](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature
|
||||
- [sceAudioOut2PortCreateEx](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature
|
||||
- [sceAudioOut2InitializeEx](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature
|
||||
- [sceAudioOut2InitializeEx](../../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) (libSceAudioOut) - the PS5 12.00 module (0x2a640) stores its 32-bit argument and runs `sceAudioOut2Initialize`; the argument's meaning is unknown and it is ignored
|
||||
- [sceAudioOut2Terminate](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature
|
||||
- [sceAudioOut2PortRegister](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature
|
||||
- [sceAudioOut2PortUnregister](../../../core/libs/prx/libSceAudioOut/src/Unimplemented.cpp) (libSceAudioOut) - unknown signature
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
- [sceNetGetDnsInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the `(info, int flags)` signature, the two IPv4 server slots in network byte order and the server count as the return value follow the PS4 [OpenOrbis musl resolver](https://github.com/OpenOrbis/musl/blob/2b0bb8ce2fa64ac255cb656785729d159a5f074c/src/network/resolvconf.c) and [OpenOrbis Net.h](https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/blob/0a1aaf9dd4a92695538bdeb09fb056d06dd11725/include/orbis/Net.h); unverified on PS5 hardware. It reports the host DNS servers of the interface `sceNetCtlGetInfo` reports, never servers set through `sceNetSetDnsInfo`. Zeroing the unused slots and returning 0 without a host IPv4 interface are unverified. Nonzero flags and a null info throw because their console results are unknown
|
||||
- [sceNetGetRandom](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - generates values with the host random device; the system library maintains its own seeded PRNG state.
|
||||
- [sceNetInetPtonEx](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the `(int af, const char* src, void* dst, int flags)` signature follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/net/net.cpp); IPv4 conversion and the ignored flags follow PS4 firmware 10.71, not yet confirmed on PS5. There IPv4 text is read as `inet_aton` reads it (`a`, `a.b`, `a.b.c`, hex and octal parts, values wrapping at 32 bits, the text ending at the first space, tab or VT), except that a part starting with `09` is decimal (`"0989"` is 989) and an octal part with `8` or `9` fails. Flags 0, each single bit up to 0x100, 0x10000, 0x80000000 and -1 gave the same results; other values were not measured. LF, FF and CR as terminators, and `NET_EINVAL` for a null `src` or `dst`, follow `sceNetInetPton` and were not measured for this function. IPv6 converts as `sceNetInetPton` does.
|
||||
- [sceNetMemoryAllocate and sceNetMemoryFree](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - allocations use the guest heap instead of the system network pool. Pool limits, its allocation accounting and free-time fill are not reproduced. Allocation flag 4 bypasses the system pool reserve threshold; it throws here because the pool reserve is not implemented. Flag 2 zero-fills and other bits are ignored, matching the system allocator.
|
||||
- [sceNetMemoryAllocate and sceNetMemoryFree](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - allocations use the guest heap instead of the system network pool. Pool limits, its allocation accounting and free-time fill are not reproduced: a reused block allocated without flag 2 holds 0xdd bytes on the console and its previous contents here. Flag 4 bypasses the system pool reserve threshold; with no pool there is no threshold, so it allocates as other flags do. What the console returns once the pool is exhausted is unmeasured; AnyPS5 returns null without setting an errno. Measured on a PS5 (firmware 13.60): a size of 0 or less returns null and leaves `sceNetErrnoLoc` unchanged, flag 2 zero-fills, flags 1, 4, 8, 0x10, 0x100 and 0x80000000 had no visible effect, and every block of 1 to 65536 bytes was 64-byte aligned
|
||||
- [sceNetThreadExit](../../../core/libs/prx/libSceNet/Export.cpp) is a zero-argument, non-returning void export in the native module. The protected host stub still declares int and contains `return 0`; changing it to void requires changing that protected body, so the return-type mismatch is retained.
|
||||
- [sceNetConfigCleanUpAllInterfaces](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the native routine consumes no arguments; the throwing implementation remains unchanged.
|
||||
- [sceNetDhcpdStart](../../../core/libs/prx/libSceNet/Export.cpp): six arguments and the three-argument address callback are confirmed. The 24-byte parameter contains five network-order addresses and a client limit. Individual address roles and the two unused thread-option meanings remain unresolved.
|
||||
@@ -26,11 +26,12 @@
|
||||
|
||||
### Functional
|
||||
|
||||
- [sceNetUsleep](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) prints nothing for a negative count, where the console prints `[LIBNET] usleep <count>` to standard output before failing with `EINVAL`. What an interrupted sleep returns is unmeasured: AnyPS5 returns the result of libkernel's `sceKernelUsleep`, a cancellation point. The signed 32-bit count (the upper half of the register is ignored) and the 0 result were measured on a PS5 (firmware 13.60)
|
||||
- [sceNetResolverStartAton6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) reverse-resolves a 16-byte IPv6 address through the host resolver (`getnameinfo` with `NI_NAMEREQD`) into the caller's buffer of `len` bytes, as `sceNetResolverStartAton` does for IPv4. The `(rid, addr, hostname, len, timeout, retry, flags)` signature comes from [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Socket.cs#L992-L997). An address with no name, or a name longer than `len - 1` bytes, fails with `RESOLVER_ENODNS` (`0x804101E1`) and leaves the buffer untouched; whether the console truncates instead is not verified. Abort flag 2 (ATON preservation) fails the next call with `EINTR` as it does `sceNetResolverStartAton`. Timeout and retry are ignored, and any nonzero flags throw because their meaning is unknown
|
||||
- [sceNetAccept](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) inherits the listener's non-blocking mode through the existing `SO_NBIO` setter, as the libkernel socket path does and FreeBSD's [`sonewconn`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/uipc_socket.c#L571) copies the socket state. The mode is sampled when accept starts; changes between connection creation and accept are not modelled. Not measured on a console.
|
||||
|
||||
- [sceNetConnect to AF_UNSPEC](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) dissolves a datagram association as FreeBSD 11.4's `udp_disconnect()` does, which resets the local address to the wildcard address and keeps the port. On Linux a socket bound to an explicit port keeps its local address as well (`127.0.0.1:<port>` stays `127.0.0.1:<port>`), because a bound socket cannot be bound again; one bound to port 0 is bound again to the wildcard address with the port it had
|
||||
- [sceNetSetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) sets `SO_RCVTIMEO` and `SO_SNDTIMEO` as the host socket's timeouts. Windows sockets stay non-blocking and the library waits for readiness until the stored timeout. A stream receive that times out still reports `ETIMEDOUT` rather than FreeBSD's `EAGAIN`; a datagram receive reports `EAGAIN`. Blocking stream sends return the queued byte count on timeout, or `EAGAIN` when no bytes were queued.
|
||||
- [sceNetSetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) sets `SO_RCVTIMEO` and `SO_SNDTIMEO` as the host socket's timeouts. Windows sockets stay non-blocking and the library waits for readiness until the stored timeout. Blocking stream sends return the queued byte count on timeout, or `EAGAIN` when no bytes were queued.
|
||||
- [sceNetSetsockopt, sceNetGetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) take `SO_SNDTIMEO` and `SO_RCVTIMEO` (0x1005, 0x1006) as a 16-byte `struct timeval`, as FreeBSD 11.4's [`sosetopt` and `sogetopt`](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/sys/kern/uipc_socket.c#L2657-L2690) do, and Sony's 0x1105 and 0x1106 as an `int` of microseconds. A [console log of ProsperoLight](https://github.com/blackbearreloaded/ProsperoLight/issues/49) (firmware 12.20) shows receives timing out every 100 ms after it gave 0x1006 a `{0, 100000}` timeval. The errors for a value shorter than a timeval (`EINVAL`) or out of range (`EDOM`), and `sceNetGetsockopt` cutting the timeval to the caller's length, follow FreeBSD's `sooptcopyin` and `sooptcopyout` and are not measured on a console
|
||||
- [sceNetSetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) stores `SO_ONESBCAST` (`0x10000`, [shadPS4's `ORBIS_NET_SO_ONESBCAST`](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/net/net_types.h)) per socket and `sceNetGetsockopt` reads back the value that was set, as shadPS4 does; PPSA34501 sets it and crashes later when it fails with `EOPNOTSUPP`. Sends ignore it. FreeBSD 11.4's [ip(4)](https://man.freebsd.org/cgi/man.cgi?manpath=FreeBSD+11.4-RELEASE&query=ip&sektion=4) documents the like-named `IPPROTO_IP` option `IP_ONESBCAST`: a datagram sent to an interface's directed broadcast address, such as 192.168.2.255, leaves through that interface with its destination rewritten to 255.255.255.255, and only on a socket allowed to set `SO_BROADCAST`. Here host routing selects the outgoing interface and the destination is not rewritten. shadPS4 also leaves sends unchanged, and its [TODO](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/net/net.cpp#L2467-L2470) reads the option the other way round: a send to 255.255.255.255 would be converted to the interface's broadcast address. Whether the console's `SOL_SOCKET` option behaves like `IP_ONESBCAST`, and what it reads back, is not verified on hardware. FreeBSD 11.4's `sogetopt()` reads several boolean socket options back as their flag bit (`so_options & optname`, `sys/kern/uipc_socket.c`), not as the value set; whether the console does so for `0x10000` is unknown
|
||||
- [sceNetGetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) reads `SO_ERROR` from the host socket. Winsock does not clear a socket's pending error when it is read, so on Windows the library reports a pending error once and shows it again only after a new `sceNetConnect`; a second failure with the same code on the same socket without a `sceNetConnect` in between would be hidden.
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
- [libSceNpTrophy2 pending exports](../../../core/libs/prx/libSceNpTrophy2/Export.cpp) - 23 named callable exports were identified in the PS5 3.20 export table and cross-checked against firmware 13.60. Signatures and behavior remain unverified; declarations use the unknown-signature convention and throw through `NotImplemented_nid_no_patch`. Evidence: [3.20 export list](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNpTrophy2.c).
|
||||
### Functional
|
||||
|
||||
- [Trophy queries](../../../core/libs/prx/libSceNpTrophy2/src/TrophyInfo.cpp) report the title's [trophy data](../../../core/libs/prx/libSceNpTrophy2/src/TrophyPackage.cpp) (`sce_sys/trophy2/trophyNN.ucp`) and the unlocks in the [trophy store](../../../core/libs/prx/libSceNpTrophy2/src/TrophyStore.cpp). Without the file, or when it is malformed, they report the old placeholders and `sceNpTrophy2GetTrophyInfoArray` lists only trophy 0. Icons are not read. Hidden trophies report their names and descriptions; whether the console blanks them before the unlock is not known. `progress_percentage` weighs gold 6, silver 2 and bronze 1 and leaves the platinum out, as KytyPS5 does (the ratio of the published PSN trophy points); the console's formula is not known. A trophy or group that is not in the file throws. A context that was not created or was destroyed reports placeholders and no unlocks and returns success; the console's errors for it are unknown.
|
||||
- [Trophy queries](../../../core/libs/prx/libSceNpTrophy2/src/TrophyInfo.cpp) - not confirmed on the console: `sceNpTrophy2GetTrophyInfo` of a trophy id that is not in the title's [trophy data](../../../core/libs/prx/libSceNpTrophy2/src/TrophyPackage.cpp) (`sce_sys/trophy2/trophyNN.ucp`) returns `0x8055390A` (KytyPS5's `NP_TROPHY2_ERROR_INVALID_TROPHY_ID`) and writes nothing; a context not created or destroyed reports placeholders and no unlocks and returns success; hidden trophies report their names and descriptions before the unlock; `progress_percentage` weighs gold 6, silver 2 and bronze 1 and leaves the platinum out, as KytyPS5 does (the ratio of the published PSN trophy points). A group that is not in the data throws. Without the file, or when it is malformed, the queries report the old placeholders and `sceNpTrophy2GetTrophyInfoArray` lists only trophy 0. Icons are not read. Unlocks come from the [trophy store](../../../core/libs/prx/libSceNpTrophy2/src/TrophyStore.cpp).
|
||||
- [Trophy data limits](../../../core/libs/prx/libSceNpTrophy2/src/TrophyPackage.cpp): a `trophyNN.ucp` or `udsNN.ucp` larger than 256 MiB or listing more than 4096 files, and a JSON file in it that nests deeper than 64 levels or holds more than 1048576 values, are not used, and the title runs as without that file. The limits keep the memory the reader takes to the file and about 140 MiB of parsed JSON; the console's limits are not known.
|
||||
- [Text language](../../../core/libs/prx/libSceNpTrophy2/src/TrophyPackage.cpp): texts come from `tropmeta_<tag>.json` for the console language, then `en-US`, then the file's `defaultLanguage`. The tags for languages 0 to 29 are KytyPS5's; `uk-UA` for 30 (Ukrainian) is an assumption.
|
||||
- [Platinum](../../../core/libs/prx/libSceNpTrophy2/src/TitleTrophies.cpp): an unlock through any path unlocks the platinum that the trophy names in `platinumTrophyId` once every trophy naming that platinum is unlocked, as KytyPS5 does; trophies without `platinumTrophyId` are not required. With the title's trophy data loaded, a posted `_UnlockTrophy` of a trophy that is not in the data, of a platinum or of a progressive trophy is not stored and prints a `[trophy] ignored` line, as KytyPS5 refuses them, so a progressive trophy unlocks only through its statistic; without trophy data any id of 0 or more is stored, including ids the data would make the platinum, a progressive trophy or no trophy, and the platinum is not unlocked when the trophies it needs are. The console's behaviour is not known.
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
- [realpath](../../../core/libs/prx/libc/src/RealPath.cpp) (libc) follows FreeBSD 11's [`realpath.c`](https://github.com/freebsd/freebsd-src/blob/stable/11/lib/libc/stdlib/realpath.c) except for symlinks. The console's guest filesystem has none, so a host symlink under the guest root is followed for existence and type but not expanded, and `..` drops the previous component lexically, as `Resolve` does for every guest path. Through a host link `linkdir -> sub/deep`, `linkdir/../x` gives `x` beside the link, not beside its target.
|
||||
- [strtod, strtof, strtold](../../../core/libs/prx/libc/src/Strings.cpp) (libc) - `strtod` and `wcstod` (and `atof`, which calls `strtod` here) read a `NAN(...)` sequence as FreeBSD 11's gdtoa [`hexnan`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/contrib/gdtoa/hexnan.c) does: hexadecimal digits with an optional `0x`, blanks starting a new 32-bit word, the first 16 digits kept and the payload cut to 52 bits, a zero payload made 1, any other character giving the default NaN but consuming up to the closing parenthesis, and an unterminated sequence keeping its payload while consuming only `nan`. The payload goes under the quiet bit with the sign, as in gdtoa's [`strtod`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/contrib/gdtoa/strtod.c#L316-L328). `strtof`, `wcstof`, `strtold` and `wcstold` return the positive default quiet NaN (`0x7fc00000`; exponent `0x7fff`, significand `0xc000000000000000`) whatever the sign and the sequence, and consume a sequence of letters, digits and underscores up to its closing parenthesis. The console's `libSceLibcInternal.sprx`, called from a payload process on a PS5 (firmware 12.70), gives these results for the 64 NaN inputs tried through `strtod` and `strtof`, 50 of them also through `strtold` and `wcstod`, and 18 through `wcstof` and `wcstold`, and, through `strtod`, for `nan(1 2)` and for `nan( ` followed by NUL and `)`; that a title's own `libc.prx` does the same is assumed. In `hexnan`, a blank after a payload digit, while a 32-bit word of the payload is still free, starts a blank skip that has no NUL check, so when only blanks follow up to the terminating NUL the console's `strtod` reads past it: `nan(1 ` followed by NUL and `)` consumes the `)` after the NUL, so the result depends on the bytes after the string, and here that case throws. A blank right after `nan(` is skipped with a NUL check, and the console stopped at the NUL there
|
||||
- [remquol](../../../core/libs/prx/libc/src/MathExact.cpp) (libc) leaves `*quo` unchanged when the result is NaN; the `libc.prx` bundled with PPSA08427 writes a value that depends on earlier x87 state (`remquol(1, 0)` stored 0 on a first call and 4 on later ones)
|
||||
- [_Stod](../../../core/libs/prx/libc/src/DinkumwareInternals.cpp) (libSceLibcInternal, implemented in libc) throws for hexadecimal text with a non-zero power-of-ten scale. The console's result there does not follow a simple rule: `0x3` with a scale of -1 gives 0.29999999999999999
|
||||
- [Long double math in MathExact.cpp](../../../core/libs/prx/libc/src/MathExact.cpp) (libc) was compared with the `libc.prx` bundled with PPSA08427 only for canonical 80-bit encodings; unnormals, pseudo-denormals and pseudo-infinities passed to arithmetic functions (not the classifiers) follow the host x87 instead of the console
|
||||
- [rename, sceKernelRename](../../../core/libs/prx/libc/include/RenameDot.hpp) (libc, libkernel) fail a last path component of `.` or `..` with `EINVAL`, after FreeBSD 11.4's [`lookup`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/vfs_lookup.c), which refuses `.` and, through its `ISDOTDOT` and `ISLASTCN` flags, `..` as the last component for the `DELETE` and `RENAME` operations. `rmdir("dir/.")`, `unlink("dir/.")` and the same calls with `..` follow the same lookup rule in FreeBSD but are not changed here: the guest still resolves the dot component away and acts on `dir` or its parent. Both arguments are checked before any host access, so `rename` of a missing source onto `dir/.` answers `EINVAL`, where FreeBSD's [`kern_renameat`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/vfs_syscalls.c) looks the source up first and answers `ENOENT`. A backslash counts as a path separator here (the guest path resolver turns it into `/`); FreeBSD treats it as an ordinary name character. None of this was measured on a console.
|
||||
- [nan and nanf](../../../core/libs/prx/libc/src/Math.cpp) (libc) return fixed quiet NaN values (`0x7fc00000` and `0x7ff8000000000000`), ignoring the tag argument as observed in the console's `libc.prx` (both exports load a global constant via `mov rax,[rip+..]; vmovsd xmm0,[rax]; ret`); VLC (PPSA85300) stops on nanf at start-up after strsep.
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
- [sceKernelGetSocPowerConsumption](../../../core/libs/prx/libkernel/System/src/ThermalSensors.cpp) (libkernel) - returns `EINVAL` and writes nothing, as `sceKernelGetSocSensorTemperature` does when the host has no sensor: the host's package power is not read (Linux RAPL `energy_uj` is readable by root only, and Windows has no general source). The prototype is not documented. PS5LM Plus (PPSA99581) passes a zeroed 128-byte buffer and, on success, reads its first 32-bit word as milliwatts, accepting 1000 to 350000; on failure it shows 0 W
|
||||
- [scePthreadMutexattrSetprotocol](../../../core/libs/prx/libkernel/Pthread/src/Mutex.cpp) (libkernel) - accepts 0 to 2 as a PS5 (firmware 13.60) does and returns `EINVAL` otherwise, but the protocol is not stored or applied: priority inheritance and protection are not modelled. On the console a null or uninitialized mutex attribute returns `EINVAL` from the SCE attribute calls, `scePthreadMutexInit` takes an uninitialized attribute as the default one, and a destroyed mutex returns `EINVAL` from `scePthreadMutexDestroy`; a null mutex pointer crashed the payload and still throws
|
||||
- [pthread_mutexattr_setprotocol, pthread_mutexattr_getprotocol, pthread_mutexattr_setprioceiling, pthread_mutexattr_getprioceiling](../../../core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp) (libkernel) - the protocol and the priority ceiling are stored and reported as in [FreeBSD 11 libthr](https://github.com/freebsd/freebsd-src/blob/releng/11.0/lib/libthr/thread/thr_mutexattr.c#L192-L245) (the ceiling is set or read only under `PTHREAD_PRIO_PROTECT`, and setting a protocol resets it to the highest `SCHED_RR` priority). A null, uninitialized or destroyed attribute returns `EINVAL` before the other arguments are checked; this error behavior is unverified on the console. Mutexes do not apply the protocol or ceiling: a priority-inherit or priority-protect mutex locks like a normal one and does not change its owner's priority
|
||||
- [sceCoredumpSetUserDataType](../../../core/libs/prx/libkernel/System/src/Coredump.cpp) (libkernel, NID `Uxqkdta7wEg`) - `(int type)` follows the PS5 12.00 `libkernel.sprx` at 0x138B0: the sign-extended `type` goes to syscall 601 (command 29, third argument 0), and the raw result comes back with no SCE code mapping, so a kernel error is -1 with `errno` set. Which types the kernel accepts is not measured. No dump is produced here, so every type returns 0. Palworld (PPSA23126, #2598) and Hello Neighbor (PPSA35154, discussion #6991) import it; #4144 left it throwing because no signature was known
|
||||
- [sceKernelGetMainSocId and sceKernelGetCpumode](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) both return 0, and [sceKernelGetCpuFrequency](../../../core/libs/prx/libkernel/Time/Time.cpp) returns the host TSC frequency measured for `sceKernelGetTscFrequency`, not the console CPU clock. The PS5's SoC id and CPU mode are unknown; shadPS4's PS4 values are [0x710f10/0x740f30 and 0, 2 or 5 from the param.sfo CPU-mode flags](https://github.com/shadps4-emu/shadPS4/blob/e0a466753a1a/src/core/libraries/kernel/process.cpp). PPSA99203 only logs them (`[console] main SoC id: result=%#x out=%#x`, `CPU: %llu Hz, cpumode %d`), passing a pointer that `sceKernelGetMainSocId` leaves untouched
|
||||
- [sceKernelEnableDmemAliasing](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) returns 0 and changes nothing: direct memory can always be mapped more than once here, as `DoMapDirect` in [DirectMemory.cpp](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) only checks that the physical pages are allocated, so there is no aliasing to turn on. [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/e0a466753a1aacbc8db8253be685bf7ab4d9daae/src/core/libraries/kernel/memory.cpp#L39-L43) (PS4) sets a flag there that lifts the `EBUSY` its [`sceKernelMapDirectMemory2`](https://github.com/shadps4-emu/shadPS4/blob/e0a466753a1aacbc8db8253be685bf7ab4d9daae/src/core/libraries/kernel/memory.cpp#L299-L301), and [`sceKernelMapDirectMemory` for SDK 2.50 and later](https://github.com/shadps4-emu/shadPS4/blob/e0a466753a1aacbc8db8253be685bf7ab4d9daae/src/core/libraries/kernel/memory.cpp#L237-L242), return for direct memory that is [already mapped](https://github.com/shadps4-emu/shadPS4/blob/e0a466753a1aacbc8db8253be685bf7ab4d9daae/src/core/memory.cpp#L548-L553); that check is not applied here. The no-argument `int` signature follows shadPS4 and ProsperoEden (PPSA99008), which calls it before mapping the same direct memory again. Whether the PS5 refuses such a mapping without the call is unverified
|
||||
- [sceKernelInternalMemoryGetAvailableSize](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - internal pool allocation and statistics are not implemented, so a non-null query throws without changing its output. Native null output returns -1; an absent pool returns -2, a failed statistics query returns -3, and success writes total pool bytes minus used bytes.
|
||||
|
||||
@@ -12,6 +12,8 @@ Select or drop multiple app ZIP archives to convert them into one ZIP bundle. Th
|
||||
|
||||
## Input and conversion
|
||||
|
||||
The SELF unwrapper refuses containers requiring more than 64 MiB of total alignment padding before allocating the output buffer. This limit counts padding across file-backed and empty segments, not segment data; a refusal preserves the input and any existing output file.
|
||||
|
||||
Use a clean ELF executable. Place its bundled ELF modules in `sce_module/`, `sce_modules/`, or `prx/` beside the input executable, or under the parent directory selected with `--sce-module-path <path>`. The option accepts absolute paths and paths relative to the current working directory; do not include the `sce_module` directory itself. Only files directly inside the module directories are processed. SELF/fSELF containers are skipped with a warning; unwrap them before relinking if they need to be included. `tools/unwrap_self.py <input.self> <output.elf>` does that for a decrypted container: it places the segments at the offsets of the SELF segment records and restores `p_offset % p_align == p_vaddr % p_align`, and it refuses a container whose load segments all look encrypted. The command-line relinker skips an ELF module as a backup copy, with a message, when its file name extends the name of another ELF module in the same directory and both have the same SONAME, such as a dumper's `libX.prx.esbak` or `libX.prxbak` beside an ELF `libX.prx`; a single backup beside a SELF container is still relinked. `prx/` can coexist with either `sce_module/` or `sce_modules/`. Both `sce_module/` and `sce_modules/` present is an error. All three absent is an error unless an explicit `--module-dir` is supplied.
|
||||
|
||||
```text
|
||||
|
||||
+11
-5
@@ -25,7 +25,10 @@ def quote(value):
|
||||
return '"' + value.replace("\\", "\\\\").replace('"', '\\"').replace("$", "\\$") + '"'
|
||||
|
||||
|
||||
def main():
|
||||
def main(argv=None):
|
||||
for stream in (sys.stdout, sys.stderr):
|
||||
if hasattr(stream, "reconfigure"):
|
||||
stream.reconfigure(encoding="utf-8")
|
||||
parser = argparse.ArgumentParser(description="Write the configure check results of a CMake cache as an initial cache file")
|
||||
parser.add_argument("cache", help="CMakeCache.txt of a configured tree")
|
||||
parser.add_argument("seed", help="file to pass to cmake -C")
|
||||
@@ -33,7 +36,7 @@ def main():
|
||||
help="seed matching check names only; repeat to include several patterns")
|
||||
parser.add_argument("--exclude", action="append", default=[], metavar="GLOB",
|
||||
help="omit matching check names after inclusion; patterns are case-sensitive")
|
||||
args = parser.parse_args()
|
||||
args = parser.parse_args(argv)
|
||||
cache, seed = Path(args.cache), Path(args.seed)
|
||||
if cache.resolve() == seed.resolve() or (cache.exists() and seed.exists() and cache.samefile(seed)):
|
||||
parser.error("seed output would overwrite the input cache")
|
||||
@@ -44,9 +47,12 @@ def main():
|
||||
found = {name: entry for name, entry in results.items()
|
||||
if (not args.include or any(fnmatch.fnmatchcase(name, pattern) for pattern in args.include))
|
||||
and not any(fnmatch.fnmatchcase(name, pattern) for pattern in args.exclude)}
|
||||
seed.parent.mkdir(parents=True, exist_ok=True)
|
||||
seed.write_text("".join(f"set({quote(name)} {quote(value)} CACHE INTERNAL {quote(doc)})\n"
|
||||
for name, (value, doc) in sorted(found.items())), encoding="utf-8")
|
||||
try:
|
||||
seed.parent.mkdir(parents=True, exist_ok=True)
|
||||
seed.write_text("".join(f"set({quote(name)} {quote(value)} CACHE INTERNAL {quote(doc)})\n"
|
||||
for name, (value, doc) in sorted(found.items())), encoding="utf-8")
|
||||
except OSError as error:
|
||||
parser.error(f"{seed}: {error}")
|
||||
print(f"{len(found)} check results")
|
||||
return 0
|
||||
|
||||
|
||||
+10
-7
@@ -16,7 +16,7 @@ BATCH = 20000
|
||||
class Build:
|
||||
def __init__(self, path):
|
||||
cache = {}
|
||||
for line in (Path(path) / "CMakeCache.txt").read_text(encoding="utf-8").splitlines():
|
||||
for line in (Path(path) / "CMakeCache.txt").read_text(encoding="utf-8-sig").splitlines():
|
||||
name, _, value = line.partition("=")
|
||||
cache[name.partition(":")[0]] = value
|
||||
self.path = os.path.normpath(cache["CMAKE_CACHEFILE_DIR"])
|
||||
@@ -225,7 +225,10 @@ def escape(name):
|
||||
return name.replace("$", "$$").replace(":", "$:").replace(" ", "$ ")
|
||||
|
||||
|
||||
def main():
|
||||
def main(argv=None):
|
||||
for stream in (sys.stdout, sys.stderr):
|
||||
if hasattr(stream, "reconfigure"):
|
||||
stream.reconfigure(encoding="utf-8")
|
||||
parser = argparse.ArgumentParser(description="Select the tests whose build inputs a change touches")
|
||||
sub = parser.add_subparsers(dest="command", required=True)
|
||||
d = sub.add_parser("dump", help="write the test graph of a built tree")
|
||||
@@ -241,15 +244,15 @@ def main():
|
||||
c = sub.add_parser("ctest", help="run ctest on a tree, limited to the selected tests if select wrote any")
|
||||
c.add_argument("build")
|
||||
c.add_argument("arguments", nargs=argparse.REMAINDER, help="ctest arguments")
|
||||
args = parser.parse_args()
|
||||
args = parser.parse_args(argv)
|
||||
if args.command == "ctest":
|
||||
selected = Path(args.build, "select.tests")
|
||||
extra = ["-I", selected.read_text(encoding="utf-8").strip()] if selected.exists() else []
|
||||
extra = ["-I", selected.read_text(encoding="utf-8-sig").strip()] if selected.exists() else []
|
||||
return subprocess.run(["ctest", "--test-dir", args.build, *args.arguments, *extra]).returncode
|
||||
if args.command == "explain":
|
||||
try:
|
||||
graph = json.loads(args.graph.read_text(encoding="utf-8"))
|
||||
changed = [line.strip() for line in args.changed.read_text(encoding="utf-8").splitlines() if line.strip()]
|
||||
graph = json.loads(args.graph.read_text(encoding="utf-8-sig"))
|
||||
changed = [line.strip() for line in args.changed.read_text(encoding="utf-8-sig").splitlines() if line.strip()]
|
||||
reasons = {}
|
||||
picked, full = select(graph, changed, reasons)
|
||||
except (OSError, ValueError) as error:
|
||||
@@ -270,7 +273,7 @@ def main():
|
||||
if not os.path.exists(args.graph):
|
||||
print("full run: no test graph")
|
||||
return 0
|
||||
graph = json.loads(Path(args.graph).read_text(encoding="utf-8"))
|
||||
graph = json.loads(Path(args.graph).read_text(encoding="utf-8-sig"))
|
||||
changed = [line.strip() for line in Path(args.changed).read_text(encoding="utf-8-sig").splitlines() if line.strip()]
|
||||
picked, full = select(graph, changed)
|
||||
if picked is None:
|
||||
|
||||
@@ -62,6 +62,14 @@ class ConfigureSeedPathTests(unittest.TestCase):
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertIn("HAVE_FEATURE", seed.read_text(encoding="utf-8"))
|
||||
|
||||
def test_unwritable_seed_is_reported_without_a_traceback(self):
|
||||
seed_dir = self.root / "seed_dir"
|
||||
seed_dir.mkdir()
|
||||
result = self.run_seed(seed_dir)
|
||||
self.assertEqual(result.returncode, 2, result.stderr)
|
||||
self.assertIn(str(seed_dir), result.stderr)
|
||||
self.assertNotIn("Traceback", result.stderr)
|
||||
|
||||
def test_same_path_preserves_cache(self):
|
||||
self.assert_rejected(self.cache)
|
||||
|
||||
|
||||
@@ -230,6 +230,40 @@ class BuildErrorTests(unittest.TestCase):
|
||||
self.assertIn("CMAKE_CACHEFILE_DIR", result.stderr)
|
||||
self.assertNotIn("Traceback", result.stderr)
|
||||
|
||||
def test_cache_with_utf8_bom_is_read(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
cache = Path(directory) / "CMakeCache.txt"
|
||||
cache.write_text("\ufeffCMAKE_CACHEFILE_DIR:INTERNAL=C:/work/build\nCMAKE_HOME_DIRECTORY:INTERNAL=C:/work/source\n", encoding="utf-8")
|
||||
with mock.patch.object(select_tests.Build, "run", return_value="Build file encoding: UTF-8"):
|
||||
build = select_tests.Build(directory)
|
||||
self.assertEqual(build.path, os.path.normpath("C:/work/build"))
|
||||
self.assertEqual(build.source, os.path.normpath("C:/work/source"))
|
||||
|
||||
def test_explain_accepts_utf8_bom_in_graph_and_changed(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
graph_path = root / "graph.json"
|
||||
graph = {
|
||||
"tests": {
|
||||
"unit": {
|
||||
"target": "tests/unit",
|
||||
"arguments": [],
|
||||
"registered": [],
|
||||
"sets": [],
|
||||
"requires": [],
|
||||
"depends": [],
|
||||
}
|
||||
},
|
||||
"closures": {"tests/unit": ["src/main.c"]},
|
||||
}
|
||||
graph_path.write_text("\ufeff" + json.dumps(graph), encoding="utf-8")
|
||||
changed_path = root / "changed.txt"
|
||||
changed_path.write_text("\ufeffsrc/main.c\n", encoding="utf-8")
|
||||
result = self.run_tool("explain", graph_path, changed_path)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
data = json.loads(result.stdout)
|
||||
self.assertEqual(data["tests"], ["unit"])
|
||||
|
||||
|
||||
class CaseInsensitivePathTests(unittest.TestCase):
|
||||
def build(self):
|
||||
|
||||
@@ -473,7 +473,7 @@ class UnwrapTest(unittest.TestCase):
|
||||
|
||||
def allocate(value):
|
||||
if isinstance(value, int):
|
||||
self.assertLessEqual(value, len(container) + unwrap_self.MAX_EMPTY_LOAD_PADDING)
|
||||
self.assertLessEqual(value, len(container) + unwrap_self.MAX_ALIGNMENT_PADDING)
|
||||
return bytearray(value)
|
||||
|
||||
for previous in (None, b"previous output"):
|
||||
@@ -486,7 +486,7 @@ class UnwrapTest(unittest.TestCase):
|
||||
contextlib.redirect_stderr(io.StringIO()) as stderr:
|
||||
self.assertEqual(unwrap_self.main(), 2)
|
||||
self.assertEqual(stdout.getvalue(), "")
|
||||
self.assertIn("empty load alignment requires more than", stderr.getvalue())
|
||||
self.assertIn("segment alignment requires more than", stderr.getvalue())
|
||||
self.assertNotIn("Traceback", stderr.getvalue())
|
||||
if previous is not None:
|
||||
self.assertEqual(output.read_bytes(), previous)
|
||||
@@ -494,20 +494,104 @@ class UnwrapTest(unittest.TestCase):
|
||||
self.assertFalse(output.exists())
|
||||
self.assertEqual(source.read_bytes(), container)
|
||||
|
||||
def test_excessive_file_backed_padding_is_refused_before_allocation(self):
|
||||
cases = [
|
||||
([(PT_LOAD, 5, 0x1234, (1 << 40) - 1, 0x100, 1 << 40)],
|
||||
[(0x2804, 0x800, 0x100)], {0x800: b"x" * 0x100}),
|
||||
([(PT_LOAD, 5, 0x1234, 0, 0x100, PAGE),
|
||||
(SCE_COMMENT, 0, 0xB00, 0, 0x20, 1 << 40)],
|
||||
[(0x2804, 0x800, 0x100)], {0x800: b"x" * 0x100, 0xB00: pattern(9, 0x20)}),
|
||||
]
|
||||
source = Path(self.directory) / "input.self"
|
||||
output = Path(self.directory) / "output.elf"
|
||||
for segments, records, payloads in cases:
|
||||
container = build_container(segments, records, payloads)
|
||||
source.write_bytes(container)
|
||||
|
||||
def allocate(value):
|
||||
if isinstance(value, int):
|
||||
self.assertLessEqual(value, len(container) + 64 * 1024 * 1024)
|
||||
return bytearray(value)
|
||||
|
||||
for previous in (None, b"previous output"):
|
||||
with self.subTest(segments=segments, previous=previous):
|
||||
output.unlink(missing_ok=True)
|
||||
if previous is not None:
|
||||
output.write_bytes(previous)
|
||||
with mock.patch.object(unwrap_self, "bytearray", side_effect=allocate, create=True), \
|
||||
mock.patch.object(sys, "argv", ["unwrap_self.py", str(source), str(output)]), \
|
||||
contextlib.redirect_stdout(io.StringIO()) as stdout, \
|
||||
contextlib.redirect_stderr(io.StringIO()) as stderr:
|
||||
self.assertEqual(unwrap_self.main(), 2)
|
||||
self.assertEqual(stdout.getvalue(), "")
|
||||
self.assertIn("alignment requires more than", stderr.getvalue())
|
||||
self.assertNotIn("Traceback", stderr.getvalue())
|
||||
self.assertEqual(source.read_bytes(), container)
|
||||
if previous is None:
|
||||
self.assertFalse(output.exists())
|
||||
else:
|
||||
self.assertEqual(output.read_bytes(), previous)
|
||||
|
||||
def test_alignment_padding_limit_is_cumulative(self):
|
||||
for empty in (False, True):
|
||||
with self.subTest(empty=empty):
|
||||
segments = [(PT_LOAD, 5, 0x1234, 0x80, 0x20, 0x100),
|
||||
(PT_LOAD, 6, 0x5678, 0x580, 0 if empty else 0x10, 0x1000)]
|
||||
records = [(0x800, 0x800, 0x20)]
|
||||
payloads = {0x800: pattern(1, 0x20)}
|
||||
if not empty:
|
||||
records.append((0x100800, 0xA00, 0x10))
|
||||
payloads[0xA00] = pattern(9, 0x10)
|
||||
source = Path(self.directory) / "input.self"
|
||||
output = Path(self.directory) / "output.elf"
|
||||
container = build_container(segments, records, payloads)
|
||||
source.write_bytes(container)
|
||||
output.write_bytes(b"previous output")
|
||||
with mock.patch.object(unwrap_self, "MAX_ALIGNMENT_PADDING", 0x15F):
|
||||
with self.assertRaisesRegex(unwrap_self.Refused, "alignment requires more than"):
|
||||
unwrap_self.unwrap(source, output)
|
||||
self.assertEqual(output.read_bytes(), b"previous output")
|
||||
self.assertEqual(source.read_bytes(), container)
|
||||
with mock.patch.object(unwrap_self, "MAX_ALIGNMENT_PADDING", 0x160):
|
||||
unwrap_self.unwrap(source, output)
|
||||
data, headers = read_program_headers(output)
|
||||
for header in headers:
|
||||
self.assertEqual(header[2] % header[7], header[3] % header[7])
|
||||
self.assertEqual(data[headers[0][2]:headers[0][2] + 0x20], payloads[0x800])
|
||||
if not empty:
|
||||
self.assertEqual(data[headers[1][2]:headers[1][2] + 0x10], payloads[0xA00])
|
||||
|
||||
def test_large_alignment_with_small_padding_remains_supported(self):
|
||||
payload = b"x" * 0x100
|
||||
segments = [(PT_LOAD, 5, 0x1234, FIRST_REGION + 0x10, len(payload), 1 << 40)]
|
||||
container = build_container(segments, [(0x2804, 0x800, len(payload))], {0x800: payload})
|
||||
source = Path(self.directory) / "input.self"
|
||||
output = Path(self.directory) / "output.elf"
|
||||
source.write_bytes(container)
|
||||
with mock.patch.object(unwrap_self, "MAX_ALIGNMENT_PADDING", 0x10):
|
||||
unwrap_self.unwrap(source, output)
|
||||
data, headers = read_program_headers(output)
|
||||
load = headers[0]
|
||||
self.assertEqual(load[2] % load[7], load[3] % load[7])
|
||||
self.assertEqual(load[2], FIRST_REGION + 0x10)
|
||||
self.assertEqual(data[load[2]:load[2] + load[5]], payload)
|
||||
self.assertEqual(source.read_bytes(), container)
|
||||
|
||||
def test_empty_load_padding_limit_counts_only_added_bytes(self):
|
||||
baseline, output = self.convert(build_container(default_segments(), default_records(), default_payloads()))
|
||||
backed = [(*segment[:5], 1) for segment in default_segments()]
|
||||
baseline, output = self.convert(build_container(backed, default_records(), default_payloads()))
|
||||
self.assertEqual(baseline.returncode, 0, baseline.stderr)
|
||||
baseline_size = output.stat().st_size
|
||||
limit = 0x100
|
||||
for extra in (-1, limit, limit + 1):
|
||||
with self.subTest(extra=extra):
|
||||
address = 0x10000 + baseline_size + extra
|
||||
segments = default_segments() + [(PT_LOAD, 6, 0, address, 0, 0x10000)]
|
||||
segments = backed + [(PT_LOAD, 6, 0, address, 0, 0x10000)]
|
||||
container = build_container(segments, default_records(), default_payloads())
|
||||
source = Path(self.directory) / "input.self"
|
||||
source.write_bytes(container)
|
||||
previous = output.read_bytes()
|
||||
with mock.patch.object(unwrap_self, "MAX_EMPTY_LOAD_PADDING", limit):
|
||||
with mock.patch.object(unwrap_self, "MAX_ALIGNMENT_PADDING", limit):
|
||||
if extra <= limit:
|
||||
unwrap_self.unwrap(source, output)
|
||||
data, headers = read_program_headers(output)
|
||||
@@ -515,7 +599,7 @@ class UnwrapTest(unittest.TestCase):
|
||||
self.assertEqual(headers[3][2], baseline_size + extra)
|
||||
self.assertEqual(headers[3][2] % headers[3][7], address % headers[3][7])
|
||||
else:
|
||||
with self.assertRaisesRegex(unwrap_self.Refused, "empty load alignment requires more than"):
|
||||
with self.assertRaisesRegex(unwrap_self.Refused, "segment alignment requires more than"):
|
||||
unwrap_self.unwrap(source, output)
|
||||
self.assertEqual(output.read_bytes(), previous)
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ RECORD_SIZE = 0x20
|
||||
RECORD_HAS_SEGMENT = 0x800
|
||||
RECORD_SEGMENT_SHIFT = 20
|
||||
FIRST_REGION = 0x400
|
||||
MAX_EMPTY_LOAD_PADDING = 64 * 1024 * 1024
|
||||
MAX_ALIGNMENT_PADDING = 64 * 1024 * 1024
|
||||
PT_LOAD = 1
|
||||
PT_NOTE = 4
|
||||
ENTROPY_LIMIT = 7.5
|
||||
@@ -273,9 +273,11 @@ def unwrap(path, output, verbose=False):
|
||||
regions.sort(key=lambda region: region["old"])
|
||||
program_offset = struct.unpack_from("<Q", elf_header, 0x20)[0]
|
||||
cursor = max(FIRST_REGION, program_offset + len(headers) * 56)
|
||||
padding = 0
|
||||
for region in regions:
|
||||
alignment = region["align"] if region["align"] and (region["align"] & (region["align"] - 1)) == 0 else 1
|
||||
placed = cursor + (region["vaddr"] - cursor) % alignment
|
||||
padding += placed - cursor
|
||||
region["new"] = placed
|
||||
cursor = placed + region["size"]
|
||||
|
||||
@@ -283,8 +285,9 @@ def unwrap(path, output, verbose=False):
|
||||
for header in headers:
|
||||
if header["type"] == PT_LOAD and header["size"] == 0:
|
||||
empty_end = max(empty_end, header["vaddr"] % max(header["align"], 1))
|
||||
if empty_end - cursor > MAX_EMPTY_LOAD_PADDING:
|
||||
raise Refused("empty load alignment requires more than %d bytes of output padding" % MAX_EMPTY_LOAD_PADDING)
|
||||
padding += empty_end - cursor
|
||||
if padding > MAX_ALIGNMENT_PADDING:
|
||||
raise Refused("segment alignment requires more than %d bytes of output padding" % MAX_ALIGNMENT_PADDING)
|
||||
cursor = empty_end
|
||||
|
||||
result = bytearray(cursor)
|
||||
|
||||
Reference in new issue
Block a user