90 changes: 84 additions & 6 deletions src/multiint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -747,7 +747,7 @@ void runConnectionScreen(void)
bMultiMessages = false;
break;
case CON_TYPESID_START+0: // Lobby button
if (LobbyError != ERROR_CHEAT)
if (LobbyError != ERROR_INVALID)
{
setLobbyError(ERROR_NOERROR);
}
Expand Down Expand Up @@ -990,7 +990,7 @@ static void addGames(void)
txt = _("Game is full");
break;
case ERROR_KICKED:
case ERROR_CHEAT:
case ERROR_INVALID:
txt = _("You were kicked!");
break;
case ERROR_WRONGVERSION:
Expand Down Expand Up @@ -1879,6 +1879,7 @@ bool recvTeamRequest(NETQUEUE queue)

if (!NetPlay.isHost || !bHosted) // Only host should act, and only if the game hasn't started yet.
{
ASSERT(false, "Host only routine detected for client!");
return true;
}

Expand All @@ -1889,15 +1890,23 @@ bool recvTeamRequest(NETQUEUE queue)

if (player > MAX_PLAYERS || team > MAX_PLAYERS)
{
debug(LOG_NET, "NET_TEAMREQUEST invalid, player %d team, %d", (int) player, (int) team);
debug(LOG_ERROR, "Invalid NET_TEAMREQUEST from player %d: Tried to change player %d (team %d)",
queue.index, (int)player, (int)team);
return false;
}

if (whosResponsible(player) != queue.index)
{
HandleBadParam("NET_TEAMREQUEST given incorrect params.", player, queue.index);
return false;
}

if (NetPlay.players[player].team != team)
{
resetReadyStatus(false);
}
debug(LOG_NET, "%s is now part of team: %d", NetPlay.players[player].name, (int) team);
changeTeam(player, team); // we do this regardless, in case of sync issues

return true;
Expand All @@ -1911,7 +1920,7 @@ static bool SendReadyRequest(UBYTE player, bool bReady)
}
else
{
NETbeginEncode(NETbroadcastQueue(), NET_READY_REQUEST);
NETbeginEncode(NETnetQueue(NET_HOST_ONLY), NET_READY_REQUEST);
NETuint8_t(&player);
NETbool(&bReady);
NETend();
Expand All @@ -1926,6 +1935,7 @@ bool recvReadyRequest(NETQUEUE queue)

if (!NetPlay.isHost || !bHosted) // Only host should act, and only if the game hasn't started yet.
{
ASSERT(false, "Host only routine detected for client!");
return true;
}

Expand All @@ -1941,6 +1951,12 @@ bool recvReadyRequest(NETQUEUE queue)
return false;
}

if (whosResponsible(player) != queue.index)
{
HandleBadParam("NET_READY_REQUEST given incorrect params.", player, queue.index);
return false;
}

// do not allow players to select 'ready' if we are sending a map too them!
// TODO: make a new icon to show this state?
if (NetPlay.players[player].wzFile.isSending)
Expand Down Expand Up @@ -1981,6 +1997,7 @@ static bool changePosition(UBYTE player, UBYTE position)
debug(LOG_ERROR, "Failed to swap positions for player %d, position %d", (int)player, (int)position);
if (player < game.maxPlayers && position < game.maxPlayers)
{
debug(LOG_NET, "corrupted positions :player (%hhu) new position (%hhu) old position (%d)", player, position, NetPlay.players[player].position );
// Positions were corrupted. Attempt to fix.
NetPlay.players[player].position = position;
NETBroadcastPlayerInfo(player);
Expand Down Expand Up @@ -2026,6 +2043,7 @@ bool changeColour(unsigned player, int col, bool isHost)
if (player < game.maxPlayers && col < MAX_PLAYERS)
{
// Colours were corrupted. Attempt to fix.
debug(LOG_NET, "corrupted colors :player (%hhu) new position (%hhu) old color (%d)", player, col, NetPlay.players[player].colour );
setPlayerColour(player, col);
NetPlay.players[player].colour = col;
NETBroadcastPlayerInfo(player);
Expand Down Expand Up @@ -2076,6 +2094,7 @@ bool recvColourRequest(NETQUEUE queue)

if (!NetPlay.isHost || !bHosted) // Only host should act, and only if the game hasn't started yet.
{
ASSERT(false, "Host only routine detected for client!");
return true;
}

Expand All @@ -2091,6 +2110,12 @@ bool recvColourRequest(NETQUEUE queue)
return false;
}

if (whosResponsible(player) != queue.index)
{
HandleBadParam("NET_COLOURREQUEST given incorrect params.", player, queue.index);
return false;
}

resetReadyStatus(false);

return changeColour(player, col, false);
Expand All @@ -2102,6 +2127,7 @@ bool recvPositionRequest(NETQUEUE queue)

if (!NetPlay.isHost || !bHosted) // Only host should act, and only if the game hasn't started yet.
{
ASSERT(false, "Host only routine detected for client!");
return true;
}

Expand All @@ -2110,13 +2136,20 @@ bool recvPositionRequest(NETQUEUE queue)
NETuint8_t(&position);
NETend();
debug(LOG_NET, "Host received position request from player %d to %d", player, position);

if (player > MAX_PLAYERS || position > MAX_PLAYERS)
{
debug(LOG_ERROR, "Invalid NET_POSITIONREQUEST from player %d: Tried to change player %d to %d",
queue.index, (int)player, (int)position);
return false;
}

if (whosResponsible(player) != queue.index)
{
HandleBadParam("NET_POSITIONREQUEST given incorrect params.", player, queue.index);
return false;
}

resetReadyStatus(false);

return changePosition(player, position);
Expand Down Expand Up @@ -3048,6 +3081,19 @@ static void processMultiopWidgets(UDWORD id)
NETsetPlayerConnectionStatus(CONNECTIONSTATUS_NORMAL, NET_ALL_PLAYERS);
}
}

