Prerequisites
Game Version
Bug Description
Summary
The game's random number generator (RNG) seed for multiplayer matches is fully deterministic. Once the seed is known, all "random" outcomes — most notably faction assignment when players select "Random" — can be predicted. Depending on the multiplayer backend, the seed is either directly controlled by the host (LAN) or visible to all clients before the game starts (GeneralsOnline). This undermines fair play in competitive and casual multiplayer.
How the seed flows
LAN
- Host sets the seed — When a LAN game lobby is created, the host calls
setSeed(GetTickCount()) in LANAPI::RequestGameCreate.
- Seed is broadcast in plaintext — The seed is serialized into the game options string as
SD=<value> via GameInfoToAsciiString() and sent over unencrypted UDP to all clients. The host periodically re-broadcasts this via RequestGameOptions / RequestGameAnnounce.
- All clients apply the seed — At game start, every client calls
InitGameLogicRandom(m_currentGame->getSeed()).
On LAN the host has full unilateral control over the seed.
GeneralsOnline
- Server generates the seed — The host client's
CreateLobby request does not include a seed. The server assigns RNGSeed and returns it in the lobby JSON response (OnlineServices_LobbyInterface.cpp).
- Seed is distributed to every client before game start — All lobby members receive
lobby.rng_seed as part of the LobbyEntry struct when they sync with the lobby. NGMPGame::SyncWithLobby calls setSeed(lobby.rng_seed).
On GeneralsOnline the host cannot choose the seed, but every client can observe it before the match begins and predict the outcome.
Faction resolution (both backends)
populateRandomSideAndColor() in GameLogic.cpp resolves every PLAYERTEMPLATE_RANDOM slot using GameLogicRandomValue(), which is seeded by the host-/server-provided seed. The existing MORE_RANDOM mitigation attempts to discard early values, but the discard count itself is derived from the seed (GetGameLogicRandomSeed() % 7), so it is equally predictable.
How the seed can be obtained or abused
| Vector |
Applies to |
Description |
| Host manipulation |
LAN |
The host can restart lobbies until GetTickCount() yields a favourable seed, or a modified client can set an arbitrary seed directly. |
| Memory scraping |
LAN, GeneralsOnline |
Any player can read the seed from the game process memory (theGameLogicBaseSeed / m_seed in GameInfo) before the match begins and compute the faction table offline. |
| LAN traffic sniffing |
LAN |
Game options are broadcast as unencrypted UDP containing SD=<seed>. Any device on the network can capture and decode it. |
| Lobby API observation |
GeneralsOnline |
The RNGSeed field is part of the lobby response JSON visible to every client. Even though the server generates it, any client (or third-party tool querying the API) can read the seed and predict assignments before the game launches. |
Impact on fair play
- On LAN, a host who wants a specific faction matchup can trivially achieve it.
- On any backend, any player with basic tooling can predict what "Random" will resolve to for every slot, removing the strategic uncertainty that "Random" is supposed to provide.
- In ranked/competitive play this directly compromises match integrity.
Proposed solution
Short term (server-side, GeneralsOnline):
- Do not distribute the RNG seed to clients until the moment of game launch (i.e. after all players have locked in and the countdown completes). This closes the window for pre-game prediction.
- Ensure the seed is generated with a cryptographically secure RNG on the server.
Long term (all backends including LAN):
Implement a commit-reveal multi-party entropy protocol:
- Each player generates a local secret random value and broadcasts a hash (commitment) during the pre-game phase.
- Once all commitments are collected, each player reveals their secret.
- The final game seed is computed as a deterministic combination (e.g. XOR or cryptographic hash) of all revealed values.
- Any player who fails to reveal, or whose reveal doesn't match the commitment, triggers a lobby abort or re-roll.
This ensures no single client — including the host — can determine or predict the seed, and passive observation of network traffic or memory is insufficient to derive the final seed before all parties have committed.
Relevant code
Reproduction Steps
- Host a multiplayer game (LAN or GeneralsOnline).
- Have one or more players select "Random" faction.
- Start the game and note the assigned factions.
- Re-host with the same seed (on LAN: set
m_seed in memory or replay the same GetTickCount() value; on GeneralsOnline: read RNGSeed from the lobby API response) and the same lobby configuration.
- Observe that the "random" faction assignments are identical — they are fully determined by the seed.
- On LAN, alternatively capture the UDP broadcast to obtain the
SD= value and compute the expected assignments offline before the game begins.
Additional Context
No response
Prerequisites
Game Version
Bug Description
Summary
The game's random number generator (RNG) seed for multiplayer matches is fully deterministic. Once the seed is known, all "random" outcomes — most notably faction assignment when players select "Random" — can be predicted. Depending on the multiplayer backend, the seed is either directly controlled by the host (LAN) or visible to all clients before the game starts (GeneralsOnline). This undermines fair play in competitive and casual multiplayer.
How the seed flows
LAN
setSeed(GetTickCount())inLANAPI::RequestGameCreate.SD=<value>viaGameInfoToAsciiString()and sent over unencrypted UDP to all clients. The host periodically re-broadcasts this viaRequestGameOptions/RequestGameAnnounce.InitGameLogicRandom(m_currentGame->getSeed()).On LAN the host has full unilateral control over the seed.
GeneralsOnline
CreateLobbyrequest does not include a seed. The server assignsRNGSeedand returns it in the lobby JSON response (OnlineServices_LobbyInterface.cpp).lobby.rng_seedas part of theLobbyEntrystruct when they sync with the lobby.NGMPGame::SyncWithLobbycallssetSeed(lobby.rng_seed).On GeneralsOnline the host cannot choose the seed, but every client can observe it before the match begins and predict the outcome.
Faction resolution (both backends)
populateRandomSideAndColor()inGameLogic.cppresolves everyPLAYERTEMPLATE_RANDOMslot usingGameLogicRandomValue(), which is seeded by the host-/server-provided seed. The existingMORE_RANDOMmitigation attempts to discard early values, but the discard count itself is derived from the seed (GetGameLogicRandomSeed() % 7), so it is equally predictable.How the seed can be obtained or abused
GetTickCount()yields a favourable seed, or a modified client can set an arbitrary seed directly.theGameLogicBaseSeed/m_seedinGameInfo) before the match begins and compute the faction table offline.SD=<seed>. Any device on the network can capture and decode it.RNGSeedfield is part of the lobby response JSON visible to every client. Even though the server generates it, any client (or third-party tool querying the API) can read the seed and predict assignments before the game launches.Impact on fair play
Proposed solution
Short term (server-side, GeneralsOnline):
Long term (all backends including LAN):
Implement a commit-reveal multi-party entropy protocol:
This ensures no single client — including the host — can determine or predict the seed, and passive observation of network traffic or memory is insufficient to derive the final seed before all parties have committed.
Relevant code
LANAPI::RequestGameCreate—myGame->setSeed(GetTickCount())OnlineServices_LobbyInterface.cpp—lobbyEntryIter["RNGSeed"].get_to(lobbyEntry.rng_seed)NGMPGame::SyncWithLobby—setSeed(lobby.rng_seed)GameInfoToAsciiString—SD=%din options stringInitGameLogicRandom(getSeed())populateRandomSideAndColor— usesGameLogicRandomValueseeded by the provided seedRandomValue.cpp— deterministic PRNG; GeneralsOnline has experimentalGENERALS_ONLINE_USE_NEW_RNG_LOGIC(currently disabled) that swaps tostd::mt19937but does not change the seeding trust modelReproduction Steps
m_seedin memory or replay the sameGetTickCount()value; on GeneralsOnline: readRNGSeedfrom the lobby API response) and the same lobby configuration.SD=value and compute the expected assignments offline before the game begins.Additional Context
No response