New M_Random implementation

This commit is contained in:
tertu marybig 2023-07-26 14:53:01 +00:00 committed by Sal
parent a752e6c8e4
commit 9e5a828508
8 changed files with 259 additions and 26 deletions

View file

@ -278,4 +278,26 @@ char *I_ClipboardPaste(void)
void I_RegisterSysCommands(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" #include "../sdl/dosstr.c"

View file

@ -70,6 +70,7 @@
#include "filesrch.h" // refreshdirmenu #include "filesrch.h" // refreshdirmenu
#include "g_input.h" // tutorial mode control scheming #include "g_input.h" // tutorial mode control scheming
#include "m_perfstats.h" #include "m_perfstats.h"
#include "m_random.h"
#ifdef CMAKECONFIG #ifdef CMAKECONFIG
#include "config.h" #include "config.h"
@ -1334,11 +1335,12 @@ void D_SRB2Main(void)
snprintf(addonsdir, sizeof addonsdir, "%s%s%s", srb2home, PATHSEP, "addons"); snprintf(addonsdir, sizeof addonsdir, "%s%s%s", srb2home, PATHSEP, "addons");
I_mkdir(addonsdir, 0755); I_mkdir(addonsdir, 0755);
// rand() needs seeded regardless of password // seed M_Random because it is necessary; seed P_Random for scripts that
srand((unsigned int)time(NULL)); // might want to use random numbers immediately at start
rand(); if (!M_RandomSeedFromOS())
rand(); M_RandomSeed((UINT32)time(NULL)); // less good but serviceable
rand();
P_SetRandSeed(M_RandomizedSeed());
if (M_CheckParm("-password") && M_IsNextParm()) if (M_CheckParm("-password") && M_IsNextParm())
D_SetPassword(M_GetNextParm()); D_SetPassword(M_GetNextParm());

View file

@ -180,6 +180,11 @@ const char *I_ClipboardPaste(void)
return NULL; return NULL;
} }
size_t I_GetRandomBytes(char *destination, size_t amount)
{
return 0;
}
void I_RegisterSysCommands(void) {} void I_RegisterSysCommands(void) {}
void I_GetCursorPosition(INT32 *x, INT32 *y) void I_GetCursorPosition(INT32 *x, INT32 *y)

View file

@ -49,6 +49,10 @@ size_t I_GetFreeMem(size_t *total);
*/ */
precise_t I_GetPreciseTime(void); 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 /** \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. this function for the program's duration MUST return the same value.
*/ */

View file

@ -1256,8 +1256,6 @@ static int libd_RandomKey(lua_State *L)
INT32 a = (INT32)luaL_checkinteger(L, 1); INT32 a = (INT32)luaL_checkinteger(L, 1);
HUDONLY HUDONLY
if (a > 65536)
LUA_UsageWarning(L, "v.RandomKey: range > 65536 is undefined behavior");
lua_pushinteger(L, M_RandomKey(a)); lua_pushinteger(L, M_RandomKey(a));
return 1; return 1;
} }
@ -1268,13 +1266,6 @@ static int libd_RandomRange(lua_State *L)
INT32 b = (INT32)luaL_checkinteger(L, 2); INT32 b = (INT32)luaL_checkinteger(L, 2);
HUDONLY 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)); lua_pushinteger(L, M_RandomRange(a, b));
return 1; return 1;
} }

View file