if (NetPlay.isHost && game.alliance != ALLIANCES_TEAMS)
{
if(mouseDown(MOUSE_RMB) && player != NetPlay.hostPlayer) // both buttons....
{
char *msg;

sasprintf(&msg, _("The host has kicked %s from the game!"), getPlayerName(player));
sendTextMessage(msg, true);
kickPlayer(player, "you are unwanted by the host.", ERROR_KICKED);
resetReadyStatus(true); //reset and send notification to all clients
}
}
}

if (id >= MULTIOP_COLOUR_START && id <= MULTIOP_COLOUR_END && (id - MULTIOP_COLOUR_START == selectedPlayer || NetPlay.isHost))
Expand Down Expand Up @@ -3112,8 +3158,8 @@ static void processMultiopWidgets(UDWORD id)
char *msg;

sasprintf(&msg, _("The host has kicked %s from the game!"), getPlayerName(teamChooserUp));
sendTextMessage(msg, true);
kickPlayer(teamChooserUp, "you are unwanted by the host.", ERROR_KICKED);
sendTextMessage(msg, true);
resetReadyStatus(true); //reset and send notification to all clients
closeTeamChooser();
}
Expand Down Expand Up @@ -3211,10 +3257,23 @@ void frontendMultiMessages(void)
uint32_t reason;
uint32_t victim;

if(!NetPlay.isHost) // only host should act
{
ASSERT(false, "Host only routine detected for client!");
break;
}

NETbeginDecode(queue, NET_FILE_CANCELLED);
NETuint32_t(&victim);
NETuint32_t(&reason);
NETend();

if (whosResponsible(victim) != queue.index)
{
HandleBadParam("NET_FILE_CANCELLED given incorrect params.", victim, queue.index);
return;
}

switch (reason)
{
case STUCK_IN_FILE_LOOP:
Expand Down Expand Up @@ -3294,6 +3353,12 @@ void frontendMultiMessages(void)
break;
}

if (whosResponsible(player_id) != queue.index && queue.index != NET_HOST_ONLY)
{
HandleBadParam("NET_PLAYER_DROPPED given incorrect params.", player_id, queue.index);
break;
}

debug(LOG_INFO,"** player %u has dropped!", player_id);

