Skip to content

feat(network): Send product information to distinguish clients#1404

Open
Caball009 wants to merge 10 commits intoTheSuperHackers:mainfrom
Caball009:show_clients_with_sh_patch
Open

feat(network): Send product information to distinguish clients#1404
Caball009 wants to merge 10 commits intoTheSuperHackers:mainfrom
Caball009:show_clients_with_sh_patch

Conversation

@Caball009
Copy link

@Caball009 Caball009 commented Aug 1, 2025

This PR makes our clients send product information to distinguish them from retail clients primarily. Retail clients will ignore the product information data that's sent to them.

Product information is exchanged on demand and never broadcast. A client is considered 'patched' if it responds to a product info request (or, in pre-match, if it sends one). The implementation consists of three parts.

  1. player - player in lobby:
  • When a player detects a new player in the lobby, it sends a product info request to that player.
  • If the other player responds with an acknowledgement, they are considered patched.
  1. player - host in lobby:
  • When a player detects a new game host in the lobby, it sends a product info request to that host.
  • If the host responds with an acknowledgement, it is considered patched.
  1. players in pre-match (game room):
  • When a player joins a match, it sends a product info request to all players in that match.
  • Existing players treat this request as confirmation that the joining player is patched (no explicit acknowledgement required).
Examples of future use cases, visualizing who's not using the retail client

LAN Lobby:
image

Pre-match / Game Options:
image

Status in match:
image

TODO:

@Caball009 Caball009 added Enhancement Is new feature or request GUI For graphical user interface Network Anything related to network, servers Gen Relates to Generals ZH Relates to Zero Hour labels Aug 1, 2025
@Caball009 Caball009 marked this pull request as ready for review August 10, 2025 19:34
@Caball009 Caball009 requested a review from a team August 11, 2025 15:24
Copy link

@xezon xezon left a comment

Choose a reason for hiding this comment

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

Good effort.

This work relates to #16

I think the packets need redesigning.

@L3-M
Copy link

L3-M commented Oct 23, 2025

What is the current status of this change? I think this would be a nice change for the first release.

@L3-M L3-M added this to the Important features milestone Oct 23, 2025
@Caball009
Copy link
Author

Caball009 commented Nov 1, 2025

What is the current status of this change? I think this would be a nice change for the first release.

The current implementation ought to be changed to make it more efficient / optimal, but that requires much more work. I did some of the work locally, but it didn't really work out iirc, so the PR is kind of stuck for now.

@Caball009 Caball009 force-pushed the show_clients_with_sh_patch branch from 9fb6a01 to c4e3f1d Compare November 6, 2025 13:52
@Caball009
Copy link
Author

I've revisited this PR and was able to move away from the broadcast implementation. It feels better now. I put networking related changes in one commit and the color / status stuff in another.

What sort of information do we want to exchange just to establish which clients are on a patched version? Maybe it's a good idea to send the commit hash as well.

@Caball009 Caball009 requested a review from a team November 6, 2025 14:04
@Caball009
Copy link
Author

Caball009 commented Nov 6, 2025

Short overview of the new implementation. Patch information is not broadcast and only sent when needed.

Interacting with players in the lobby:

  1. When a player detects a new player in the lobby, they send a patch information request to that player.
  2. If the other player responds with an acknowledgement, they are on a 'patched version'.

Interacting with game hosts in the lobby:

  1. When a player in the lobby detects a new game host, they send a patch information request to that host.
  2. If the game host responds with an acknowledgement, they are on a 'patched version'.

Interacting with players in the pre-match game:

  1. When a player joins a match, they send all players in that match a patch information request.
  2. If the other players respond with an acknowledgement, they are on a 'patched version'.
  3. The other players consider the patch information request from the joining player as acknowledgement.

Currently, players in the lobby do not have a way to know which players in a match are on a 'patched version' except the host. It doesn't seem necessary to me to have the host communicate this information, and it also breaks the current pattern where each client only sends data about itself.

Copy link

@xezon xezon left a comment

Choose a reason for hiding this comment

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

This change is going into the right direction but needs more polishing and wider scope.

Are the pictures of the first post still relevant?

The terminology of the network data needs changing from "Patch" to "Product", because "Patch" is a much more limited term than "Product" is.

The ProductInfo message needs to contain all relevant information to describe the Product that the remote player is using. A version number alone will not be enough to properly describe a Product. Think of 100 different products that the user could interact with in the lobby.

@Caball009
Copy link
Author

Caball009 commented Nov 8, 2025

Are the pictures of the first post still relevant?

