From ff3daea78ce99491df0c245ea9fcff944bf3c9a9 Mon Sep 17 00:00:00 2001 From: maj00r Date: Mon, 14 Sep 2026 17:34:59 +0200 Subject: [PATCH 1/5] seed the random engines from the system pool, not std::random_device --- application/application.cpp | 17 ++++++++++++++--- application/editoruipanels.cpp | 3 +-- utilities/utilities.cpp | 29 +++++++++++++++++++++++++---- utilities/utilities.h | 11 +++++++++++ utilities/uuid.hpp | 3 ++- 5 files changed, 53 insertions(+), 10 deletions(-) diff --git a/application/application.cpp b/application/application.cpp index e5d0b6ba..49d493c2 100644 --- a/application/application.cpp +++ b/application/application.cpp @@ -321,7 +321,7 @@ int eu07_application::init(int Argc, char *Argv[]) } if (!Global.random_seed) - Global.random_seed = std::random_device{}(); + Global.random_seed = clock_seed(); Global.random_engine.seed(Global.random_seed); // configure the OS console according to Globals.ShowSystemConsole. @@ -1140,10 +1140,21 @@ int eu07_application::init_settings(int Argc, char *Argv[]) Global.local_start_vehicle = ToLower(Argv[++i]); } } + else if (token == "-seed") + { + // a number is the seed; anything else is a word that stands for one. said in the + // log as it was given, because that is what gets passed on to somebody else + if (i + 1 < Argc) + { + Global.random_seed = seed_of(Argv[++i]); + WriteLog("seed given on the command line: " + std::string(Argv[i]) + " -> " + std::to_string(Global.random_seed)); + } + } else { std::cout << "usage: " << std::string(Argv[0]) << " [-s sceneryfilepath]" - << " [-v vehiclename]" << std::endl; + << " [-v vehiclename]" + << " [-seed number|word]" << std::endl; return -1; } } @@ -1421,7 +1432,7 @@ int eu07_application::init_data() int eu07_application::init_modes() { - Global.local_random_engine.seed(std::random_device{}()); + Global.local_random_engine.seed(clock_seed()); if ((!Global.network_servers.empty() || Global.network_client) && Global.SceneryFile.empty()) { diff --git a/application/editoruipanels.cpp b/application/editoruipanels.cpp index f15f791e..cbfe0ae7 100644 --- a/application/editoruipanels.cpp +++ b/application/editoruipanels.cpp @@ -403,10 +403,9 @@ std::string *brush_object_list::GetRandomObject() if (Objects.empty()) return ∅ - static std::mt19937 rng{std::random_device{}()}; std::uniform_int_distribution dist(0, Objects.size() - 1); - return &Objects[dist(rng)]; + return &Objects[dist(Global.local_random_engine)]; } void brush_object_list::render() diff --git a/utilities/utilities.cpp b/utilities/utilities.cpp index 156d16cc..1f5f0682 100644 --- a/utilities/utilities.cpp +++ b/utilities/utilities.cpp @@ -15,6 +15,8 @@ Copyright (C) 2007-2014 Maciej Cierniak // //#include //#include +#include +#include #include //#ifndef WIN32 //#include @@ -30,7 +32,6 @@ Copyright (C) 2007-2014 Maciej Cierniak #include "utilities/U8.h" -//#include "utilities/Logs.h" // TODO: This shouldn't be in Globals? bool DebugModeFlag = false; @@ -137,15 +138,35 @@ int Random(int min, int max) return dist(Global.random_engine); } +std::uint32_t clock_seed() +{ + static std::atomic nth{0}; + return static_cast(std::time({})) + nth.fetch_add(1, std::memory_order_relaxed); +} + +std::uint32_t seed_of(std::string const &Text) +{ + std::uint32_t number{}; + if (std::from_chars(Text.data(), Text.data() + Text.size(), number).ec == std::errc{}) + { + return number; + } + std::uint32_t hash{}; + for (auto const character : Text) + { + hash = hash * 31u + static_cast(static_cast(character)); + } + return hash; +} + std::string generate_uuid_v4() { - std::random_device rd; - std::mt19937 gen(rd()); + static thread_local std::mt19937 generator{clock_seed()}; std::uniform_int_distribution dist(0, 255); std::array bytes; for (auto &b : bytes) - b = static_cast(dist(gen)); + b = static_cast(dist(generator)); // UUID v4 (RFC 4122) bytes[6] = bytes[6] & 0x0F | 0x40; diff --git a/utilities/utilities.h b/utilities/utilities.h index bc38b125..d013fade 100644 --- a/utilities/utilities.h +++ b/utilities/utilities.h @@ -62,6 +62,17 @@ inline long Round(double const f) double Random(double a, double b); int Random(int min, int max); + +// Seed for the random engines: the clock, plus a counter so two engines seeded in +// the same second do not come out the same. +std::uint32_t clock_seed(); + +// The seed a word stands for. A number is taken as it is written; anything else is +// hashed the way Java's String.hashCode does it, so the same word gives the same +// world on every platform and every build - which is the whole point of telling +// somebody a seed. +std::uint32_t seed_of(std::string const &Text); + std::string generate_uuid_v4(); double LocalRandom(double a, double b); diff --git a/utilities/uuid.hpp b/utilities/uuid.hpp index d94fd0cc..ff32f5d2 100644 --- a/utilities/uuid.hpp +++ b/utilities/uuid.hpp @@ -6,6 +6,7 @@ #include #include #include +#include "utilities/utilities.h" class UID { public: @@ -13,7 +14,7 @@ public: static UID random() { - static thread_local std::mt19937_64 gen(std::random_device{}()); + static thread_local std::mt19937_64 gen{clock_seed()}; UID u; uint64_t a = gen(); uint64_t b = gen(); From 1c2f397fc544480a99a2c7494cc7f3a2f57bf577 Mon Sep 17 00:00:00 2001 From: maj00r Date: Mon, 14 Sep 2026 18:36:07 +0200 Subject: [PATCH 2/5] seed from ini can be word --- utilities/Globals.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/utilities/Globals.cpp b/utilities/Globals.cpp index 0ab064b4..f45b6b3e 100644 --- a/utilities/Globals.cpp +++ b/utilities/Globals.cpp @@ -726,7 +726,9 @@ bool global_settings::ConfigParseSimulation(cParser& Parser, const std::string& if (token == "randomseed") { - ParseOne(Parser, Global.random_seed, 1); + std::string text; + ParseOne(Parser, text); + Global.random_seed = seed_of(text); return true; } From 2945f3f9b2e00a87f238d512c888a87a862eab26 Mon Sep 17 00:00:00 2001 From: maj00r Date: Mon, 14 Sep 2026 19:11:07 +0200 Subject: [PATCH 3/5] use random_device for local generator --- application/application.cpp | 5 +++-- utilities/utilities.cpp | 9 +-------- utilities/utilities.h | 4 ---- utilities/uuid.hpp | 2 +- 4 files changed, 5 insertions(+), 15 deletions(-) diff --git a/application/application.cpp b/application/application.cpp index 49d493c2..718380c7 100644 --- a/application/application.cpp +++ b/application/application.cpp @@ -320,9 +320,11 @@ int eu07_application::init(int Argc, char *Argv[]) return result; } + // the run's seed, settled once and kept in Globals: the engines come out of it if (!Global.random_seed) - Global.random_seed = clock_seed(); + Global.random_seed = std::random_device{}(); Global.random_engine.seed(Global.random_seed); + Global.local_random_engine.seed(Global.random_seed); // configure the OS console according to Globals.ShowSystemConsole. // must run AFTER init_settings (so the ini-loaded value is honoured) and @@ -1432,7 +1434,6 @@ int eu07_application::init_data() int eu07_application::init_modes() { - Global.local_random_engine.seed(clock_seed()); if ((!Global.network_servers.empty() || Global.network_client) && Global.SceneryFile.empty()) { diff --git a/utilities/utilities.cpp b/utilities/utilities.cpp index 1f5f0682..92aa28ed 100644 --- a/utilities/utilities.cpp +++ b/utilities/utilities.cpp @@ -16,7 +16,6 @@ Copyright (C) 2007-2014 Maciej Cierniak //#include //#include #include -#include #include //#ifndef WIN32 //#include @@ -138,12 +137,6 @@ int Random(int min, int max) return dist(Global.random_engine); } -std::uint32_t clock_seed() -{ - static std::atomic nth{0}; - return static_cast(std::time({})) + nth.fetch_add(1, std::memory_order_relaxed); -} - std::uint32_t seed_of(std::string const &Text) { std::uint32_t number{}; @@ -161,7 +154,7 @@ std::uint32_t seed_of(std::string const &Text) std::string generate_uuid_v4() { - static thread_local std::mt19937 generator{clock_seed()}; + static thread_local std::mt19937 generator{std::random_device{}()}; std::uniform_int_distribution dist(0, 255); std::array bytes; diff --git a/utilities/utilities.h b/utilities/utilities.h index d013fade..f90bd896 100644 --- a/utilities/utilities.h +++ b/utilities/utilities.h @@ -63,10 +63,6 @@ inline long Round(double const f) double Random(double a, double b); int Random(int min, int max); -// Seed for the random engines: the clock, plus a counter so two engines seeded in -// the same second do not come out the same. -std::uint32_t clock_seed(); - // The seed a word stands for. A number is taken as it is written; anything else is // hashed the way Java's String.hashCode does it, so the same word gives the same // world on every platform and every build - which is the whole point of telling diff --git a/utilities/uuid.hpp b/utilities/uuid.hpp index ff32f5d2..3326ecf1 100644 --- a/utilities/uuid.hpp +++ b/utilities/uuid.hpp @@ -14,7 +14,7 @@ public: static UID random() { - static thread_local std::mt19937_64 gen{clock_seed()}; + static thread_local std::mt19937_64 gen{std::random_device{}()}; UID u; uint64_t a = gen(); uint64_t b = gen(); From 788135cc2df158224bf5830439e4dd95c80c9812 Mon Sep 17 00:00:00 2001 From: Hirek193 Date: Mon, 14 Sep 2026 19:26:24 +0200 Subject: [PATCH 4/5] Improve random engine entropy and clean up random engine initialization --- application/application.cpp | 4 ++-- utilities/utilities.cpp | 19 ++++++++++++++----- utilities/utilities.h | 7 ++++--- utilities/uuid.hpp | 6 +++--- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/application/application.cpp b/application/application.cpp index 49d493c2..1542e3d4 100644 --- a/application/application.cpp +++ b/application/application.cpp @@ -321,7 +321,7 @@ int eu07_application::init(int Argc, char *Argv[]) } if (!Global.random_seed) - Global.random_seed = clock_seed(); + Global.random_seed = true_random_seed(); Global.random_engine.seed(Global.random_seed); // configure the OS console according to Globals.ShowSystemConsole. @@ -1432,7 +1432,7 @@ int eu07_application::init_data() int eu07_application::init_modes() { - Global.local_random_engine.seed(clock_seed()); + Global.local_random_engine.seed(true_random_seed()); if ((!Global.network_servers.empty() || Global.network_client) && Global.SceneryFile.empty()) { diff --git a/utilities/utilities.cpp b/utilities/utilities.cpp index 1f5f0682..f5377619 100644 --- a/utilities/utilities.cpp +++ b/utilities/utilities.cpp @@ -138,10 +138,20 @@ int Random(int min, int max) return dist(Global.random_engine); } -std::uint32_t clock_seed() +std::uint64_t true_random_seed() { - static std::atomic nth{0}; - return static_cast(std::time({})) + nth.fetch_add(1, std::memory_order_relaxed); + try + { + std::random_device rd; + if (rd.entropy() > 0.0) + { + std::uint32_t high = rd(); + std::uint32_t low = rd(); + return (high << 32) | low; + } + } + catch (...) {} + return static_cast(std::chrono::high_resolution_clock::now().time_since_epoch().count()); } std::uint32_t seed_of(std::string const &Text) @@ -161,12 +171,11 @@ std::uint32_t seed_of(std::string const &Text) std::string generate_uuid_v4() { - static thread_local std::mt19937 generator{clock_seed()}; std::uniform_int_distribution dist(0, 255); std::array bytes; for (auto &b : bytes) - b = static_cast(dist(generator)); + b = static_cast(dist(Global.random_engine)); // UUID v4 (RFC 4122) bytes[6] = bytes[6] & 0x0F | 0x40; diff --git a/utilities/utilities.h b/utilities/utilities.h index d013fade..70cf1c07 100644 --- a/utilities/utilities.h +++ b/utilities/utilities.h @@ -63,9 +63,10 @@ inline long Round(double const f) double Random(double a, double b); int Random(int min, int max); -// Seed for the random engines: the clock, plus a counter so two engines seeded in -// the same second do not come out the same. -std::uint32_t clock_seed(); +/// @author lxvia +/// @brief Returns a random seed from the system's entropy source, if available. Otherwise, falls back to a clock-based seed. +/// @return A random seed as 64 bit unsigned integer +std::uint64_t true_random_seed(); // The seed a word stands for. A number is taken as it is written; anything else is // hashed the way Java's String.hashCode does it, so the same word gives the same diff --git a/utilities/uuid.hpp b/utilities/uuid.hpp index ff32f5d2..5eeb89f4 100644 --- a/utilities/uuid.hpp +++ b/utilities/uuid.hpp @@ -7,6 +7,7 @@ #include #include #include "utilities/utilities.h" +#include "utilities/Globals.h" class UID { public: @@ -14,10 +15,9 @@ public: static UID random() { - static thread_local std::mt19937_64 gen{clock_seed()}; UID u; - uint64_t a = gen(); - uint64_t b = gen(); + uint64_t a = Global.random_engine(); + uint64_t b = Global.random_engine(); for (int i = 0; i < 8; ++i) u.bytes[i] = uint8_t(a >> (i * 8) & 0xFF); for (int i = 0; i < 8; ++i) u.bytes[8 + i] = uint8_t(b >> (i * 8) & 0xFF); From 3fa3853018120bd8c439417e792b20e99d37e2e4 Mon Sep 17 00:00:00 2001 From: maj00r Date: Mon, 14 Sep 2026 22:51:33 +0200 Subject: [PATCH 5/5] fix for sonar --- application/application.cpp | 11 +++++++---- utilities/utilities.cpp | 20 ++++++++++++++------ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/application/application.cpp b/application/application.cpp index f5bec7cf..a6b88ae8 100644 --- a/application/application.cpp +++ b/application/application.cpp @@ -31,6 +31,7 @@ http://mozilla.org/MPL/2.0/. #include "utilities/dictionary.h" #include "version_info.h" #include +#include #include "utilities/translation.h" #if WITH_DISCORD_RPC @@ -320,9 +321,11 @@ int eu07_application::init(int Argc, char *Argv[]) return result; } - // the run's seed, settled once and kept in Globals: the engines come out of it + // the run's seed, settled once and kept in Globals: the engines come out of it. + // narrowed on purpose - mt19937 and seed_of() both work on 32 bits, and that is + // the seed somebody can write down and pass on if (!Global.random_seed) - Global.random_seed = true_random_seed(); + Global.random_seed = static_cast(true_random_seed()); Global.random_engine.seed(Global.random_seed); Global.local_random_engine.seed(Global.random_seed); @@ -1149,7 +1152,7 @@ int eu07_application::init_settings(int Argc, char *Argv[]) if (i + 1 < Argc) { Global.random_seed = seed_of(Argv[++i]); - WriteLog("seed given on the command line: " + std::string(Argv[i]) + " -> " + std::to_string(Global.random_seed)); + WriteLog(std::format("seed given on the command line: {} -> {}", Argv[i], Global.random_seed)); } } else @@ -1434,7 +1437,7 @@ int eu07_application::init_data() int eu07_application::init_modes() { - Global.local_random_engine.seed(true_random_seed()); + Global.local_random_engine.seed(static_cast(true_random_seed())); if ((!Global.network_servers.empty() || Global.network_client) && Global.SceneryFile.empty()) { diff --git a/utilities/utilities.cpp b/utilities/utilities.cpp index 5ff79092..c1d0b73d 100644 --- a/utilities/utilities.cpp +++ b/utilities/utilities.cpp @@ -29,6 +29,7 @@ Copyright (C) 2007-2014 Maciej Cierniak #include "utilities/Globals.h" #include "utilities/parser.h" #include "utilities/U8.h" +#include "utilities/Logs.h" @@ -142,21 +143,28 @@ std::uint64_t true_random_seed() try { std::random_device rd; - if (rd.entropy() > 0.0) + if (rd.entropy() > 0.0) { - std::uint32_t high = rd(); - std::uint32_t low = rd(); + // NOTE: both halves have to be 64 bit wide before the shift - shifting a std::uint32_t + // by 32 is undefined behaviour, and drops the high half of the seed + std::uint64_t const high{rd()}; + std::uint64_t const low{rd()}; return (high << 32) | low; } } - catch (...) {} + catch (std::exception const &Error) + { + // std::random_device throws when the machine has no usable hardware entropy source; + // the clock below stands in for it, which is worth saying out loud since it is a + // weaker seed than the caller asked for + WriteLog(std::format("random: no hardware entropy source ({}), seeding from the clock instead", Error.what())); + } return static_cast(std::chrono::high_resolution_clock::now().time_since_epoch().count()); } std::uint32_t seed_of(std::string const &Text) { - std::uint32_t number{}; - if (std::from_chars(Text.data(), Text.data() + Text.size(), number).ec == std::errc{}) + if (std::uint32_t number{}; std::from_chars(Text.data(), Text.data() + Text.size(), number).ec == std::errc{}) { return number; }