diff --git a/src/android/i_system.c b/src/android/i_system.c
index ff8b88de539353bfd93ae612a3739770b267c416..9d798d452fb1b36460553fd35870894be2a3e406 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 2db4002580151c622395e2ed78756793221457b6..b7b7f6616db7d89372eeeb03e6d23b5a6e87a684 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"
@@ -1341,11 +1342,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 8556c0248651d04469b4c1114a780bba47824421..125d2e8aec897f288995649aec2476989b4136d6 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 deea9f8a8de8b0a5128336a03c4ca4d6069b0867..834dd4091487b295fd1faed9d11018e498d79b12 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 c7f67e93ac07d194a25a2ce365726219d52a3ee4..2e3bb9c683677be05181fef6f4010d37446c8325 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 8b5138b9c86f77e6fa4a0d0867e4e62198fb2dca..536fbfbbd1077abf6ae400bd4d8773a7574e4b08 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 824287e27d486906c60d976e60817cb5ac9ffb73..a7c07a46b5e2c951548d67f409287e80345c20dd 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 847ab2646f2502151a45de214640f1da5945f578..c21226ac3fba998ed0590f3efcebb0ee3482f8be 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 <ntsecapi.h>
+#undef SystemFunction036
+
 #endif
 #include <stdio.h>
 #include <stdlib.h>
@@ -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];