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
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),
upTime(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 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() const { return m_isMuted; }

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

protected:
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 to that player.
// - 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 to that host.
// - If the host responds with an acknowledgement, it is considered patched.
// 3. players in pre-match (game room):
// - 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.

// - 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

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
struct
{
UnsignedInt flags;
UnsignedInt upTime;
UnsignedInt exeCRC;
UnsignedInt iniCRC;
UnsignedInt fpMathCRC;
WideChar data[201];
} ProductInfo;
};
};
#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();

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 buildProductInfoMessage();
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() { return m_name; }
Expand All @@ -52,6 +52,8 @@ class LANPlayer
void setNext( LANPlayer *next ) { m_next = next; }
UnsignedInt getIP() { return m_IP; }
void setIP( UnsignedInt IP ) { m_IP = IP; }
UnsignedInt getProductInfoFlags() const { return m_productInfoFlags; }
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
};
10 changes: 10 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();
}

void GameSlot::saveOffOriginalInfo()
Expand Down Expand Up @@ -1493,7 +1494,16 @@ 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)
{
DEBUG_ASSERTCRASH(game->getConstSlot(i)->getIP() == newSlot[i].getIP(), ("Game slot transition was unexpected"));
newSlot[i].setProductInfo(game->getConstSlot(i)->getProductInfo());
}

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() : m_transport(nullptr)
m_lastUpdate = 0;
m_transport = new Transport;
m_isActive = TRUE;
m_productInfoMessage = buildProductInfoMessage();
}

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

}

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.

{
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))
{
Int localSlot = m_currentGame->getLocalSlotNum();
for (Int i = 0; i < MAX_SLOTS; ++i)
Expand Down Expand Up @@ -425,6 +426,26 @@ void LANAPI::update()
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