From 25ca067ec3a32f56f82f1fa4c95763df6e37b8ce Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 6 Apr 2026 10:04:29 -0700
Subject: [PATCH] Don't join games with a different version or sprite pack
Also fixes a few edge cases in joining/kicking in the multiplayer lobby.
---
game/gameinfo.cpp | 68 +++++++++++++++++++++++++++++++++++++++++++----
game/gameinfo.h | 5 ++++
game/lobby.cpp | 39 ++++++++++++++-------------
game/replay.cpp | 13 ++-------
4 files changed, 91 insertions(+), 34 deletions(-)
diff --git a/game/gameinfo.cpp b/game/gameinfo.cpp
index 13ece0a3..16eae493 100644
--- a/game/gameinfo.cpp
+++ b/game/gameinfo.cpp
@@ -45,6 +45,8 @@ GameInfo::Reset()
numNodes = 0;
SDL_zero(nodes);
SDL_zero(players);
+ replayVersion = REPLAY_VERSION;
+ spriteCRC = gSpriteCRC;
}
void
@@ -88,6 +90,12 @@ GameInfo::SetPlayerSlot(int slot, const char *name, Uint8 controlMask)
}
player->controlMask = controlMask;
+ if (controlMask == CONTROL_NETWORK) {
+ player->available = true;
+ } else {
+ player->available = false;
+ }
+
UpdateUI(player);
}
@@ -118,8 +126,7 @@ GameInfo::AddNetworkPlayer(Uint32 nodeID, const IPaddress &address, const char *
++numNodes;
for (slot = 0; slot < MAX_PLAYERS; ++slot) {
- if (!players[slot].nodeID &&
- players[slot].controlMask == CONTROL_NETWORK) {
+ if (players[slot].available) {
break;
}
}
@@ -128,6 +135,7 @@ GameInfo::AddNetworkPlayer(Uint32 nodeID, const IPaddress &address, const char *
GameInfoPlayer *player = &players[slot];
player->nodeID = nodeID;
SDL_strlcpy(player->name, name, sizeof(player->name));
+ player->available = false;
UpdateUI(player);
@@ -145,6 +153,8 @@ GameInfo::CopyFrom(const GameInfo &rhs)
lives = rhs.lives;
turbo = rhs.turbo;
gameMode = rhs.gameMode;
+ replayVersion = rhs.replayVersion;
+ spriteCRC = rhs.spriteCRC;
for (i = 0; i < MAX_NODES; ++i) {
const GameInfoNode *node = rhs.GetNode(i);
@@ -182,6 +192,7 @@ GameInfo::CopyFrom(const GameInfo &rhs)
} else {
players[i].controlMask = CONTROL_NONE;
}
+ players[i].available = player->available;
}
UpdateUI();
@@ -246,6 +257,23 @@ GameInfo::ReadFromPacket(DynamicPacket &packet)
nodes[HOST_NODE].address = packet.address;
}
+ if (!packet.Read(replayVersion)) {
+ // Older version
+ replayVersion = 0;
+ }
+
+ if (!packet.Read(spriteCRC)) {
+ // Older version
+ spriteCRC = 0;
+ }
+
+ for (i = 0; i < MAX_PLAYERS; ++i) {
+ if (!packet.Read(players[i].available)) {
+ // Older version
+ players[i].available = false;
+ }
+ }
+
return true;
}
@@ -272,6 +300,13 @@ GameInfo::WriteToPacket(DynamicPacket &packet)
packet.Write(players[i].nodeID);
packet.Write(players[i].name);
}
+
+ packet.Write(replayVersion);
+ packet.Write(spriteCRC);
+
+ for (i = 0; i < MAX_PLAYERS; ++i) {
+ packet.Write(players[i].available);
+ }
}
void
@@ -325,6 +360,7 @@ GameInfo::RemoveNode(Uint32 nodeID)
for (int j = i; j < (GetNumNodes() - 1); ++j) {
nodes[j] = nodes[j + 1];
}
+ SDL_zero(nodes[GetNumNodes() - 1]);
--numNodes;
} else {
++i;
@@ -345,6 +381,7 @@ GameInfo::RemovePlayer(int index)
player->nodeID = 0;
SDL_zero(player->name);
player->controlMask = CONTROL_NETWORK;
+ player->available = true;
UpdateUI(player);
}
@@ -436,8 +473,7 @@ bool
GameInfo::IsFull() const
{
for (int i = 0; i < MAX_PLAYERS; ++i) {
- if (!players[i].nodeID &&
- players[i].controlMask == CONTROL_NETWORK) {
+ if (players[i].available) {
return false;
}
}
@@ -508,6 +544,7 @@ GameInfo::BindPlayerToUI(int index, UIElement *element)
}
player->UI.element = element;
+ player->UI.join = element->GetElement<UIElement>("join");
player->UI.desc = element->GetElement<UIElement>("desc");
player->UI.name = element->GetElement<UIElement>("name");
player->UI.host = element->GetElement<UIElement>("host");
@@ -562,6 +599,8 @@ GameInfo::UpdateUI(GameInfoPlayer *player)
return;
}
+ bool enableJoin = true;
+
if (player->UI.name && player->UI.host) {
const GameInfoNode *node = GetNodeByID(player->nodeID);
if (!node || node->nodeID == localID) {
@@ -571,7 +610,26 @@ GameInfo::UpdateUI(GameInfoPlayer *player)
player->UI.name->Show();
player->UI.name->SetText(player->name);
player->UI.host->Show();
- player->UI.host->SetText(NET_GetAddressString(node->address.host));
+ if (replayVersion != REPLAY_VERSION) {
+ player->UI.host->SetText("(different version)");
+ enableJoin = false;
+ } else if (spriteCRC != gSpriteCRC) {
+ player->UI.host->SetText("(different sprites)");
+ enableJoin = false;
+ } else if (IsFull() && !HasLocalControl()) {
+ player->UI.host->SetText("(no ships available)");
+ enableJoin = false;
+ } else {
+ player->UI.host->SetText(NET_GetAddressString(node->address.host));
+ }
+ }
+ }
+
+ if (player->UI.join) {
+ if (enableJoin) {
+ player->UI.join->SetDisabled(false);
+ } else {
+ player->UI.join->SetDisabled(true);
}
}
diff --git a/game/gameinfo.h b/game/gameinfo.h
index 1e8193bd..e63456ba 100644
--- a/game/gameinfo.h
+++ b/game/gameinfo.h
@@ -102,9 +102,11 @@ struct GameInfoPlayer
Uint32 nodeID;
char name[MAX_NAMELEN+1];
Uint8 controlMask;
+ Uint8 available;
struct {
UIElement *element;
+ UIElement *join;
UIElement *desc;
UIElement *name;
UIElement *host;
@@ -239,6 +241,9 @@ class GameInfo
Uint8 turbo;
Uint8 gameMode;
+ Uint32 replayVersion;
+ Uint32 spriteCRC;
+
Uint32 localID;
Uint8 paused;
diff --git a/game/lobby.cpp b/game/lobby.cpp
index e0e28a7a..7fdf3050 100644
--- a/game/lobby.cpp
+++ b/game/lobby.cpp
@@ -52,12 +52,10 @@ class SelectControlCallback : public UIClickCallback
virtual void operator()() {
if (m_game.IsHosting()) {
// Kick any player that was connected
- if (m_controlType != CONTROL_NETWORK) {
- const GameInfoPlayer* player = m_game.GetPlayer(m_index);
- int nodeIndex = m_game.GetNodeIndex(player->nodeID);
- if (nodeIndex >= 0) {
- m_lobby->SendKick(nodeIndex);
- }
+ const GameInfoPlayer* player = m_game.GetPlayer(m_index);
+ int nodeIndex = m_game.GetNodeIndex(player->nodeID);
+ if (nodeIndex >= 0) {
+ m_lobby->SendKick(nodeIndex);
}
if (m_controlType != CONTROL_NONE && m_controlType != CONTROL_NETWORK) {
@@ -504,8 +502,7 @@ LobbyDialogDelegate::SetState(LOBBY_STATE state)
for (i = 0; i < m_game.GetNumNodes(); ++i) {
SendKick(i);
}
- } else if (m_state == STATE_JOINING ||
- m_state == STATE_JOINED) {
+ } else if (m_state == STATE_JOINING || m_state == STATE_JOINED) {
// Notify the host that we're gone
SendLeaveRequest();
}
@@ -1045,17 +1042,23 @@ LobbyDialogDelegate::ProcessRequestJoin(DynamicPacket &packet)
return;
}
- m_game.AddNetworkPlayer(nodeID, packet.address, name);
-
- // Let everybody know!
- m_reply.StartLobbyMessage(LOBBY_GAME_INFO);
- m_reply.Write((Uint32)0);
- m_game.WriteToPacket(m_reply);
- for (int i = 0; i < m_game.GetNumNodes(); ++i) {
- if (m_game.IsNetworkNode(i)) {
- IPaddress address = m_game.GetNode(i)->address;
- NET_SendDatagram(gSocket, address.host, address.port, m_reply.data, m_reply.len);
+ if (m_game.AddNetworkPlayer(nodeID, packet.address, name)) {
+ // Let everybody know!
+ m_reply.StartLobbyMessage(LOBBY_GAME_INFO);
+ m_reply.Write((Uint32)0);
+ m_game.WriteToPacket(m_reply);
+ for (int i = 0; i < m_game.GetNumNodes(); ++i) {
+ if (m_game.IsNetworkNode(i)) {
+ IPaddress address = m_game.GetNode(i)->address;
+ NET_SendDatagram(gSocket, address.host, address.port, m_reply.data, m_reply.len);
+ }
}
+ } else {
+ m_reply.StartLobbyMessage(LOBBY_KICK);
+ m_reply.Write(m_game.gameID);
+ m_reply.Write(nodeID);
+
+ NET_SendDatagram(gSocket, packet.address.host, packet.address.port, m_reply.data, m_reply.len);
}
}
diff --git a/game/replay.cpp b/game/replay.cpp
index 96d36a85..8b737757 100644
--- a/game/replay.cpp
+++ b/game/replay.cpp
@@ -121,18 +121,12 @@ Replay::Load(const char *file, bool headerOnly)
}
if (!headerOnly) {
- if (!SDL_ReadIO(fp, &version, 1)) {
- SDL_Log("Couldn't read data: %s", SDL_GetError());
- goto done;
- }
- if (version != REPLAY_VERSION) {
+ if (m_game.replayVersion != REPLAY_VERSION) {
SDL_Log("Unsupported data version %d, expected %d", version, REPLAY_VERSION);
goto done;
}
- Uint32 spriteCRC = 0;
- SDL_ReadU32LE(fp, &spriteCRC);
- if (spriteCRC != gSpriteCRC) {
+ if (m_game.spriteCRC != gSpriteCRC) {
SDL_Log("Game uses a different sprite pack, ignoring");
goto done;
}
@@ -197,9 +191,6 @@ Replay::Save(const char *file)
goto done;
}
- SDL_WriteU8(fp, REPLAY_VERSION);
- SDL_WriteU32LE(fp, gSpriteCRC);
-
destLen = compressBound(m_data.Size());
data.Reset();
data.Grow(destLen);