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
6 changes: 4 additions & 2 deletions Core/GameEngine/Include/Common/MessageStream.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ class GameMessage : public MemoryPoolObject
MSG_CLEAR_GAME_DATA, ///< Clear all game data in memory
MSG_NEW_GAME, ///< Start a new game

MSG_LOGIC_CRC_PLAYBACK, ///< TheSuperHackers @tweak CRC from the logic used internally for replay playback.
Comment thread
Caball009 marked this conversation as resolved.

// "meta" messages should be thought of as "virtual keystrokes" -- they exist
// solely to provide an abstraction layer useful for keyboard/mouse remapping.
// they should NEVER be sent over the network.
Expand Down Expand Up @@ -601,8 +603,8 @@ class GameMessage : public MemoryPoolObject
MSG_SET_REPLAY_CAMERA, ///< Track camera pos for replays
MSG_SELF_DESTRUCT, ///< Destroys a player's units (for copy protection or to quit to observer)
MSG_CREATE_FORMATION, ///< Creates a formation.
MSG_LOGIC_CRC, ///< CRC from the logic passed around in a network game :)
MSG_SET_MINE_CLEARING_DETAIL, ///< CRC from the logic passed around in a network game :)
MSG_LOGIC_CRC, ///< CRC from the logic passed around in a network game
MSG_SET_MINE_CLEARING_DETAIL, ///< Order selected unit(s) to clear mines
MSG_ENABLE_RETALIATION_MODE, ///< Turn retaliation mode on or off.

MSG_BEGIN_DEBUG_NETWORK_MESSAGES = 1900, ///< network messages that exist only in debug/internal builds. all grouped separately.
Expand Down
1 change: 1 addition & 0 deletions Core/GameEngine/Source/Common/MessageStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ const char *GameMessage::getCommandTypeAsString(GameMessage::Type t)
CASE_LABEL(MSG_MOUSE_RIGHT_DOUBLE_CLICK)
CASE_LABEL(MSG_CLEAR_GAME_DATA)
CASE_LABEL(MSG_NEW_GAME)
CASE_LABEL(MSG_LOGIC_CRC_PLAYBACK)
CASE_LABEL(MSG_BEGIN_META_MESSAGES)
CASE_LABEL(MSG_META_SAVE_VIEW1)
CASE_LABEL(MSG_META_SAVE_VIEW2)
Expand Down
12 changes: 8 additions & 4 deletions Core/GameEngine/Source/GameClient/GameClientDispatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,18 @@ GameMessageDisposition GameClientMessageDispatcher::translateGameMessage(const G
{
if (msg->getType() >= GameMessage::MSG_BEGIN_NETWORK_MESSAGES && msg->getType() <= GameMessage::MSG_END_NETWORK_MESSAGES)
return KEEP_MESSAGE;
if (msg->getType() == GameMessage::MSG_NEW_GAME || msg->getType() == GameMessage::MSG_CLEAR_GAME_DATA)
return KEEP_MESSAGE;

if (msg->getType() == GameMessage::MSG_FRAME_TICK)
switch (msg->getType())
{
case GameMessage::MSG_FRAME_TICK:
case GameMessage::MSG_CLEAR_GAME_DATA:
case GameMessage::MSG_NEW_GAME:
case GameMessage::MSG_LOGIC_CRC_PLAYBACK:
return KEEP_MESSAGE;
}

//DEBUG_LOG(("GameClientMessageDispatcher::translateGameMessage() - eating a %s on frame %d",
//((GameMessage *)msg)->getCommandAsString(), TheGameClient->getFrame()));
//msg->getCommandAsString(), TheGameClient->getFrame()));

return DESTROY_MESSAGE;
}
40 changes: 16 additions & 24 deletions Core/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,6 @@ bool changeLogicTimeScale(FpsValueChange change)
}


static Bool isSystemMessage( const GameMessage *msg );

enum{ DROPPED_MAX_PARTICLE_COUNT = 1000};

