mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-28 00:35:47 +00:00
237 lines
12 KiB
C++
237 lines
12 KiB
C++
#include "prx/libSceJxr/libSceJxr.hpp"
|
|
#include "prx/libc/include/GuestArena.hpp"
|
|
#include "prx/libkernel/include/Api.hpp"
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <limits>
|
|
#include <span>
|
|
#include <stdexcept>
|
|
#include <vector>
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
constexpr std::size_t PacketSize = 4096;
|
|
alignas(8192) std::array<std::uint8_t, 16384> ringStorage;
|
|
std::span<std::uint8_t, 8192> ring(reinterpret_cast<std::uint8_t*>((reinterpret_cast<std::uintptr_t>(ringStorage.data()) + 16383) & ~std::uintptr_t{16383}), 8192);
|
|
std::array<std::uint8_t, PacketSize> output;
|
|
std::vector<char> calls;
|
|
void* lastBuffer;
|
|
std::size_t lastSize;
|
|
std::size_t lastPosition;
|
|
std::int64_t readResult;
|
|
std::int64_t writeResult;
|
|
std::int64_t seekResult;
|
|
Jxr::Stream* lastStream;
|
|
|
|
struct Mutation {
|
|
Jxr::BitIo* io = nullptr;
|
|
std::uint8_t* afterSeek = nullptr;
|
|
Jxr::Stream* afterSeekStream = nullptr;
|
|
std::uint8_t* afterRead = nullptr;
|
|
std::uint8_t* afterWrite = nullptr;
|
|
} mutation;
|
|
|
|
void Require(bool result, const char* message) {
|
|
if (result) return;
|
|
std::fprintf(stderr, "JXR stream: %s\n", message);
|
|
std::abort();
|
|
}
|
|
|
|
std::int64_t APS5_VABI Read(Jxr::Stream* stream, void* buffer, std::size_t size) {
|
|
calls.push_back('r');
|
|
lastStream = stream;
|
|
lastBuffer = buffer;
|
|
lastSize = size;
|
|
auto* bytes = static_cast<std::uint8_t*>(buffer);
|
|
for (std::size_t i = 0; i < size; ++i) bytes[i] = static_cast<std::uint8_t>(i * 17 + lastPosition);
|
|
if (mutation.afterRead) mutation.io->start = mutation.afterRead;
|
|
return readResult;
|
|
}
|
|
|
|
std::int64_t APS5_VABI Write(Jxr::Stream*, const void* buffer, std::size_t size) {
|
|
calls.push_back('w');
|
|
lastBuffer = const_cast<void*>(buffer);
|
|
lastSize = size;
|
|
Require(size == output.size(), "write length differs from one packet");
|
|
std::memcpy(output.data(), buffer, size);
|
|
if (mutation.afterWrite) mutation.io->start = mutation.afterWrite;
|
|
return writeResult;
|
|
}
|
|
|
|
std::int64_t APS5_VABI SetPosition(Jxr::Stream*, std::size_t position) {
|
|
calls.push_back('s');
|
|
lastPosition = position;
|
|
if (mutation.afterSeek) mutation.io->start = mutation.afterSeek;
|
|
if (mutation.afterSeekStream) mutation.io->stream = mutation.afterSeekStream;
|
|
return seekResult;
|
|
}
|
|
|
|
void Reads(Jxr::Stream& stream) {
|
|
for (std::size_t half : {std::size_t{0}, PacketSize}) {
|
|
for (std::size_t position : {std::size_t{0}, std::size_t{91}, std::numeric_limits<std::size_t>::max() - 100}) {
|
|
for (std::int64_t result : {std::int64_t{0}, std::int64_t{-73}, std::int64_t{0x100000001}}) {
|
|
std::fill(ring.begin(), ring.end(), 0xcc);
|
|
calls.clear();
|
|
readResult = result;
|
|
seekResult = -99;
|
|
Jxr::BitIo io{0x77777777, 0x12345678, 11, ~8192, ring.data() + half, ring.data() + (half ^ PacketSize), &stream, position};
|
|
const auto initial = io;
|
|
Require(readIS_nid_postfix(nullptr, &io) == 0, "read helper propagated an ignored callback error");
|
|
Require(calls == std::vector<char>({'s', 'r'}), "read callbacks are absent or out of order");
|
|
Require(lastPosition == position && lastSize == PacketSize && lastBuffer == initial.start, "read callback arguments differ");
|
|
Require(io.referenceOffset == position + PacketSize && io.start == ring.data() + (half ^ PacketSize), "read cursor did not advance and wrap");
|
|
Require(io.current == initial.current && io.accumulator == initial.accumulator && io.bitsUsed == initial.bitsUsed && io.mask == initial.mask && io.stream == initial.stream, "read changed unrelated bit state");
|
|
std::uint32_t shadow;
|
|
std::memcpy(&shadow, initial.start, sizeof(shadow));
|
|
Require(io.shadow == shadow, "read shadow is not the first four bytes in host order");
|
|
for (std::size_t i = 0; i < PacketSize; ++i) {
|
|
Require(initial.start[i] == static_cast<std::uint8_t>(i * 17 + position), "refill data differs");
|
|
Require(ring[(half ^ PacketSize) + i] == 0xcc, "refill changed the other half of the ring");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Writes(Jxr::Stream& stream) {
|
|
for (std::size_t i = 0; i < ring.size(); ++i) ring[i] = static_cast<std::uint8_t>(i * 23);
|
|
for (std::size_t half : {std::size_t{0}, PacketSize}) {
|
|
for (std::int64_t result : {std::int64_t{0}, std::int64_t{-73}, std::int64_t{0x100000001}, std::numeric_limits<std::int64_t>::min()}) {
|
|
calls.clear();
|
|
writeResult = result;
|
|
Jxr::BitIo io{7, 19, 13, ~8192, ring.data() + half, ring.data() + (half ^ PacketSize), &stream, 1234};
|
|
const auto initial = io;
|
|
Require(writeIS_nid_postfix(nullptr, &io) == result, "write result was truncated or changed");
|
|
Require(calls == std::vector<char>({'w'}) && lastBuffer == initial.start && lastSize == PacketSize, "write callback arguments differ");
|
|
Require(std::equal(output.begin(), output.end(), initial.start), "write data differs from buffered bytes");
|
|
Require(io.start == (result < 0 ? initial.start : ring.data() + (half ^ PacketSize)), "failed write advanced or successful write did not wrap");
|
|
Require(io.shadow == initial.shadow && io.accumulator == initial.accumulator && io.bitsUsed == initial.bitsUsed && io.mask == initial.mask && io.current == initial.current && io.stream == initial.stream && io.referenceOffset == initial.referenceOffset, "write changed unrelated bit state");
|
|
}
|
|
}
|
|
}
|
|
|
|
void Buffered() {
|
|
for (std::size_t half : {std::size_t{0}, PacketSize}) {
|
|
for (std::size_t consumed : {std::size_t{0}, PacketSize - 1}) {
|
|
Jxr::BitIo io{123, 456, 7, ~8192, ring.data() + half, ring.data() + half + consumed, nullptr, 789};
|
|
const auto initial = io;
|
|
calls.clear();
|
|
Require(readIS_nid_postfix(nullptr, &io) == 0 && writeIS_nid_postfix(nullptr, &io) == 0, "buffered data unexpectedly required a stream");
|
|
Require(calls.empty() && std::memcmp(&initial, &io, sizeof(io)) == 0, "buffered operation mutated state or performed I/O");
|
|
}
|
|
}
|
|
}
|
|
|
|
void CallbackMutation(Jxr::Stream& stream) {
|
|
Jxr::Stream replacement{};
|
|
readResult = -73;
|
|
seekResult = -99;
|
|
std::fill(ring.begin(), ring.end(), 0xcc);
|
|
calls.clear();
|
|
Jxr::BitIo io{7, 19, 13, ~8192, ring.data(), ring.data() + PacketSize, &stream, 91};
|
|
mutation = {&io, ring.data() + PacketSize, &replacement, nullptr, nullptr};
|
|
Require(readIS_nid_postfix(nullptr, &io) == 0, "seek mutation read failed");
|
|
Require(calls == std::vector<char>({'s', 'r'}) && lastStream == &stream, "read did not retain the stream selected before seek");
|
|
Require(lastBuffer == ring.data() + PacketSize && lastSize == PacketSize, "read did not use the buffer selected by seek");
|
|
Require(io.stream == &replacement && io.start == ring.data() && io.referenceOffset == 91 + PacketSize, "seek mutation was lost before wrapping");
|
|
std::uint32_t shadow;
|
|
std::memcpy(&shadow, ring.data() + PacketSize, sizeof(shadow));
|
|
Require(io.shadow == shadow && ring.front() == 0xcc, "seek-selected buffer shadow differs");
|
|
|
|
std::fill(ring.begin(), ring.end(), 0xab);
|
|
calls.clear();
|
|
io = {7, 19, 13, ~8192, ring.data(), ring.data() + PacketSize, &stream, 123};
|
|
mutation = {&io, nullptr, nullptr, ring.data() + PacketSize, nullptr};
|
|
Require(readIS_nid_postfix(nullptr, &io) == 0, "read mutation failed");
|
|
Require(lastBuffer == ring.data() && io.start == ring.data(), "read mutation did not precede final wrap");
|
|
Require(io.shadow == 0xabababab && io.referenceOffset == 123 + PacketSize, "shadow did not use the pointer changed by read");
|
|
|
|
for (std::int64_t result : {std::int64_t{0}, std::int64_t{0x100000001}, std::int64_t{-73}}) {
|
|
std::fill(ring.begin(), ring.end(), 0xef);
|
|
calls.clear();
|
|
io = {7, 19, 13, ~8192, ring.data(), ring.data() + PacketSize, &stream, 789};
|
|
mutation = {&io, nullptr, nullptr, nullptr, ring.data() + PacketSize};
|
|
writeResult = result;
|
|
Require(writeIS_nid_postfix(nullptr, &io) == result, "mutating write result differs");
|
|
Require(calls == std::vector<char>({'w'}) && lastBuffer == ring.data(), "mutating write buffer differs");
|
|
Require(io.start == ring.data() + (result < 0 ? PacketSize : 0), "write callback mutation did not precede success wrap or failed return");
|
|
Require(io.referenceOffset == 789 && io.shadow == 7, "mutating write changed unrelated state");
|
|
}
|
|
mutation = {};
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
std::int64_t APS5_VABI ObserveRead(Jxr::Stream*, void*, std::size_t) {
|
|
calls.push_back('r');
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
void GuestMapping(Jxr::Stream& stream) {
|
|
constexpr std::size_t mappingSize = 0x4000;
|
|
std::int64_t physical = 0;
|
|
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, mappingSize, 0, 0, &physical) == 0, "allocate guest packet backing");
|
|
void* mapping = nullptr;
|
|
Require(sceKernelMapDirectMemory(&mapping, mappingSize, 3, 0, physical, 0) == 0 && mapping, "map guest packet backing");
|
|
Require(GuestArena::GuestArenaContains_nid_postfix(mapping, mappingSize), "packet mapping is outside the guest arena");
|
|
auto* bytes = static_cast<std::uint8_t*>(mapping);
|
|
std::fill_n(bytes, mappingSize, 0xcc);
|
|
calls.clear();
|
|
readResult = 0;
|
|
seekResult = 0;
|
|
Jxr::BitIo io{7, 19, 13, ~8192, bytes, bytes + PacketSize, &stream, 91};
|
|
Require(readIS_nid_postfix(nullptr, &io) == 0, "guest packet refill failed");
|
|
Require(calls == std::vector<char>({'s', 'r'}) && lastBuffer == bytes && lastSize == PacketSize, "guest packet callback arguments differ");
|
|
Require(io.start == bytes + PacketSize && io.referenceOffset == 91 + PacketSize, "guest packet refill did not advance");
|
|
for (std::size_t i = 0; i < mappingSize; ++i) {
|
|
Require(bytes[i] == (i < PacketSize ? static_cast<std::uint8_t>(i * 17 + 91) : 0xcc), "guest packet refill changed unexpected bytes");
|
|
}
|
|
#ifdef _WIN32
|
|
Require(sceKernelMprotect(mapping, mappingSize, 1) == 0, "protect guest packet backing read-only");
|
|
{
|
|
GuestArena::HostWrite access(bytes, PacketSize);
|
|
Require(!access.Open(), "read-only guest packet backing accepted a host write");
|
|
}
|
|
Jxr::Stream observedStream = stream;
|
|
observedStream.read = ObserveRead;
|
|
io = {7, 19, 13, ~8192, bytes, bytes + PacketSize, &observedStream, 91};
|
|
const auto initial = io;
|
|
std::array<std::uint8_t, mappingSize> before;
|
|
std::copy_n(bytes, mappingSize, before.begin());
|
|
calls.clear();
|
|
bool rejected = false;
|
|
try {
|
|
readIS_nid_postfix(nullptr, &io);
|
|
} catch (const std::runtime_error&) {
|
|
rejected = true;
|
|
}
|
|
Require(rejected && calls == std::vector<char>({'s'}), "rejected guest packet write invoked the read callback");
|
|
Require(std::memcmp(&io, &initial, sizeof(io)) == 0 && std::equal(before.begin(), before.end(), bytes), "rejected guest packet write mutated state or backing");
|
|
Require(sceKernelMprotect(mapping, mappingSize, 3) == 0, "restore writable guest packet backing");
|
|
#endif
|
|
Require(sceKernelMunmap(reinterpret_cast<std::uint64_t>(mapping), mappingSize) == 0, "unmap guest packet backing");
|
|
Require(sceKernelReleaseDirectMemory(physical, mappingSize) == 0, "release guest packet backing");
|
|
}
|
|
|
|
}
|
|
|
|
int main() {
|
|
Jxr::Stream stream{};
|
|
stream.read = Read;
|
|
stream.write = Write;
|
|
stream.setPosition = SetPosition;
|
|
Reads(stream);
|
|
Writes(stream);
|
|
Buffered();
|
|
CallbackMutation(stream);
|
|
GuestMapping(stream);
|
|
return 0;
|
|
}
|