MultiPlayerLeave(player_id); // get rid of their stuff
Expand Down Expand Up @@ -3321,6 +3386,11 @@ void frontendMultiMessages(void)
break;
}
case NET_FIREUP: // campaign game started.. can fire the whole shebang up...
if (NET_HOST_ONLY != queue.index)
{
HandleBadParam("NET_FIREUP given incorrect params.", 255, queue.index);
break;
}
debug(LOG_NET, "NET_FIREUP was received ...");
if(ingame.localOptionsReceived)
{
Expand Down Expand Up @@ -3355,7 +3425,15 @@ void frontendMultiMessages(void)

if (player_id == NET_HOST_ONLY)
{
debug(LOG_ERROR, "someone tried to kick the host--check your netplay logs!");
char buf[250]= {'\0'};

ssprintf(buf, "*Player %d (%s : %s) tried to kick %u", (int) queue.index, NetPlay.players[queue.index].name, NetPlay.players[queue.index].IPtextAddress, player_id);
NETlogEntry(buf, SYNC_FLAG, 0);
debug(LOG_ERROR, "%s", buf);
if (NetPlay.isHost)
{
NETplayerKicked((unsigned int) queue.index);
}
break;
}

Expand Down Expand Up @@ -3568,7 +3646,7 @@ bool startMultiOptions(bool bReenter)
loadMapPreview(false);
addTopForm();

if (getLobbyError() != ERROR_CHEAT)
if (getLobbyError() != ERROR_INVALID)
{
setLobbyError(ERROR_NOERROR);
}
Expand Down
19 changes: 16 additions & 3 deletions src/multijoin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,6 @@ bool MultiPlayerLeave(UDWORD playerIndex)
NetPlay.players[playerIndex].wzFile.isSending = false;
NetPlay.players[playerIndex].needFile = false;
}
NetPlay.players[playerIndex].kick = true; // Don't wait for GAME_GAME_TIME messages from them.

if (widgGetFromID(psWScreen, IDRET_FORM))
{
Expand Down Expand Up @@ -333,8 +332,10 @@ bool MultiPlayerJoin(UDWORD playerIndex)

// setup data for this player, then broadcast it to the other players.
setupNewPlayer(playerIndex); // setup all the guff for that player.
sendOptions();

if (bHosted)
{
sendOptions();
}
// if skirmish and game full, then kick...
if (NetPlay.playercount > game.maxPlayers)
{
Expand Down Expand Up @@ -376,6 +377,12 @@ bool recvDataCheck(NETQUEUE queue)
uint32_t player = queue.index;
uint32_t tempBuffer[DATA_MAXDATA] = {0};

if(!NetPlay.isHost) // only host should act
{
ASSERT(false, "Host only routine detected for client!");
return false;
}

NETbeginDecode(queue, NET_DATA_CHECK);
for(i = 0; i < DATA_MAXDATA; i++)
{
Expand All @@ -389,6 +396,12 @@ bool recvDataCheck(NETQUEUE queue)
return false;
}

if (whosResponsible(player) != queue.index)
{
HandleBadParam("NET_DATA_CHECK given incorrect params.", player, queue.index);
return false;
}

debug(LOG_NET, "** Received NET_DATA_CHECK from player %u", player);

if (NetPlay.isHost)
Expand Down
16 changes: 16 additions & 0 deletions src/multimenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1512,6 +1512,22 @@ void intProcessMultiMenu(UDWORD id)
{
i = id - MULTIMENU_CHANNEL;
openchannels[i] = !openchannels[i];

if(mouseDown(MOUSE_RMB) && NetPlay.isHost) // both buttons....
{
char buf[250];

// Allow the host to kick the AI only in a MP game, or if they activated cheats in a skirmish game
if (NetPlay.bComms || Cheated)
{
ssprintf(buf, _("The host has kicked %s from the game!"), getPlayerName((unsigned int) i));
sendTextMessage(buf, true);
ssprintf(buf, _("kicked %s : %s from the game, and added them to the banned list!"), getPlayerName((unsigned int) i), NetPlay.players[i].IPtextAddress);
NETlogEntry(buf, SYNC_FLAG, (unsigned int) i);
kickPlayer((unsigned int) i, "you are unwanted by the host.", ERROR_KICKED);
return;
}
}
}

//radar gifts
Expand Down
7 changes: 7 additions & 0 deletions src/multiopt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,20 @@
#include "template.h"
#include "lib/framework/wzapp.h"

extern bool bHosted;

// send complete game info set!
void sendOptions()
{
bool dummy = true;
unsigned int i;

if (!NetPlay.isHost || !bHosted) // Only host should act, and only if the game hasn't started yet.
{
ASSERT(false, "Host only routine detected for client or not hosting yet!");
return;
}

NETbeginEncode(NETbroadcastQueue(), NET_OPTIONS);

// First send information about the game
Expand Down
47 changes: 38 additions & 9 deletions src/multiplay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ bool multiPlayerLoop(void)
NETlogEntry(msg, SYNC_FLAG, index);

#ifndef DEBUG
kickPlayer(index, "it is not nice to cheat!", ERROR_CHEAT);
kickPlayer(index, "invalid data!", ERROR_INVALID);
#endif
debug(LOG_WARNING, "Kicking Player %s (%u), they tried to bypass data integrity check!", getPlayerName(index), index);
}
Expand Down Expand Up @@ -655,12 +655,19 @@ bool recvMessage(void)
}
NETend();

if (whosResponsible(player_id) != queue.index && queue.index != NET_HOST_ONLY)
{
HandleBadParam("NET_PLAYER_DROPPED given incorrect params.", player_id, queue.index);
break;
}

debug(LOG_INFO,"** player %u has dropped!", player_id);

if (NetPlay.players[player_id].allocated)
{
MultiPlayerLeave(player_id); // get rid of their stuff
}
{
MultiPlayerLeave(player_id); // get rid of their stuff
NET_InitPlayer(player_id, false);
}
NETsetPlayerConnectionStatus(CONNECTIONSTATUS_PLAYER_DROPPED, player_id);
break;
}
Expand Down Expand Up @@ -718,11 +725,20 @@ bool recvMessage(void)
NETuint32_t(&player_id);
NETstring(reason, MAX_KICK_REASON);
NETenum(&KICK_TYPE);
NETenum(&KICK_TYPE);
NETend();