static Bool canSelectionSalvage( const Object *targetObj)
Expand Down Expand Up @@ -2538,11 +2536,24 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage
{
GameMessage::Type t = msg->getType();
GameMessageDisposition disp = KEEP_MESSAGE;

// We want to always be able to get to the options menu even during no input times and a clear game data message should always go through
if (t != GameMessage::MSG_META_OPTIONS && t != GameMessage::MSG_CLEAR_GAME_DATA &&
!TheInGameUI->getInputEnabled() && !isSystemMessage(msg))
switch (t)
{
return DESTROY_MESSAGE;
case GameMessage::MSG_FRAME_TICK:
case GameMessage::MSG_CLEAR_GAME_DATA:
case GameMessage::MSG_LOGIC_CRC_PLAYBACK:
case GameMessage::MSG_META_OPTIONS:
case GameMessage::MSG_META_DEMO_INSTANT_QUIT:
case GameMessage::MSG_DESTROY_SELECTED_GROUP:
case GameMessage::MSG_SET_REPLAY_CAMERA:
case GameMessage::MSG_LOGIC_CRC:
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
break;
default:
if (!TheInGameUI->getInputEnabled())
return DESTROY_MESSAGE;

break;
}

#if defined(RTS_DEBUG)
Expand Down Expand Up @@ -5599,22 +5610,3 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage
return disp;

}

static Bool isSystemMessage( const GameMessage *msg )
{
if (!msg) {
return false;
}
GameMessage::Type msgType = msg->getType();

switch (msgType)
{
case GameMessage::MSG_DESTROY_SELECTED_GROUP:
case GameMessage::MSG_LOGIC_CRC:
case GameMessage::MSG_SET_REPLAY_CAMERA:
case GameMessage::MSG_FRAME_TICK:
case GameMessage::MSG_META_DEMO_INSTANT_QUIT:
return TRUE;
}
return FALSE;
}
22 changes: 19 additions & 3 deletions Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -430,14 +430,19 @@
GameMessage::Type msgType = msg->getType();
switch( msgType )
{
case GameMessage::MSG_CLEAR_GAME_DATA:
{
onClearGameData(msg, currentlySelectedGroup);
break;
}
case GameMessage::MSG_NEW_GAME:
{
onNewGame(msg);
break;
}
case GameMessage::MSG_CLEAR_GAME_DATA:
case GameMessage::MSG_LOGIC_CRC_PLAYBACK:
{
onClearGameData(msg, currentlySelectedGroup);
onLogicCrcForPlayback(msg);

Check failure on line 445 in Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp

View workflow job for this annotation

GitHub Actions / Build Generals / win32-vcpkg-debug+t+e

'onLogicCrcForPlayback': identifier not found

Check failure on line 445 in Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp

View workflow job for this annotation

GitHub Actions / Build Generals / win32-vcpkg-profile+t+e

'onLogicCrcForPlayback': identifier not found

Check failure on line 445 in Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp

View workflow job for this annotation

GitHub Actions / Build Generals / win32-vcpkg+t+e

'onLogicCrcForPlayback': identifier not found
break;
}
case GameMessage::MSG_META_BEGIN_PATH_BUILD:
Expand Down Expand Up @@ -917,6 +922,17 @@
return true;
}

bool GameLogic::onLogicCrcForPlayback(MAYBE_UNUSED GameMessage *msg)

Check failure on line 925 in Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp

View workflow job for this annotation

GitHub Actions / Build Generals / win32-vcpkg-debug+t+e

'onLogicCrcForPlayback': is not a member of 'GameLogic'

Check failure on line 925 in Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp

View workflow job for this annotation

GitHub Actions / Build Generals / win32-vcpkg-profile+t+e

'onLogicCrcForPlayback': is not a member of 'GameLogic'

Check failure on line 925 in Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp

View workflow job for this annotation

GitHub Actions / Build Generals / win32-vcpkg+t+e