Yes, the colors are still like that; the status text will be slightly different. My current idea is that it shows the Git short hash string there (there isn't much room for additional text, unfortunately).

The terminology of the network data needs changing from "Patch" to "Product", because "Patch" is a much more limited term than "Product" is.

Fair enough. Everything should say 'product' instead of 'patch' now.

The ProductInfo message needs to contain all relevant information to describe the Product that the remote player is using. A version number alone will not be enough to properly describe a Product. Think of 100 different products that the user could interact with in the lobby.

I added a couple of stuff (e.g. exe and ini crc values), but I don't feel like this is final version yet.

@Caball009 Caball009 changed the title feat(gui): Distinguish patch players from retail players by a colored name and different status feat(network): Send product information to distinguish clients (with a colored name and different status) Nov 8, 2025
@Caball009
Copy link
Author

I could use some feedback on this PR.

Copy link

@xezon xezon left a comment

Choose a reason for hiding this comment

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

Current thoughts on this. No exhaustive review.

@greptile-apps
Copy link

greptile-apps bot commented Feb 1, 2026

Greptile Summary

This PR implements a product information exchange protocol to distinguish community-patched clients from retail clients. The implementation adds a request/response pattern across three contexts: lobby player-to-player, lobby player-to-host, and in-match player-to-player exchanges. Product info includes flags (NO_RETAIL, SHELLMAP_ENABLED, ZERO_MAPS_STARTED), CRC checksums, launch time, and version strings (title, version, author, git hash). The data is serialized using ETX (0x03) separators in a 201-character buffer and stored in GameSlot and LANPlayer structures. The protocol is backward compatible - retail clients ignore unknown message types.

  • Added GameSlot::ProductInfo struct with comprehensive version and runtime information
  • Implemented 6 new message types for product info request/response across different game contexts
  • Added string serialization/deserialization helpers using ETX character separators
  • Integrated product info requests at appropriate trigger points (new player detection, game join)
  • Added m_launchTime and m_startedGamesCount tracking for runtime metadata
  • Product info is retained across slot updates when same player remains in slot

Confidence Score: 5/5

  • This PR is safe to merge - well-structured protocol extension with backward compatibility
  • Clean implementation of a non-invasive feature that extends the network protocol without breaking existing functionality. The code is well-commented, handles edge cases appropriately, and previous reviewer concerns have been addressed with assertions. The backward compatibility ensures retail clients continue to work normally.
  • No files require special attention

Important Files Changed

Filename Overview
Core/GameEngine/Include/GameNetwork/GameInfo.h Added ProductInfo struct to GameSlot with flags, CRCs, and version strings - clean data structure design
Core/GameEngine/Include/GameNetwork/LANAPI.h Added 6 new message types and helper methods for product info exchange - clear protocol extension
Core/GameEngine/Source/GameNetwork/LANAPI.cpp Added message handling dispatch for 6 product info message types and cached product info message
Core/GameEngine/Source/GameNetwork/LANAPIhandlers.cpp Implemented product info request/response handlers for lobby, game, and match contexts with string serialization

Sequence Diagram

sequenceDiagram
    participant P1 as Player 1 (Lobby)
    participant P2 as Player 2 (Lobby)
    participant H as Host (Lobby)
    participant PM as Players (In Match)

    Note over P1,P2: Lobby: Player-to-Player
    P1->>P2: MSG_LOBBY_REQUEST_PRODUCT_INFO
    P2->>P1: MSG_LOBBY_RESPONSE_PRODUCT_INFO
    Note over P1: P2 marked as patched

    Note over P1,H: Lobby: Player-to-Host
    P1->>H: MSG_GAME_REQUEST_PRODUCT_INFO
    H->>P1: MSG_GAME_RESPONSE_PRODUCT_INFO
    Note over P1: Host marked as patched

    Note over PM: Pre-Match: Join Trigger
    PM->>PM: Player joins match
    PM->>PM: MSG_MATCH_REQUEST_PRODUCT_INFO (broadcast)
    PM->>PM: MSG_MATCH_RESPONSE_PRODUCT_INFO (responses)
    Note over PM: All players mark each other as patched
Loading

Last reviewed commit: 391bdf7

greptile-apps[bot]

This comment was marked as resolved.

greptile-apps[bot]

This comment was marked as resolved.

@Caball009 Caball009 force-pushed the show_clients_with_sh_patch branch from 540c3e7 to f2651b9 Compare February 5, 2026 01:17
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

3 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

@Caball009
Copy link
Author

Caball009 commented Feb 5, 2026

I'm slowly moving forward with this PR, though there are still several unresolved conversations. I've decided that the code for colored names is best suited for a follow-up PR.

@xezon
Copy link

xezon commented Feb 8, 2026

Please set to draft if this is still WIP. It is not clear to me if Review can continue.

@Caball009 Caball009 changed the title feat(network): Send product information to distinguish clients (with a colored name and different status) feat(network): Send product information to distinguish clients Feb 8, 2026
@Caball009 Caball009 removed the GUI For graphical user interface label Feb 8, 2026
@Caball009
Copy link
Author

Caball009 commented Feb 8, 2026

Please set to draft if this is still WIP. It is not clear to me if Review can continue.

This PR is ready for review. Perhaps there are a few minor details that may still change. I hope #2100 won't take much longer so I can include it in this PR.

For anyone who wants to test this, you can revert the reverted commit so that you can see the colored names and status again.

@xezon
Copy link

xezon commented Feb 13, 2026

So #2100 is meant to be concluded before this change? I thought we would do #2100 afterwards.

Copy link

@xezon xezon left a comment

Choose a reason for hiding this comment

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

Looks promising but there is still some polishing to do.

// A client is considered 'patched' if it responds to a product info request (or, in pre-match, if it sends one).
// The implementation consists of three parts.
// 1. player - player in lobby:
// - When a player detects a new player in the lobby, it sends a product info request.
Copy link

Choose a reason for hiding this comment

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

Is there precedence to this type of communication? Is it expected to be ok if there are 100 people in the Network lobby that receive and respond these new messages?

Will a response just reach the client that made the request or is the response broadcasted to everyone?

Copy link
Author

Choose a reason for hiding this comment

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

Will a response just reach the client that made the request or is the response broadcasted to everyone?

Only the client that made the request. I'll update the comment to reflect that.

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

10 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@Caball009
Copy link
Author

Caball009 commented Feb 22, 2026

I just addressed most of the review feedback

I'd like to reduce the string buffer size from 200 characters to 190. For reference, we barely even use 50 characters depending on the user name: Community Patch�~1297�By Caball009�~8c71c3a61 I'm slightly worried that we'll want to add several additional fields later and not have enough room to do so in this type.

@Caball009
Copy link
Author

So #2100 is meant to be concluded before this change? I thought we would do #2100 afterwards.

There's been some discussion about what #2100 should do, but I think returning the hash can be done before this PR so it can be included.

@xezon
Copy link

xezon commented Mar 2, 2026

I'm slightly worried that we'll want to add several additional fields later and not have enough room to do so in this type.

How much is left now? Are there not more than 200 characters of spare space?

// - When a player detects a new game host in the lobby, it sends a product info request to that host.
// - If the host responds with an acknowledgement, it is considered patched.
// 3. players in pre-match:
// - When a player joins a match, it sends a product info request to all players in that match.
Copy link

Choose a reason for hiding this comment

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

Do the previous players also receive Product Info from the new joined player?

m_transport->queueSend(ip, lobbyPort, (unsigned char *)msg, sizeof(LANMessage) /*, 0, 0 */);
}
else if ((m_currentGame != nullptr) && (m_currentGame->getIsDirectConnect()))
else if (m_currentGame != nullptr && (m_currentGame->getIsDirectConnect() || !broadcast))
Copy link

