char *CL_SetConfigString (int index, dbuffer *msg) { if (!Com_CheckConfigStringIndex(index)) Com_Error(ERR_DROP, "invalid access to configstring array with index: %i", index); /* change the string in cl * there may be overflows in i==CS_TILES - but thats ok * see definition of configstrings and MAX_TILESTRINGS */ if (index == CS_TILES || index == CS_POSITIONS) NET_ReadString(msg, cl.configstrings[index], MAX_TOKEN_CHARS * MAX_TILESTRINGS); else NET_ReadString(msg, cl.configstrings[index], sizeof(cl.configstrings[index])); return cl.configstrings[index]; }
static void NET_SV_ParseHolePunch(net_packet_t *packet) { const char *addr_string; net_packet_t *sendpacket; net_addr_t *addr; addr_string = NET_ReadString(packet); if (addr_string == NULL) { NET_Log("server: error: hole punch request but no address provided"); return; } addr = NET_ResolveAddress(server_context, addr_string); if (addr == NULL) { NET_Log("server: error: failed to resolve address: %s", addr_string); return; } sendpacket = NET_NewPacket(16); NET_WriteInt16(sendpacket, NET_PACKET_TYPE_NAT_HOLE_PUNCH); NET_SendPacket(addr, sendpacket); NET_FreePacket(sendpacket); NET_ReleaseAddress(addr); NET_Log("server: sent hole punch to %s", addr_string); }
/** * @brief Reads from a buffer according to format; version without syntactic sugar for variable arguments, to call it from other functions with variable arguments * @sa SV_ReadFormat * @param[in] buf The buffer we read the data from * @param[in] format The format string may not be nullptr * @param ap The variadic function argument list corresponding to the format string */ void NET_vReadFormat (dbuffer* buf, const char* format, va_list ap) { while (*format) { const char typeID = *format++; switch (typeID) { case 'c': *va_arg(ap, int*) = NET_ReadChar(buf); break; case 'b': *va_arg(ap, int*) = NET_ReadByte(buf); break; case 's': *va_arg(ap, int*) = NET_ReadShort(buf); break; case 'l': *va_arg(ap, int*) = NET_ReadLong(buf); break; case 'p': NET_ReadPos(buf, *va_arg(ap, vec3_t*)); break; case 'g': NET_ReadGPos(buf, *va_arg(ap, pos3_t*)); break; case 'd': NET_ReadDir(buf, *va_arg(ap, vec3_t*)); break; case 'a': *va_arg(ap, float*) = NET_ReadAngle(buf); break; case '!': format++; break; case '&': { char* str = va_arg(ap, char*); const size_t length = va_arg(ap, size_t); NET_ReadString(buf, str, length); break; } case '*': { const int n = NET_ReadShort(buf); *va_arg(ap, int*) = n; byte* p = va_arg(ap, byte*); for (int i = 0; i < n; i++) *p++ = NET_ReadByte(buf); } break; default: Com_Error(ERR_DROP, "ReadFormat: Unknown type!"); } } /* Too many arguments for the given format; too few cause crash above */ #ifdef PARANOID if (!ap) Com_Error(ERR_DROP, "ReadFormat: Too many arguments!"); #endif }
char *NET_EndSecureDemo(sha1_digest_t demo_hash) { net_packet_t *request, *response; net_addr_t *master_addr; char *signature; master_addr = NET_Query_ResolveMaster(query_context); // Construct end request and send to master server. request = NET_NewPacket(10); NET_WriteInt16(request, NET_MASTER_PACKET_TYPE_SIGN_END); NET_WriteSHA1Sum(request, demo_hash); NET_WriteString(request, securedemo_start_message); NET_SendPacket(master_addr, request); NET_FreePacket(request); // Block for response. The response packet simply contains a string // with the ASCII signature. response = BlockForPacket(master_addr, NET_MASTER_PACKET_TYPE_SIGN_END_RESPONSE, SIGNATURE_TIMEOUT_SECS * 1000); if (response == NULL) { return NULL; } signature = NET_ReadString(response); NET_FreePacket(response); return signature; }
void NET_SkipFormat (dbuffer* buf, const char* format) { while (*format) { const char typeID = *format++; switch (typeID) { case 'c': NET_ReadChar(buf); break; case 'b': NET_ReadByte(buf); break; case 's': NET_ReadShort(buf); break; case 'l': NET_ReadLong(buf); break; case 'p': { vec3_t v; NET_ReadPos(buf, v); break; } case 'g': { pos3_t p; NET_ReadGPos(buf, p); break; } case 'd': { vec3_t v; NET_ReadDir(buf, v); break; } case 'a': NET_ReadAngle(buf); break; case '!': format++; break; case '&': NET_ReadString(buf, nullptr, 0); break; case '*': { const int n = NET_ReadShort(buf); for (int i = 0; i < n; i++) NET_ReadByte(buf); break; } default: Com_Error(ERR_DROP, "ReadFormat: Unknown type!"); } } }
boolean NET_ReadWaitData(net_packet_t *packet, net_waitdata_t *data) { int i; char *s; if (!NET_ReadInt8(packet, (unsigned int *) &data->num_players) || !NET_ReadInt8(packet, (unsigned int *) &data->num_drones) || !NET_ReadInt8(packet, (unsigned int *) &data->ready_players) || !NET_ReadInt8(packet, (unsigned int *) &data->max_players) || !NET_ReadInt8(packet, (unsigned int *) &data->is_controller) || !NET_ReadSInt8(packet, &data->consoleplayer)) { return false; } for (i = 0; i < data->num_players && i < NET_MAXPLAYERS; ++i) { s = NET_ReadString(packet); if (s == NULL || strlen(s) >= MAXPLAYERNAME) { return false; } strcpy(data->player_names[i], s); s = NET_ReadString(packet); if (s == NULL || strlen(s) >= MAXPLAYERNAME) { return false; } strcpy(data->player_addrs[i], s); } return NET_ReadSHA1Sum(packet, data->wad_sha1sum) && NET_ReadSHA1Sum(packet, data->deh_sha1sum) && NET_ReadInt8(packet, (unsigned int *) &data->is_freedoom); }
static void NET_CL_ParseConsoleMessage(net_packet_t *packet) { char *msg; msg = NET_ReadString(packet); if (msg == NULL) { return; } printf("Message from server: "); NET_SafePuts(msg); }
boolean NET_ReadQueryData(net_packet_t *packet, net_querydata_t *query) { boolean result; query->version = NET_ReadString(packet); result = query->version != NULL && NET_ReadInt8(packet, (unsigned int *) &query->server_state) && NET_ReadInt8(packet, (unsigned int *) &query->num_players) && NET_ReadInt8(packet, (unsigned int *) &query->max_players) && NET_ReadInt8(packet, (unsigned int *) &query->gamemode) && NET_ReadInt8(packet, (unsigned int *) &query->gamemission); if (result) { query->description = NET_ReadString(packet); return query->description != NULL; } else { return false; } }
static void NET_Query_ParseMasterResponse(net_addr_t *master_addr, net_packet_t *packet) { unsigned int packet_type; query_target_t *target; char *addr_str; net_addr_t *addr; // Read the header. We are only interested in query responses. if (!NET_ReadInt16(packet, &packet_type) || packet_type != NET_MASTER_PACKET_TYPE_QUERY_RESPONSE) { return; } // Read a list of strings containing the addresses of servers // that the master knows about. for (;;) { addr_str = NET_ReadString(packet); if (addr_str == NULL) { break; } // Resolve address and add to targets list if it is not already // there. addr = NET_ResolveAddress(query_context, addr_str); if (addr != NULL) { GetTargetForAddr(addr, true); } } // Mark the master as having responded. target = GetTargetForAddr(master_addr, true); target->state = QUERY_TARGET_RESPONDED; }
boolean NET_StartSecureDemo(prng_seed_t seed) { net_packet_t *request, *response; net_addr_t *master_addr; char *signature; boolean result; NET_Query_Init(); master_addr = NET_Query_ResolveMaster(query_context); // Send request packet to master server. request = NET_NewPacket(10); NET_WriteInt16(request, NET_MASTER_PACKET_TYPE_SIGN_START); NET_SendPacket(master_addr, request); NET_FreePacket(request); // Block for response and read contents. // The signed start message will be saved for later. response = BlockForPacket(master_addr, NET_MASTER_PACKET_TYPE_SIGN_START_RESPONSE, SIGNATURE_TIMEOUT_SECS * 1000); result = false; if (response != NULL) { if (NET_ReadPRNGSeed(response, seed)) { signature = NET_ReadString(response); if (signature != NULL) { securedemo_start_message = strdup(signature); result = true; } } NET_FreePacket(response); } return result; }
static void NET_Conn_ParseReject(net_connection_t *conn, net_packet_t *packet) { char *msg; msg = NET_ReadString(packet); if (msg == NULL) { return; } if (conn->state == NET_CONN_STATE_CONNECTING) { // rejected by server conn->state = NET_CONN_STATE_DISCONNECTED; conn->disconnect_reason = NET_DISCONNECT_REMOTE; printf("Rejected by server: "); NET_SafePuts(msg); } }
static void NET_SV_ParseSYN(net_packet_t *packet, net_client_t *client, net_addr_t *addr) { unsigned int magic; unsigned int cl_gamemode, cl_gamemission; unsigned int cl_recording_lowres; unsigned int cl_drone; unsigned int is_freedoom; md5_digest_t deh_md5sum, wad_md5sum; char *player_name; char *client_version; int i; // read the magic number if (!NET_ReadInt32(packet, &magic)) { return; } if (magic != NET_MAGIC_NUMBER) { // invalid magic number return; } // Check the client version is the same as the server client_version = NET_ReadString(packet); if (client_version == NULL) { return; } if (strcmp(client_version, PACKAGE_STRING) != 0) { //! // @category net // // When running a netgame server, ignore version mismatches between // the server and the client. Using this option may cause game // desyncs to occur, or differences in protocol may mean the netgame // will simply not function at all. // if (M_CheckParm("-ignoreversion") == 0) { NET_SV_SendReject(addr, "Version mismatch: server version is: " PACKAGE_STRING); return; } } // read the game mode and mission if (!NET_ReadInt16(packet, &cl_gamemode) || !NET_ReadInt16(packet, &cl_gamemission) || !NET_ReadInt8(packet, &cl_recording_lowres) || !NET_ReadInt8(packet, &cl_drone) || !NET_ReadMD5Sum(packet, wad_md5sum) || !NET_ReadMD5Sum(packet, deh_md5sum) || !NET_ReadInt8(packet, &is_freedoom)) { return; } if (!NET_ValidGameMode(cl_gamemode, cl_gamemission)) { return; } // read the player's name player_name = NET_ReadString(packet); if (player_name == NULL) { return; } // received a valid SYN // not accepting new connections? if (server_state != SERVER_WAITING_START) { NET_SV_SendReject(addr, "Server is not currently accepting connections"); return; } // allocate a client slot if there isn't one already if (client == NULL) { // find a slot, or return if none found for (i=0; i<MAXNETNODES; ++i) { if (!clients[i].active) { client = &clients[i]; break; } } if (client == NULL) { return; } } else { // If this is a recently-disconnected client, deactivate // to allow immediate reconnection if (client->connection.state == NET_CONN_STATE_DISCONNECTED) { client->active = false; } } // New client? if (!client->active) { int num_players; // Before accepting a new client, check that there is a slot // free NET_SV_AssignPlayers(); num_players = NET_SV_NumPlayers(); if ((!cl_drone && num_players >= MAXPLAYERS) || NET_SV_NumClients() >= MAXNETNODES) { NET_SV_SendReject(addr, "Server is full!"); return; } // TODO: Add server option to allow rejecting clients which // set lowres_turn. This is potentially desirable as the // presence of such clients affects turning resolution. // Adopt the game mode and mission of the first connecting client if (num_players == 0 && !cl_drone) { sv_gamemode = cl_gamemode; sv_gamemission = cl_gamemission; } // Save the MD5 checksums memcpy(client->wad_md5sum, wad_md5sum, sizeof(md5_digest_t)); memcpy(client->deh_md5sum, deh_md5sum, sizeof(md5_digest_t)); client->is_freedoom = is_freedoom; // Check the connecting client is playing the same game as all // the other clients if (cl_gamemode != sv_gamemode || cl_gamemission != sv_gamemission) { NET_SV_SendReject(addr, "You are playing the wrong game!"); return; } // Activate, initialize connection NET_SV_InitNewClient(client, addr, player_name); client->recording_lowres = cl_recording_lowres; client->drone = cl_drone; } if (client->connection.state == NET_CONN_STATE_WAITING_ACK) { // force an acknowledgement client->connection.last_send_time = -1; } }
static void NET_CL_ParseWaitingData(net_packet_t *packet) { unsigned int num_players; unsigned int num_drones; unsigned int is_controller; signed int player_number; char *player_names[MAXPLAYERS]; char *player_addr[MAXPLAYERS]; md5_digest_t wad_md5sum, deh_md5sum; unsigned int server_is_freedoom; size_t i; if (!NET_ReadInt8(packet, &num_players) || !NET_ReadInt8(packet, &num_drones) || !NET_ReadInt8(packet, &is_controller) || !NET_ReadSInt8(packet, &player_number)) { // invalid packet return; } if (num_players > MAXPLAYERS) { // insane data return; } if ((player_number >= 0 && drone) || (player_number < 0 && !drone) || (player_number >= (signed int) num_players)) { // Invalid player number return; } // Read the player names for (i=0; i<num_players; ++i) { player_names[i] = NET_ReadString(packet); player_addr[i] = NET_ReadString(packet); if (player_names[i] == NULL || player_addr[i] == NULL) { return; } } if (!NET_ReadMD5Sum(packet, wad_md5sum) || !NET_ReadMD5Sum(packet, deh_md5sum) || !NET_ReadInt8(packet, &server_is_freedoom)) { return; } net_clients_in_game = num_players; net_drones_in_game = num_drones; net_client_controller = is_controller != 0; net_player_number = player_number; for (i=0; i<num_players; ++i) { strncpy(net_player_names[i], player_names[i], MAXPLAYERNAME); net_player_names[i][MAXPLAYERNAME-1] = '\0'; strncpy(net_player_addresses[i], player_addr[i], MAXPLAYERNAME); net_player_addresses[i][MAXPLAYERNAME-1] = '\0'; } memcpy(net_server_wad_md5sum, wad_md5sum, sizeof(md5_digest_t)); memcpy(net_server_deh_md5sum, deh_md5sum, sizeof(md5_digest_t)); net_server_is_freedoom = server_is_freedoom; net_client_received_wait_data = true; }
/** * @brief The current net_message is parsed for the given client */ void SV_ExecuteClientMessage (client_t * cl, int cmd, struct dbuffer *msg) { if (cmd == -1) return; switch (cmd) { default: Com_Printf("SV_ExecuteClientMessage: unknown command char '%d'\n", cmd); SV_DropClient(cl, "Unknown command\n"); return; case clc_nop: break; case clc_ack: cl->lastmessage = svs.realtime; break; case clc_userinfo: NET_ReadString(msg, cl->userinfo, sizeof(cl->userinfo)); Com_DPrintf(DEBUG_SERVER, "userinfo from client: %s\n", cl->userinfo); SV_UserinfoChanged(cl); break; case clc_stringcmd: { char str[1024]; NET_ReadString(msg, str, sizeof(str)); Com_DPrintf(DEBUG_SERVER, "stringcmd from client: %s\n", str); SV_ExecuteUserCommand(cl, str); if (cl->state == cs_free) return; /* disconnect command */ break; } case clc_action: /* client actions are handled by the game module */ sv->messageBuffer = msg; TH_MutexLock(svs.serverMutex); svs.ge->ClientAction(cl->player); TH_MutexUnlock(svs.serverMutex); sv->messageBuffer = NULL; break; case clc_endround: /* player wants to end round */ sv->messageBuffer = msg; TH_MutexLock(svs.serverMutex); svs.ge->ClientEndRound(cl->player); TH_MutexUnlock(svs.serverMutex); sv->messageBuffer = NULL; break; case clc_teaminfo: /* player sends team info */ /* actors spawn accordingly */ sv->messageBuffer = msg; TH_MutexLock(svs.serverMutex); svs.ge->ClientTeamInfo(cl->player); TH_MutexUnlock(svs.serverMutex); sv->messageBuffer = NULL; SV_SetClientState(cl, cs_spawned); break; case clc_initactorstates: /* player sends team info */ /* actors spawn accordingly */ sv->messageBuffer = msg; TH_MutexLock(svs.serverMutex); svs.ge->ClientInitActorStates(cl->player); TH_MutexUnlock(svs.serverMutex); sv->messageBuffer = NULL; break; } }
static void NET_SV_ParseSYN(net_packet_t *packet, net_client_t *client, net_addr_t *addr) { unsigned int magic; net_connect_data_t data; net_packet_t *reply; net_protocol_t protocol; char *player_name; char *client_version; int num_players; int i; NET_Log("server: processing SYN packet"); // Read the magic number and check it is the expected one. if (!NET_ReadInt32(packet, &magic)) { NET_Log("server: error: no magic number for SYN"); return; } switch (magic) { case NET_MAGIC_NUMBER: break; case NET_OLD_MAGIC_NUMBER: NET_Log("server: error: client using old magic number: %d", magic); NET_SV_SendReject(addr, "You are using an old client version that is not supported by " "this server. This server is running " PACKAGE_STRING "."); return; default: NET_Log("server: error: wrong magic number: %d", magic); return; } // Read the client version string. We actually now only use this when // sending a reject message, as we only reject if we can't negotiate a // common protocol (below). client_version = NET_ReadString(packet); if (client_version == NULL) { NET_Log("server: error: no version from client"); return; } // Read the client's list of accepted protocols. Net play between forks // of Chocolate Doom is accepted provided that they can negotiate a // common accepted protocol. protocol = NET_ReadProtocolList(packet); if (protocol == NET_PROTOCOL_UNKNOWN) { char reject_msg[256]; M_snprintf(reject_msg, sizeof(reject_msg), "Version mismatch: server version is: " PACKAGE_STRING "; " "client is: %s. No common compatible protocol could be " "negotiated.", client_version); NET_SV_SendReject(addr, reject_msg); NET_Log("server: error: no common protocol"); return; } // Read connect data, and check that the game mode/mission are valid // and the max_players value is in a sensible range. if (!NET_ReadConnectData(packet, &data)) { NET_Log("server: error: failed to read connect data"); return; } if (!D_ValidGameMode(data.gamemission, data.gamemode) || data.max_players > NET_MAXPLAYERS) { NET_Log("server: error: invalid connect data, max_players=%d, " "gamemission=%d, gamemode=%d", data.max_players, data.gamemission, data.gamemode); return; } // Read the player's name player_name = NET_ReadString(packet); if (player_name == NULL) { NET_Log("server: error: failed to read player name"); return; } // At this point we have received a valid SYN. // Not accepting new connections? if (server_state != SERVER_WAITING_LAUNCH) { NET_Log("server: error: not in waiting launch state, server_state=%d", server_state); NET_SV_SendReject(addr, "Server is not currently accepting connections"); return; } // Before accepting a new client, check that there is a slot free. NET_SV_AssignPlayers(); num_players = NET_SV_NumPlayers(); if ((!data.drone && num_players >= NET_SV_MaxPlayers()) || NET_SV_NumClients() >= MAXNETNODES) { NET_Log("server: no more players, num_players=%d, max=%d", num_players, NET_SV_MaxPlayers()); NET_SV_SendReject(addr, "Server is full!"); return; } // TODO: Add server option to allow rejecting clients which set // lowres_turn. This is potentially desirable as the presence of such // clients affects turning resolution. // Adopt the game mode and mission of the first connecting client: if (num_players == 0 && !data.drone) { sv_gamemode = data.gamemode; sv_gamemission = data.gamemission; NET_Log("server: new game, mode=%d, mission=%d", sv_gamemode, sv_gamemission); } // Check the connecting client is playing the same game as all // the other clients if (data.gamemode != sv_gamemode || data.gamemission != sv_gamemission) { char msg[128]; NET_Log("server: wrong mode/mission, %d != %d || %d != %d", data.gamemode, sv_gamemode, data.gamemission, sv_gamemission); M_snprintf(msg, sizeof(msg), "Game mismatch: server is %s (%s), client is %s (%s)", D_GameMissionString(sv_gamemission), D_GameModeString(sv_gamemode), D_GameMissionString(data.gamemission), D_GameModeString(data.gamemode)); NET_SV_SendReject(addr, msg); return; } // Allocate a client slot if there isn't one already if (client == NULL) { // find a slot, or return if none found for (i=0; i<MAXNETNODES; ++i) { if (!clients[i].active) { client = &clients[i]; break; } } if (client == NULL) { return; } } else { // If this is a recently-disconnected client, deactivate // to allow immediate reconnection if (client->connection.state == NET_CONN_STATE_DISCONNECTED) { client->active = false; } } // Client already connected? if (client->active) { NET_Log("server: client is already initialized (duplicate SYN?)"); return; } // Activate, initialize connection NET_SV_InitNewClient(client, addr, protocol); // Save the SHA1 checksums and other details. memcpy(client->wad_sha1sum, data.wad_sha1sum, sizeof(sha1_digest_t)); memcpy(client->deh_sha1sum, data.deh_sha1sum, sizeof(sha1_digest_t)); client->is_freedoom = data.is_freedoom; client->max_players = data.max_players; client->name = M_StringDuplicate(player_name); client->recording_lowres = data.lowres_turn; client->drone = data.drone; client->player_class = data.player_class; // Send a reply back to the client, indicating a successful connection // and specifying the protocol that will be used for communications. reply = NET_Conn_NewReliable(&client->connection, NET_PACKET_TYPE_SYN); NET_WriteString(reply, PACKAGE_STRING); NET_WriteProtocol(reply, protocol); }
static int SV_ReadString (char *str, size_t length) { return NET_ReadString(sv->messageBuffer, str, length); }
/** * @brief Responses to broadcasts, etc * @sa CL_ReadPackets * @sa CL_Frame * @sa SVC_DirectConnect * @param[in,out] msg The client stream message buffer to read from */ static void CL_ConnectionlessPacket (dbuffer* msg) { char s[512]; NET_ReadStringLine(msg, s, sizeof(s)); Cmd_TokenizeString(s, false); const char* c = Cmd_Argv(0); Com_DPrintf(DEBUG_CLIENT, "server OOB: %s (%s)\n", c, Cmd_Args()); /* server connection */ if (Q_streq(c, CL_CMD_CLIENT_CONNECT)) { int i; for (i = 1; i < Cmd_Argc(); i++) { if (char const* const p = Q_strstart(Cmd_Argv(i), "dlserver=")) { Com_sprintf(cls.downloadReferer, sizeof(cls.downloadReferer), "ufo://%s", cls.servername); CL_SetHTTPServer(p); if (cls.downloadServer[0]) Com_Printf("HTTP downloading enabled, URL: %s\n", cls.downloadServer); } } if (cls.state == ca_connected) { Com_Printf("Dup connect received. Ignored.\n"); return; } dbuffer buf(5); NET_WriteByte(&buf, clc_stringcmd); NET_WriteString(&buf, NET_STATE_NEW "\n"); NET_WriteMsg(cls.netStream, buf); GAME_InitMissionBriefing(_("Loading")); return; } /* remote command from gui front end */ if (Q_streq(c, CL_CMD_COMMAND)) { if (!NET_StreamIsLoopback(cls.netStream)) { Com_Printf("Command packet from remote host. Ignored.\n"); return; } else { char str[512]; NET_ReadString(msg, str, sizeof(str)); Cbuf_AddText("%s\n", str); } return; } /* ping from server */ if (Q_streq(c, CL_CMD_PING)) { NET_OOB_Printf(cls.netStream, SV_CMD_ACK); return; } /* echo request from server */ if (Q_streq(c, CL_CMD_ECHO)) { NET_OOB_Printf(cls.netStream, "%s", Cmd_Argv(1)); return; } /* print */ if (Q_streq(c, SV_CMD_PRINT)) { NET_ReadString(msg, popupText, sizeof(popupText)); /* special reject messages needs proper handling */ if (strstr(popupText, REJ_PASSWORD_REQUIRED_OR_INCORRECT)) { UI_PushWindow("serverpassword"); if (Q_strvalid(Cvar_GetString("password"))) { Cvar_Set("password", ""); CL_Drop(); UI_Popup(_("Connection failure"), _("The password you specified was wrong.")); } else { CL_Drop(); UI_Popup(_("Connection failure"), _("This server requires a password.")); } } else if (strstr(popupText, REJ_SERVER_FULL)) { CL_Drop(); UI_Popup(_("Connection failure"), _("This server is full.")); } else if (strstr(popupText, REJ_BANNED)) { CL_Drop(); UI_Popup(_("Connection failure"), _("You are banned on this server.")); } else if (strstr(popupText, REJ_GAME_ALREADY_STARTED)) { CL_Drop(); UI_Popup(_("Connection failure"), _("The game has already started.")); } else if (strstr(popupText, REJ_SERVER_VERSION_MISMATCH)) { CL_Drop(); UI_Popup(_("Connection failure"), _("The server is running a different version of the game.")); } else if (strstr(popupText, BAD_RCON_PASSWORD)) { Cvar_Set("rcon_password", ""); UI_Popup(_("Bad rcon password"), _("The rcon password you specified was wrong.")); } else if (strstr(popupText, REJ_CONNECTION_REFUSED)) { CL_Drop(); UI_Popup(_("Connection failure"), _("The server refused the connection.")); } else if (Q_strvalid(popupText)) { UI_Popup(_("Notice"), _(popupText)); } return; } if (!GAME_HandleServerCommand(c, msg)) Com_Printf("Unknown command received \"%s\"\n", c); }