'onLogicCrcForPlayback': is not a member of 'GameLogic'
{
const UnsignedInt newCRC = msg->getArgument(0)->integer;
Comment thread
Caball009 marked this conversation as resolved.
//DEBUG_LOG(("Saw CRC of %X from playback. Our CRC is %X. Arg count is %d",
//newCRC, getCRC(), msg->getArgumentCount()));

TheRecorder->handleCRCMessage(newCRC, -1, true);

return true;
}

bool GameLogic::onBeginPathBuild(MAYBE_UNUSED GameMessage *msg)
{
DEBUG_LOG(("META: begin path build"));
Expand Down Expand Up @@ -2418,7 +2434,7 @@
//DEBUG_LOG(("Saw CRC of %X from player %d. Our CRC is %X. Arg count is %d",
//newCRC, msgPlayer->getPlayerIndex(), getCRC(), msg->getArgumentCount()));

TheRecorder->handleCRCMessage(newCRC, msgPlayer->getPlayerIndex(), (msg->getArgument(1)->boolean));
TheRecorder->handleCRCMessage(newCRC, msgPlayer->getPlayerIndex(), false);
}

return true;
Expand Down
1 change: 1 addition & 0 deletions GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ class GameLogic : public SubsystemInterface, public Snapshot

bool onNewGame(GameMessage *msg);
bool onClearGameData(GameMessage *msg, AIGroupPtr &currentlySelectedGroup);
bool onLogicCrcForPlayback(GameMessage *msg);
bool onBeginPathBuild(GameMessage *msg);
bool onEndPathBuild(GameMessage *msg);
bool onSetRallyPoint(GameMessage *msg);
Expand Down
7 changes: 4 additions & 3 deletions GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1587,9 +1587,10 @@ RecorderClass::CullBadCommandsResult RecorderClass::cullBadCommands() {

while (msg != nullptr) {
next = msg->next();
if ((msg->getType() > GameMessage::MSG_BEGIN_NETWORK_MESSAGES) &&
(msg->getType() < GameMessage::MSG_END_NETWORK_MESSAGES) &&
(msg->getType() != GameMessage::MSG_LOGIC_CRC)) {
if (msg->getType() > GameMessage::MSG_BEGIN_NETWORK_MESSAGES && msg->getType() < GameMessage::MSG_END_NETWORK_MESSAGES)
Comment thread
macroscopeapp[bot] marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this test for playback crc like it did before for logic crc?

@Caball009 Caball009 Oct 14, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, because RecorderClass::cullBadCommands "prevents the user from inserting" network messages. Previously, MSG_LOGIC_CRC needed to be excluded from this check, because that did get inserted by the user for replay playback (and is in the network message range).

EDIT: MSG_LOGIC_CRC_PLAYBACK is not in the network range.

{
DEBUG_ASSERTCRASH(msg->getType() != GameMessage::MSG_LOGIC_CRC,
("Use MSG_LOGIC_CRC_PLAYBACK instead of MSG_LOGIC_CRC during replay playback"));

deleteInstance(msg);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3768,11 +3768,13 @@ void GameLogic::update()
if (generateForSolo || generateForMP)
{
m_CRC = getCRC( CRC_RECALC );
bool isPlayback = (TheRecorder && TheRecorder->isPlaybackMode());
const bool isPlayback = (TheRecorder && TheRecorder->isPlaybackMode());

GameMessage *msg = newInstance(GameMessage)(GameMessage::MSG_LOGIC_CRC);
// TheSuperHackers @tweak Caball009 30/09/2026 Playback CRC is now separate from regular CRC,
// removing the need for the second parameter used to distinguish them. Older clients can still
// parse newer replays because GameMessage::getArgument returns false for the missing playback argument.
GameMessage *msg = newInstance(GameMessage)(isPlayback ? GameMessage::MSG_LOGIC_CRC_PLAYBACK : GameMessage::MSG_LOGIC_CRC);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense cache this message type in GameLogic::startNewGame ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean?

msg->appendIntegerArgument(m_CRC);
msg->appendBooleanArgument(isPlayback);

// TheSuperHackers @info helmutbuhler 13/04/2025
// During replay simulation, we bypass TheMessageStream and instead put the CRC message
Expand Down
Loading