Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Core/GameEngine/Include/GameNetwork/GameInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -273,8 +273,9 @@ UnsignedShort GameInfo::getSuperweaponRestriction() const { return m_superweapon
Bool GameInfo::oldFactionsOnly() const { return m_oldFactionsOnly; }
void GameInfo::setOldFactionsOnly( Bool oldFactionsOnly ) { m_oldFactionsOnly = oldFactionsOnly; }

AsciiString GameInfoToAsciiString( const GameInfo *game );
Bool ParseAsciiStringToGameInfo( GameInfo *game, AsciiString options );
// TheSuperHackers @info arcticdolphin 02/03/2026 Added includeSeed and requireSeed parameters, defaulted to retail behavior.
AsciiString GameInfoToAsciiString( const GameInfo *game, Bool includeSeed = TRUE );
Bool ParseAsciiStringToGameInfo( GameInfo *game, AsciiString options, Bool requireSeed = TRUE );


/**
Expand Down
73 changes: 73 additions & 0 deletions Core/GameEngine/Include/GameNetwork/LANAPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,13 @@ struct LANMessage
MSG_INACTIVE, ///< I've alt-tabbed out. Unaccept me cause I'm a poo-flinging monkey.

MSG_REQUEST_GAME_INFO, ///< For direct connect, get the game info from a specific IP Address

#if !RETAIL_COMPATIBLE_NETWORKING
// TheSuperHackers @feature arcticdolphin 02/03/2026 Commit-reveal protocol message types.
MSG_SEED_COMMIT, ///< Seed commitment broadcast
MSG_SEED_REVEAL, ///< Seed reveal broadcast
MSG_SEED_READY, ///< Seed protocol complete acknowledgment
#endif
} messageType;

WideChar name[g_lanPlayerNameLength+1]; ///< My name, for convenience
Expand Down Expand Up @@ -267,6 +274,29 @@ struct LANMessage
char options[m_lanMaxOptionsLength+1];
} GameOptions;

#if !RETAIL_COMPATIBLE_NETWORKING
// TheSuperHackers @feature arcticdolphin 02/03/2026 Commit-reveal protocol message payloads.
struct
{
BYTE roundNonce[4]; ///< First 4 bytes of host commit: ties this message to the current round
BYTE commit[32]; ///< SHA-256(secret || senderSlot)
BYTE senderSlot; ///< Slot index of the sending player
} SeedCommit;

struct
{
BYTE roundNonce[4]; ///< First 4 bytes of host commit: ties this message to the current round
BYTE secret[16]; ///< The original 128-bit secret value
BYTE senderSlot; ///< Slot index of the sending player
} SeedReveal;

struct
{
BYTE roundNonce[4]; ///< First 4 bytes of host commit: ties this ack to the current round
BYTE senderSlot; ///< Slot index of the sending player
} SeedReady;
#endif

};
};
#pragma pack(pop)
Expand Down Expand Up @@ -386,6 +416,49 @@ class LANAPI : public LANAPIInterface

Bool m_isActive; ///< is the game currently active?

#if !RETAIL_COMPATIBLE_NETWORKING
// TheSuperHackers @feature arcticdolphin 02/03/2026 Commit-reveal protocol state.
enum SeedPhase
{
SEED_PHASE_NONE = 0, ///< Not in protocol
SEED_PHASE_AWAITING_COMMITS, ///< Host: waiting for all commits; Non-host: committed, awaiting reveal trigger
SEED_PHASE_AWAITING_REVEALS, ///< Host: waiting for all reveals; Non-host: revealed, awaiting game start
};
static const UnsignedInt s_seedPhaseTimeoutMs; ///< Per-phase timeout
static const UnsignedInt s_seedResendIntervalMs; ///< Interval between seed message resends
SeedPhase m_seedPhase;
Bool m_seedReady; ///< TRUE once seed protocol completed successfully
BYTE m_localSeedSecret[16]; ///< Local random 128-bit secret
BYTE m_localSeedCommit[32]; ///< Commitment hash of local secret
BYTE m_slotSeedCommit[MAX_SLOTS][32];///< Received commits per slot
BYTE m_slotSeedReveal[MAX_SLOTS][16];///< Received 128-bit secrets per slot
Bool m_slotCommitReceived[MAX_SLOTS];
Bool m_slotRevealReceived[MAX_SLOTS];
Bool m_slotSeedReady[MAX_SLOTS]; ///< Which slots have acknowledged seed ready
BYTE m_slotPendingRevealSecret[MAX_SLOTS][16]; ///< Buffered early reveal secret (before commit arrived)
BYTE m_slotPendingRevealNonce[MAX_SLOTS][4]; ///< Round nonce from buffered early reveal
Bool m_slotPendingRevealValid[MAX_SLOTS]; ///< Whether a pending reveal is buffered for this slot
UnsignedInt m_seedPhaseDeadline; ///< Phase deadline
UnsignedInt m_seedResendTime; ///< Next seed message resend time

void resetSeedProtocolState(); ///< Clear all per-round seed protocol state
void beginSeedCommitPhase(); ///< Generate secret, broadcast commit, enter commit phase
void beginSeedRevealPhase(); ///< Enter reveal phase and broadcast own reveal
void finalizeSeed(); ///< XOR secrets, set seed, notify readiness
void abortSeedProtocol(const wchar_t *reason = nullptr);
void processVerifiedReveal(Int slot, const BYTE secret[16], const BYTE roundNonce[4]); ///< Verify nonce+commitment, store reveal, advance protocol
void flushPendingReveal(Int slot); ///< Drain buffered early reveal for slot if commit is now available
Bool checkSeedSlotFlags(const Bool flags[], Bool skipLocal) const;
Bool allSeedCommitsReceived() { return checkSeedSlotFlags(m_slotCommitReceived, TRUE); }
Bool allSeedRevealsReceived() { return checkSeedSlotFlags(m_slotRevealReceived, TRUE); }
Bool allSeedReadyReceived() { return checkSeedSlotFlags(m_slotSeedReady, FALSE); } ///< Includes local slot
static Bool generateLocalSecret(BYTE secret[16]);
static Bool computeSeedCommitment(const BYTE secret[16], BYTE senderSlot, BYTE outCommit[32]);
void handleSeedCommit(LANMessage *msg, UnsignedInt senderIP);
void handleSeedReveal(LANMessage *msg, UnsignedInt senderIP);
void handleSeedReady(LANMessage *msg, UnsignedInt senderIP);
#endif

protected:
void sendMessage(LANMessage *msg, UnsignedInt ip = 0); // Convenience function
void removePlayer(LANPlayer *player);
Expand Down
46 changes: 38 additions & 8 deletions Core/GameEngine/Source/GameNetwork/GameInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -888,7 +888,8 @@ Bool GameInfo::isSandbox()

static const char slotListID = 'S';

AsciiString GameInfoToAsciiString( const GameInfo *game )
// TheSuperHackers @info arcticdolphin 02/03/2026 Added includeSeed parameter.
AsciiString GameInfoToAsciiString( const GameInfo *game, Bool includeSeed )
{
if (!game)
return AsciiString::TheEmptyString;
Expand Down Expand Up @@ -917,14 +918,26 @@ AsciiString GameInfoToAsciiString( const GameInfo *game )
DEBUG_LOG(("Map name is %s", mapName.str()));
}

#if RETAIL_COMPATIBLE_NETWORKING
// TheSuperHackers @info arcticdolphin 03/03/2026 Added includeSeed parameter to conditionally insert SD=.
(void)includeSeed;
const Bool emitSeed = TRUE;
#else
const Bool emitSeed = includeSeed;
#endif

AsciiString seedField;
if (emitSeed)
seedField.format("SD=%d;", game->getSeed());

AsciiString optionsString;
#if RTS_GENERALS
optionsString.format("M=%2.2x%s;MC=%X;MS=%d;SD=%d;C=%d;", game->getMapContentsMask(), newMapName.str(),
game->getMapCRC(), game->getMapSize(), game->getSeed(), game->getCRCInterval());
optionsString.format("M=%2.2x%s;MC=%X;MS=%d;%sC=%d;", game->getMapContentsMask(), newMapName.str(),
game->getMapCRC(), game->getMapSize(), seedField.str(), game->getCRCInterval());
#else
optionsString.format("US=%d;M=%2.2x%s;MC=%X;MS=%d;SD=%d;C=%d;SR=%u;SC=%u;O=%c;", game->getUseStats(), game->getMapContentsMask(), newMapName.str(),
game->getMapCRC(), game->getMapSize(), game->getSeed(), game->getCRCInterval(), game->getSuperweaponRestriction(),
game->getStartingCash().countMoney(), game->oldFactionsOnly() ? 'Y' : 'N' );
optionsString.format("US=%d;M=%2.2x%s;MC=%X;MS=%d;%sC=%d;SR=%u;SC=%u;O=%c;", game->getUseStats(), game->getMapContentsMask(), newMapName.str(),
game->getMapCRC(), game->getMapSize(), seedField.str(), game->getCRCInterval(), game->getSuperweaponRestriction(),
game->getStartingCash().countMoney(), game->oldFactionsOnly() ? 'Y' : 'N');
#endif

//add player info for each slot
Expand Down Expand Up @@ -1000,8 +1013,14 @@ static Int grabHexInt(const char *s)
Int b = strtol(tmp, nullptr, 16);
return b;
}
Bool ParseAsciiStringToGameInfo(GameInfo *game, AsciiString options)

// TheSuperHackers @info arcticdolphin 02/03/2026 Added requireSeed parameter.
Bool ParseAsciiStringToGameInfo(GameInfo *game, AsciiString options, Bool requireSeed)
{
#if RETAIL_COMPATIBLE_NETWORKING
// TheSuperHackers @info arcticdolphin 02/03/2026 requireSeed is unused in retail builds; seed is always required.
(void)requireSeed;
#endif
// Parse game options
char *buf = strdup(options.str());
char *bufPtr = buf;
Expand Down Expand Up @@ -1482,7 +1501,12 @@ Bool ParseAsciiStringToGameInfo(GameInfo *game, AsciiString options)
// * StartingCash
// * OldFactionsOnly
// In Generals they never were.
#if !RETAIL_COMPATIBLE_NETWORKING
// TheSuperHackers @info arcticdolphin 02/03/2026 SD= may be absent when requireSeed=false.
if (optionsOk && sawMap && sawMapCRC && sawMapSize && (!requireSeed || sawSeed) && sawSlotlist && sawCRC)
#else
if (optionsOk && sawMap && sawMapCRC && sawMapSize && sawSeed && sawSlotlist && sawCRC)
#endif
{
// We were setting the Global Data directly here, but Instead, I'm now
// first setting the data in game. We'll set the global data when
Expand All @@ -1499,7 +1523,13 @@ Bool ParseAsciiStringToGameInfo(GameInfo *game, AsciiString options)
game->setMapCRC(mapCRC);
game->setMapSize(mapSize);
game->setMapContentsMask(mapContentsMask);
game->setSeed(seed);
// TheSuperHackers @info arcticdolphin 02/03/2026 Only apply seed when SD= was present.
// When SD= is omitted (non-retail commit-reveal path), leave the seed unchanged so that
// periodic game-options re-broadcasts do not overwrite the value set by finalizeSeed().
if (sawSeed)
{
game->setSeed(seed);
}
game->setCRCInterval(crc);
game->setUseStats(useStats);
game->setSuperweaponRestriction(restriction);
Expand Down
Loading
Loading