mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-29 12:15:39 +00:00
283 lines
12 KiB
C++
283 lines
12 KiB
C++
#include "prx/libSceJxr/include/Api.hpp"
|
|
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
#include <array>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <exception>
|
|
#include <stdexcept>
|
|
#include <thread>
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#else
|
|
#include <dlfcn.h>
|
|
#include <sys/mman.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
extern "C" int JxrReferenceCase_nid_no_patch(std::uint8_t, int, int, std::uint8_t*, std::uint64_t*);
|
|
using Query = decltype(&PixelFormatLookup_nid_postfix);
|
|
static_assert(sizeof(JxrPixelInfo) == 56 && alignof(JxrPixelInfo) == 8);
|
|
static_assert(offsetof(JxrPixelInfo, guid) == 0 && offsetof(JxrPixelInfo, channels) == 8);
|
|
static_assert(offsetof(JxrPixelInfo, colorFormat) == 16 && offsetof(JxrPixelInfo, bitDepth) == 20);
|
|
static_assert(offsetof(JxrPixelInfo, bitsPerUnit) == 24 && offsetof(JxrPixelInfo, flags) == 32);
|
|
static_assert(offsetof(JxrPixelInfo, interpretation) == 40 && offsetof(JxrPixelInfo, sampleFormat) == 52);
|
|
|
|
namespace {
|
|
|
|
void Require(bool condition, const char* message) {
|
|
if (condition) return;
|
|
std::fprintf(stderr, "JXR lookup: %s\n", message);
|
|
std::exit(1);
|
|
}
|
|
|
|
struct Reference {
|
|
std::array<std::uint8_t, 16> guid{};
|
|
std::array<std::uint64_t, 9> fields{};
|
|
};
|
|
|
|
bool ReadReference(unsigned hash, bool reverse, bool white, Reference& expected) {
|
|
const auto result = JxrReferenceCase_nid_no_patch(static_cast<std::uint8_t>(hash), reverse, white,
|
|
expected.guid.data(), expected.fields.data());
|
|
Require(result >= 0, "native lookup reference failed");
|
|
return result != 0;
|
|
}
|
|
|
|
JxrPixelInfo BackwardInput(const Reference& source) {
|
|
JxrPixelInfo input{};
|
|
input.guid = reinterpret_cast<void*>(std::uintptr_t{1});
|
|
input.channels = UINT64_MAX;
|
|
input.colorFormat = -1;
|
|
input.bitDepth = -1;
|
|
input.bitsPerUnit = UINT32_MAX;
|
|
input.flags = source.fields[4] | UINT64_C(0xfedcba9880000040);
|
|
input.interpretation = static_cast<std::uint32_t>(source.fields[5]);
|
|
input.samplesPerPixel = static_cast<std::uint32_t>(source.fields[6]);
|
|
input.bitsPerSample = static_cast<std::uint32_t>(source.fields[7]);
|
|
input.sampleFormat = static_cast<std::uint32_t>(source.fields[8]);
|
|
return input;
|
|
}
|
|
|
|
void CheckValue(const JxrPixelInfo& actual, const Reference& expected) {
|
|
const std::array<std::uint64_t, 9> fields{{actual.channels, static_cast<std::uint32_t>(actual.colorFormat),
|
|
static_cast<std::uint32_t>(actual.bitDepth), actual.bitsPerUnit, actual.flags, actual.interpretation,
|
|
actual.samplesPerPixel, actual.bitsPerSample, actual.sampleFormat}};
|
|
Require(fields == expected.fields, "metadata differs from pinned native reference");
|
|
Require(actual.guid != nullptr, "successful lookup returned null GUID");
|
|
Require(std::memcmp(actual.guid, expected.guid.data(), 16) == 0, "GUID differs from pinned native reference");
|
|
}
|
|
|
|
void PaddingCases(Query query, bool reverse) {
|
|
constexpr auto begin = offsetof(JxrPixelInfo, bitsPerUnit) + sizeof(JxrPixelInfo::bitsPerUnit);
|
|
constexpr auto size = offsetof(JxrPixelInfo, flags) - begin;
|
|
static_assert(begin == 28 && size == 4);
|
|
const std::array<std::byte, size> zero{};
|
|
struct Guarded {
|
|
std::uint64_t before;
|
|
JxrPixelInfo value;
|
|
std::uint64_t after;
|
|
};
|
|
unsigned formats = 0;
|
|
for (unsigned hash = 0; hash < 256; ++hash) {
|
|
Reference forward;
|
|
if (!ReadReference(hash, false, false, forward)) continue;
|
|
Reference expected = forward;
|
|
Guarded record;
|
|
std::memset(&record, 0xa5, sizeof(record));
|
|
if (reverse) {
|
|
record.value = BackwardInput(forward);
|
|
Require(ReadReference(hash, true, false, expected), "padding reverse reference missing");
|
|
} else {
|
|
record.value.guid = forward.guid.data();
|
|
}
|
|
auto* bytes = reinterpret_cast<std::byte*>(&record.value);
|
|
std::memset(bytes + begin, 0xa5, size);
|
|
Require(query(&record.value, reverse ? 1 : 0) == 0, "padding lookup succeeds");
|
|
CheckValue(record.value, expected);
|
|
Require(std::memcmp(bytes + begin, zero.data(), size) == 0,
|
|
reverse ? "reverse success padding is not zero" : "forward success padding is not zero");
|
|
Require(record.before == UINT64_C(0xa5a5a5a5a5a5a5a5) && record.after == UINT64_C(0xa5a5a5a5a5a5a5a5),
|
|
"padding lookup changed record canaries");
|
|
++formats;
|
|
}
|
|
Require(formats != 0, "padding reference inventory empty");
|
|
std::printf("JXR %s padding passed across %u reference hashes\n", reverse ? "reverse" : "forward", formats);
|
|
}
|
|
|
|
class GuardedTail {
|
|
void* memory;
|
|
std::size_t page;
|
|
void* data;
|
|
public:
|
|
GuardedTail(const void* source, std::size_t bytes) {
|
|
#ifdef _WIN32
|
|
SYSTEM_INFO info;
|
|
GetSystemInfo(&info);
|
|
page = info.dwPageSize;
|
|
memory = VirtualAlloc(nullptr, page * 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
|
|
Require(memory != nullptr, "guard allocation");
|
|
DWORD previous;
|
|
Require(VirtualProtect(static_cast<unsigned char*>(memory) + page, page, PAGE_NOACCESS, &previous) != 0, "guard protection");
|
|
#else
|
|
page = static_cast<std::size_t>(sysconf(_SC_PAGESIZE));
|
|
memory = mmap(nullptr, page * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
|
Require(memory != MAP_FAILED, "guard allocation");
|
|
Require(mprotect(static_cast<unsigned char*>(memory) + page, page, PROT_NONE) == 0, "guard protection");
|
|
#endif
|
|
Require(bytes <= page, "guard fixture size");
|
|
data = static_cast<unsigned char*>(memory) + page - bytes;
|
|
std::memcpy(data, source, bytes);
|
|
}
|
|
void ReadOnly() {
|
|
#ifdef _WIN32
|
|
DWORD previous;
|
|
Require(VirtualProtect(memory, page, PAGE_READONLY, &previous) != 0, "read-only protection");
|
|
#else
|
|
Require(mprotect(memory, page, PROT_READ) == 0, "read-only protection");
|
|
#endif
|
|
}
|
|
~GuardedTail() {
|
|
#ifdef _WIN32
|
|
VirtualFree(memory, 0, MEM_RELEASE);
|
|
#else
|
|
munmap(memory, page * 2);
|
|
#endif
|
|
}
|
|
template<class T> T* As() const { return static_cast<T*>(data); }
|
|
};
|
|
|
|
void Cases(Query query) {
|
|
Require(query(nullptr, 2) == -106, "invalid mode has signed 64-bit unsupported-format result");
|
|
Reference forward;
|
|
unsigned formats = 0;
|
|
for (unsigned hash = 0; hash < 256; ++hash) {
|
|
if (!ReadReference(hash, false, false, forward)) continue;
|
|
++formats;
|
|
JxrPixelInfo input;
|
|
std::memset(&input, 0xa5, sizeof(input));
|
|
input.guid = forward.guid.data();
|
|
const auto originalGuid = forward.guid;
|
|
Require(query(&input, 0) == 0, "forward lookup");
|
|
CheckValue(input, forward);
|
|
Require(forward.guid == originalGuid, "lookup modified input GUID");
|
|
Reference backward;
|
|
Require(ReadReference(hash, true, false, backward), "backward native case missing");
|
|
input = BackwardInput(forward);
|
|
Require(query(&input, 1) == 0, "backward lookup ignores GUID and high flag bits");
|
|
CheckValue(input, backward);
|
|
}
|
|
Require(formats != 0, "reference format inventory empty");
|
|
Reference white;
|
|
Require(ReadReference(5, false, false, forward), "black-white reference");
|
|
Require(ReadReference(5, true, true, white), "white-is-zero reference");
|
|
Require(forward.fields[5] == 1 && white.fields[5] == 0, "duplicate GUID reference order");
|
|
auto input = BackwardInput(forward);
|
|
input.interpretation = 0;
|
|
Require(query(&input, 1) == 0, "backward white-is-zero lookup");
|
|
CheckValue(input, white);
|
|
Require(query(&input, 0) == 0, "forward lookup selects first duplicate GUID");
|
|
CheckValue(input, forward);
|
|
for (unsigned mode = 2; mode < 256; ++mode) {
|
|
std::memset(&input, 0xa5, sizeof(input));
|
|
std::array<std::byte, sizeof(input)> before;
|
|
std::memcpy(before.data(), &input, sizeof(input));
|
|
Require(query(&input, static_cast<std::uint8_t>(mode)) == -106, "invalid mode error");
|
|
Require(std::memcmp(&input, before.data(), sizeof(input)) == 0, "invalid mode changed record");
|
|
Require(query(reinterpret_cast<JxrPixelInfo*>(std::uintptr_t{1}), static_cast<std::uint8_t>(mode)) == -106,
|
|
"invalid mode must not read inaccessible record");
|
|
}
|
|
std::array<std::uint8_t, 16> unknown{};
|
|
std::memset(&input, 0xff, sizeof(input));
|
|
input.guid = unknown.data();
|
|
std::array<std::byte, sizeof(input)> before;
|
|
std::memcpy(before.data(), &input, sizeof(input));
|
|
Require(query(&input, 0) == -106 && std::memcmp(&input, before.data(), sizeof(input)) == 0, "unknown GUID unchanged");
|
|
Require(query(&input, 1) == -106 && std::memcmp(&input, before.data(), sizeof(input)) == 0, "unknown TIFF fields unchanged");
|
|
GuardedTail failedRecord(&input, sizeof(input));
|
|
failedRecord.ReadOnly();
|
|
Require(query(failedRecord.As<JxrPixelInfo>(), 0) == -106, "failed lookup does not write read-only record");
|
|
const void* unknownPointer = unknown.data();
|
|
GuardedTail failedPrefix(&unknownPointer, sizeof(unknownPointer));
|
|
failedPrefix.ReadOnly();
|
|
Require(query(failedPrefix.As<JxrPixelInfo>(), 0) == -106, "failed forward lookup only reads GUID pointer");
|
|
Require(ReadReference(8, false, false, forward), "Gray8 reference");
|
|
Require(forward.fields[0] == 1 && forward.fields[7] == 8, "Gray8 reference values");
|
|
GuardedTail guid(forward.guid.data(), 16);
|
|
guid.ReadOnly();
|
|
input = {};
|
|
input.guid = guid.As<void>();
|
|
GuardedTail record(&input, sizeof(input));
|
|
Require(query(record.As<JxrPixelInfo>(), 0) == 0, "bounded record and read-only GUID");
|
|
CheckValue(*record.As<JxrPixelInfo>(), forward);
|
|
input = BackwardInput(forward);
|
|
input.guid = nullptr;
|
|
Require(query(&input, 1) == 0, "backward lookup ignores null GUID");
|
|
CheckValue(input, forward);
|
|
alignas(16) std::array<std::uint8_t, 17> unalignedGuid{};
|
|
std::memcpy(unalignedGuid.data() + 1, forward.guid.data(), 16);
|
|
input = {};
|
|
input.guid = unalignedGuid.data() + 1;
|
|
Require(query(&input, 0) == 0, "unaligned GUID lookup");
|
|
CheckValue(input, forward);
|
|
const auto borrowedGuid = record.As<JxrPixelInfo>()->guid;
|
|
std::array<std::thread, 4> threads;
|
|
for (auto& thread : threads) thread = std::thread([&] {
|
|
for (unsigned i = 0; i < 32; ++i) {
|
|
JxrPixelInfo value{};
|
|
value.guid = forward.guid.data();
|
|
Require(query(&value, 0) == 0, "concurrent independent lookup");
|
|
CheckValue(value, forward);
|
|
Require(value.guid == borrowedGuid, "borrowed GUID pointer changed across calls");
|
|
}
|
|
});
|
|
for (auto& thread : threads) thread.join();
|
|
std::printf("JXR guest lookup cases passed across %u reference hashes\n", formats);
|
|
}
|
|
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
#ifdef _WIN32
|
|
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
|
|
#endif
|
|
try {
|
|
if (argc == 2 && std::strcmp(argv[1], "reference") == 0) {
|
|
Reference gray;
|
|
Require(ReadReference(8, false, false, gray) && gray.fields[0] == 1 && gray.fields[7] == 8, "native Gray8 reference");
|
|
unsigned formats = 0;
|
|
for (unsigned hash = 0; hash < 256; ++hash) { Reference item; if (ReadReference(hash, false, false, item)) ++formats; }
|
|
std::printf("Pinned native JXR reference passed with %u hashes\n", formats);
|
|
return 0;
|
|
}
|
|
Require(argc == 3 || argc == 4, "expected library, export mode and optional padding direction");
|
|
const char* name = std::strcmp(argv[2], "patched") == 0 ? "5Gjz1nLOKvo" : "PixelFormatLookup_nid_postfix";
|
|
#ifdef _WIN32
|
|
const auto library = LoadLibraryA(argv[1]);
|
|
Require(library != nullptr, "load requested library");
|
|
const auto query = reinterpret_cast<Query>(GetProcAddress(library, name));
|
|
#else
|
|
const auto library = dlopen(argv[1], RTLD_NOW | RTLD_LOCAL);
|
|
Require(library != nullptr, "load requested library");
|
|
const auto query = reinterpret_cast<Query>(dlsym(library, name));
|
|
#endif
|
|
Require(query != nullptr, "missing lookup export");
|
|
if (argc == 4) {
|
|
Require(std::strcmp(argv[3], "forward") == 0 || std::strcmp(argv[3], "reverse") == 0, "padding direction");
|
|
PaddingCases(query, std::strcmp(argv[3], "reverse") == 0);
|
|
} else {
|
|
Cases(query);
|
|
}
|
|
#ifdef _WIN32
|
|
FreeLibrary(library);
|
|
#else
|
|
dlclose(library);
|
|
#endif
|
|
return 0;
|
|
} catch (const std::exception& error) {
|
|
std::fprintf(stderr, "JXR lookup exception: %s\n", error.what());
|
|
return 1;
|
|
}
|
|
}
|