#include "prx/libc/include/General.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #else #include #include #endif struct GuestIovec { void* base; std::size_t length; }; extern "C" { int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t); int APS5_VABI sceKernelClose(int); std::int64_t APS5_VABI sceKernelLseek(int, std::int64_t, int); std::int64_t APS5_VABI sceKernelReadv(int, const GuestIovec*, int); std::int64_t APS5_VABI sceKernelWritev(int, const GuestIovec*, int); std::int64_t APS5_VABI sceKernelPreadv(int, const GuestIovec*, int, std::int64_t); std::int64_t APS5_VABI sceKernelPwritev(int, const GuestIovec*, int, std::int64_t); std::int64_t APS5_VABI readv_nid_postfix(int, const GuestIovec*, int); std::int64_t APS5_VABI writev_nid_postfix(int, const GuestIovec*, int); std::int64_t APS5_VABI _readv_nid_postfix(int, const GuestIovec*, int); std::int64_t APS5_VABI _writev_nid_postfix(int, const GuestIovec*, int); std::int64_t APS5_VABI preadv_nid_postfix(int, const GuestIovec*, int, std::int64_t); std::int64_t APS5_VABI pwritev_nid_postfix(int, const GuestIovec*, int, std::int64_t); int* APS5_VABI __error_nid_postfix(); } static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "File vector memory check failed at line %d\n", line); std::abort(); } } #define Require(value) Check((value), __LINE__) struct Transfer { const char* name; bool posix; bool positioned; bool write; std::function call; }; static std::uintptr_t observedTable = 0; static std::size_t observedBytes = 0; static void ObserveRead(std::uintptr_t address, std::size_t bytes) { if (address == observedTable) observedBytes = bytes; } static void Protect(void* address, std::size_t size, bool readable) { #ifdef _WIN32 DWORD previous = 0; Require(VirtualProtect(address, size, readable ? PAGE_READONLY : PAGE_NOACCESS, &previous) != 0); #else Require(::mprotect(address, size, readable ? PROT_READ : PROT_NONE) == 0); #endif } int main(int argc, char** argv) { const std::array transfers{{ {"readv", true, false, false, readv_nid_postfix}, {"writev", true, false, true, writev_nid_postfix}, {"preadv", true, true, false, [](int fd, const GuestIovec* iov, int count) { return preadv_nid_postfix(fd, iov, count, 1); }}, {"pwritev", true, true, true, [](int fd, const GuestIovec* iov, int count) { return pwritev_nid_postfix(fd, iov, count, 1); }}, {"_readv", true, false, false, _readv_nid_postfix}, {"_writev", true, false, true, _writev_nid_postfix}, {"sceKernelReadv", false, false, false, sceKernelReadv}, {"sceKernelWritev", false, false, true, sceKernelWritev}, {"sceKernelPreadv", false, true, false, [](int fd, const GuestIovec* iov, int count) { return sceKernelPreadv(fd, iov, count, 1); }}, {"sceKernelPwritev", false, true, true, [](int fd, const GuestIovec* iov, int count) { return sceKernelPwritev(fd, iov, count, 1); }}, }}; #ifdef _WIN32 SYSTEM_INFO system{}; GetSystemInfo(&system); const std::size_t page = system.dwPageSize; auto* memory = static_cast(VirtualAlloc(nullptr, page * 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); Require(memory != nullptr); #else const auto page = static_cast(::sysconf(_SC_PAGESIZE)); auto* memory = static_cast(::mmap(nullptr, page * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); Require(memory != MAP_FAILED); #endif std::array bytes{'A', 'B'}; const std::array vectors{{{bytes.data(), 1}, {bytes.data() + 1, 1}}}; std::memcpy(memory, vectors.data(), sizeof(vectors)); std::memcpy(memory + page - sizeof(GuestIovec), vectors.data(), sizeof(GuestIovec)); Protect(memory, page, true); Protect(memory + page, page, false); const auto path = std::filesystem::temp_directory_path() / ("anyps5-iovec-memory-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); AddPathAlias_nid_no_patch("/anyps5-iovec-memory", path.parent_path().string().c_str()); const auto guestPath = "/anyps5-iovec-memory/" + path.filename().string(); for (const auto& transfer : transfers) { if (argc > 1 && transfer.name != std::string(argv[1])) continue; std::fprintf(stderr, "%s\n", transfer.name); { std::ofstream file(path, std::ios::binary); file << "ABCD"; Require(static_cast(file)); } const int fd = sceKernelOpen(guestPath.c_str(), SCE_KERNEL_O_RDWR, 0); Require(fd >= 0); const std::array invalid{{ nullptr, reinterpret_cast(std::uintptr_t{1}), reinterpret_cast(memory + page), reinterpret_cast(memory + page - sizeof(GuestIovec)), reinterpret_cast(memory + page - sizeof(GuestIovec) / 2), reinterpret_cast(UINTPTR_MAX - sizeof(GuestIovec) + 1), }}; for (const auto* table : invalid) { *__error_nid_postfix() = 0; const auto expected = transfer.posix ? -1 : static_cast(0x8002000eu); Require(transfer.call(fd, table, 2) == expected); if (transfer.posix) Require(*__error_nid_postfix() == 14); Require(sceKernelLseek(fd, 0, SEEK_CUR) == 0); Require((bytes == std::array{'A', 'B'})); std::ifstream file(path, std::ios::binary); std::string contents; file >> contents; Require(contents == "ABCD"); Require(transfer.call(fd, table, 0) == 0); const auto invalidCount = transfer.posix ? -1 : static_cast(0x80020016u); Require(transfer.call(fd, table, -1) == invalidCount); if (transfer.posix) Require(*__error_nid_postfix() == 22); Require(transfer.call(fd, table, 1025) == invalidCount); if (transfer.posix) Require(*__error_nid_postfix() == 22); } observedTable = reinterpret_cast(memory); observedBytes = 0; GuestArena::GuestArenaSetHostReadHook_nid_no_patch(ObserveRead); Require(transfer.call(fd, reinterpret_cast(memory), 2) == 2); GuestArena::GuestArenaSetHostReadHook_nid_no_patch(nullptr); Require(observedBytes == sizeof(vectors)); Require(sceKernelLseek(fd, 0, SEEK_CUR) == (transfer.positioned ? 0 : 2)); Require(std::memcmp(memory, vectors.data(), sizeof(vectors)) == 0); if (transfer.write) { std::ifstream file(path, std::ios::binary); std::string contents; file >> contents; Require(contents == (transfer.positioned ? "AABD" : "ABCD")); } else { Require((bytes == (transfer.positioned ? std::array{'B', 'C'} : std::array{'A', 'B'}))); } Require(sceKernelLseek(fd, 0, SEEK_SET) == 0); Require(transfer.call(fd, reinterpret_cast(memory + page - sizeof(GuestIovec)), 1) == 1); Require(sceKernelLseek(fd, 0, SEEK_CUR) == (transfer.positioned ? 0 : 1)); Require(sceKernelLseek(fd, 0, SEEK_SET) == 0); std::array maximum{}; maximum.back() = {bytes.data(), 1}; Require(transfer.call(fd, maximum.data(), static_cast(maximum.size())) == 1); Require(sceKernelLseek(fd, 0, SEEK_CUR) == (transfer.positioned ? 0 : 1)); Require(sceKernelClose(fd) == 0); bytes = {'A', 'B'}; } Require(std::filesystem::remove(path)); #ifdef _WIN32 Require(VirtualFree(memory, 0, MEM_RELEASE) != 0); #else Require(::munmap(memory, page * 2) == 0); #endif }