if (player_id == NET_HOST_ONLY)
{
debug(LOG_ERROR, "someone tried to kick the host--check your netplay logs!");
char buf[250]= {'\0'};

ssprintf(buf, "Player %d (%s : %s) tried to kick %u", (int) queue.index, NetPlay.players[queue.index].name, NetPlay.players[queue.index].IPtextAddress, player_id);
NETlogEntry(buf, SYNC_FLAG, 0);
debug(LOG_ERROR, "%s", buf);
if (NetPlay.isHost)
{
NETplayerKicked((unsigned int) queue.index);
}
break;
}
else if (selectedPlayer == player_id) // we've been told to leave.
Expand Down Expand Up @@ -769,7 +785,20 @@ bool recvMessage(void)
return true;
}

void HandleBadParam(const char *msg, const int from, const int actual)
{
char buf[255];
LOBBY_ERROR_TYPES KICK_TYPE = ERROR_INVALID;

ssprintf(buf, "!!>Msg: %s, Actual: %d, Bad: %d", msg, actual, from);
NETlogEntry(buf, SYNC_FLAG, actual);
if (NetPlay.isHost)
{
ssprintf(buf, "Auto kicking player %s, invalid command received.", NetPlay.players[actual].name);
sendTextMessage(buf, true);
kickPlayer(actual, buf, KICK_TYPE);
}
}
// ////////////////////////////////////////////////////////////////////////////
// Research Stuff. Nat games only send the result of research procedures.
bool SendResearch(uint8_t player, uint32_t index, bool trigger)
Expand Down Expand Up @@ -1266,7 +1295,7 @@ bool recvTextMessage(NETQUEUE queue)
playerIndex = queue.index; // Fix corrupted playerIndex.
}

if (playerIndex >= MAX_PLAYERS)
if (playerIndex >= MAX_PLAYERS || (!NetPlay.players[playerIndex].allocated && NetPlay.players[playerIndex].ai == AI_OPEN))
{
return false;
}
Expand Down Expand Up @@ -1535,10 +1564,10 @@ bool recvMapFileRequested(NETQUEUE queue)
PHYSFS_sint64 fileSize_64;
PHYSFS_file *pFileHandle;

// We are not the host, so we don't care. (in fact, this would be a error)
if(!NetPlay.isHost)
if(!NetPlay.isHost) // only host should act
{
return true;
ASSERT(false, "Host only routine detected for client!");
return false;
}

// Check to see who wants the file
Expand Down
2 changes: 1 addition & 1 deletion src/multiplay.h
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ void addTemplateBack(unsigned player, DROID_TEMPLATE *psNew);
// syncing.
extern bool sendScoreCheck (void); //score check only(frontend)
extern bool sendPing (void); // allow game to request pings.

extern void HandleBadParam(const char *msg, const int from, const int actual);
// multijoin
extern bool sendResearchStatus (STRUCTURE *psBuilding, UDWORD index, UBYTE player, bool bStart);

Expand Down
9 changes: 9 additions & 0 deletions src/multistat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@ void recvMultiStats(NETQUEUE queue)

if (playerIndex >= MAX_PLAYERS)
{
NETend();
return;
}


if (playerIndex != queue.index && queue.index != NET_HOST_ONLY)
{
HandleBadParam("NET_PLAYER_STATS given incorrect params.", playerIndex, queue.index);
NETend();
return;
}

Expand Down