// An reverse-engineered implementation of the PRNG used in the SSX games. #include #include // only bring this in for the test #ifdef COMPILE_TEST #include #include #endif struct bxPsuedoRng { /** * The initalization state. * Weirdly, these constants seem to show up in tables * to convert from milliseconds -> NTP fractional time. * * Interesting source for psuedorandom constants. */ constexpr static std::uint32_t InitState[6] { 0xf22d0e56, 0x883126e9, 0xc624dd2f, 0x702c49c, 0x9e353f7d, 0x6fdf3b64 }; constexpr explicit bxPsuedoRng() { std::memcpy(this, &InitState[0], sizeof(*this)); } constexpr explicit bxPsuedoRng(std::uint32_t seed) { Seed(seed); } constexpr void Seed(std::uint32_t seed = 0) { state[0] = seed + InitState[0]; state[1] = seed + InitState[1]; state[2] = seed + InitState[2]; state[3] = seed + InitState[3]; state[4] = seed + InitState[4]; state[5] = seed + InitState[5]; } // These were implemented in the original code so I'm just doing it here to be nice constexpr void CopyTo(bxPsuedoRng* dest) { std::memcpy(dest, this, sizeof(*this)); } constexpr void CopyFrom(bxPsuedoRng* src) { std::memcpy(this, src, sizeof(*this)); } /** * Get a random number. */ constexpr std::uint32_t NextInt() { std::uint32_t tempOutput = state[4] + state[5]; bool startCycleIf = (state[4] + state[5] < state[5]) || (state[4] + state[5] < state[4]); // start cycle? std::uint32_t prevState = state[3]; state[4] = tempOutput; tempOutput += startCycleIf + prevState; std::uint32_t prevState2 = state[2]; state[3] = tempOutput; tempOutput += (tempOutput < prevState) + prevState2; prevState = state[1]; state[2] = tempOutput; tempOutput += (tempOutput < prevState2) + prevState; state[1] = tempOutput; tempOutput += state[0] + (tempOutput < prevState); // Set output "register" to the output of all the cycles combined, // and add 1 to the final "register". state[0] = tempOutput; // Make sure the entire state is nonzero, including the output // "register". This really should be cleaned up :( state[5]++; if (state[5] + 1 == 0) { auto* ptr = &state[4]; *ptr++; if (*ptr == 0) { ptr = &state[3]; *ptr++; if (*ptr == 0) { ptr = &state[2]; *ptr++; if (*ptr == 0) { ptr = &state[1]; *ptr++; if (*ptr == 0) { tempOutput++; state[0] = tempOutput; } } } } } return state[0]; } /** * The PRNG state, which I call "registers" for simplicity. * * Things I know: * [0] is the output register */ std::uint32_t state[6]; }; #ifdef COMPILE_TEST // The following is a simple driver program // which just cycles the PRNG 1000 times. // I use this in a very simple test one-liner I ran while simplifiying/cleaning up the PRNG: // // g++ -std=c++20 -DCOMPILE_TEST bxprng_new.cpp -o bxprng; ./bxprng >prelim; diff working prelim // // The "working" file, attached to this Gist, is the output of simply getting the decompiled code // to even compile. (since I assume that would work without introducing bugs). If you see no output // (besides gcc warnings) from this command, the test passed (prelim file is exactly the same as "working"). int main() { bxPsuedoRng rng; // Run 1000 cycles, printing out results as we go. for(int i = 0; i < 1000; ++i) { auto next = rng.NextInt(); std::printf("cycle %d: 0x%08x (decimal: %d / %u)\n", i + 1, next, next, next); } // The state after 1000 cycles should always match this, since // we don't seed the PRNG. assert(rng.state[0] == 0x9213d468); assert(rng.state[1] == 0x04dfa254); assert(rng.state[2] == 0xddf4b3e5); assert(rng.state[3] == 0x90ef376d); assert(rng.state[4] == 0x9e3cdd49); assert(rng.state[5] == 0x6fdf3f4c); } #endif // COMPILE_TEST