1 /* Tetrinet for Linux, by Andrew Church <achurch@achurch.org>
2 * This program is public domain.
12 #include <sys/types.h>
13 #include <netinet/in.h>
14 /* Due to glibc brokenness, we can't blindly include this. Yet another
15 * reason to not use glibc. */
16 /* #include <netinet/protocols.h> */
18 #include <sys/socket.h>
25 /*************************************************************************/
27 static int linuxmode
= 0; /* 1: don't try to be compatible with Windows */
28 static int ipv6_only
= 0; /* 1: only use IPv6 (when available) */
32 static int listen_sock
= -1;
34 static int listen_sock6
= -1;
36 static int player_socks
[6] = {-1,-1,-1,-1,-1,-1};
37 static unsigned char player_ips
[6][4];
38 static int player_modes
[6];
40 /* Which players have already lost in the current game? */
41 static int player_lost
[6];
43 /* We re-use a lot of variables from the main code */
45 /*************************************************************************/
46 /*************************************************************************/
48 /* Convert a 2-byte hex value to an integer. */
50 int xtoi(const char *buf
)
55 val
= (buf
[0] - '0') << 4;
57 val
= (toupper(buf
[0]) - 'A' + 10) << 4;
61 val
|= toupper(buf
[1]) - 'A' + 10;
65 /*************************************************************************/
67 /* Return a string containing the winlist in a format suitable for sending
71 static char *winlist_str()
73 static char buf
[1024];
78 for (i
= 0; i
< MAXWINLIST
&& *winlist
[i
].name
; i
++) {
79 s
+= snprintf(s
, sizeof(buf
)-(s
-buf
),
80 linuxmode
? " %c%s;%d;%d" : " %c%s;%d",
81 winlist
[i
].team
? 't' : 'p',
82 winlist
[i
].name
, winlist
[i
].points
, winlist
[i
].games
);
87 /*************************************************************************/
88 /*************************************************************************/
90 /* Read the configuration file. */
92 void read_config(void)
94 char buf
[1024], *s
, *t
;
101 snprintf(buf
, sizeof(buf
), "%s/.tetrinet", s
);
102 if (!(f
= fopen(buf
, "r")))
104 while (fgets(buf
, sizeof(buf
), f
)) {
105 s
= strtok(buf
, " ");
108 } else if (strcmp(s
, "linuxmode") == 0) {
109 if (s
= strtok(NULL
, " "))
111 } else if (strcmp(s
, "ipv6_only") == 0) {
112 if (s
= strtok(NULL
, " "))
114 } else if (strcmp(s
, "averagelevels") == 0) {
115 if (s
= strtok(NULL
, " "))
116 level_average
= atoi(s
);
117 } else if (strcmp(s
, "classic") == 0) {
118 if (s
= strtok(NULL
, " "))
120 } else if (strcmp(s
, "initiallevel") == 0) {
121 if (s
= strtok(NULL
, " "))
122 initial_level
= atoi(s
);
123 } else if (strcmp(s
, "levelinc") == 0) {
124 if (s
= strtok(NULL
, " "))
126 } else if (strcmp(s
, "linesperlevel") == 0) {
127 if (s
= strtok(NULL
, " "))
128 lines_per_level
= atoi(s
);
129 } else if (strcmp(s
, "pieces") == 0) {
131 while (i
< 7 && (s
= strtok(NULL
, " ")))
132 piecefreq
[i
++] = atoi(s
);
133 } else if (strcmp(s
, "specialcapacity") == 0) {
134 if (s
= strtok(NULL
, " "))
135 special_capacity
= atoi(s
);
136 } else if (strcmp(s
, "specialcount") == 0) {
137 if (s
= strtok(NULL
, " "))
138 special_count
= atoi(s
);
139 } else if (strcmp(s
, "speciallines") == 0) {
140 if (s
= strtok(NULL
, " "))
141 special_lines
= atoi(s
);
142 } else if (strcmp(s
, "specials") == 0) {
144 while (i
< 9 && (s
= strtok(NULL
, " ")))
145 specialfreq
[i
++] = atoi(s
);
146 } else if (strcmp(s
, "winlist") == 0) {
148 while (i
< MAXWINLIST
&& (s
= strtok(NULL
, " "))) {
153 strncpy(winlist
[i
].name
, s
, sizeof(winlist
[i
].name
)-1);
154 winlist
[i
].name
[sizeof(winlist
[i
].name
)-1] = 0;
158 *winlist
[i
].name
= 0;
161 winlist
[i
].team
= atoi(s
);
165 *winlist
[i
].name
= 0;
168 winlist
[i
].points
= atoi(s
);
169 winlist
[i
].games
= atoi(t
+1);
177 /*************************************************************************/
179 /* Re-write the configuration file. */
181 void write_config(void)
190 snprintf(buf
, sizeof(buf
), "%s/.tetrinet", s
);
191 if (!(f
= fopen(buf
, "w")))
194 fprintf(f
, "winlist");
195 for (i
= 0; i
< MAXSAVEWINLIST
&& *winlist
[i
].name
; i
++) {
196 fprintf(f
, " %s;%d;%d;%d", winlist
[i
].name
, winlist
[i
].team
,
197 winlist
[i
].points
, winlist
[i
].games
);
201 fprintf(f
, "classic %d\n", old_mode
);
203 fprintf(f
, "initiallevel %d\n", initial_level
);
204 fprintf(f
, "linesperlevel %d\n", lines_per_level
);
205 fprintf(f
, "levelinc %d\n", level_inc
);
206 fprintf(f
, "averagelevels %d\n", level_average
);
208 fprintf(f
, "speciallines %d\n", special_lines
);
209 fprintf(f
, "specialcount %d\n", special_count
);
210 fprintf(f
, "specialcapacity %d\n", special_capacity
);
212 fprintf(f
, "pieces");
213 for (i
= 0; i
< 7; i
++)
214 fprintf(f
, " %d", piecefreq
[i
]);
217 fprintf(f
, "specials");
218 for (i
= 0; i
< 9; i
++)
219 fprintf(f
, " %d", specialfreq
[i
]);
222 fprintf(f
, "linuxmode %d\n", linuxmode
);
223 fprintf(f
, "ipv6_only %d\n", ipv6_only
);
228 /*************************************************************************/
229 /*************************************************************************/
231 /* Send a message to a single player. */
233 static void send_to(int player
, const char *format
, ...)
239 va_start(args
, format
);
240 vsnprintf(buf
, sizeof(buf
), format
, args
);
241 if (player_socks
[player
-1] >= 0)
242 sockprintf(player_socks
[player
-1], "%s", buf
);
245 /*************************************************************************/
247 /* Send a message to all players. */
249 static void send_to_all(const char *format
, ...)
255 va_start(args
, format
);
256 vsnprintf(buf
, sizeof(buf
), format
, args
);
257 for (i
= 0; i
< 6; i
++) {
258 if (player_socks
[i
] >= 0)
259 sockprintf(player_socks
[i
], "%s", buf
);
263 /*************************************************************************/
265 /* Send a message to all players but the given one. */
267 static void send_to_all_but(int player
, const char *format
, ...)
273 va_start(args
, format
);
274 vsnprintf(buf
, sizeof(buf
), format
, args
);
275 for (i
= 0; i
< 6; i
++) {
276 if (i
+1 != player
&& player_socks
[i
] >= 0)
277 sockprintf(player_socks
[i
], "%s", buf
);
281 /*************************************************************************/
283 /* Send a message to all players but those on the same team as the given
287 static void send_to_all_but_team(int player
, const char *format
, ...)
292 char *team
= teams
[player
-1];
294 va_start(args
, format
);
295 vsnprintf(buf
, sizeof(buf
), format
, args
);
296 for (i
= 0; i
< 6; i
++) {
297 if (i
+1 != player
&& player_socks
[i
] >= 0 &&
298 (!team
|| !teams
[i
] || strcmp(teams
[i
], team
) != 0))
299 sockprintf(player_socks
[i
], "%s", buf
);
303 /*************************************************************************/
304 /*************************************************************************/
306 /* Add points to a given player's [team's] winlist entry, or make a new one
310 static void add_points(int player
, int points
)
314 if (!players
[player
-1])
316 for (i
= 0; i
< MAXWINLIST
&& *winlist
[i
].name
; i
++) {
317 if (!winlist
[i
].team
&& !teams
[player
-1]
318 && strcmp(winlist
[i
].name
, players
[player
-1]) == 0)
320 if (winlist
[i
].team
&& teams
[player
-1]
321 && strcmp(winlist
[i
].name
, teams
[player
-1]) == 0)
324 if (i
== MAXWINLIST
) {
325 for (i
= 0; i
< MAXWINLIST
&& winlist
[i
].points
>= points
; i
++)
330 if (!*winlist
[i
].name
) {
331 if (teams
[player
-1]) {
332 strncpy(winlist
[i
].name
, teams
[player
-1], sizeof(winlist
[i
].name
)-1);
333 winlist
[i
].name
[sizeof(winlist
[i
].name
)-1] = 0;
336 strncpy(winlist
[i
].name
, players
[player
-1], sizeof(winlist
[i
].name
)-1);
337 winlist
[i
].name
[sizeof(winlist
[i
].name
)-1] = 0;
341 winlist
[i
].points
+= points
;
344 /*************************************************************************/
346 /* Add a game to a given player's [team's] winlist entry. */
348 static void add_game(int player
)
352 if (!players
[player
-1])
354 for (i
= 0; i
< MAXWINLIST
&& *winlist
[i
].name
; i
++) {
355 if (!winlist
[i
].team
&& !teams
[player
-1]
356 && strcmp(winlist
[i
].name
, players
[player
-1]) == 0)
358 if (winlist
[i
].team
&& teams
[player
-1]
359 && strcmp(winlist
[i
].name
, teams
[player
-1]) == 0)
362 if (i
== MAXWINLIST
|| !*winlist
[i
].name
)
367 /*************************************************************************/
369 /* Sort the winlist. */
371 static void sort_winlist()
373 int i
, j
, best
, bestindex
;
375 for (i
= 0; i
< MAXWINLIST
&& *winlist
[i
].name
; i
++) {
376 best
= winlist
[i
].points
;
378 for (j
= i
+1; j
< MAXWINLIST
&& *winlist
[j
].name
; j
++) {
379 if (winlist
[j
].points
> best
) {
380 best
= winlist
[j
].points
;
384 if (bestindex
!= i
) {
386 memcpy(&tmp
, &winlist
[i
], sizeof(WinInfo
));
387 memcpy(&winlist
[i
], &winlist
[bestindex
], sizeof(WinInfo
));
388 memcpy(&winlist
[bestindex
], &tmp
, sizeof(WinInfo
));
393 /*************************************************************************/
395 /* Take care of a player losing (which may end the game). */
397 static void player_loses(int player
)
399 int i
, j
, order
, end
= 1, winner
= -1, second
, third
;
401 if (player
< 1 || player
> 6 || player_socks
[player
-1] < 0)
404 for (i
= 1; i
<= 6; i
++) {
405 if (player_lost
[i
-1] > order
)
406 order
= player_lost
[i
-1];
408 player_lost
[player
-1] = order
+1;
409 for (i
= 1; i
<= 6; i
++) {
410 if (player_socks
[i
-1] >= 0 && !player_lost
[i
-1]) {
413 } else if (!teams
[winner
-1] || !teams
[i
-1]
414 || strcasecmp(teams
[winner
-1],teams
[i
-1]) != 0) {
421 send_to_all("endgame");
423 /* Catch the case where no players are left (1-player game) */
425 send_to_all("playerwon %d", winner
);
426 add_points(winner
, 3);
428 for (i
= 1; i
<= 6; i
++) {
429 if (player_lost
[i
-1] > order
430 && (!teams
[winner
-1] || !teams
[i
-1]
431 || strcasecmp(teams
[winner
-1],teams
[i
-1]) != 0)) {
432 order
= player_lost
[i
-1];
437 add_points(second
, 2);
438 player_lost
[second
-1] = 0;
441 for (i
= 1; i
<= 6; i
++) {
442 if (player_lost
[i
-1] > order
443 && (!teams
[winner
-1] || !teams
[i
-1]
444 || strcasecmp(teams
[winner
-1],teams
[i
-1]) != 0)
445 && (!teams
[second
-1] || !teams
[i
-1]
446 || strcasecmp(teams
[second
-1],teams
[i
-1]) != 0)) {
447 order
= player_lost
[i
-1];
452 add_points(third
, 1);
453 for (i
= 1; i
<= 6; i
++) {
455 for (j
= 1; j
< i
; j
++) {
456 if (teams
[j
-1] && strcasecmp(teams
[i
-1],teams
[j
-1])==0)
462 if (player_socks
[i
-1] >= 0)
468 send_to_all("winlist %s", winlist_str());
470 /* One more possibility: the only player playing left the game, which
471 * means there are now no players left. */
472 if (!players
[0] && !players
[1] && !players
[2] && !players
[3]
473 && !players
[4] && !players
[5])
477 /*************************************************************************/
478 /*************************************************************************/
480 /* Parse a line from a client. Destroys the buffer it's given as a side
481 * effect. Return 0 if the command is unknown (or bad syntax), else 1.
484 static int server_parse(int player
, char *buf
)
487 int i
, tetrifast
= 0;
489 cmd
= strtok(buf
, " ");
494 } else if (strcmp(cmd
, "tetrisstart") == 0) {
496 s
= strtok(NULL
, " ");
497 t
= strtok(NULL
, " ");
500 for (i
= 1; i
<= 6; i
++) {
501 if (players
[i
-1] && strcasecmp(s
, players
[i
-1]) == 0) {
502 send_to(player
, "noconnecting Nickname already exists on server!");
506 players
[player
-1] = strdup(s
);
508 free(teams
[player
-1]);
509 teams
[player
-1] = NULL
;
510 player_modes
[player
-1] = tetrifast
;
511 send_to(player
, "%s %d", tetrifast
? ")#)(!@(*3" : "playernum", player
);
512 send_to(player
, "winlist %s", winlist_str());
513 for (i
= 1; i
<= 6; i
++) {
514 if (i
!= player
&& players
[i
-1]) {
515 send_to(player
, "playerjoin %d %s", i
, players
[i
-1]);
516 send_to(player
, "team %d %s", i
, teams
[i
-1] ? teams
[i
-1] : "");
520 send_to(player
, "ingame");
521 player_lost
[player
-1] = 1;
523 send_to_all_but(player
, "playerjoin %d %s", player
, players
[player
-1]);
525 } else if (strcmp(cmd
, "tetrifaster") == 0) {
529 } else if (strcmp(cmd
, "team") == 0) {
530 s
= strtok(NULL
, " ");
531 t
= strtok(NULL
, "");
532 if (!s
|| atoi(s
) != player
)
537 teams
[player
] = strdup(t
);
539 teams
[player
] = NULL
;
540 send_to_all_but(player
, "team %d %s", player
, t
? t
: "");
542 } else if (strcmp(cmd
, "pline") == 0) {
543 s
= strtok(NULL
, " ");
544 t
= strtok(NULL
, "");
545 if (!s
|| atoi(s
) != player
)
549 send_to_all_but(player
, "pline %d %s", player
, t
);
551 } else if (strcmp(cmd
, "plineact") == 0) {
552 s
= strtok(NULL
, " ");
553 t
= strtok(NULL
, "");
554 if (!s
|| atoi(s
) != player
)
558 send_to_all_but(player
, "plineact %d %s", player
, t
);
560 } else if (strcmp(cmd
, "startgame") == 0) {
562 char piecebuf
[101], specialbuf
[101];
564 for (i
= 1; i
< player
; i
++) {
565 if (player_socks
[i
-1] >= 0)
568 s
= strtok(NULL
, " ");
569 t
= strtok(NULL
, " ");
573 if ((i
&& playing_game
) || (!i
&& !playing_game
))
575 if (!i
) { /* end game */
576 send_to_all("endgame");
581 for (i
= 0; i
< 7; i
++) {
583 memset(piecebuf
+total
, '1'+i
, piecefreq
[i
]);
584 total
+= piecefreq
[i
];
588 send_to_all("plineact 0 cannot start game: Piece frequencies do not total 100 percent!");
592 for (i
= 0; i
< 9; i
++) {
594 memset(specialbuf
+total
, '1'+i
, specialfreq
[i
]);
595 total
+= specialfreq
[i
];
599 send_to_all("plineact 0 cannot start game: Special frequencies do not total 100 percent!");
604 for (i
= 1; i
<= 6; i
++) {
605 if (player_socks
[i
-1] < 0)
607 /* XXX First parameter is stack height */
608 send_to(i
, "%s %d %d %d %d %d %d %d %s %s %d %d",
609 player_modes
[i
-1] ? "*******" : "newgame",
610 0, initial_level
, lines_per_level
, level_inc
,
611 special_lines
, special_count
, special_capacity
,
612 piecebuf
, specialbuf
, level_average
, old_mode
);
614 memset(player_lost
, 0, sizeof(player_lost
));
616 } else if (strcmp(cmd
, "pause") == 0) {
619 s
= strtok(NULL
, " ");
624 i
= 1; /* to make sure it's not anything else */
625 if ((i
&& game_paused
) || (!i
&& !game_paused
))
628 send_to_all("pause %d", i
);
630 } else if (strcmp(cmd
, "playerlost") == 0) {
631 if (!(s
= strtok(NULL
, " ")) || atoi(s
) != player
)
633 player_loses(player
);
635 } else if (strcmp(cmd
, "f") == 0) { /* field */
636 if (!(s
= strtok(NULL
, " ")) || atoi(s
) != player
)
638 if (!(s
= strtok(NULL
, "")))
640 send_to_all_but(player
, "f %d %s", player
, s
);
642 } else if (strcmp(cmd
, "lvl") == 0) {
643 if (!(s
= strtok(NULL
, " ")) || atoi(s
) != player
)
645 if (!(s
= strtok(NULL
, " ")))
647 levels
[player
] = atoi(s
);
648 send_to_all_but(player
, "lvl %d %d", player
, levels
[player
]);
650 } else if (strcmp(cmd
, "sb") == 0) {
654 if (!(s
= strtok(NULL
, " ")))
657 if (!(type
= strtok(NULL
, " ")))
659 if (!(s
= strtok(NULL
, " ")))
664 if (to
< 0 || to
> 6 || player_socks
[to
-1] < 0 || player_lost
[to
-1])
667 send_to_all_but_team(player
, "sb %d %s %d", to
, type
, from
);
669 send_to_all_but(player
, "sb %d %s %d", to
, type
, from
);
671 } else if (strcmp(cmd
, "gmsg") == 0) {
672 if (!(s
= strtok(NULL
, "")))
674 send_to_all("gmsg %s", s
);
676 } else { /* unrecognized command */
684 /*************************************************************************/
685 /*************************************************************************/
687 static void sigcatcher(int sig
)
691 signal(SIGHUP
, sigcatcher
);
692 send_to_all("winlist %s", winlist_str());
693 } else if (sig
== SIGTERM
|| sig
== SIGINT
) {
695 signal(sig
, SIG_IGN
);
699 /*************************************************************************/
701 /* Returns 0 on success, desired program exit code on failure */
705 struct sockaddr_in sin
;
707 struct sockaddr_in6 sin6
;
711 /* Set up some sensible defaults */
712 *winlist
[0].name
= 0;
720 special_capacity
= 18;
738 /* (Try to) read the config file */
741 /* Catch some signals */
742 signal(SIGHUP
, sigcatcher
);
743 signal(SIGINT
, sigcatcher
);
744 signal(SIGTERM
, sigcatcher
);
746 /* Set up a listen socket */
748 listen_sock
= socket(AF_INET
, SOCK_STREAM
, IPPROTO_TCP
);
749 if (listen_sock
>= 0){
751 if (setsockopt(listen_sock
,SOL_SOCKET
,SO_REUSEADDR
,&i
,sizeof(i
))==0) {
752 memset(&sin
, 0, sizeof(sin
));
753 sin
.sin_family
= AF_INET
;
754 sin
.sin_port
= htons(31457);
755 if (bind(listen_sock
, (struct sockaddr
*)&sin
, sizeof(sin
)) == 0) {
756 if (listen(listen_sock
, 5) == 0) {
769 /* Set up an IPv6 listen socket if possible */
770 listen_sock6
= socket(AF_INET6
, SOCK_STREAM
, IPPROTO_TCP
);
771 if (listen_sock6
>= 0) {
773 if (setsockopt(listen_sock6
,SOL_SOCKET
,SO_REUSEADDR
,&i
,sizeof(i
))==0) {
774 memset(&sin6
, 0, sizeof(sin6
));
775 sin6
.sin6_family
= AF_INET6
;
776 sin6
.sin6_port
= htons(31457);
777 if (bind(listen_sock6
,(struct sockaddr
*)&sin6
,sizeof(sin6
))==0) {
778 if (listen(listen_sock6
, 5) == 0) {
789 #else /* !HAVE_IPV6 */
791 fprintf(stderr
,"ipv6_only specified but IPv6 support not available\n");
794 #endif /* HAVE_IPV6 */
807 /*************************************************************************/
809 static void check_sockets()
815 if (listen_sock
>= 0)
816 FD_SET(listen_sock
, &fds
);
819 if (listen_sock6
>= 0)
820 FD_SET(listen_sock6
, &fds
);
821 if (listen_sock6
> maxfd
)
822 maxfd
= listen_sock6
;
824 for (i
= 0; i
< 6; i
++) {
825 if (player_socks
[i
] != -1) {
826 if (player_socks
[i
] < 0)
827 fd
= (~player_socks
[i
]) - 1;
829 fd
= player_socks
[i
];
836 if (select(maxfd
+1, &fds
, NULL
, NULL
, NULL
) <= 0)
839 if (listen_sock
>= 0 && FD_ISSET(listen_sock
, &fds
)) {
840 struct sockaddr_in sin
;
841 int len
= sizeof(sin
);
842 fd
= accept(listen_sock
, (struct sockaddr
*)&sin
, &len
);
844 for (i
= 0; i
< 6 && player_socks
[i
] != -1; i
++)
847 sockprintf(fd
, "noconnecting Too many players on server!");
850 player_socks
[i
] = ~(fd
+1);
851 memcpy(player_ips
[i
], &sin
.sin_addr
, 4);
854 } /* if (FD_ISSET(listen_sock)) */
857 if (listen_sock6
>= 0 && FD_ISSET(listen_sock6
, &fds
)) {
858 struct sockaddr_in6 sin6
;
859 int len
= sizeof(sin6
);
860 fd
= accept(listen_sock6
, (struct sockaddr
*)&sin6
, &len
);
862 for (i
= 0; i
< 6 && player_socks
[i
] != -1; i
++)
865 sockprintf(fd
, "noconnecting Too many players on server!");
868 player_socks
[i
] = ~(fd
+1);
869 memcpy(player_ips
[i
], (char *)(&sin6
.sin6_addr
)+12, 4);
872 } /* if (FD_ISSET(listen_sock6)) */
875 for (i
= 0; i
< 6; i
++) {
878 if (player_socks
[i
] == -1)
880 if (player_socks
[i
] < 0)
881 fd
= (~player_socks
[i
]) - 1;
883 fd
= player_socks
[i
];
884 if (!FD_ISSET(fd
, &fds
))
886 sgets(buf
, sizeof(buf
), fd
);
887 if (player_socks
[i
] < 0) {
888 /* Messy decoding stuff */
889 char iphashbuf
[16], newbuf
[1024];
893 if (strlen(buf
) < 2*13) { /* "tetrisstart " + initial byte */
895 player_socks
[i
] = -1;
899 sprintf(iphashbuf
, "%d", ip
[0]*54 + ip
[1]*41 + ip
[2]*29 + ip
[3]*17);
901 for (j
= 2; buf
[j
] && buf
[j
+1]; j
+= 2) {
903 temp
= d
= xtoi(buf
+j
);
904 d
^= iphashbuf
[((j
/2)-1) % strlen(iphashbuf
)];
911 if (strncmp(newbuf
, "tetrisstart ", 12) != 0) {
913 player_socks
[i
] = -1;
916 /* Buffers should be the same size, but let's be paranoid */
917 strncpy(buf
, newbuf
, sizeof(buf
));
918 buf
[sizeof(buf
)-1] = 0;
919 player_socks
[i
] = fd
; /* Has now registered */
920 } /* if client not registered */
921 if (!server_parse(i
+1, buf
)) {
923 player_socks
[i
] = -1;
925 send_to_all("playerleave %d", i
+1);
936 } /* for each player socket */
939 /*************************************************************************/
949 if ((i
= init()) != 0)
954 if (listen_sock
>= 0)
957 if (listen_sock6
>= 0)
960 for (i
= 0; i
< 6; i
++)
961 close(player_socks
[i
]);
965 /*************************************************************************/