/* Tetrinet for Linux, by Andy Church * This program is public domain. * * Tetrinet server code */ #include #include #include #include #include #include #include /* Due to glibc brokenness, we can't blindly include this. Yet another * reason to not use glibc. */ /* #include */ #include #include #include #include "tetrinet.h" #include "tetris.h" #include "server.h" #include "sockets.h" /*************************************************************************/ static int linuxmode = 0; /* Don't try to be compatible with Windows */ static int quit = 0; static time_t add_line_when = 0; /*mp*/ static time_t time_before_adding_lines = 60; /*mp*/ static time_t time_between_adding_lines = 10; /*mp*/ static int listen_sock = -1; static int player_socks[6] = {-1,-1,-1,-1,-1,-1}; /* Which players have already lost in the current game? */ static int player_lost[6]; /* We re-use a lot of variables from the main code */ /*************************************************************************/ /*************************************************************************/ /* Convert a 2-byte hex value to an integer. */ int xtoi(const char *buf) { int val; if (buf[0] <= '9') val = (buf[0] - '0') << 4; else val = (toupper(buf[0]) - 'A' + 10) << 4; if (buf[1] <= '9') val |= buf[1] - '0'; else val |= toupper(buf[1]) - 'A' + 10; return val; } /*************************************************************************/ /* Return a string containing the winlist in a format suitable for sending * to clients. */ static char *winlist_str() { static char buf[1024]; char *s; int i; s = buf; for (i = 0; i < MAXWINLIST && *winlist[i].name; i++) { s += snprintf(s, sizeof(buf)-(s-buf), linuxmode ? " %c%s;%d;%d" : " %c%s;%d", winlist[i].team ? 't' : 'p', winlist[i].name, winlist[i].points, winlist[i].games); } return buf; } /*************************************************************************/ /*************************************************************************/ /* Read the configuration file. */ void read_config(void) { char buf[1024], *s, *t; FILE *f; int i; s = getenv("HOME"); if (!s) s = "/etc"; snprintf(buf, sizeof(buf), "%s/.tetrinet", s); if (!(f = fopen(buf, "r"))) return; while (fgets(buf, sizeof(buf), f)) { s = strtok(buf, " "); if (!s) { continue; } else if (strcmp(s, "linuxmode") == 0) { if (s = strtok(NULL, " ")) linuxmode = atoi(s); } else if (strcmp(s, "averagelevels") == 0) { if (s = strtok(NULL, " ")) level_average = atoi(s); } else if (strcmp(s, "classic") == 0) { if (s = strtok(NULL, " ")) old_mode = atoi(s); } else if (strcmp(s, "initiallevel") == 0) { if (s = strtok(NULL, " ")) initial_level = atoi(s); } else if (strcmp(s, "levelinc") == 0) { if (s = strtok(NULL, " ")) level_inc = atoi(s); } else if (strcmp(s, "linesperlevel") == 0) { if (s = strtok(NULL, " ")) lines_per_level = atoi(s); } else if (strcmp(s, "pieces") == 0) { i = 0; while (i < 7 && (s = strtok(NULL, " "))) piecefreq[i++] = atoi(s); } else if (strcmp(s, "specialcapacity") == 0) { if (s = strtok(NULL, " ")) special_capacity = atoi(s); } else if (strcmp(s, "specialcount") == 0) { if (s = strtok(NULL, " ")) special_count = atoi(s); } else if (strcmp(s, "speciallines") == 0) { if (s = strtok(NULL, " ")) special_lines = atoi(s); } else if (strcmp(s, "specials") == 0) { i = 0; while (i < 9 && (s = strtok(NULL, " "))) specialfreq[i++] = atoi(s); } else if (strcmp(s, "winlist") == 0) { i = 0; while (i < MAXWINLIST && (s = strtok(NULL, " "))) { t = strchr(s, ';'); if (!t) break; *t++ = 0; strncpy(winlist[i].name, s, sizeof(winlist[i].name)-1); winlist[i].name[sizeof(winlist[i].name)-1] = 0; s = t; t = strchr(s, ';'); if (!t) { *winlist[i].name = 0; break; } winlist[i].team = atoi(s); s = t+1; t = strchr(s, ';'); if (!t) { *winlist[i].name = 0; break; } winlist[i].points = atoi(s); winlist[i].games = atoi(t+1); i++; } } else if (strcmp(s, "addlineswhen") == 0) { /*mp*/ time_before_adding_lines = 60*atoi(strtok(NULL, " ")); time_between_adding_lines = atoi(strtok(NULL, " ")); }; } fclose(f); } /*************************************************************************/ /* Re-write the configuration file. */ void write_config(void) { char buf[1024], *s; FILE *f; int i; s = getenv("HOME"); if (!s) s = "/etc"; snprintf(buf, sizeof(buf), "%s/.tetrinet", s); if (!(f = fopen(buf, "w"))) return; fprintf(f, "winlist"); for (i = 0; i < MAXSAVEWINLIST && *winlist[i].name; i++) { fprintf(f, " %s;%d;%d;%d", winlist[i].name, winlist[i].team, winlist[i].points, winlist[i].games); } fputc('\n', f); fprintf(f, "classic %d\n", old_mode); fprintf(f, "initiallevel %d\n", initial_level); fprintf(f, "linesperlevel %d\n", lines_per_level); fprintf(f, "levelinc %d\n", level_inc); fprintf(f, "averagelevels %d\n", level_average); fprintf(f, "speciallines %d\n", special_lines); fprintf(f, "specialcount %d\n", special_count); fprintf(f, "specialcapacity %d\n", special_capacity); fprintf(f, "pieces"); for (i = 0; i < 7; i++) fprintf(f, " %d", piecefreq[i]); fputc('\n', f); fprintf(f, "specials"); for (i = 0; i < 9; i++) fprintf(f, " %d", specialfreq[i]); fputc('\n', f); fprintf(f, "addlineswhen %d %d", time_before_adding_lines / 60, time_between_adding_lines); /*mp*/ fputc('\n', f); fclose(f); } /*************************************************************************/ /*************************************************************************/ /* Send a message to a single player. */ static void send_to(int player, const char *format, ...) { va_list args; char buf[1024]; int i; va_start(args, format); vsnprintf(buf, sizeof(buf), format, args); if (player_socks[player-1] >= 0) sockprintf(player_socks[player-1], "%s", buf); } /*************************************************************************/ /* Send a message to all players. */ static void send_to_all(const char *format, ...) { va_list args; char buf[1024]; int i; va_start(args, format); vsnprintf(buf, sizeof(buf), format, args); for (i = 0; i < 6; i++) { if (player_socks[i] >= 0) sockprintf(player_socks[i], "%s", buf); } } /*************************************************************************/ /* Send a message to all players but the given one. */ static void send_to_all_but(int player, const char *format, ...) { va_list args; char buf[1024]; int i; va_start(args, format); vsnprintf(buf, sizeof(buf), format, args); for (i = 0; i < 6; i++) { if (i+1 != player && player_socks[i] >= 0) sockprintf(player_socks[i], "%s", buf); } } /*************************************************************************/ /* Send a message to all players but those on the same team as the given * player. */ static void send_to_all_but_team(int player, const char *format, ...) { va_list args; char buf[1024]; int i; char *team = teams[player-1]; va_start(args, format); vsnprintf(buf, sizeof(buf), format, args); for (i = 0; i < 6; i++) { if (i+1 != player && player_socks[i] >= 0 && (!team || !teams[i] || strcmp(teams[i], team) != 0)) sockprintf(player_socks[i], "%s", buf); } } /*************************************************************************/ /*************************************************************************/ /* Add points to a given player's [team's] winlist entry, or make a new one * if they rank. */ static void add_points(int player, int points) { int i; if (!players[player-1]) return; for (i = 0; i < MAXWINLIST && *winlist[i].name; i++) { if (!winlist[i].team && !teams[player-1] && strcmp(winlist[i].name, players[player-1]) == 0) break; if (winlist[i].team && teams[player-1] && strcmp(winlist[i].name, teams[player-1]) == 0) break; } if (i == MAXWINLIST) { for (i = 0; i < MAXWINLIST && winlist[i].points >= points; i++) ; } if (i == MAXWINLIST) return; if (!*winlist[i].name) { if (teams[player-1]) { strncpy(winlist[i].name, teams[player-1], sizeof(winlist[i].name)-1); winlist[i].name[sizeof(winlist[i].name)-1] = 0; winlist[i].team = 1; } else { strncpy(winlist[i].name, players[player-1], sizeof(winlist[i].name)-1); winlist[i].name[sizeof(winlist[i].name)-1] = 0; winlist[i].team = 0; } } winlist[i].points += points; } /*************************************************************************/ /* Add a game to a given player's [team's] winlist entry. */ static void add_game(int player) { int i; if (!players[player-1]) return; for (i = 0; i < MAXWINLIST && *winlist[i].name; i++) { if (!winlist[i].team && !teams[player-1] && strcmp(winlist[i].name, players[player-1]) == 0) break; if (winlist[i].team && teams[player-1] && strcmp(winlist[i].name, teams[player-1]) == 0) break; } if (i == MAXWINLIST || !*winlist[i].name) return; winlist[i].games++; } /*************************************************************************/ /* Sort the winlist. */ static void sort_winlist() { int i, j, best, bestindex; for (i = 0; i < MAXWINLIST && *winlist[i].name; i++) { best = winlist[i].points; bestindex = i; for (j = i+1; j < MAXWINLIST && *winlist[j].name; j++) { if (winlist[j].points > best) { best = winlist[j].points; bestindex = j; } } if (bestindex != i) { WinInfo tmp; memcpy(&tmp, &winlist[i], sizeof(WinInfo)); memcpy(&winlist[i], &winlist[bestindex], sizeof(WinInfo)); memcpy(&winlist[bestindex], &tmp, sizeof(WinInfo)); } } } /*************************************************************************/ /* Take care of a player losing (which may end the game). */ static void player_loses(int player) { int i, j, order, end = 1, winner = -1, second, third; if (player < 1 || player > 6 || player_socks[player-1] < 0) return; order = 0; for (i = 1; i <= 6; i++) { if (player_lost[i-1] > order) order = player_lost[i-1]; } player_lost[player-1] = order+1; for (i = 1; i <= 6; i++) { if (player_socks[i-1] >= 0 && !player_lost[i-1]) { if (winner < 0) { winner = i; } else if (!teams[winner-1] || !teams[i-1] || strcasecmp(teams[winner-1],teams[i-1]) != 0) { end = 0; break; } } } if (end) { send_to_all("endgame"); playing_game = 0; /* Catch the case where no players are left (1-player game) */ if (winner > 0) { send_to_all("playerwon %d", winner); add_points(winner, 3); order = 0; for (i = 1; i <= 6; i++) { if (player_lost[i-1] > order && (!teams[winner-1] || !teams[i-1] || strcasecmp(teams[winner-1],teams[i-1]) != 0)) { order = player_lost[i-1]; second = i; } } if (order) { add_points(second, 2); player_lost[second-1] = 0; } order = 0; for (i = 1; i <= 6; i++) { if (player_lost[i-1] > order && (!teams[winner-1] || !teams[i-1] || strcasecmp(teams[winner-1],teams[i-1]) != 0) && (!teams[second-1] || !teams[i-1] || strcasecmp(teams[second-1],teams[i-1]) != 0)) { order = player_lost[i-1]; third = i; } } if (order) add_points(third, 1); for (i = 1; i <= 6; i++) { if (teams[i-1]) { for (j = 1; j < i; j++) { if (teams[j-1] && strcasecmp(teams[i-1],teams[j-1])==0) break; } if (j < i) continue; } if (player_socks[i-1] >= 0) add_game(i); } } sort_winlist(); write_config(); send_to_all("winlist %s", winlist_str()); } /* One more possibility: the only player playing left the game, which * means there are now no players left. */ if (!players[0] && !players[1] && !players[2] && !players[3] && !players[4] && !players[5]) playing_game = 0; } /*************************************************************************/ /*************************************************************************/ /* Parse a line from a client. Destroys the buffer it's given as a side * effect. Return 0 if the command is unknown (or bad syntax), else 1. */ static int server_parse(int player, char *buf) { char *cmd, *s, *t; int i; cmd = strtok(buf, " "); if (!cmd) { return 1; } else if (strcmp(cmd, "tetrisstart") == 0) { s = strtok(NULL, " "); t = strtok(NULL, " "); if (!t) return 0; for (i = 1; i <= 6; i++) { if (players[i-1] && strcasecmp(s, players[i-1]) == 0) { send_to(player, "noconnecting Nickname already exists on server!"); return 0; } } players[player-1] = strdup(s); if (teams[player-1]) free(teams[player-1]); teams[player-1] = NULL; send_to(player, "playernum %d", player); send_to(player, "winlist %s", winlist_str()); for (i = 1; i <= 6; i++) { if (i != player && players[i-1]) { send_to(player, "playerjoin %d %s", i, players[i-1]); send_to(player, "team %d %s", i, teams[i-1] ? teams[i-1] : ""); } } if (playing_game) { send_to(player, "ingame"); player_lost[player-1] = 1; } send_to_all_but(player, "playerjoin %d %s", player, players[player-1]); } else if (strcmp(cmd, "team") == 0) { s = strtok(NULL, " "); t = strtok(NULL, ""); if (!s || atoi(s) != player) return 0; if (teams[player]) free(teams[player]); if (t) teams[player] = strdup(t); else teams[player] = NULL; send_to_all_but(player, "team %d %s", player, t ? t : ""); } else if (strcmp(cmd, "pline") == 0) { s = strtok(NULL, " "); t = strtok(NULL, ""); if (!s || atoi(s) != player) return 0; if (!t) t = ""; send_to_all_but(player, "pline %d %s", player, t); } else if (strcmp(cmd, "plineact") == 0) { s = strtok(NULL, " "); t = strtok(NULL, ""); if (!s || atoi(s) != player) return 0; if (!t) t = ""; send_to_all_but(player, "plineact %d %s", player, t); } else if (strcmp(cmd, "startgame") == 0) { int total; char piecebuf[101], specialbuf[101]; for (i = 1; i < player; i++) { if (player_socks[i-1] >= 0) return 1; } s = strtok(NULL, " "); t = strtok(NULL, " "); if (!s) return 1; i = atoi(s); if ((i && playing_game) || (!i && !playing_game)) return 1; if (!i) { /* end game */ send_to_all("endgame"); playing_game = 0; return 1; } total = 0; for (i = 0; i < 7; i++) { if (piecefreq[i]) memset(piecebuf+total, '1'+i, piecefreq[i]); total += piecefreq[i]; } piecebuf[100] = 0; if (total != 100) { send_to_all("plineact 0 cannot start game: Piece frequencies do not total 100 percent!"); return 1; } total = 0; for (i = 0; i < 9; i++) { if (specialfreq[i]) memset(specialbuf+total, '1'+i, specialfreq[i]); total += specialfreq[i]; } specialbuf[100] = 0; if (total != 100) { send_to_all("plineact 0 cannot start game: Special frequencies do not total 100 percent!"); return 1; } playing_game = 1; game_paused = 0; add_line_when = time(NULL) + time_before_adding_lines; /*mp*/ for (i = 1; i <= 6; i++) { if (player_socks[i-1] < 0) continue; /* XXX First parameter is stack height */ send_to(i, "newgame %d %d %d %d %d %d %d %s %s %d %d", 0, initial_level, lines_per_level, level_inc, special_lines, special_count, special_capacity, piecebuf, specialbuf, level_average, old_mode); } memset(player_lost, 0, sizeof(player_lost)); } else if (strcmp(cmd, "pause") == 0) { if (!playing_game) return 1; s = strtok(NULL, " "); if (!s) return 1; i = atoi(s); if (i) i = 1; /* to make sure it's not anything else */ if ((i && game_paused) || (!i && !game_paused)) return 1; game_paused = i; send_to_all("pause %d", i); } else if (strcmp(cmd, "playerlost") == 0) { if (!(s = strtok(NULL, " ")) || atoi(s) != player) return 1; player_loses(player); } else if (strcmp(cmd, "f") == 0) { /* field */ if (!(s = strtok(NULL, " ")) || atoi(s) != player) return 1; if (!(s = strtok(NULL, ""))) s = ""; send_to_all_but(player, "f %d %s", player, s); } else if (strcmp(cmd, "lvl") == 0) { if (!(s = strtok(NULL, " ")) || atoi(s) != player) return 1; if (!(s = strtok(NULL, " "))) return 1; levels[player] = atoi(s); send_to_all_but(player, "lvl %d %d", player, levels[player]); } else if (strcmp(cmd, "sb") == 0) { int from, to; char *type; if (!(s = strtok(NULL, " "))) return 1; to = atoi(s); if (!(type = strtok(NULL, " "))) return 1; if (!(s = strtok(NULL, " "))) return 1; from = atoi(s); if (from != player) return 1; if (to < 0 || to > 6 || player_socks[to-1] < 0 || player_lost[to-1]) return 1; if (to == 0) send_to_all_but_team(player, "sb %d %s %d", to, type, from); else send_to_all_but(player, "sb %d %s %d", to, type, from); } else if (strcmp(cmd, "gmsg") == 0) { if (!(s = strtok(NULL, ""))) return 1; send_to_all("gmsg %s", s); } else { /* unrecognized command */ return 0; } return 1; } /*************************************************************************/ /*************************************************************************/ static void sigcatcher(int sig) { if (sig == SIGHUP) { read_config(); signal(SIGHUP, sigcatcher); send_to_all("winlist %s", winlist_str()); } else if (sig == SIGTERM || sig == SIGINT) { quit = 1; signal(sig, SIG_IGN); } } /*************************************************************************/ static int init() { struct sockaddr_in sin; int i; /* Set up some sensible defaults */ *winlist[0].name = 0; old_mode = 1; initial_level = 1; lines_per_level = 2; level_inc = 1; level_average = 1; special_lines = 1; special_count = 1; special_capacity = 18; piecefreq[0] = 14; piecefreq[1] = 14; piecefreq[2] = 15; piecefreq[3] = 14; piecefreq[4] = 14; piecefreq[5] = 14; piecefreq[6] = 15; specialfreq[0] = 18; specialfreq[1] = 18; specialfreq[2] = 3; specialfreq[3] = 12; specialfreq[4] = 0; specialfreq[5] = 16; specialfreq[6] = 3; specialfreq[7] = 12; specialfreq[8] = 18; /* (Try to) read the config file */ read_config(); /* Catch some signals */ signal(SIGHUP, sigcatcher); signal(SIGINT, sigcatcher); signal(SIGTERM, sigcatcher); /* Set up a listen socket */ /* Because we don't necessarily have , we have to * hardcode this (use getprotobyname()? I'm lazy). glibc sucks. */ listen_sock = socket(AF_INET, SOCK_STREAM, 6); if (listen_sock < 0) return 1; i = 1; if (setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0) { close(listen_sock); return 1; } memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(31457); if (bind(listen_sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) { close(listen_sock); return 1; } if (listen(listen_sock, 10) < 0) { close(listen_sock); return 1; } return 0; } /*************************************************************************/ static void check_sockets() { fd_set fds; int i, fd, maxfd; char buf[1024]; struct sockaddr_in sin; struct timeval timeout; /*mp*/ int len = sizeof(sin); FD_ZERO(&fds); FD_SET(listen_sock, &fds); maxfd = listen_sock; for (i = 0; i < 6; i++) { if (player_socks[i] != -1) { if (player_socks[i] < 0) fd = (~player_socks[i]) - 1; else fd = player_socks[i]; FD_SET(fd, &fds); if (fd > maxfd) maxfd = fd; } } timeout.tv_sec = 0L; timeout.tv_usec = 500L; /*mp*/ if (select(maxfd+1, &fds, NULL, NULL, &timeout) <= 0) /*mp*/ return; if (FD_ISSET(listen_sock, &fds)) { fd = accept(listen_sock, (struct sockaddr *)&sin, &len); if (fd >= 0) { for (i = 0; i < 6 && player_socks[i] != -1; i++) ; if (i == 6) { sockprintf(fd, "noconnecting Too many players on server!"); close(fd); } else { player_socks[i] = ~(fd+1); } } } /* if (FD_ISSET(listen_sock)) */ for (i = 0; i < 6; i++) { if (player_socks[i] == -1) continue; if (player_socks[i] < 0) fd = (~player_socks[i]) - 1; else fd = player_socks[i]; if (!FD_ISSET(fd, &fds)) continue; sgets(buf, sizeof(buf), fd); if (player_socks[i] < 0) { /* Messy decoding stuff */ char iphashbuf[16], newbuf[1024]; unsigned char *ip; int j, c, d; if (strlen(buf) < 2*13) { /* "tetrisstart " + initial byte */ close(fd); player_socks[i] = -1; continue; } if (getsockname(fd, (struct sockaddr *)&sin, &len) < 0) { sockprintf(fd, "noconnecting Server error: can't get socket name: %s", strerror(errno)); close(fd); player_socks[i] = -1; continue; } ip = (char *) &sin.sin_addr; sprintf(iphashbuf, "%d", ip[0]*54 + ip[1]*41 + ip[2]*29 + ip[3]*17); c = xtoi(buf); for (j = 2; buf[j] && buf[j+1]; j += 2) { int temp; temp = d = xtoi(buf+j); d ^= iphashbuf[((j/2)-1) % strlen(iphashbuf)]; d += 255 - c; d %= 255; newbuf[j/2-1] = d; c = temp; } newbuf[j/2-1] = 0; if (strncmp(newbuf, "tetrisstart ", 12) != 0) { close(fd); player_socks[i] = -1; continue; } strcpy(buf, newbuf); /* Safe: buffers are the same size */ player_socks[i] = fd; /* Has now registered */ } /* if client not registered */ if (!server_parse(i+1, buf)) { close(fd); player_socks[i] = -1; if (players[i]) { send_to_all("playerleave %d", i+1); if (playing_game) player_loses(i+1); free(players[i]); players[i] = NULL; if (teams[i]) { free(teams[i]); teams[i] = NULL; } } } } /* for each player socket */ } /*************************************************************************/ #ifdef SERVER_ONLY int main() #else int server_main() #endif { int i; if ((i = init()) != 0) return i; static time_t now; /*mp*/ while (!quit) { /*mp*/ check_sockets(); time(&now); /*mp*/ if(playing_game && add_line_when <= now) { /*mp*/ send_to_all("sb 0 cs1 0"); add_line_when += time_between_adding_lines; }; }; /*mp*/ write_config(); close(listen_sock); for (i = 0; i < 6; i++) close(player_socks[i]); return 0; } /*************************************************************************/