mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-03 12:57:27 +00:00
193 lines
6.5 KiB
C++
193 lines
6.5 KiB
C++
#include "prx/libSceUlt/include/Api.hpp"
|
|
#include "prx/libkernel/include/Api.hpp"
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <memory>
|
|
#include <semaphore>
|
|
#include <stdexcept>
|
|
#include <thread>
|
|
|
|
namespace {
|
|
|
|
void Require(bool value, int line) {
|
|
if (value) return;
|
|
std::fprintf(stderr, "ULT lifecycle check failed at line %d\n", line);
|
|
std::abort();
|
|
}
|
|
|
|
#define CHECK(value) Require((value), __LINE__)
|
|
|
|
struct alignas(8) Runtime { std::byte bytes[4096]{}; };
|
|
struct alignas(8) Thread { std::byte bytes[512]{}; };
|
|
|
|
struct HostThread {
|
|
std::thread thread;
|
|
void* result = nullptr;
|
|
};
|
|
|
|
std::atomic<bool> entryFinished{false};
|
|
std::atomic<int> joinCalls{0};
|
|
std::atomic<int> hostThreads{0};
|
|
std::binary_semaphore joining{0};
|
|
std::binary_semaphore releaseJoin{0};
|
|
bool gateJoin = false;
|
|
int createFailure = 0;
|
|
int joinFailure = 0;
|
|
|
|
void Await(std::binary_semaphore& gate) {
|
|
CHECK(gate.try_acquire_for(std::chrono::seconds(2)));
|
|
}
|
|
|
|
std::int32_t APS5_VABI Entry(std::uint64_t argument) {
|
|
void* self = nullptr;
|
|
CHECK(sceUltUlthreadGetSelf(&self) == ULT_OK && self != nullptr);
|
|
CHECK(sceUltUlthreadYield() == ULT_OK);
|
|
return static_cast<std::int32_t>(argument);
|
|
}
|
|
|
|
void Create(Runtime& runtime, Thread& thread) {
|
|
CHECK(sceUltUlthreadRuntimeCreate(&runtime, "runtime", 1, 1, nullptr, nullptr, 0) == ULT_OK);
|
|
CHECK(sceUltUlthreadCreate(&thread, "thread", Entry, 73, nullptr, 0, &runtime, nullptr, 0) == ULT_OK);
|
|
entryFinished.wait(false);
|
|
}
|
|
|
|
void Finish(Runtime& runtime, Thread& thread) {
|
|
std::int32_t status = -1;
|
|
CHECK(sceUltUlthreadJoin(&thread, &status) == ULT_OK && status == 73);
|
|
CHECK(hostThreads.load() == 0);
|
|
CHECK(sceUltUlthreadJoin(&thread, &status) == ULT_ERROR_STATE && status == 73);
|
|
CHECK(sceUltUlthreadRuntimeDestroy(&runtime) == ULT_OK);
|
|
CHECK(sceUltFinalize() == ULT_OK);
|
|
}
|
|
|
|
void CreateFailure(int failure) {
|
|
Runtime runtime;
|
|
Thread thread;
|
|
CHECK(sceUltUlthreadRuntimeCreate(&runtime, "runtime", 1, 1, nullptr, nullptr, 0) == ULT_OK);
|
|
createFailure = failure;
|
|
bool threw = false;
|
|
int result = ULT_OK;
|
|
try {
|
|
result = sceUltUlthreadCreate(&thread, "thread", Entry, 73, nullptr, 0, &runtime, nullptr, 0);
|
|
} catch (const std::runtime_error&) {
|
|
threw = true;
|
|
}
|
|
CHECK(threw == (failure != 1));
|
|
if (failure == 1) CHECK(result == ULT_ERROR_AGAIN);
|
|
if (failure == 3) {
|
|
CHECK(sceUltFinalize() == ULT_ERROR_BUSY);
|
|
CHECK(sceUltUlthreadRuntimeDestroy(&runtime) == ULT_ERROR_STATE);
|
|
Finish(runtime, thread);
|
|
return;
|
|
}
|
|
CHECK(hostThreads.load() == 0 && !entryFinished.load());
|
|
CHECK(sceUltUlthreadTryJoin(&thread, nullptr) == ULT_ERROR_STATE);
|
|
CHECK(sceUltUlthreadCreate(&thread, "retry", Entry, 73, nullptr, 0, &runtime, nullptr, 0) == ULT_OK);
|
|
Finish(runtime, thread);
|
|
}
|
|
|
|
void Ownership(bool nonblocking) {
|
|
Runtime runtime;
|
|
Thread thread;
|
|
Create(runtime, thread);
|
|
gateJoin = true;
|
|
std::int32_t firstStatus = -1;
|
|
int firstResult = ULT_ERROR_STATE;
|
|
std::thread first([&] {
|
|
firstResult = nonblocking ? sceUltUlthreadTryJoin(&thread, &firstStatus) : sceUltUlthreadJoin(&thread, &firstStatus);
|
|
});
|
|
Await(joining);
|
|
std::int32_t secondStatus = -2;
|
|
CHECK(sceUltUlthreadJoin(&thread, &secondStatus) == ULT_ERROR_STATE && secondStatus == -2);
|
|
CHECK(sceUltUlthreadTryJoin(&thread, &secondStatus) == ULT_ERROR_STATE && secondStatus == -2);
|
|
CHECK(joinCalls.load() == 1);
|
|
CHECK(sceUltUlthreadCreate(&thread, "replacement", Entry, 74, nullptr, 0, &runtime, nullptr, 0) == ULT_ERROR_STATE);
|
|
CHECK(sceUltUlthreadRuntimeDestroy(&runtime) == ULT_ERROR_STATE);
|
|
CHECK(sceUltFinalize() == ULT_ERROR_BUSY);
|
|
releaseJoin.release();
|
|
first.join();
|
|
CHECK(firstResult == ULT_OK && firstStatus == 73 && hostThreads.load() == 0);
|
|
CHECK(sceUltUlthreadTryJoin(&thread, &secondStatus) == ULT_ERROR_STATE && secondStatus == -2);
|
|
gateJoin = false;
|
|
entryFinished.store(false);
|
|
CHECK(sceUltUlthreadCreate(&thread, "replacement", Entry, 73, nullptr, 0, &runtime, nullptr, 0) == ULT_OK);
|
|
Finish(runtime, thread);
|
|
}
|
|
|
|
void JoinFailure(int failure) {
|
|
Runtime runtime;
|
|
Thread thread;
|
|
Create(runtime, thread);
|
|
joinFailure = failure;
|
|
std::int32_t status = -1;
|
|
bool threw = false;
|
|
int result = ULT_OK;
|
|
try {
|
|
result = sceUltUlthreadJoin(&thread, &status);
|
|
} catch (const std::runtime_error&) {
|
|
threw = true;
|
|
}
|
|
CHECK(threw == (failure == 2));
|
|
if (!threw) CHECK(result == ULT_ERROR_STATE);
|
|
CHECK(status == -1 && hostThreads.load() == 1);
|
|
Finish(runtime, thread);
|
|
}
|
|
|
|
}
|
|
|
|
extern "C" {
|
|
|
|
int APS5_VABI scePthreadCreate(Pthread* value, const PthreadAttr*, PthreadEntry entry, void* argument, const char*) {
|
|
const int failure = createFailure;
|
|
createFailure = 0;
|
|
if (failure == 1) return 11;
|
|
if (failure == 2) throw std::runtime_error("Injected prepublication creation failure");
|
|
auto state = std::make_unique<HostThread>();
|
|
auto* raw = state.get();
|
|
state->thread = std::thread([=] {
|
|
raw->result = entry(argument);
|
|
entryFinished.store(true);
|
|
entryFinished.notify_all();
|
|
});
|
|
++hostThreads;
|
|
*value = reinterpret_cast<Pthread>(state.release());
|
|
if (failure == 3) throw std::runtime_error("Injected postpublication creation failure");
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI scePthreadJoin(Pthread value, void** result) {
|
|
const int call = joinCalls.fetch_add(1);
|
|
if (gateJoin) {
|
|
if (call != 0) return 22;
|
|
joining.release();
|
|
Await(releaseJoin);
|
|
}
|
|
const int failure = joinFailure;
|
|
joinFailure = 0;
|
|
if (failure == 1) return 22;
|
|
if (failure == 2) throw std::runtime_error("Injected precommit join failure");
|
|
CHECK(value != nullptr);
|
|
std::unique_ptr<HostThread> state(reinterpret_cast<HostThread*>(value));
|
|
state->thread.join();
|
|
if (result != nullptr) *result = state->result;
|
|
--hostThreads;
|
|
return 0;
|
|
}
|
|
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
CHECK(argc == 2);
|
|
if (std::strcmp(argv[1], "create-error") == 0) CreateFailure(1);
|
|
else if (std::strcmp(argv[1], "create-exception") == 0) CreateFailure(2);
|
|
else if (std::strcmp(argv[1], "published-exception") == 0) CreateFailure(3);
|
|
else if (std::strcmp(argv[1], "join-owner") == 0) Ownership(false);
|
|
else if (std::strcmp(argv[1], "tryjoin-owner") == 0) Ownership(true);
|
|
else if (std::strcmp(argv[1], "join-error") == 0) JoinFailure(1);
|
|
else if (std::strcmp(argv[1], "join-exception") == 0) JoinFailure(2);
|
|
else CHECK(false);
|
|
}
|