Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
33 changes: 33 additions & 0 deletions Core/GameEngine/Include/GameNetwork/GameInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,34 @@ enum
class GameSlot
{
public:
struct ProductInfo
{
ProductInfo() :
flags(0),
launchTime(0),
exeCRC(0),
iniCRC(0),
fpMathCRC(0)
{}

enum Flags CPP_11(: UnsignedInt)
{
NO_RETAIL = 1 << 0,
SHELLMAP_ENABLED = 1 << 1,
ZERO_MAPS_STARTED = 1 << 2,
};

UnsignedInt flags;
UnsignedInt launchTime;
Copy link
Copy Markdown

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
Copy Markdown
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
Copy Markdown

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
Copy Markdown

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.

UnsignedInt exeCRC;
UnsignedInt iniCRC;
UnsignedInt fpMathCRC;
UnicodeString productTitle;
UnicodeString productVersion;
UnicodeString productAuthor;
UnicodeString gitShortHash;
};

GameSlot();
virtual void reset();

Expand Down Expand Up @@ -125,6 +153,10 @@ class GameSlot

void mute( Bool isMuted ) { m_isMuted = isMuted; }
Bool isMuted( void ) const { return m_isMuted; }

void setProductInfo(const ProductInfo& productInfo) { m_productInfo = productInfo; }
const ProductInfo& getProductInfo() const { return m_productInfo; }

protected:
Comment thread
Caball009 marked this conversation as resolved.
SlotState m_state;
Bool m_isAccepted;
Expand All @@ -143,6 +175,7 @@ class GameSlot
FirewallHelperClass::FirewallBehaviorType m_NATBehavior; ///< The NAT behavior for this slot's player.
UnsignedInt m_lastFrameInGame; // only valid for human players
Bool m_disconnected; // only valid for human players
ProductInfo m_productInfo; ///< Community made product information
};

