diff --git a/src/android/i_system.c b/src/android/i_system.c index ff8b88de5..9d798d452 100644 --- a/src/android/i_system.c +++ b/src/android/i_system.c @@ -278,4 +278,26 @@ char *I_ClipboardPaste(void) void I_RegisterSysCommands(void) {} +// This is identical to the SDL implementation. +size_t I_GetRandomBytes(char *destination, size_t count) +{ + FILE *rndsource; + size_t actual_bytes; + + if (!(rndsource = fopen("/dev/urandom", "r"))) + if (!(rndsource = fopen("/dev/random", "r"))) + actual_bytes = 0; + + if (rndsource) + { + actual_bytes = fread(destination, 1, count, rndsource); + fclose(rndsource); + } + + if (actual_bytes == 0) + I_OutputMsg("I_GetRandomBytes(): couldn't get any random bytes"); + + return actual_bytes; +} + #include "../sdl/dosstr.c" diff --git a/src/d_main.c b/src/d_main.c index 5861f9886..389cdd2db 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -70,6 +70,7 @@ #include "filesrch.h" // refreshdirmenu #include "g_input.h" // tutorial mode control scheming #include "m_perfstats.h" +#include "m_random.h" #ifdef CMAKECONFIG #include "config.h" @@ -1334,11 +1335,12 @@ void D_SRB2Main(void) snprintf(addonsdir, sizeof addonsdir, "%s%s%s", srb2home, PATHSEP, "addons"); I_mkdir(addonsdir, 0755); - // rand() needs seeded regardless of password - srand((unsigned int)time(NULL)); - rand(); - rand(); - rand(); + // seed M_Random because it is necessary; seed P_Random for scripts that + // might want to use random numbers immediately at start + if (!M_RandomSeedFromOS()) + M_RandomSeed((UINT32)time(NULL)); // less good but serviceable + + P_SetRandSeed(M_RandomizedSeed()); if (M_CheckParm("-password") && M_IsNextParm()) D_SetPassword(M_GetNextParm()); diff --git a/src/dummy/i_system.c b/src/dummy/i_system.c index 8556c0248..125d2e8ae 100644 --- a/src/dummy/i_system.c +++ b/src/dummy/i_system.c @@ -180,6 +180,11 @@ const char *I_ClipboardPaste(void) return NULL; } +size_t I_GetRandomBytes(char *destination, size_t amount) +{ + return 0; +} + void I_RegisterSysCommands(void) {} void I_GetCursorPosition(INT32 *x, INT32 *y) diff --git a/src/i_system.h b/src/i_system.h index deea9f8a8..834dd4091 100644 --- a/src/i_system.h +++ b/src/i_system.h @@ -49,6 +49,10 @@ size_t I_GetFreeMem(size_t *total); */ precise_t I_GetPreciseTime(void); +/** \brief Fills a buffer with random data, returns amount of data obtained. + */ +size_t I_GetRandomBytes(char *destination, size_t count); + /** \brief Get the precision of precise_t in units per second. Invocations of this function for the program's duration MUST return the same value. */ diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c index c7f67e93a..2e3bb9c68 100644 --- a/src/lua_hudlib.c +++ b/src/lua_hudlib.c @@ -1256,8 +1256,6 @@ static int libd_RandomKey(lua_State *L) INT32 a = (INT32)luaL_checkinteger(L, 1); HUDONLY - if (a > 65536) - LUA_UsageWarning(L, "v.RandomKey: range > 65536 is undefined behavior"); lua_pushinteger(L, M_RandomKey(a)); return 1; } @@ -1268,13 +1266,6 @@ static int libd_RandomRange(lua_State *L) INT32 b = (INT32)luaL_checkinteger(L, 2); HUDONLY - if (b < a) { - INT32 c = a; - a = b; - b = c; - } - if ((b-a+1) > 65536) - LUA_UsageWarning(L, "v.RandomRange: range > 65536 is undefined behavior"); lua_pushinteger(L, M_RandomRange(a, b)); return 1; } diff --git a/src/m_random.c b/src/m_random.c index 8b5138b9c..536fbfbbd 100644 --- a/src/m_random.c +++ b/src/m_random.c @@ -3,6 +3,7 @@ // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. // Copyright (C) 2012-2016 by Matthew "Kaito Sinclaire" Walsh. +// Copyright (C) 2022-2023 by tertu marybig. // Copyright (C) 1999-2023 by Sonic Team Junior. // // This program is free software distributed under the @@ -14,11 +15,122 @@ #include "doomdef.h" #include "doomtype.h" +#include "i_system.h" // I_GetRandomBytes #include "m_random.h" #include "m_fixed.h" -#include "m_cond.h" // totalplaytime +// SFC32 random number generator implementation + +typedef struct rnstate_s { + UINT32 data[3]; + UINT32 counter; +} rnstate_t; + +/** Generate a raw uniform random number using a particular state. + * + * \param state The RNG state to use. + * \return A random UINT32. + */ +static inline UINT32 RandomState_Get32(rnstate_t *state) { + UINT32 result, b, c; + + b = state->data[1]; + c = state->data[2]; + result = state->data[0] + b + state->counter++; + + state->data[0] = b ^ (b >> 9); + state->data[1] = c * 9; + state->data[2] = ((c << 21) | (c >> 11)) + result; + + return result; +} + +/** Seed an SFC32 RNG state with up to 96 bits of seed data. + * + * \param state The RNG state to seed. + * \param seeds A pointer to up to 3 UINT32s to use as seed data. + * \param seed_count The number of seed words. + */ +static inline void RandomState_Seed(rnstate_t *state, UINT32 *seeds, size_t seed_count) +{ + size_t i; + + state->counter = 1; + + for(i = 0; i < 3; i++) + { + UINT32 seed_word; + + if(i < seed_count) + seed_word = seeds[i]; + else + seed_word = 0; + + // For SFC32, seed data should be stored in the state in reverse order. + state->data[2-i] = seed_word; + } + + for(i = 0; i < 16; i++) + RandomState_Get32(state); +} + +/** Gets a uniform number in the range [0, limit). + * Technique is based on a combination of scaling and rejection sampling + * and is adapted from Daniel Lemire. + * + * \note Any UINT32 is a valid argument for limit. + * + * \param state The RNG state to use. + * \param limit The upper limit of the range. + * \return A UINT32 in the range [0, limit). + */ +static inline UINT32 RandomState_GetKey32(rnstate_t *state, const UINT32 limit) +{ + UINT32 raw_random, scaled_lower_word; + UINT64 scaled_random; + + // This algorithm won't work correctly if passed a 0. + if (limit == 0) return 0; + + raw_random = RandomState_Get32(state); + scaled_random = (UINT64)raw_random * (UINT64)limit; + + /*The high bits of scaled_random now contain the number we want, but it is + possible, depending on the number we generated and the value of limit, + that there is bias in the result. The rest of this code is for ensuring + that does not happen. + */ + scaled_lower_word = (UINT32)scaled_random; + + // If we're lucky, we can bail out now and avoid the division + if (scaled_lower_word < limit) + { + // Scale the limit to improve the chance of success. + // After this, the first result might turn out to be good enough. + UINT32 scaled_limit; + // An explanation for this trick: scaled_limit should be + // (UINT32_MAX+1)%range, but if that was computed directly the result + // would need to be computed as a UINT64. This trick allows it to be + // computed using 32-bit arithmetic. + scaled_limit = (-limit) % limit; + + while (scaled_lower_word < scaled_limit) + { + raw_random = RandomState_Get32(state); + scaled_random = (UINT64)raw_random * (UINT64)limit; + scaled_lower_word = (UINT32)scaled_random; + } + } + + return scaled_random >> 32; +} + +// The default seed is the hexadecimal digits of pi, though it will be overwritten. +static rnstate_t m_randomstate = { + .data = {0x4A3B6035U, 0x99555606U, 0x6F603421U}, + .counter = 16 +}; // --------------------------- // RNG functions (not synched) @@ -31,13 +143,7 @@ */ fixed_t M_RandomFixed(void) { -#if RAND_MAX < 65535 - // Compensate for insufficient randomness. - fixed_t rndv = (rand()&1)<<15; - return rand()^rndv; -#else - return (rand() & 0xFFFF); -#endif + return RandomState_Get32(&m_randomstate) >> (32-FRACBITS); } /** Provides a random byte. Distribution is uniform. @@ -47,7 +153,7 @@ fixed_t M_RandomFixed(void) */ UINT8 M_RandomByte(void) { - return (rand() & 0xFF); + return RandomState_Get32(&m_randomstate) >> 24; } /** Provides a random integer for picking random elements from an array. @@ -59,7 +165,22 @@ UINT8 M_RandomByte(void) */ INT32 M_RandomKey(INT32 a) { - return (INT32)((rand()/((float)RAND_MAX+1.0f))*a); + boolean range_is_negative; + INT64 range; + INT32 random_result; + + range = a; + range_is_negative = range < 0; + + if(range_is_negative) + range = -range; + + random_result = RandomState_GetKey32(&m_randomstate, (UINT32)range); + + if(range_is_negative) + random_result = -random_result; + + return random_result; } /** Provides a random integer in a given range. @@ -72,7 +193,46 @@ INT32 M_RandomKey(INT32 a) */ INT32 M_RandomRange(INT32 a, INT32 b) { - return (INT32)((rand()/((float)RAND_MAX+1.0f))*(b-a+1))+a; + if (b < a) + { + INT32 temp; + + temp = a; + a = b; + b = temp; + } + + const UINT32 spread = b-a+1; + return (INT32)((INT64)RandomState_GetKey32(&m_randomstate, spread) + a); +} + +/** Attempts to seed the unsynched RNG from a good random number source + * provided by the operating system. + * \return true on success, false on failure. + */ +boolean M_RandomSeedFromOS(void) +{ + UINT32 complete_word_count; + + union { + UINT32 words[3]; + char bytes[sizeof(UINT32[3])]; + } seed_data; + + complete_word_count = I_GetRandomBytes((char *)&seed_data.bytes, sizeof(seed_data)) / sizeof(UINT32); + + // If we get even 1 word of seed, it's fine, but any less probably is not fine. + if (complete_word_count == 0) + return false; + + RandomState_Seed(&m_randomstate, (UINT32 *)&seed_data.words, complete_word_count); + + return true; +} + +void M_RandomSeed(UINT32 seed) +{ + RandomState_Seed(&m_randomstate, &seed, 1); } @@ -246,10 +406,18 @@ void P_SetRandSeedD(const char *rfile, INT32 rline, UINT32 seed) } /** Gets a randomized seed for setting the random seed. + * This function will never return 0, as the current P_Random implementation + * cannot handle a zero seed. Any other seed is equally likely. * * \sa P_GetRandSeed */ UINT32 M_RandomizedSeed(void) { - return ((serverGamedata->totalplaytime & 0xFFFF) << 16) | M_RandomFixed(); + UINT32 seed; + + do { + seed = RandomState_Get32(&m_randomstate); + } while(seed == 0); + + return seed; } diff --git a/src/m_random.h b/src/m_random.h index 824287e27..a7c07a46b 100644 --- a/src/m_random.h +++ b/src/m_random.h @@ -3,6 +3,7 @@ // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. // Copyright (C) 2012-2016 by Matthew "Kaito Sinclaire" Walsh. +// Copyright (C) 2022-2023 by tertu marybig. // Copyright (C) 1999-2023 by Sonic Team Junior. // // This program is free software distributed under the @@ -29,6 +30,8 @@ fixed_t M_RandomFixed(void); UINT8 M_RandomByte(void); INT32 M_RandomKey(INT32 a); INT32 M_RandomRange(INT32 a, INT32 b); +boolean M_RandomSeedFromOS(void); +void M_RandomSeed(UINT32 a); // PRNG functions #ifdef DEBUGRANDOM diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index 847ab2646..c21226ac3 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -41,6 +41,12 @@ typedef DWORD (WINAPI *p_timeGetTime) (void); typedef UINT (WINAPI *p_timeEndPeriod) (UINT); typedef HANDLE (WINAPI *p_OpenFileMappingA) (DWORD, BOOL, LPCSTR); typedef LPVOID (WINAPI *p_MapViewOfFile) (HANDLE, DWORD, DWORD, DWORD, SIZE_T); + +// This is for RtlGenRandom. +#define SystemFunction036 NTAPI SystemFunction036 +#include +#undef SystemFunction036 + #endif #include #include @@ -2776,6 +2782,38 @@ INT32 I_PutEnv(char *variable) #endif } +size_t I_GetRandomBytes(char *destination, size_t count) +{ +#if defined (__unix__) || defined (UNIXCOMMON) || defined(__APPLE__) + FILE *rndsource; + size_t actual_bytes; + + if (!(rndsource = fopen("/dev/urandom", "r"))) + if (!(rndsource = fopen("/dev/random", "r"))) + actual_bytes = 0; + + if (rndsource) + { + actual_bytes = fread(destination, 1, count, rndsource); + fclose(rndsource); + } + + if (actual_bytes == 0) + I_OutputMsg("I_GetRandomBytes(): couldn't get any random bytes"); + + return actual_bytes; +#elif defined (_WIN32) + if (RtlGenRandom(destination, count)) + return count; + + I_OutputMsg("I_GetRandomBytes(): couldn't get any random bytes"); + return 0; +#else + #warning SDL I_GetRandomBytes is not implemented on this platform. + return 0; +#endif +} + INT32 I_ClipboardCopy(const char *data, size_t size) { char storage[256];