Choose a reason for hiding this comment

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

Condition can be simplified by doing

if (m_currentGame->getIsDirectConnect())
  broadcast = false;

before

}

void LANAPI::sendMessage(LANMessage *msg, UnsignedInt ip /* = 0 */)
void LANAPI::sendMessage(LANMessage *msg, UnsignedInt ip /* = 0 */, Bool broadcast /*= TRUE*/)
Copy link

Choose a reason for hiding this comment

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

The term broadcast is confusing. Because when it is set to FALSE it will still broadcast to game room participants.

Perhaps split this function into multiple to make its usage more explicit.

For example have a LANAPI::sendMessageToGameRoomPlayers(LANMessage *msg)

}
else
{
output[ARRAY_SIZE(output) - 1] = '\0';
Copy link

Choose a reason for hiding this comment

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

Would there not already be a guaranteed null via wcslcpy?

// null terminate the output buffer to signal the end of the last input string
if (outputIndex < ARRAY_SIZE(output))
{
output[outputIndex++] = '\0';
Copy link

Choose a reason for hiding this comment

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

Would there not already be a guaranteed null via wcslcpy?

Can we just do

return outputIndex < ARRAY_SIZE(output);

const size_t length = etx_strlen_t(input + inputIndex);
output[i]->set(input + inputIndex, length);

inputIndex += length + 1;
Copy link

Choose a reason for hiding this comment

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

Perhaps write length + sizeof('\3') to make clear what the 1 means.

output[i]->set(input + inputIndex, length);

inputIndex += length + 1;
if (inputIndex >= ARRAY_SIZE(input))
Copy link

Choose a reason for hiding this comment

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

This condition looks as if it returns true when the input is exactly 200 characters, perfectly fitting?

Perhaps do inputIndex += sizeof('\3') after this condition only?

void handleGameOptions( LANMessage *msg, UnsignedInt senderIP );
void handleInActive( LANMessage *msg, UnsignedInt senderIP );

static LANMessage createProductInfoMessage();
Copy link

Choose a reason for hiding this comment

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

buildProductInfoMessage to be consistent with buildProductInfoFlags

m_logicTimeAccumulator = 0.0f;
m_quitting = FALSE;
m_isActive = FALSE;
m_launchTime = ::timeGetTime();
Copy link

Choose a reason for hiding this comment

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

What is the purpose of the launch time? I suspect to see for how long the game has been running? timeGetTime() return the milliseconds from Windows start until now. This is not a useful information for others.

What we likely need to send is a UTC time. With c++20 that would be chrono::utc_time, but with VC6 we would need gmtime https://en.cppreference.com/w/c/chrono/gmtime

};

UnsignedInt flags;
UnsignedInt launchTime;
Copy link

Choose a reason for hiding this comment

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

If this is meant to represent unix time, then it needs to be 64 bits because 32 bits value wraps in 2038.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement Is new feature or request Gen Relates to Generals Network Anything related to network, servers ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants