mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 00:22:57 -05:00
In a payload process on a PS5 (firmware 12.70), jrand48 and mrand48 return the high 32 bits of the new 48-bit state zero-extended to the 64-bit long, as FreeBSD 11.0 does, so a result with bit 31 set comes back between 0x80000000 and 0xFFFFFFFF. Here they sign-extended it, as FreeBSD 11.1 and later do with an (int32_t) cast. lrand48 and nrand48 already matched the payload process. The tests now pin its results for three seeds and the first six mrand48 values after srand48(1), compare with the host as 32-bit values, and the debt entry records the measurement. AI-assisted: yes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
237 lines
12 KiB
C++
237 lines
12 KiB
C++
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <stdexcept>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
extern "C" {
|
|
std::int64_t APS5_VABI random_nid_postfix();
|
|
void APS5_VABI srandom_nid_postfix(std::uint64_t);
|
|
char* APS5_VABI initstate_nid_postfix(std::uint64_t, char*, std::int64_t);
|
|
char* APS5_VABI setstate_nid_postfix(char*);
|
|
int* APS5_VABI __error_nid_postfix();
|
|
double APS5_VABI erand48_nid_postfix(std::uint16_t*);
|
|
std::int64_t APS5_VABI nrand48_nid_postfix(std::uint16_t*);
|
|
std::int64_t APS5_VABI jrand48_nid_postfix(std::uint16_t*);
|
|
double APS5_VABI drand48_nid_postfix();
|
|
std::int64_t APS5_VABI lrand48_nid_postfix();
|
|
std::int64_t APS5_VABI mrand48_nid_postfix();
|
|
void APS5_VABI srand48_nid_postfix(std::int64_t);
|
|
std::uint16_t* APS5_VABI seed48_nid_postfix(const std::uint16_t*);
|
|
void APS5_VABI lcong48_nid_postfix(const std::uint16_t*);
|
|
}
|
|
|
|
static int failures = 0;
|
|
|
|
static void Require(bool condition, const char* message) {
|
|
if (!condition) {
|
|
std::fprintf(stderr, "%s\n", message);
|
|
++failures;
|
|
}
|
|
}
|
|
|
|
using Sequence = std::array<std::int64_t, 5>;
|
|
|
|
static Sequence Draw() {
|
|
Sequence values{};
|
|
for (auto& value : values) value = random_nid_postfix();
|
|
return values;
|
|
}
|
|
|
|
static std::uint32_t Word(const char* table, std::size_t index) {
|
|
std::uint32_t value;
|
|
std::memcpy(&value, table + sizeof(value) * index, sizeof(value));
|
|
return value;
|
|
}
|
|
|
|
static bool SeedIs(const std::uint16_t* seed, std::uint16_t low, std::uint16_t middle, std::uint16_t high) {
|
|
return seed[0] == low && seed[1] == middle && seed[2] == high;
|
|
}
|
|
|
|
static void CheckRand48() {
|
|
std::uint16_t seed[3] = {0x330e, 0xabcd, 0x1234};
|
|
Require(erand48_nid_postfix(seed) == 0x657eb7255101p-48 && SeedIs(seed, 0x5101, 0xb725, 0x657e), "erand48 first value");
|
|
Require(nrand48_nid_postfix(seed) == 1804928587 && SeedIs(seed, 0x6378, 0x0c96, 0xd72a), "nrand48 second value");
|
|
Require(jrand48_nid_postfix(seed) == 1517566982 && SeedIs(seed, 0x2a23, 0x3c06, 0x5a74), "jrand48 third value");
|
|
Require(erand48_nid_postfix(seed) == 0x72534abf62f2p-48 && SeedIs(seed, 0x62f2, 0x4abf, 0x7253), "erand48 fourth value");
|
|
Require(nrand48_nid_postfix(seed) == 684387517 && SeedIs(seed, 0x8d15, 0xd97a, 0x5195), "nrand48 fifth value");
|
|
Require(jrand48_nid_postfix(seed) == 3807181130 && SeedIs(seed, 0xeffc, 0xf94a, 0xe2ec), "jrand48 keeps bit 31 without sign extension");
|
|
|
|
std::uint16_t zero[3] = {};
|
|
Require(nrand48_nid_postfix(zero) == 0 && SeedIs(zero, 0x000b, 0, 0), "rand48 zero seed adds the increment");
|
|
std::uint16_t top[3] = {0xffff, 0xffff, 0xffff};
|
|
const double value = erand48_nid_postfix(top);
|
|
Require(value >= 0.0 && value < 1.0, "erand48 stays below 1");
|
|
|
|
srand48_nid_postfix(0x1234abcd);
|
|
std::uint16_t expectedSeed[3] = {0x330e, 0xabcd, 0x1234};
|
|
Require(drand48_nid_postfix() == erand48_nid_postfix(expectedSeed), "drand48 matches erand48 on initialized seed");
|
|
Require(lrand48_nid_postfix() == nrand48_nid_postfix(expectedSeed), "lrand48 matches nrand48 on initialized seed");
|
|
Require(mrand48_nid_postfix() == jrand48_nid_postfix(expectedSeed), "mrand48 matches jrand48 on initialized seed");
|
|
Require(drand48_nid_postfix() == erand48_nid_postfix(expectedSeed), "drand48 fourth value matches erand48");
|
|
|
|
const std::uint16_t parameters[7] = {1, 0, 0, 3, 0, 0, 5};
|
|
lcong48_nid_postfix(parameters);
|
|
Require(lrand48_nid_postfix() == 0 && mrand48_nid_postfix() == 0, "lcong48 sets seed, multiplier and addend");
|
|
std::uint16_t high[3] = {0, 0, 1};
|
|
Require(jrand48_nid_postfix(high) == 196608 && SeedIs(high, 5, 0, 3), "lcong48 multiplier scales the seed");
|
|
std::uint16_t custom[3] = {2, 0, 0};
|
|
Require(nrand48_nid_postfix(custom) == 0 && SeedIs(custom, 11, 0, 0), "lcong48 parameters apply to nrand48");
|
|
const std::uint16_t newSeed[3] = {0x330e, 0xabcd, 0x1234};
|
|
std::uint16_t* const echoed = seed48_nid_postfix(newSeed);
|
|
Require(SeedIs(echoed, 0x330e, 0xabcd, 0x1234), "seed48 returns a copy of the new seed");
|
|
std::uint16_t again[3] = {0x330e, 0xabcd, 0x1234};
|
|
Require(drand48_nid_postfix() == erand48_nid_postfix(again), "seed48 restores the default multiplier and addend");
|
|
lcong48_nid_postfix(parameters);
|
|
srand48_nid_postfix(0x1234abcd);
|
|
std::uint16_t reset[3] = {0x330e, 0xabcd, 0x1234};
|
|
Require(lrand48_nid_postfix() == nrand48_nid_postfix(reset), "srand48 restores the default multiplier and addend");
|
|
|
|
std::uint16_t measured[3] = {0x000b, 0x0016, 0x0021};
|
|
Require(jrand48_nid_postfix(measured) == 3702088075 && SeedIs(measured, 0xe6ba, 0x618b, 0xdca9), "jrand48 matches the console for a result with bit 31 set");
|
|
std::uint16_t topBit[3] = {0, 0, 0x8000};
|
|
Require(jrand48_nid_postfix(topBit) == 2147483648, "jrand48 matches the console for 0x80000000");
|
|
std::uint16_t ones[3] = {0xffff, 0xffff, 0xffff};
|
|
Require(jrand48_nid_postfix(ones) == 4294582547, "jrand48 matches the console for an all-ones seed");
|
|
constexpr std::array<std::int64_t, 6> consoleMrand48 {178800969, 1952030186, 3585512650, 1443049011, 2428758494, 7588830};
|
|
srand48_nid_postfix(1);
|
|
for (const std::int64_t expected : consoleMrand48) Require(mrand48_nid_postfix() == expected, "mrand48 matches the console after srand48(1)");
|
|
|
|
#ifndef _WIN32
|
|
std::uint16_t guest[3] = {0x1234, 0x5678, 0x9abc};
|
|
unsigned short host[3] = {0x1234, 0x5678, 0x9abc};
|
|
for (int round = 0; round < 64; ++round) {
|
|
Require(erand48_nid_postfix(guest) == ::erand48(host), "erand48 matches the host");
|
|
Require(nrand48_nid_postfix(guest) == ::nrand48(host), "nrand48 matches the host");
|
|
Require(jrand48_nid_postfix(guest) == static_cast<std::uint32_t>(::jrand48(host)), "jrand48 matches the host's 32 bits");
|
|
}
|
|
::srand48(1234567);
|
|
srand48_nid_postfix(1234567);
|
|
for (int round = 0; round < 32; ++round) {
|
|
Require(drand48_nid_postfix() == ::drand48(), "drand48 matches the host");
|
|
Require(lrand48_nid_postfix() == ::lrand48(), "lrand48 matches the host");
|
|
Require(mrand48_nid_postfix() == static_cast<std::uint32_t>(::mrand48()), "mrand48 matches the host's 32 bits");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
int main() {
|
|
CheckRand48();
|
|
constexpr Sequence seedOne {1408992891, 1638182910, 83848093, 145962118, 627851256};
|
|
constexpr Sequence seed12345 {1052984060, 1639322404, 1971525204, 2097956667, 2012147915};
|
|
constexpr std::array<Sequence, 5> seed42ByType {{
|
|
{2060028338, 1164936638, 481681973, 1763071474, 962919018},
|
|
{660273387, 1356916325, 287587791, 1132382070, 1497594867},
|
|
{1015209508, 12952757, 1418769128, 2005808989, 1212998434},
|
|
{745791173, 418148062, 949332769, 1480536337, 177711995},
|
|
{996638292, 1209745284, 1148231710, 400392963, 1149215164},
|
|
}};
|
|
constexpr std::array<std::int64_t, 6> seed7Type1 {1271472800, 27686551, 1252069488, 1609001632, 28873941, 508017694};
|
|
|
|
Require(Draw() == seedOne, "unseeded random must match srandom(1)");
|
|
srandom_nid_postfix(1);
|
|
Require(Draw() == seedOne, "srandom(1) sequence");
|
|
srandom_nid_postfix(0x100000001ull);
|
|
Require(Draw() == seedOne, "srandom must use the low 32 bits of the seed");
|
|
srandom_nid_postfix(12345);
|
|
Require(Draw() == seed12345, "srandom(12345) sequence");
|
|
|
|
struct SizeCase {
|
|
std::int64_t size;
|
|
int type;
|
|
std::size_t usedBytes;
|
|
};
|
|
constexpr std::array<SizeCase, 10> sizes {{
|
|
{8, 0, 8}, {31, 0, 8}, {32, 1, 32}, {63, 1, 32}, {64, 2, 64},
|
|
{127, 2, 64}, {128, 3, 128}, {255, 3, 128}, {256, 4, 256}, {1000, 4, 256},
|
|
}};
|
|
std::vector<std::vector<char>> buffers;
|
|
char* defaultTable = nullptr;
|
|
char* active = nullptr;
|
|
for (const auto& sizeCase : sizes) {
|
|
auto& buffer = buffers.emplace_back(static_cast<std::size_t>(sizeCase.size) + 4, static_cast<char>(0xa5));
|
|
char* previous = initstate_nid_postfix(42, buffer.data(), sizeCase.size);
|
|
if (defaultTable == nullptr) defaultTable = previous;
|
|
else Require(previous == active, "initstate must return the previous state");
|
|
Require(Word(buffer.data(), 0) == static_cast<std::uint32_t>(sizeCase.type), "initstate type word");
|
|
Require(Draw() == seed42ByType[static_cast<std::size_t>(sizeCase.type)], "initstate(42) sequence for its size");
|
|
Require(std::all_of(buffer.begin() + static_cast<std::ptrdiff_t>(sizeCase.usedBytes), buffer.end(),
|
|
[](char value) { return value == static_cast<char>(0xa5); }), "initstate must use only the rounded state size");
|
|
active = buffer.data();
|
|
}
|
|
Require(defaultTable != nullptr && Word(defaultTable, 0) == 5 * 5 + 3, "default table must hold its rear position");
|
|
Require(setstate_nid_postfix(defaultTable) == active, "setstate must return the previous state");
|
|
Require(random_nid_postfix() == 1533042628 && random_nid_postfix() == 1225903521,
|
|
"setstate on the default table must continue the srandom(12345) sequence");
|
|
|
|
alignas(4) std::array<char, 32> small{};
|
|
alignas(4) std::array<char, 64> large{};
|
|
initstate_nid_postfix(7, small.data(), static_cast<std::int64_t>(small.size()));
|
|
for (std::size_t i = 0; i < 3; ++i) Require(random_nid_postfix() == seed7Type1[i], "initstate(7) on 32 bytes");
|
|
initstate_nid_postfix(7, large.data(), static_cast<std::int64_t>(large.size()));
|
|
Require(setstate_nid_postfix(small.data()) == large.data(), "setstate must return the 64-byte state");
|
|
Require(setstate_nid_postfix(large.data()) == small.data(), "setstate must return the 32-byte state");
|
|
Require(Word(small.data(), 0) == 5 * 3 + 1, "setstate must save the rear position of the old state");
|
|
Require(setstate_nid_postfix(small.data()) == large.data(), "setstate back to the 32-byte state");
|
|
for (std::size_t i = 3; i < seed7Type1.size(); ++i) Require(random_nid_postfix() == seed7Type1[i], "setstate must resume the sequence");
|
|
|
|
initstate_nid_postfix(7, small.data(), static_cast<std::int64_t>(small.size()));
|
|
random_nid_postfix();
|
|
alignas(4) std::array<char, 32> garbled{};
|
|
const std::uint32_t garbledWord = 5 * 7 + 1;
|
|
std::memcpy(garbled.data(), &garbledWord, sizeof(garbledWord));
|
|
Require(setstate_nid_postfix(garbled.data()) == nullptr, "setstate must reject a rear position past the degree");
|
|
Require(random_nid_postfix() == seed7Type1[1], "a rejected setstate must keep the current state");
|
|
|
|
*__error_nid_postfix() = 1234;
|
|
std::array<char, 8> tiny{};
|
|
tiny.fill(static_cast<char>(0x5a));
|
|
for (const std::int64_t size : {std::int64_t{7}, std::int64_t{0}, std::int64_t{-1}})
|
|
Require(initstate_nid_postfix(1, tiny.data(), size) == nullptr, "initstate below 8 bytes must return null");
|
|
Require(initstate_nid_postfix(1, nullptr, 4) == nullptr, "initstate below 8 bytes must return null before reading the state");
|
|
Require(*__error_nid_postfix() == 1234, "initstate below 8 bytes must leave errno unchanged");
|
|
Require(std::all_of(tiny.begin(), tiny.end(), [](char value) { return value == static_cast<char>(0x5a); }),
|
|
"initstate below 8 bytes must not write the state");
|
|
Require(random_nid_postfix() == seed7Type1[2], "a rejected initstate must keep the current state");
|
|
|
|
bool threw = false;
|
|
try {
|
|
initstate_nid_postfix(1, nullptr, 128);
|
|
} catch (const std::invalid_argument&) {
|
|
threw = true;
|
|
}
|
|
Require(threw, "initstate with a null state must throw");
|
|
threw = false;
|
|
try {
|
|
setstate_nid_postfix(nullptr);
|
|
} catch (const std::invalid_argument&) {
|
|
threw = true;
|
|
}
|
|
Require(threw, "setstate with a null state must throw");
|
|
|
|
constexpr std::size_t threadCount = 4;
|
|
constexpr std::size_t callsPerThread = 2000;
|
|
srandom_nid_postfix(99);
|
|
std::vector<std::int64_t> serial(threadCount * callsPerThread);
|
|
for (auto& value : serial) value = random_nid_postfix();
|
|
srandom_nid_postfix(99);
|
|
std::vector<std::int64_t> concurrent(threadCount * callsPerThread);
|
|
std::vector<std::thread> threads;
|
|
for (std::size_t t = 0; t < threadCount; ++t)
|
|
threads.emplace_back([&concurrent, t] {
|
|
for (std::size_t i = 0; i < callsPerThread; ++i) concurrent[t * callsPerThread + i] = random_nid_postfix();
|
|
});
|
|
for (auto& thread : threads) thread.join();
|
|
std::sort(serial.begin(), serial.end());
|
|
std::sort(concurrent.begin(), concurrent.end());
|
|
Require(serial == concurrent, "concurrent calls must draw the serial sequence");
|
|
|
|
return failures == 0 ? 0 : 1;
|
|
}
|