/******************************************************************************* * * Copyright (c) 2004-2012 Guillaume Cottenceau * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ******************************************************************************/ /* * this file holds game operations: create, join, list etc. * it should be as far away as possible from network operations */ #include #include #include #include #include #include #include #include #include #include "net.h" #include "tools.h" #include "log.h" #include "game.h" enum game_status { GAME_STATUS_OPEN, GAME_STATUS_CLOSED, GAME_STATUS_PLAYING }; #define MAX_PLAYERS_PER_GAME 5 struct game { enum game_status status; int players_number; int players_conn[MAX_PLAYERS_PER_GAME]; char* players_nick[MAX_PLAYERS_PER_GAME]; int players_started[MAX_PLAYERS_PER_GAME]; }; static GList * games = NULL; static GList * open_players = NULL; static ssize_t amount_transmitted = 0; static char ok_pong[] = "PONG"; static char ok_player_joined[] = "JOINED: %s"; static char ok_player_parted[] = "PARTED: %s"; static char ok_player_kicked[] = "KICKED: %s"; static char ok_talk[] = "TALK: %s"; static char ok_can_start[] = "GAME_CAN_START: %s"; static char wn_unknown_command[] = "UNKNOWN_COMMAND"; static char wn_missing_arguments[] = "MISSING_ARGUMENTS"; static char wn_nick_invalid[] = "INVALID_NICK"; static char wn_nick_in_use[] = "NICK_IN_USE"; static char wn_no_such_game[] = "NO_SUCH_GAME"; static char wn_game_full[] = "GAME_FULL"; static char wn_already_in_game[] = "ALREADY_IN_GAME"; static char wn_max_open_games[] = "ALREADY_MAX_OPEN_GAMES"; static char wn_not_started[] = "NOT_STARTED"; static char wn_already_ok_started[] = "ALREADY_OK_STARTED"; static char wn_not_in_game[] = "NOT_IN_GAME"; static char wn_alone_in_the_dark[] = "ALONE_IN_THE_DARK"; static char wn_not_creator[] = "NOT_CREATOR"; static char wn_no_such_player[] = "NO_SUCH_PLAYER"; static char wn_denied[] = "DENIED"; static char wn_flooding[] = "FLOODING"; static char wn_others_not_ready[] = "OTHERS_NOT_READY"; static char fl_line_unrecognized[] = "MISSING_FB_PROTOCOL_TAG"; static char fl_proto_mismatch[] = "INCOMPATIBLE_PROTOCOL"; char* nick[256]; char* geoloc[256]; char* IP[256]; int remote_proto_minor[256]; int admin_authorized[256]; // calculate the list of players for a given game static char* list_game(const struct game * g) { char list_game_str[8192] = ""; int i; for (i = 0; i < g->players_number; i++) { strconcat(list_game_str, g->players_nick[i], sizeof(list_game_str)); if (i < g->players_number - 1) strconcat(list_game_str, ",", sizeof(list_game_str)); } return memdup(list_game_str, strlen(list_game_str) + 1); } // calculate the list of players for a given game with geolocation static char* list_game_with_geolocation(const struct game * g) { char list_game_str[8192] = ""; int i; char* n; for (i = 0; i < g->players_number; i++) { strconcat(list_game_str, g->players_nick[i], sizeof(list_game_str)); n = geoloc[g->players_conn[i]]; if (n != NULL) { strconcat(list_game_str, ":", sizeof(list_game_str)); strconcat(list_game_str, n, sizeof(list_game_str)); } if (i < g->players_number - 1) strconcat(list_game_str, ",", sizeof(list_game_str)); } return memdup(list_game_str, strlen(list_game_str) + 1); } static char list_games_str[16384] __attribute__((aligned(4096))) = ""; static char list_playing_geolocs_str[16384] __attribute__((aligned(4096))) = ""; static int players_in_game; static int games_open; static int games_running; static void list_open_nicks_aux(gpointer data, gpointer user_data) { char* n = nick[GPOINTER_TO_INT(data)]; if (n == NULL) return; strconcat(list_games_str, n, sizeof(list_games_str)); n = geoloc[GPOINTER_TO_INT(data)]; if (n != NULL) { strconcat(list_games_str, ":", sizeof(list_games_str)); strconcat(list_games_str, n, sizeof(list_games_str)); } strconcat(list_games_str, ",", sizeof(list_games_str)); } static void list_games_aux(gpointer data, gpointer user_data) { const struct game* g = data; if (g->status == GAME_STATUS_OPEN) { char* game; games_open++; strconcat(list_games_str, "[", sizeof(list_games_str)); game = list_game(g); strconcat(list_games_str, game, sizeof(list_games_str)); free(game); strconcat(list_games_str, "]", sizeof(list_games_str)); } else { int i; char* geo; players_in_game += g->players_number; games_running++; for (i = 0; i < g->players_number; i++) { geo = geoloc[g->players_conn[i]]; if (geo != NULL) { strconcat(list_playing_geolocs_str, geo, sizeof(list_playing_geolocs_str)); strconcat(list_playing_geolocs_str, ",", sizeof(list_playing_geolocs_str)); } } return; } } /* Game list is of the following scheme: * 1.1 protocol: * [>] free:%d games:%d playing:%d at: * 1.0 protocol: * [>] free:%d games:%d playing:%d */ void calculate_list_games(void) { char * free_players; list_games_str[0] = '\0'; list_playing_geolocs_str[0] = '\0'; players_in_game = 0; games_open = 0; games_running = 0; g_list_foreach(open_players, list_open_nicks_aux, NULL); strconcat(list_games_str, " ", sizeof(list_games_str)); g_list_foreach(games, list_games_aux, NULL); free_players = asprintf_(" free:%d games:%d playing:%d at:%s", conns_nb() - players_in_game - 1, games_running, players_in_game, list_playing_geolocs_str); // 1: don't count myself strconcat(list_games_str, free_players, sizeof(list_games_str)); free(free_players); } static void create_game(int fd, char* nick) { struct game * g = malloc_(sizeof(struct game)); g->players_number = 1; g->players_conn[0] = fd; g->players_nick[0] = nick; g->status = GAME_STATUS_OPEN; games = g_list_append(games, g); open_players = g_list_remove(open_players, GINT_TO_POINTER(fd)); calculate_list_games(); } static int add_player(struct game * g, int fd, char* nick) { char joined_msg[1000]; int i; if (g->players_number < MAX_PLAYERS_PER_GAME) { /* inform other players */ snprintf(joined_msg, sizeof(joined_msg), ok_player_joined, nick); for (i = 0; i < g->players_number; i++) send_line_log_push(g->players_conn[i], joined_msg); g->players_conn[g->players_number] = fd; g->players_nick[g->players_number] = nick; g->players_number++; open_players = g_list_remove(open_players, GINT_TO_POINTER(fd)); calculate_list_games(); return 1; } else { free(nick); return 0; } } static int find_game_by_nick_aux(gconstpointer game, gconstpointer nick) { const struct game * g = game; if (g->status == GAME_STATUS_OPEN && streq(g->players_nick[0], (char *) nick)) return 0; else return 1; } static struct game* find_game_by_nick(char* nick) { return GListp2data(g_list_find_custom(games, nick, find_game_by_nick_aux)); } static int find_game_by_fd_aux(gconstpointer game, gconstpointer fd) { const struct game* g = game; int fd_ = GPOINTER_TO_INT(fd); int i; for (i = 0; i < g->players_number; i++) if (g->players_conn[i] == fd_) return 0; return 1; } static struct game* find_game_by_fd(int fd) { return GListp2data(g_list_find_custom(games, GINT_TO_POINTER(fd), find_game_by_fd_aux)); } int find_player_number(struct game *g, int fd) { int i; for (i = 0; i < g->players_number; i++) if (g->players_conn[i] == fd) return i; l0(OUTPUT_TYPE_ERROR, "Internal error"); exit(EXIT_FAILURE); } static void real_start_game(struct game* g) { int i; char mapping_str[4096] = ""; char can_start_msg[1000]; for (i = 0; i < g->players_number; i++) { int len = strlen(mapping_str); if (len >= sizeof(mapping_str)-1) return; mapping_str[len] = g->players_conn[i]; mapping_str[len+1] = '\0'; strconcat(mapping_str, g->players_nick[i], sizeof(mapping_str)); if (i < g->players_number - 1) strconcat(mapping_str, ",", sizeof(mapping_str)); } snprintf(can_start_msg, sizeof(can_start_msg), ok_can_start, mapping_str); for (i = 0; i < g->players_number; i++) { send_line_log_push_binary(g->players_conn[i], can_start_msg, ok_can_start); g->players_started[i] = 0; } g->status = GAME_STATUS_PLAYING; } static void start_game(int fd) { struct game * g = find_game_by_fd(fd); if (g) { if (g->players_conn[0] == fd) { if (g->players_number == 1) { send_line_log(fd, wn_alone_in_the_dark, "START"); return; } send_ok(fd, "START"); real_start_game(g); calculate_list_games(); l2(OUTPUT_TYPE_INFO, "running games increments to: %d (%d players)", games_running, players_in_game); } else { send_line_log(fd, wn_not_creator, "START"); } } else { l0(OUTPUT_TYPE_ERROR, "Internal error"); exit(EXIT_FAILURE); } } static void close_game(int fd) { struct game * g = find_game_by_fd(fd); if (g) { if (g->players_conn[0] == fd) { if (g->players_number == 1) { send_line_log(fd, wn_alone_in_the_dark, "CLOSE"); return; } send_ok(fd, "CLOSE"); g->status = GAME_STATUS_CLOSED; calculate_list_games(); } else { send_line_log(fd, wn_not_creator, "CLOSE"); } } else { l0(OUTPUT_TYPE_ERROR, "Internal error"); exit(EXIT_FAILURE); } } static int min_protocol_level(struct game* g) { int i; int minor = remote_proto_minor[g->players_conn[0]]; for (i = 1; i < g->players_number; i++) minor = MIN(minor, remote_proto_minor[g->players_conn[i]]); return minor; } static void setoptions(int fd, char* options) { struct game * g = find_game_by_fd(fd); if (g) { if (g->players_conn[0] == fd) { int i; char* msg; send_ok(fd, "SETOPTIONS"); msg = asprintf_("OPTIONS: %s,PROTOCOLLEVEL:%d", options, min_protocol_level(g)); for (i = 0; i < g->players_number; i++) if (remote_proto_minor[g->players_conn[i]] >= 1) send_line_log_push(g->players_conn[i], msg); free(msg); } else { send_line_log(fd, wn_not_creator, "SETOPTIONS"); } } else { l0(OUTPUT_TYPE_ERROR, "Internal error"); exit(EXIT_FAILURE); } } static void leader_check_game_start(int fd) { struct game * g = find_game_by_fd(fd); if (g) { if (g->status == GAME_STATUS_PLAYING) { int i; for (i = 0; i < g->players_number; i++) { if (fd != g->players_conn[i]) { if (!g->players_started[i]) { send_line_log(fd, wn_others_not_ready, "LEADER_CHECK_GAME_START"); return; } } } send_ok(fd, "LEADER_CHECK_GAME_START"); } else { send_line_log(fd, wn_not_started, "LEADER_CHECK_GAME_START"); } } else { l0(OUTPUT_TYPE_ERROR, "Internal error"); exit(EXIT_FAILURE); } } static void ok_start_game(int fd) { struct game * g = find_game_by_fd(fd); if (g) { if (g->status == GAME_STATUS_PLAYING) { int i; for (i = 0; i < g->players_number; i++) { if (g->players_conn[i] == fd) { if (!g->players_started[i]) { if (remote_proto_minor[g->players_conn[i]] >= 1) send_ok(fd, "OK_GAME_START"); g->players_started[i] = 1; l1(OUTPUT_TYPE_DEBUG, "[%d] entering prio mode", g->players_conn[i]); add_prio(g->players_conn[i]); } else { send_line_log(fd, wn_already_ok_started, "OK_GAME_START"); } } } } else { send_line_log(fd, wn_not_started, "OK_GAME_START"); } } else { l0(OUTPUT_TYPE_ERROR, "Internal error"); exit(EXIT_FAILURE); } } static void kick_player(int fd, struct game * g, char * nick) { int i; for (i = 0; i < g->players_number; i++) { if (g->players_conn[i] != fd && streq(g->players_nick[i], nick)) { send_ok(fd, "KICK"); send_line_log_push(g->players_conn[i], "KICKED"); player_part_game_(g->players_conn[i], ok_player_kicked); return; } } send_line_log(fd, wn_no_such_player, "KICK"); } void player_connects(int fd) { open_players = g_list_append(open_players, GINT_TO_POINTER(fd)); } void player_disconnects(int fd) { open_players = g_list_remove(open_players, GINT_TO_POINTER(fd)); } static void talk_serverwide_aux(gpointer data, gpointer user_data) { send_line_log_push(GPOINTER_TO_INT(data), user_data); } static gboolean check_match_alert_words(gconstpointer data, gconstpointer user_data) { const regex_t* preg = data; const char* msg = user_data; if (regexec(preg, msg, 0, NULL, 0) == 0) { return TRUE; } return FALSE; } static void talk(int fd, char* msg) { struct game * g = find_game_by_fd(fd); char talk_msg[1000]; if (g_list_any(alert_words, check_match_alert_words, msg)) l2(OUTPUT_TYPE_INFO, "message '%s' from %s matches alert words!", msg, IP[fd]); amount_talk_flood[fd]++; if (amount_talk_flood[fd] == 15) { l1(OUTPUT_TYPE_INFO, "'%s' is flooding!", IP[fd]); send_line_log(fd, wn_flooding, msg); conn_terminated(fd, "flooding"); return; } snprintf(talk_msg, sizeof(talk_msg), ok_talk, msg); if (g) { // player is in a game, it's a game-only chat int i; for (i = 0; i < g->players_number; i++) send_line_log_push(g->players_conn[i], talk_msg); } else { // player is not in a game, it's a server-wide chat g_list_foreach(open_players, talk_serverwide_aux, talk_msg); } } static void status(int fd, char* msg) { struct game * g = find_game_by_fd(fd); if (g) { char* game = list_game(g); send_line_log(fd, game, msg); free(game); } else { send_line_log(fd, wn_not_in_game, msg); } } static void status_geo(int fd, char* msg) { struct game * g = find_game_by_fd(fd); if (g) { char* game = list_game_with_geolocation(g); send_line_log(fd, game, msg); free(game); } else { send_line_log(fd, wn_not_in_game, msg); } } static void protocol_level(int fd, char* msg) { // Find the smallest minor protocol level among players in game struct game * g = find_game_by_fd(fd); if (g) { char* response; int level = min_protocol_level(g); response = asprintf_("%d", level); send_line_log(fd, response, msg); free(response); } else { send_line_log(fd, wn_not_in_game, msg); } } static gboolean nick_available_aux(gconstpointer data, gconstpointer user_data) { const struct game* g = data; const char* nick = user_data; int i; for (i = 0; i < g->players_number; i++) if (streq(g->players_nick[i], nick)) return TRUE; return FALSE; } static int nick_available(char* nick) { return !g_list_any(games, nick_available_aux, nick); } static gboolean already_in_game_aux(gconstpointer data, gconstpointer user_data) { const struct game* g = data; int fd = GPOINTER_TO_INT(user_data); int i; for (i = 0; i < g->players_number; i++) if (g->players_conn[i] == fd) return TRUE; return FALSE; } static int already_in_game(int fd) { return g_list_any(games, already_in_game_aux, GINT_TO_POINTER(fd)); } static int is_nick_ok(char* nick) { int i; if (strlen(nick) > 10) return 0; for (i = 0; i < strlen(nick); i++) { if (!((nick[i] >= 'a' && nick[i] <= 'z') || (nick[i] >= 'A' && nick[i] <= 'Z') || (nick[i] >= '0' && nick[i] <= '9') || nick[i] == '-' || nick[i] == '_')) { return 0; } } return 1; } /* true return value indicates that connection must be closed */ int process_msg(int fd, char* msg) { int client_proto_major; int client_proto_minor; char * args; char * ptr, * ptr2; char * msg_orig; /* check for leading protocol tag */ if (!str_begins_static_str(msg, "FB/") || strlen(msg) < 8) { // 8 stands for "FB/M.m f"(oo) send_line_log(fd, fl_line_unrecognized, msg); return 1; } /* check if client protocol is compatible; for simplicity, we don't support client protocol more recent * than server protocol, we suppose that our servers are upgraded when a new release appears (but of * course client protocol older is supported within the major protocol) */ client_proto_major = charstar_to_int(msg + 3); client_proto_minor = charstar_to_int(msg + 5); if (client_proto_major != proto_major || client_proto_minor > proto_minor) { send_line_log(fd, fl_proto_mismatch, msg); return 1; } if (remote_proto_minor[fd] == -1) remote_proto_minor[fd] = client_proto_minor; msg_orig = strdup(msg); /* after protocol, first word is command, then possible args */ current_command = msg + 7; // 7 stands for "FB/M.m " if ((ptr = strchr(current_command, ' '))) { *ptr = '\0'; args = current_command + strlen(current_command) + 1; } else args = NULL; if (streq(current_command, "PING")) { send_line_log(fd, ok_pong, msg_orig); } else if (streq(current_command, "NICK")) { if (!args) { send_line_log(fd, wn_missing_arguments, msg_orig); } else { if ((ptr = strchr(args, ' '))) *ptr = '\0'; if (strlen(args) > 10) args[10] = '\0'; if (!is_nick_ok(args)) { send_line_log(fd, wn_nick_invalid, msg_orig); } else { if (nick[fd] != NULL) { free(nick[fd]); } nick[fd] = strdup(args); calculate_list_games(); send_ok(fd, msg_orig); } } } else if (streq(current_command, "GEOLOC")) { if (!args) { send_line_log(fd, wn_missing_arguments, msg_orig); } else { if ((ptr = strchr(args, ' '))) *ptr = '\0'; if (strlen(args) > 13) // sign, 4 digits, dot, colon, sign, 4 digits, dot args[13] = '\0'; if (geoloc[fd] != NULL) { free(geoloc[fd]); } geoloc[fd] = strdup(args); calculate_list_games(); send_ok(fd, msg_orig); } } else if (streq(current_command, "CREATE")) { if (!args) { send_line_log(fd, wn_missing_arguments, msg_orig); } else { if ((ptr = strchr(args, ' '))) *ptr = '\0'; if (strlen(args) > 10) args[10] = '\0'; if (!is_nick_ok(args)) { send_line_log(fd, wn_nick_invalid, msg_orig); } else if (!nick_available(args)) { send_line_log(fd, wn_nick_in_use, msg_orig); } else if (already_in_game(fd)) { send_line_log(fd, wn_already_in_game, msg_orig); } else if (games_open == 16) { // FB client can display 16 max send_line_log(fd, wn_max_open_games, msg_orig); } else { create_game(fd, strdup(args)); send_ok(fd, msg_orig); } } } else if (streq(current_command, "JOIN")) { if (!args || !(ptr = strchr(args, ' '))) { send_line_log(fd, wn_missing_arguments, msg_orig); } else { struct game * g; char* nick = ptr + 1; *ptr = '\0'; if ((ptr2 = strchr(ptr, ' '))) *ptr2 = '\0'; if (strlen(nick) > 10) nick[10] = '\0'; if (!is_nick_ok(nick)) { send_line_log(fd, wn_nick_invalid, msg_orig); } else if (!nick_available(nick)) { send_line_log(fd, wn_nick_in_use, msg_orig); } else if (already_in_game(fd)) { send_line_log(fd, wn_already_in_game, msg_orig); } else if (!(g = find_game_by_nick(args))) { send_line_log(fd, wn_no_such_game, msg_orig); } else { if (add_player(g, fd, strdup(nick))) send_ok(fd, msg_orig); else send_line_log(fd, wn_game_full, msg_orig); } } } else if (streq(current_command, "KICK")) { if (!args) { send_line_log(fd, wn_missing_arguments, msg_orig); } else { if ((ptr = strchr(args, ' '))) *ptr = '\0'; if (strlen(args) > 10) args[10] = '\0'; if (!already_in_game(fd)) { send_line_log(fd, wn_not_in_game, msg_orig); } else { struct game * g = find_game_by_fd(fd); if (g->players_conn[0] != fd) { send_line_log(fd, wn_not_creator, msg_orig); } else { kick_player(fd, g, args); } } } } else if (streq(current_command, "PART")) { if (!already_in_game(fd)) { send_line_log(fd, wn_not_in_game, msg_orig); } else { player_part_game(fd); send_ok(fd, msg_orig); } } else if (streq(current_command, "LIST")) { send_line_log(fd, list_games_str, msg_orig); } else if (streq(current_command, "STATUS")) { // 1.0 command if (!already_in_game(fd)) { send_line_log(fd, wn_not_in_game, msg_orig); } else { status(fd, msg_orig); } } else if (streq(current_command, "STATUSGEO")) { if (!already_in_game(fd)) { send_line_log(fd, wn_not_in_game, msg_orig); } else { status_geo(fd, msg_orig); } } else if (streq(current_command, "PROTOCOL_LEVEL")) { if (!already_in_game(fd)) { send_line_log(fd, wn_not_in_game, msg_orig); } else { protocol_level(fd, msg_orig); } } else if (streq(current_command, "TALK")) { if (!args) { send_line_log(fd, wn_missing_arguments, msg_orig); } else { talk(fd, args); } } else if (streq(current_command, "START")) { if (!already_in_game(fd)) { send_line_log(fd, wn_not_in_game, msg_orig); } else { start_game(fd); } } else if (streq(current_command, "CLOSE")) { if (!already_in_game(fd)) { send_line_log(fd, wn_not_in_game, msg_orig); } else { close_game(fd); } } else if (streq(current_command, "SETOPTIONS")) { if (!args) { send_line_log(fd, wn_missing_arguments, msg_orig); } else if (!already_in_game(fd)) { send_line_log(fd, wn_not_in_game, msg_orig); } else { setoptions(fd, args); } } else if (streq(current_command, "LEADER_CHECK_GAME_START")) { if (!already_in_game(fd)) { send_line_log(fd, wn_not_in_game, msg_orig); } else { leader_check_game_start(fd); } } else if (streq(current_command, "OK_GAME_START")) { if (!already_in_game(fd)) { send_line_log(fd, wn_not_in_game, msg_orig); } else { ok_start_game(fd); } } else if (streq(current_command, "ADMIN_REREAD")) { if (!admin_authorized[fd]) { send_line_log(fd, wn_denied, msg_orig); } else { reread(); send_ok(fd, "ADMIN_REREAD"); } } else { send_line_log(fd, wn_unknown_command, msg); } free(msg_orig); current_command = NULL; return 0; } ssize_t get_reset_amount_transmitted(void) { ssize_t ret = amount_transmitted; amount_transmitted = 0; return ret; } static void conn_to_terminate_helper(gpointer data, gpointer user_data) { conn_terminated(GPOINTER_TO_INT(data), "system error on send (probably peer shutdown or try again)"); } void process_msg_prio_(int fd, char* msg, ssize_t len, struct game* g) { GList * conn_to_terminate = NULL; if (!g) g = find_game_by_fd(fd); if (g) { int i; for (i = 0; i < g->players_number; i++) { // Pings are for the server only. Don't broadcast them to save bandwidth. if (len == 3 && msg[1] == 'p') { // nada // Emitter wants to receive synchro message as well } else if (g->players_conn[i] == fd && len > 2 && msg[1] == '!') { char synchro4self[] = "?!\n"; ssize_t retval; synchro4self[0] = fd; l1(OUTPUT_TYPE_DEBUG, "[%d] sending self synchro", g->players_conn[i]); retval = send(g->players_conn[i], synchro4self, sizeof(synchro4self) - 1, MSG_NOSIGNAL|MSG_DONTWAIT); if (retval != sizeof(synchro4self) - 1) { if (retval != -1) { l4(OUTPUT_TYPE_INFO, "[%d] short send of %zd instead of %zd bytes from %d - destination is not reading data " "(illegal FB client) or our upload bandwidth is saturated - sorry, cannot continue serving " "this client in this situation, closing connection", g->players_conn[i], retval, sizeof(synchro4self) - 1, fd); } conn_to_terminate = g_list_append(conn_to_terminate, GINT_TO_POINTER(g->players_conn[i])); } } else if (g->players_conn[i] != fd) { ssize_t retval; l3(OUTPUT_TYPE_DEBUG, "[%d] sending %zd bytes to %d", fd, len, g->players_conn[i]); retval = send(g->players_conn[i], msg, len, MSG_NOSIGNAL|MSG_DONTWAIT); if (retval != len) { if (retval != -1) { l4(OUTPUT_TYPE_INFO, "[%d] short send of %zd instead of %zd bytes from %d - destination is not reading data " "(illegal FB client) or our upload bandwidth is saturated - sorry, cannot continue serving " "this client in this situation, closing connection", g->players_conn[i], retval, len, fd); } conn_to_terminate = g_list_append(conn_to_terminate, GINT_TO_POINTER(g->players_conn[i])); } } } if (conn_to_terminate) { g_list_foreach(conn_to_terminate, conn_to_terminate_helper, NULL); g_list_free(conn_to_terminate); } } else { l1(OUTPUT_TYPE_ERROR, "Internal error: could not find game by fd: %d", fd); exit(EXIT_FAILURE); } } void process_msg_prio(int fd, char* msg, ssize_t len) { process_msg_prio_(fd, msg, len, NULL); } void player_part_game(int fd) { player_part_game_(fd, NULL); } void player_part_game_(int fd, char* reason) { struct game * g = find_game_by_fd(fd); if (g) { char * save_nick; int j; int i = find_player_number(g, fd); // remove parting player from game save_nick = g->players_nick[i]; for (j = i; j < g->players_number - 1; j++) { g->players_conn[j] = g->players_conn[j + 1]; g->players_nick[j] = g->players_nick[j + 1]; g->players_started[j] = g->players_started[j + 1]; } g->players_number--; // completely remove game if empty if (g->players_number == 0) { int was_running = g->status == GAME_STATUS_PLAYING; games = g_list_remove(games, g); free(g); calculate_list_games(); if (was_running) l2(OUTPUT_TYPE_INFO, "running games decrements to: %d (%d players)", games_running, players_in_game); } else { if (g->status == GAME_STATUS_PLAYING) { // inform other players, playing state char leave_player_prio_msg[] = "?l\n"; leave_player_prio_msg[0] = fd; process_msg_prio_(fd, leave_player_prio_msg, strlen(leave_player_prio_msg), g); } else { char parted_msg[1000]; // inform other players, non-playing state snprintf(parted_msg, sizeof(parted_msg), reason ? reason : ok_player_parted, save_nick); for (j = 0; j < g->players_number; j++) send_line_log_push(g->players_conn[j], parted_msg); } calculate_list_games(); } free(save_nick); open_players = g_list_append(open_players, GINT_TO_POINTER(fd)); } }