/**
Expand Down
47 changes: 46 additions & 1 deletion Core/GameEngine/Include/GameNetwork/LANAPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,25 @@ 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

// TheSuperHackers @feature Caball009 05/02/2026 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.
Comment thread
Caball009 marked this conversation as resolved.
Outdated
// - If the other player responds with an acknowledgement, they are considered patched.
// 2. player - host in lobby:
// - When a player detects a new game host in the lobby, it sends a product info request.
// - 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 existing players.
// - Existing players treat this request as confirmation that the joining player is patched (no explicit acknowledgement required).
MSG_GAME_REQUEST_PRODUCT_INFO = 1000,
MSG_GAME_RESPONSE_PRODUCT_INFO,
MSG_LOBBY_REQUEST_PRODUCT_INFO,
MSG_LOBBY_RESPONSE_PRODUCT_INFO,
MSG_MATCH_REQUEST_PRODUCT_INFO,
MSG_MATCH_RESPONSE_PRODUCT_INFO,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

As mentioned in the other file, perhaps a better terminology for these are:

GAME to LOBBY_ROOM aka a Game Room in the Lobby
LOBBY to LOBBY_PLAYER aka a Player in the Lobby
MATCH to ROOM_PLAYER aka a Player in the Game Room

} messageType;

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

// ProductInfo is sent with REQUEST_PRODUCT_INFO and RESPONSE_PRODUCT_INFO
Comment thread
xezon marked this conversation as resolved.
struct
{
UnsignedInt flags;
UnsignedInt launchTime;
UnsignedInt exeCRC;
UnsignedInt iniCRC;
UnsignedInt fpMathCRC;
WideChar data[201];
Comment thread
xezon marked this conversation as resolved.
} ProductInfo;
Comment thread
Caball009 marked this conversation as resolved.
Comment thread
xezon marked this conversation as resolved.
};
};
Comment thread
Caball009 marked this conversation as resolved.
#pragma pack(pop)
Expand Down Expand Up @@ -386,14 +415,22 @@ class LANAPI : public LANAPIInterface

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

LANMessage m_productInfoMessage; ///< store product info message to avoid having to recreate it multiple times

protected:
void sendMessage(LANMessage *msg, UnsignedInt ip = 0); // Convenience function
void sendMessage(LANMessage *msg, UnsignedInt ip = 0, Bool broadcast = TRUE); // Convenience function
void removePlayer(LANPlayer *player);
void removeGame(LANGameInfo *game);
void addPlayer(LANPlayer *player);
void addGame(LANGameInfo *game);
AsciiString createSlotString( void );

static UnsignedInt buildProductInfoFlags();
static void setProductInfoFromLocalData(GameSlot &slot);
static void setProductInfoFromMessage(GameSlot &slot, LANMessage *msg);
static Bool setProductInfoStrings(const UnicodeString(&input)[4], WideChar(&output)[201]);
static Bool getProductInfoStrings(WideChar(&input)[201], UnicodeString*(&output)[4]);

// Functions to handle incoming messages -----------------------------------
void handleRequestLocations( LANMessage *msg, UnsignedInt senderIP );
void handleGameAnnounce( LANMessage *msg, UnsignedInt senderIP );
Expand All @@ -412,4 +449,12 @@ class LANAPI : public LANAPIInterface
void handleGameOptions( LANMessage *msg, UnsignedInt senderIP );
void handleInActive( LANMessage *msg, UnsignedInt senderIP );

static LANMessage createProductInfoMessage();
Comment thread
Caball009 marked this conversation as resolved.
Outdated
void sendProductInfoMessage(LANMessage::Type messageType, UnsignedInt senderIP);
void handleGameProductInfoRequest(LANMessage *msg, UnsignedInt senderIP);
void handleGameProductInfoResponse(LANMessage *msg, UnsignedInt senderIP);
void handleLobbyProductInfoRequest(LANMessage *msg, UnsignedInt senderIP);
void handleLobbyProductInfoResponse(LANMessage *msg, UnsignedInt senderIP);
void handleMatchProductInfoRequest(LANMessage *msg, UnsignedInt senderIP);
void handleMatchProductInfoResponse(LANMessage *msg, UnsignedInt senderIP);
};
5 changes: 4 additions & 1 deletion Core/GameEngine/Include/GameNetwork/LANPlayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
class LANPlayer
{
public:
LANPlayer() { m_name = m_login = m_host = L""; m_lastHeard = 0; m_next = nullptr; m_IP = 0; }
LANPlayer() { m_name = m_login = m_host = L""; m_lastHeard = 0; m_next = nullptr; m_IP = 0; m_productInfoFlags = 0; }

// Access functions
UnicodeString getName( void ) { return m_name; }
Expand All @@ -52,6 +52,8 @@ class LANPlayer
void setNext( LANPlayer *next ) { m_next = next; }
UnsignedInt getIP( void ) { return m_IP; }
void setIP( UnsignedInt IP ) { m_IP = IP; }
UnsignedInt getProductInfoFlags() const { return m_productInfoFlags; }
Comment thread
Caball009 marked this conversation as resolved.
void setProductInfoFlags(UnsignedInt productInfoFlags) { m_productInfoFlags = productInfoFlags; }

protected:
UnicodeString m_name; ///< Player name
Expand All @@ -60,4 +62,5 @@ class LANPlayer
UnsignedInt m_lastHeard; ///< The last time we heard from this player (for timeout purposes)
LANPlayer *m_next; ///< Linked list pointer
UnsignedInt m_IP; ///< Player's IP
UnsignedInt m_productInfoFlags; ///< Community made product information flags
};
7 changes: 7 additions & 0 deletions Core/GameEngine/Source/GameNetwork/GameInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ void GameSlot::reset()
m_origPlayerTemplate = -1;
m_origStartPos = -1;
m_origColor = -1;
m_productInfo = ProductInfo();
Comment thread
Caball009 marked this conversation as resolved.
}

void GameSlot::saveOffOriginalInfo( void )
Expand Down Expand Up @@ -1493,7 +1494,13 @@ Bool ParseAsciiStringToGameInfo(GameInfo *game, AsciiString options)
//DEBUG_LOG(("ParseAsciiStringToGameInfo - game options all good, setting info"));

for(Int i = 0; i<MAX_SLOTS; i++)
{
// retain the product information if a slot is still occupied by the same player
if (game->getConstSlot(i)->getState() == SLOT_PLAYER && newSlot[i].getState() == SLOT_PLAYER)
newSlot[i].setProductInfo(game->getConstSlot(i)->getProductInfo());
Comment thread
Caball009 marked this conversation as resolved.

game->setSlot(i,newSlot[i]);
}

game->setMap(mapName);
game->setMapCRC(mapCRC);
Expand Down
28 changes: 26 additions & 2 deletions Core/GameEngine/Source/GameNetwork/LANAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ LANAPI::LANAPI( void ) : m_transport(nullptr)
m_lastUpdate = 0;
m_transport = new Transport;
m_isActive = TRUE;
m_productInfoMessage = createProductInfoMessage();
}

LANAPI::~LANAPI( void )
Expand Down Expand Up @@ -179,13 +180,13 @@ void LANAPI::reset( void )

}

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

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
Copy Markdown
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
Copy Markdown

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.

{
if (ip != 0)
{
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))
Comment thread
xezon marked this conversation as resolved.
{
Int localSlot = m_currentGame->getLocalSlotNum();
for (Int i = 0; i < MAX_SLOTS; ++i)
Expand Down Expand Up @@ -425,6 +426,26 @@ void LANAPI::update( void )
handleInActive( msg, senderIP );
break;

// exchange product information with other players
case LANMessage::MSG_GAME_REQUEST_PRODUCT_INFO:
handleGameProductInfoRequest(msg, senderIP);
break;
case LANMessage::MSG_GAME_RESPONSE_PRODUCT_INFO:
handleGameProductInfoResponse(msg, senderIP);
break;
case LANMessage::MSG_LOBBY_REQUEST_PRODUCT_INFO:
handleLobbyProductInfoRequest(msg, senderIP);
break;
case LANMessage::MSG_LOBBY_RESPONSE_PRODUCT_INFO:
handleLobbyProductInfoResponse(msg, senderIP);
break;
case LANMessage::MSG_MATCH_REQUEST_PRODUCT_INFO:
handleMatchProductInfoRequest(msg, senderIP);
break;
case LANMessage::MSG_MATCH_RESPONSE_PRODUCT_INFO:
handleMatchProductInfoResponse(msg, senderIP);
break;

default:
DEBUG_LOG(("Unknown LAN message type %d", msg->messageType));
}
Expand Down Expand Up @@ -906,6 +927,9 @@ void LANAPI::RequestGameCreate( UnicodeString gameName, Bool isDirectConnect )
newSlot.setLogin(m_userName);
newSlot.setHost(m_hostName);

// set product information for local game slot
setProductInfoFromLocalData(newSlot);

myGame->setSlot(0,newSlot);
myGame->setNext(nullptr);
LANPreferences pref;
Expand Down
Loading
Loading