@ -3,6 +3,7 @@
// Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 1998-2000 by DooM Legacy Team. // Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 2012-2016 by Matthew "Kaito Sinclaire" Walsh. // Copyright (C) 2012-2016 by Matthew "Kaito Sinclaire" Walsh.
// Copyright (C) 2022-2023 by tertu marybig.
// Copyright (C) 1999-2023 by Sonic Team Junior. // Copyright (C) 1999-2023 by Sonic Team Junior.
// //
// This program is free software distributed under the // This program is free software distributed under the
@ -14,11 +15,122 @@
#include "doomdef.h" #include "doomdef.h"
#include "doomtype.h" #include "doomtype.h"
#include "i_system.h" // I_GetRandomBytes
#include "m_random.h" #include "m_random.h"
#include "m_fixed.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) // RNG functions (not synched)
@ -31,13 +143,7 @@
*/ */
fixed_t M_RandomFixed(void) fixed_t M_RandomFixed(void)
{ {
#if RAND_MAX < 65535 return RandomState_Get32(&m_randomstate) >> (32-FRACBITS);
// Compensate for insufficient randomness.
fixed_t rndv = (rand()&1)<<15;
return rand()^rndv;
#else
return (rand() & 0xFFFF);
#endif
} }
/** Provides a random byte. Distribution is uniform. /** Provides a random byte. Distribution is uniform.
@ -47,7 +153,7 @@ fixed_t M_RandomFixed(void)
*/ */
UINT8 M_RandomByte(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. /** Provides a random integer for picking random elements from an array.
@ -59,7 +165,22 @@ UINT8 M_RandomByte(void)
*/ */
INT32 M_RandomKey(INT32 a) 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. /** Provides a random integer in a given range.
@ -72,7 +193,46 @@ INT32 M_RandomKey(INT32 a)
*/ */
INT32 M_RandomRange(INT32 a, INT32 b) 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. /** 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 * \sa P_GetRandSeed
*/ */
UINT32 M_RandomizedSeed(void) UINT32 M_RandomizedSeed(void)
{ {
return ((serverGamedata->totalplaytime & 0xFFFF) << 16) | M_RandomFixed(); UINT32 seed;
do {
seed = RandomState_Get32(&m_randomstate);
} while(seed == 0);
return seed;
} }

View file

@ -3,6 +3,7 @@
// Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 1998-2000 by DooM Legacy Team. // Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 2012-2016 by Matthew "Kaito Sinclaire" Walsh. // Copyright (C) 2012-2016 by Matthew "Kaito Sinclaire" Walsh.
// Copyright (C) 2022-2023 by tertu marybig.
// Copyright (C) 1999-2023 by Sonic Team Junior. // Copyright (C) 1999-2023 by Sonic Team Junior.
// //
// This program is free software distributed under the // This program is free software distributed under the
@ -29,6 +30,8 @@ fixed_t M_RandomFixed(void);
UINT8 M_RandomByte(void); UINT8 M_RandomByte(void);
INT32 M_RandomKey(INT32 a); INT32 M_RandomKey(INT32 a);
INT32 M_RandomRange(INT32 a, INT32 b); INT32 M_RandomRange(INT32 a, INT32 b);
boolean M_RandomSeedFromOS(void);
void M_RandomSeed(UINT32 a);
// PRNG functions // PRNG functions
#ifdef DEBUGRANDOM #ifdef DEBUGRANDOM

View file

@ -41,6 +41,12 @@ typedef DWORD (WINAPI *p_timeGetTime) (void);
typedef UINT (WINAPI *p_timeEndPeriod) (UINT); typedef UINT (WINAPI *p_timeEndPeriod) (UINT);
typedef HANDLE (WINAPI *p_OpenFileMappingA) (DWORD, BOOL, LPCSTR); typedef HANDLE (WINAPI *p_OpenFileMappingA) (DWORD, BOOL, LPCSTR);
typedef LPVOID (WINAPI *p_MapViewOfFile) (HANDLE, DWORD, DWORD, DWORD, SIZE_T); typedef LPVOID (WINAPI *p_MapViewOfFile) (HANDLE, DWORD, DWORD, DWORD, SIZE_T);
// This is for RtlGenRandom.
#define SystemFunction036 NTAPI SystemFunction036
#include <ntsecapi.h>
#undef SystemFunction036
#endif #endif
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -2776,6 +2782,38 @@ INT32 I_PutEnv(char *variable)
#endif #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) INT32 I_ClipboardCopy(const char *data, size_t size)
{ {
char storage[256]; char storage[256];