Skip to content

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

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

feat(network): Send product information to distinguish clients#1404
Caball009 wants to merge 19 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.

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.

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?

Copy link
Author

Choose a reason for hiding this comment

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

Yes. Does the comment suggest otherwise to you?

Copy link

Choose a reason for hiding this comment

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

Yes it is not clear to me.

It says:

Existing players treat this request as confirmation that the joining player is patched

This implies that existing player do not receive product info but just know that the requester is not a Retail guy.

}

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)

Copy link
Author

Choose a reason for hiding this comment

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

This is getting outside the scope of this PR imo.

Copy link

Choose a reason for hiding this comment

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

Should be simple enough. Can write like so:

void LANAPI::sendMessage(LANMessage *msg, UnsignedInt ip /* = 0 */)
{
	if (ip != 0)
	{
		m_transport->queueSend(ip, lobbyPort, (unsigned char *)msg, sizeof(LANMessage) /*, 0, 0 */);
	}
	else if ((m_currentGame != nullptr) && (m_currentGame->getIsDirectConnect()))
	{
		sendMessageToGameRoomPlayers(msg);
	}
	else
	{
		m_transport->queueSend(m_broadcastAddr, lobbyPort, (unsigned char *)msg, sizeof(LANMessage) /*, 0, 0 */);
	}
}

void LANAPI::sendMessageToGameRoomPlayers(LANMessage *msg)
{
	if (!m_currentGame)
		return;

	Int localSlot = m_currentGame->getLocalSlotNum();
	for (Int i = 0; i < MAX_SLOTS; ++i)
	{
		if (i != localSlot) {
			GameSlot *slot = m_currentGame->getSlot(i);
			if ((slot != nullptr) && (slot->isHuman())) {
				m_transport->queueSend(slot->getIP(), lobbyPort, (unsigned char *)msg, sizeof(LANMessage) /*, 0, 0 */);
			}
		}
	}
}

This way you can simply call sendMessageToGameRoomPlayers(msg) instead of doing the argument tricks.

}
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);

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?

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

Copy link
Author

Choose a reason for hiding this comment

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

The only reason I went with launchTime is previous feedback from you tbh.

I'd rather go back to up time than doing much work for launch time. I see no benefit knowing the launch time, just the up time.

Copy link

Choose a reason for hiding this comment

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

Yes but uptime is not particularly meaningful because it expires with passing time. Launch time is conceptually good but it needs the right value.

};

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.

Copy link
Author

Choose a reason for hiding this comment

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

Same comment as above. That's just 4 wasted bytes as far as I'm concerned.

Copy link

Choose a reason for hiding this comment

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

Hmm ok. You could lower the precision of the 64 bit time from seconds to minutes but that also seems a bit hacky. You could go back to uptime but then it is an accurate value, unless you also keep the arrival time of the uptime to add the delta on top.

Unclear to me what the most elegant solution here is. What is this time for? To see when someone has not restarted his game before a match?

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 tell whether a remote client has played other matches beforehand, then how about count the number of matches played instead of the uptime? Then the value can also be just 2 or 1 bytes. When match count shows 0, then we know it is a fresh client start. When uptime shows 3 hours, we can only suspect that the match count is larger than 0. It could be 0 even after 3 hours of uptime.

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