1 #undef DEBUG
2
3 #include "config.h"
4 #include "copyright.h"
5
6 #ifdef META
7 #include INC_LIMITS
8 #include INC_FCNTL
9 #include <stdio.h>
10 #include <unistd.h>
11 #include <ctype.h>
12 #include <sys/types.h>
13 #include <sys/wait.h>
14 #include <time.h>
15 #include <stdlib.h>
16 #include INC_SYS_SELECT
17 #include INC_STRINGS
18 #include <sys/select.h>
19 #include <sys/socket.h>
20 #include INC_NETINET_IN
21 #include <arpa/inet.h>
22 #include <netdb.h>
23 #include <errno.h>
24 #include <signal.h>
25
26 #include "Wlib.h"
27 #include "defs.h"
28 #include "struct.h"
29 #include "data.h"
30 #include "version.h"
31 #include "patchlevel.h"
32
33 #include "badversion.h"
34 #include "defaults.h"
35 #include "newwin.h"
36
37 #include "parsemeta.h"
38
39 #ifdef WIN32 /* socket garbage in case the client is not running on NT */
40 #define read(f,b,l) recv(f,b,l,0)
41 #define write(f,b,l) send(f,b,l,0)
42 #define close(s) closesocket(s)
43 #endif
44
45 /* Constants */
46
47 #define BUF 6144
48 #define LINE 100 /* Width of a meta-server line */
49 #define MAXMETABYTES 2048 /* maximum metaserver UDP packet size */
50 static int msock = -1; /* the socket to talk to the metaservers */
51 static int sent = 0; /* number of solicitations sent */
52 static int seen = 0; /* number of replies seen */
53 static int verbose = 0; /* whether to talk a lot about it all */
54 static time_t last; /* time of last refresh */
55
56 /* Local Types */
57
58 struct servers {
59 char address[LINE]; /* host name or ip address of server */
60 int port;
61 int age; /* age in seconds as received */
62 time_t when; /* date time this record received */
63 int refresh; /* data needs redisplaying */
64 int lifetime; /* remaining cache life of entry */
65 int players;
66 int status;
67 char typeflag;
68 char comment[LINE];
69 pid_t pid; /* our last known child playing here */
70 int exitstatus; /* exit status of last known child here */
71 int observer; /* set if child is an observer */
72 };
73
74 struct servers *serverlist = NULL; /* The record for each server. */
75 static int num_servers = 0; /* The number of servers. */
76 static int chosen = -1; /* Arrow key chosen server. */
77 static int metaHeight = 0; /* The number of list lines. */
78 static char *metaWindowName; /* The window's name. */
79 static int statusLevel;
80 static W_Window metaWin, metaList, metaHelpWin = NULL;
81 void *logo;
82
83 /* button offsets from end of list */
84 #define B_ADD 4
85 #define B_REFRESH 3
86 #define B_HELP 2
87 #define B_QUIT 1
88
89 #define N_TITLES 1
90 #define N_BUTTONS 4
91 #define N_GAP 1
92 #define N_OVERHEAD N_TITLES+N_BUTTONS+N_GAP
93
94 /* The status strings: The order of the strings up until statusNull is
95 * important because the meta-client will display all the strings up to a
96 * particular point.
97 *
98 * The strings after statusNull are internal status types and are formatted
99 * separatly from the other strings.
100 *
101 * The string corresponding to "statusNull" is assigned to thoes servers which
102 * have "statusNobody" or earlier strings in old, cached, meta-server data. */
103
104 char *statusStrings[] =
105 {"", "Wait queue:", "-", "Timed out", "No connection",
106 "Active", "CANNOT CONNECT", "DEFAULT SERVER"};
107
108 enum statusTypes {
109 statusOpen = 0, statusWait, statusNobody, statusTout, statusNoConnect,
110 statusNull, statusCantConnect, statusDefault
111 };
112
113 static const int defaultStatLevel = statusTout;
114
115 /* Functions */
116 extern void terminate(int error);
117
118 char *metahelp_message[] =
119 {
120 "Netrek Server List - Help",
121 "",
122 "This is a list of Netrek servers, from the Metaserver, or on your",
123 "local network. You can do this:",
124 "",
125 " - click on a server to join the game there,",
126 "",
127 " - middle-click on a server to immediately join as guest,",
128 "",
129 " - right-click on a server to observe the game there,",
130 "",
131 " - click refresh to reload the list of servers,",
132 "",
133 "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --",
134 "",
135 "Tips for new Netrek players:",
136 "",
137 " 1. when you enter the game, press the number 4 to start moving,",
138 " other numbers are slower or faster,",
139 "",
140 " 2. you are the white ship in the centre of the left-hand screen,",
141 "",
142 " 3. use right-click on the mouse to steer toward a point in space,",
143 " you will find it easier to turn at lower speeds,",
144 "",
145 " 4. use left-click on the mouse to fire a torpedo, they travel over",
146 " time so you have to point ahead of your target, they hurt enemy",
147 " ships but usually pass right through your team,",
148 "",
149 " 5. use middle-click on your mouse to fire a phaser, they are",
150 " instant so point at your target, but only work well close up,",
151 "",
152 " 6. the aim of the game is to capture planets, killing enemies is",
153 " only a means to an end, and dying is a good thing, because you get a",
154 " new ship,",
155 "",
156 " 7. but if someone kills you, they can begin to capture planets, so",
157 " it is best to not die in vain,",
158 "",
159 " 8. people in your team will try to communicate so that you can all",
160 " do things together, because when you cooperate better than the other",
161 " team, you win.",
162 "",
163 " 9. use the 'h' key to see a list of commands for your ship.",
164 "",
165 "Observing is a good way to learn. When you join as an observer, you",
166 "won't see much until you use 'l' (lower-case L) to lock on to a",
167 "player. Choose a good player and watch what they do.",
168 "",
169 "For more information on Netrek, visit http://netrek.org/beginner",
170 NULL
171 };
172
make_help()173 static void make_help()
174 {
175 int i, l, h, w;
176
177 /* calculate width and height required */
178 h = (sizeof(metahelp_message)/sizeof(char *));
179 w = 0;
180 for (i=0; i<h; i++) {
181 char *line = metahelp_message[i];
182 if (line == NULL) break;
183 l = strlen(line);
184 if (l > w) w = l;
185 }
186 metaHelpWin = W_MakeWindow("Netrek Server List - Help", 500, 500,
187 w * W_Textwidth + 40, h * W_Textheight + 40,
188 0, 2, foreColor);
189 }
190
expo_help()191 static void expo_help()
192 {
193 int i, h;
194 h = (sizeof(metahelp_message)/sizeof(char *));
195 for (i=0; i<h; i++) {
196 char *line = metahelp_message[i];
197 if (line == NULL) break;
198 W_WriteText(metaHelpWin, 20, i * W_Textheight + 20, textColor, line, -1,
199 W_RegularFont);
200 }
201 }
202
show_help()203 static void show_help()
204 {
205 W_MapWindow(metaHelpWin);
206 }
207
hide_help()208 static void hide_help()
209 {
210 W_UnmapWindow(metaHelpWin);
211 }
212
toggle_help()213 static void toggle_help()
214 {
215 if (W_IsMapped(metaHelpWin)) {
216 hide_help();
217 } else {
218 show_help();
219 }
220 }
221
ReadMetasSend()222 static int ReadMetasSend()
223 {
224 char *metaservers; /* our copy of the metaserver host names */
225 char *token; /* current metaserver host name */
226 struct sockaddr_in address; /* the address of the metaservers */
227 static char *req; /* the request packet for the metaserver */
228 static int reqlen; /* the length of the request packet */
229
230 last = time(NULL);
231
232 /* host names of metaservers, default in data.c, comma delimited */
233 if ((getdefault("metaserver")) != NULL)
234 metaserver = getdefault("metaserver");
235
236 /* port number of metaservers, unlikely to change, not a list */
237 metaport = intDefault("metaport", metaport);
238
239 /* whether to report everything that happens */
240 verbose = booleanDefault("metaverbose", verbose);
241
242 /* create the socket */
243 if (msock < 0) {
244 msock = socket(AF_INET, SOCK_DGRAM, 0);
245 if (msock < 0) { perror("ReadMetasSend: socket"); return 0; }
246
247 /* bind the socket to any address */
248 address.sin_addr.s_addr = INADDR_ANY;
249 address.sin_family = AF_INET;
250 address.sin_port = 0;
251 if (bind(msock,(struct sockaddr *)&address, sizeof(address)) < 0) {
252 perror("ReadMetasSend: bind");
253 close(msock);
254 return 0;
255 }
256 req = malloc(80);
257 snprintf(req, 80, "?version=netrek-client-cow-%s.%d",
258 mvers, PATCHLEVEL);
259 reqlen = strlen(req);
260 }
261
262 /* send request to a multicast metaserver on local area network */
263 address.sin_family = AF_INET;
264 address.sin_port = htons(metaport);
265 address.sin_addr.s_addr = inet_addr("224.0.0.1");
266 if (verbose) fprintf(stderr,
267 "requesting player list from nearby servers on %s\n",
268 inet_ntoa(address.sin_addr));
269
270 if (sendto(msock, req, reqlen, 0, (struct sockaddr *)&address,
271 sizeof(address)) < 0) {
272 perror("ReadMetasSend: sendto");
273 } else {
274 sent++;
275 }
276
277 /* try each of the comma delimited metaserver host names */
278 metaservers = strdup(metaserver);
279 token = strtok(metaservers,",");
280
281 while (token != NULL) {
282 /* compose the address structure */
283 address.sin_family = AF_INET;
284 address.sin_port = htons(metaport);
285
286 /* skip any blanks */
287 while (*token == ' ') token++;
288
289 /* attempt numeric translation first */
290 if ((address.sin_addr.s_addr = inet_addr(token)) == -1) {
291 struct hostent *hp;
292
293 /* then translation by name */
294 if ((hp = gethostbyname(token)) == NULL) {
295 /* if it didn't work, return failure and warning */
296 fprintf(stderr,
297 "cannot resolve host %s, check for DNS problems?\n",
298 token);
299 } else {
300 int i;
301
302 /* resolution worked, send a query to every metaserver listed */
303 for(i=0;;i++) {
304
305 /* check for end of list of addresses */
306 if (hp->h_addr_list[i] == NULL) break;
307 address.sin_addr.s_addr = *(long *) hp->h_addr_list[i];
308 if (verbose) fprintf(stderr,
309 "requesting player list from metaserver %s at %s\n",
310 token, inet_ntoa(address.sin_addr));
311 if (sendto(msock, req, reqlen, 0, (struct sockaddr *)&address,
312 sizeof(address)) < 0) {
313 perror("ReadMetasSend: sendto");
314 } else {
315 sent++;
316 }
317 }
318 }
319 } else {
320 /* call to inet_addr() worked, host name is in IP address form */
321 if (verbose) fprintf(stderr,
322 "requesting player list from metaserver %s\n",
323 inet_ntoa(address.sin_addr));
324 if (sendto(msock, req, reqlen, 0, (struct sockaddr *)&address,
325 sizeof(address)) < 0) {
326 perror("ReadMetasSend: sendto");
327 } else {
328 sent++;
329 }
330 }
331
332 /* look for next host name in list */
333 token = strtok(NULL,",");
334 } /* while (token != NULL) */
335
336 metaWindowName = "Netrek Server List";
337 return sent;
338 }
339
340 /* allocate or extend the server list */
grow(int servers)341 static void grow(int servers)
342 {
343 int size;
344 if (serverlist == NULL) {
345 size = sizeof(struct servers) * servers;
346 serverlist = (struct servers *) malloc(size);
347 } else {
348 size = sizeof(struct servers) * (servers + num_servers);
349 serverlist = (struct servers *) realloc(serverlist, size);
350 }
351 }
352
server_find(char * address,int port)353 static struct servers *server_find(char *address, int port)
354 {
355 int j;
356
357 for(j=0;j<num_servers;j++) {
358 struct servers *sp = serverlist + j;
359 if ((!strcmp(sp->address, address)) && (sp->port == port)) {
360 return sp;
361 }
362 }
363 return NULL;
364 }
365
version_r(struct sockaddr_in * address)366 static void version_r(struct sockaddr_in *address) {
367 char *p;
368 int servers, i;
369 time_t now = time(NULL);
370
371 /* number of servers */
372 p = strtok(NULL,"\n");
373 if (p == NULL) return;
374 servers = atoi(p);
375
376 /* sanity check on number of servers */
377 if (servers > 2048) return;
378 if (servers < 0) return;
379
380 if (verbose) fprintf(stderr,
381 "metaserver at %s responded with %d server%s\n",
382 inet_ntoa(address->sin_addr),
383 servers,
384 servers == 1 ? "" : "s" );
385
386 if (servers == 0) return;
387
388 /* for each server listed by this metaserver packet */
389 for(i=0;i<servers;i++) {
390 struct servers *sp = NULL;
391 char *host, type;
392 int port, status, age, players, queue, throwaway;
393
394 throwaway = 0;
395
396 host = strtok(NULL,","); /* hostname */
397 if (host == NULL) continue;
398
399 p = strtok(NULL,","); /* port */
400 if (p == NULL) continue;
401 port = atoi(p);
402
403 p = strtok(NULL,","); /* status */
404 if (p == NULL) continue;
405 status = atoi(p);
406
407 /* ignore servers based on status */
408 if (status > statusLevel)
409 throwaway++;
410 /* the sp->why_dead workaround */
411 if (status == 6)
412 if ((status - 3) <= statusLevel)
413 throwaway--;
414
415 p = strtok(NULL,","); /* age (of data in seconds) */
416 if (p == NULL) continue;
417 age = atoi(p);
418
419 p = strtok(NULL,","); /* players */
420 if (p == NULL) continue;
421 players = atoi(p);
422
423 p = strtok(NULL,","); /* queue size */
424 if (p == NULL) continue;
425 queue = atoi(p);
426
427 p = strtok(NULL,"\n"); /* server type */
428 if (p == NULL) continue;
429 type = p[0];
430
431 /* ignore paradise servers */
432 if (type == 'P') throwaway++;
433
434 /* if it's to be thrown away, do not add this server, skip to next */
435 if (throwaway) continue;
436
437 /* find in current server list? */
438 sp = server_find(host, port);
439
440 /* if it was found, check age */
441 if (sp != NULL) {
442 if ((now-age) < (sp->when-sp->age)) {
443 sp->age = now - (sp->when-sp->age);
444 sp->when = now;
445 sp->refresh = 1;
446 sp->lifetime = 20;
447 continue;
448 } else {
449 sp->age = age;
450 sp->when = now;
451 sp->lifetime = 20;
452 }
453 } else {
454 /* not found, store it at the end of the list */
455 grow(1);
456 sp = serverlist + num_servers;
457 num_servers++;
458 strncpy(sp->address,host,LINE);
459 sp->port = port;
460 sp->age = age;
461 sp->when = now;
462 sp->lifetime = 4;
463 }
464 sp->refresh = 1;
465
466 /* from meta.h of metaserver code */
467 #define SS_WORKING 0
468 #define SS_QUEUE 1
469 #define SS_OPEN 2
470 #define SS_EMPTY 3
471 #define SS_NOCONN 4
472 #define SS_INIT 5
473 /* not a real metaserver number, but overcomes a limitation of dropping text */
474 /* description of sp->why_dead */
475 #define SS_TOUT 6
476 switch (status) {
477 case SS_QUEUE:
478 sp->status = statusWait;
479 sp->players = queue;
480 break;
481 case SS_OPEN:
482 sp->status = statusOpen;
483 sp->players = players;
484 break;
485 case SS_EMPTY:
486 sp->status = statusNobody;
487 sp->players = 0;
488 break;
489 case SS_TOUT:
490 sp->status = statusTout;
491 sp->players = 0;
492 break;
493 case SS_NOCONN: /* no connection */
494 case SS_WORKING: /* metaserver should not return this */
495 case SS_INIT: /* nor this */
496 default:
497 sp->status = statusNoConnect;
498 sp->players = 0;
499 break;
500 }
501 sp->typeflag = type;
502 strcpy(sp->comment, "");
503 sp->pid = -1;
504 sp->exitstatus = 0;
505 sp->observer = 0;
506 }
507 }
508
version_s(struct sockaddr_in * address)509 static void version_s(struct sockaddr_in *address)
510 {
511 char *p;
512 time_t now = time(NULL);
513
514 /* use return address on packet as host address for this server,
515 since it isn't practical for the server to know it's own address; as
516 is the case with multihomed machines */
517 char *host = inet_ntoa(address->sin_addr);
518
519 if (verbose) fprintf(stderr, "server at %s responded\n", host);
520
521 p = strtok(NULL,","); /* server type */
522 if (p == NULL) return;
523 char type = p[0];
524
525 /* ignore paradise servers */
526 if (type == 'P') return;
527
528 p = strtok(NULL,","); /* comment */
529 if (p == NULL) return;
530 char *comment = strdup(p);
531 if (strlen(comment) > LINE) comment[LINE] = '\0';
532
533 p = strtok(NULL,","); /* number of ports */
534 if (p == NULL) return;
535 // int ports = atoi(p); /* not currently used */
536
537 // TODO: accept more than one port reply
538
539 p = strtok(NULL,","); /* port */
540 if (p == NULL) return;
541 int port = atoi(p);
542
543 p = strtok(NULL,","); /* players */
544 if (p == NULL) return;
545 int players = atoi(p);
546
547 p = strtok(NULL,","); /* queue size */
548 if (p == NULL) return;
549 // int queue = atoi(p); /* not currently used */
550
551 /* find in current server list? */
552 struct servers *sp = server_find(host, port);
553
554 /* if it was not found, add it to the end of the list */
555 if (sp == NULL) {
556 grow(1);
557 sp = serverlist + num_servers;
558 num_servers++;
559 }
560
561 /* add or update the entry */
562 strncpy(sp->address, host, LINE);
563 sp->port = port;
564 sp->age = 0;
565 sp->when = now;
566 sp->refresh = 1;
567 sp->lifetime = 20;
568 sp->players = players;
569 if (type == 'u' && players == 0) {
570 sp->status = statusNobody;
571 } else {
572 sp->status = statusOpen;
573 }
574 sp->typeflag = type;
575 strncpy(sp->comment, comment, LINE);
576 sp->pid = -1;
577 sp->exitstatus = 0;
578 sp->observer = 0;
579 free(comment);
580 }
581
ReadMetasRecv(int x)582 static int ReadMetasRecv(int x)
583 {
584 struct sockaddr_in address; /* the address of the metaservers */
585 socklen_t length; /* length of the address */
586 int bytes; /* number of bytes received from meta' */
587 fd_set readfds; /* the file descriptor set for select() */
588 struct timeval timeout; /* timeout for select() call */
589 char packet[MAXMETABYTES]; /* buffer for packet returned by meta' */
590 char *p;
591
592 /* now await and process replies */
593
594 FD_ZERO(&readfds);
595 if (msock >= 0) FD_SET(msock, &readfds);
596 timeout.tv_sec = 10;
597 timeout.tv_usec = 0;
598
599 if (x != -1) FD_SET(x, &readfds);
600 if (select(FD_SETSIZE, &readfds, NULL, NULL, &timeout) < 0) {
601 if (errno == EINTR) return 0;
602 perror("ReadMetasRecv: select");
603 return 0;
604 }
605
606 /* if x activity, return immediately */
607 if (x != -1 && FD_ISSET(x, &readfds)) return 0;
608 if (msock < 0) return 0;
609
610 /* if the wait timed out, then we give up */
611 if (!FD_ISSET(msock, &readfds)) return 0;
612
613 /* so we have data back from a metaserver or server */
614 length = sizeof(address);
615 bytes = recvfrom(msock, packet, MAXMETABYTES, 0,
616 (struct sockaddr *)&address, &length );
617 if (bytes < 0) {
618 perror("ReadMetasRecv: recvfrom");
619 return 0;
620 }
621
622 /* terminate the packet received */
623 packet[bytes] = 0;
624 #ifdef DEBUG
625 fprintf(stderr, "%s", packet);
626 #endif /* DEBUG */
627
628 /* process the packet, updating our server list */
629
630 /* version identifier */
631 p = strtok(packet,",");
632 if (p == NULL) return 0;
633
634 switch (p[0]) {
635 case 'r': version_r(&address); seen++; break;
636 case 's': version_s(&address); seen++; break;
637 }
638
639 return 1;
640 }
641
SaveMetasCache()642 static void SaveMetasCache()
643 {
644 FILE *cache;
645 char cacheFileName[PATH_MAX];
646 char tmpFileName[PATH_MAX];
647 char *cacheName;
648 int len;
649
650 cacheName = getdefault("metaUDPCache");
651 /* overwrite existing file if possible */
652 if (cacheName && !findfile(cacheName, cacheFileName))
653 strcpy(cacheFileName, cacheName);
654
655 if (cacheName)
656 {
657 len = strlen(cacheFileName);
658 strcpy(tmpFileName, cacheFileName);
659
660 /* create a temporary file with roughly the same name */
661 if ((cacheFileName[len - 1] == 'T') || (cacheFileName[len - 1] == 't'))
662 tmpFileName[len-1] = 'R';
663 else
664 tmpFileName[len-1] = 'T';
665
666 cache = fopen(tmpFileName, "w");
667
668 if (cache == NULL)
669 {
670 fprintf(stderr,
671 "Cannot create a metaUDPCache temporary file `%s'\n",
672 tmpFileName);
673 fprintf(stderr, "Meta-server read will not be cached.\n");
674 }
675 }
676 else
677 {
678 cache = NULL;
679 }
680
681
682 if (cache != NULL)
683 {
684
685 if (fwrite(&statusLevel, sizeof(statusLevel), 1, cache) != 1 ||
686 fwrite(&num_servers, sizeof(num_servers), 1, cache) != 1 ||
687 fwrite(serverlist, sizeof(struct servers), num_servers, cache) != num_servers) {
688 int xerrno = errno;
689 fclose(cache);
690 #ifdef _MSC_VER
691 _unlink(tmpFileName);
692 #else
693 unlink(tmpFileName);
694 #endif
695 errno = xerrno;
696 perror("Could not write to the new cache file");
697 }
698
699 fclose(cache);
700
701 #ifdef WIN32
702 /* Can't rename file to existing name under NT */
703 #ifdef _MSC_VER
704 _unlink(cacheName);
705 #else
706 unlink(cacheName);
707 #endif
708 #endif
709 if (rename(tmpFileName, cacheName) == -1)
710 perror("Could not rename new cache file");
711 }
712
713 }
714
LoadMetasCache()715 static void LoadMetasCache()
716 {
717 FILE *cache;
718 char *cacheName;
719 char cacheFileName[PATH_MAX];
720 int i;
721
722 cacheName = getdefault("metaUDPCache");
723
724 if(!cacheName)
725 {
726 num_servers = 0;
727 return;
728 }
729
730 findfile(cacheName, cacheFileName);
731
732 cache = fopen(cacheFileName, "r");
733 if (cache == NULL)
734 {
735 num_servers = 0;
736 return;
737 }
738
739 /* ignore the cache if user changed statusLevel */
740 if (fread(&i, sizeof(i), 1, cache) != 1 ||
741 i != statusLevel)
742 {
743 num_servers = 0;
744 fclose(cache);
745 return;
746 }
747
748 /* read the server list into memory from the file */
749 if (fread(&num_servers, sizeof(num_servers), 1, cache) != 1) {
750 num_servers = 0;
751 fclose(cache);
752 return;
753 }
754 serverlist = (struct servers *) malloc(sizeof(struct servers)*num_servers);
755 if (fread(serverlist, sizeof(struct servers), num_servers, cache) != num_servers) {
756 free(serverlist);
757 serverlist = NULL;
758 num_servers = 0;
759 fclose(cache);
760 return;
761 }
762 fclose(cache);
763
764 /* hunt and kill old server lines from cache */
765 for(i=0;i<num_servers;i++)
766 {
767 int j;
768
769 /* mark each server as needing to be refreshed */
770 serverlist[i].refresh = 1;
771
772 /* skip the deletion below if the entry was received recently */
773 if (serverlist[i].lifetime-- > 0) continue;
774
775 /* delete this entry by moving the ones above down */
776 for(j=i;j<num_servers-1;j++)
777 {
778 memcpy(&serverlist[j],&serverlist[j+1],sizeof(struct servers));
779 }
780
781 /* adjust the current entry pointer, limit, and resize the memory */
782 i--;
783 num_servers--;
784 serverlist =
785 (struct servers *) realloc(serverlist,
786 sizeof(struct servers) * (num_servers));
787 }
788 }
789
790
parsemeta()791 void parsemeta()
792 /* Read and Parse the metaserver information, either from the metaservers
793 * by UDP (1), from a single metaserver by TCP (3), or from the cache (2).
794 */
795 {
796 statusLevel = intDefault("metaStatusLevel", defaultStatLevel);
797
798 if (statusLevel < 0)
799 statusLevel = 0;
800 else if (statusLevel >= statusNull)
801 statusLevel = statusNull - 1;
802
803 ReadMetasSend();
804 LoadMetasCache();
805 metaHeight = 2 + N_OVERHEAD;
806 }
807
808
redraw(int i)809 static void redraw(int i)
810 /* Redraw line i in the list */
811 {
812 char buf[LINE + 1];
813 struct servers *sp;
814
815 /* can't say a thing if line is beyond server list */
816 if (i >= num_servers) {
817 /* but we can at least blank the line shown */
818 if (i < metaHeight-3) W_WriteText(metaList, 0, i+1, W_White, "", 0, 0);
819 return;
820 }
821
822 sp = serverlist + i;
823
824 snprintf(buf, LINE, "%-40s ",
825 strlen(sp->comment) > 0 ? sp->comment : sp->address);
826 buf[80] = '\0';
827
828 switch(sp->status) {
829 case statusOpen:
830 snprintf(buf + strlen(buf), sp->players, "################");
831 snprintf(buf + strlen(buf), 17 - sp->players, " ");
832 strcat(buf, " ");
833 break;
834 case statusWait:
835 snprintf(buf + strlen(buf), 16, "################ Q%2d", sp->players);
836 break;
837 default:
838 snprintf(buf + strlen(buf), 16, "%-16s", statusStrings[sp->status]);
839 strcat(buf, " ");
840 break;
841 }
842
843 switch (sp->typeflag) {
844 case 'P':
845 strcat(buf, "Paradise");
846 break;
847 case 'B':
848 strcat(buf, "Bronco ");
849 break;
850 case 'C':
851 strcat(buf, "Chaos ");
852 break;
853 case 'I':
854 strcat(buf, "INL ");
855 break;
856 case 'S':
857 strcat(buf, "Sturgeon");
858 break;
859 case 'H':
860 strcat(buf, "Hockey ");
861 break;
862 case 'F':
863 strcat(buf, "Dogfight");
864 break;
865 default:
866 strcat(buf, "Unknown ");
867 break;
868 }
869
870 {
871 int age = sp->age;
872 char *units;
873
874 if (age > 86400) {
875 age = age / 86400;
876 units = "d";
877 } else if (age > 3600) {
878 age = age / 3600;
879 units = "h";
880 } else if (age > 90) {
881 age = age / 60;
882 units = "m";
883 } else {
884 units = "s";
885 }
886 sprintf(buf + strlen(buf), " %4d%s", age, units);
887 }
888
889 strcat(buf, " ");
890 if (sp->pid != -1) {
891 strcat(buf, sp->observer ? "Observing" : "Playing" );
892 } else {
893 switch (sp->exitstatus) {
894 case EXIT_FORK_FAILURE:
895 strcat(buf, "Cannot Start");
896 break;
897 case EXIT_UNKNOWN:
898 case EXIT_OK:
899 break;
900 case EXIT_CONNECT_FAILURE:
901 strcat(buf, "Connect Fail");
902 break;
903 case EXIT_LOGIN_FAILURE:
904 strcat(buf, "Login Fail");
905 break;
906 default:
907 {
908 int badversion = (sp->exitstatus - EXIT_BADVERSION_BASE);
909 if (badversion >= 0 && badversion <= MAXBADVERSION) {
910 strcat(buf, badversion_short_strings[badversion]);
911 } else {
912 strcat(buf, "Unknown Response");
913 }
914 }
915 break;
916 }
917 }
918
919 W_Color color = W_White;
920 if (i == chosen) color = W_Green;
921 if (sp->status == statusCantConnect) color = W_Red;
922 if (sp->pid != -1) color = W_Cyan;
923
924 W_WriteText(metaList, 0, i+1, color, buf, -1, 0);
925 sp->refresh = 0;
926 }
927
928
929 static char add_buffer[LINE];
930 static int add_offset;
931
add_init()932 static void add_init()
933 {
934 add_buffer[0] = '\0';
935 add_offset = 0;
936 }
937
add_redraw()938 static void add_redraw()
939 {
940 char buf[LINE + 1];
941
942 snprintf(buf, LINE, "Add a server: %s_", add_buffer);
943 W_WriteText(metaList, 0, metaHeight-B_ADD, W_Yellow, buf, -1, 0);
944 }
945
add_commit()946 static void add_commit()
947 {
948 struct servers *sp;
949
950 grow(1);
951 sp = serverlist + num_servers;
952 num_servers++;
953 strncpy(sp->address, add_buffer, LINE);
954 sp->port = 2592;
955 sp->age = 0;
956 sp->when = time(NULL);
957 sp->refresh = 1;
958 sp->lifetime = 20;
959 sp->players = 0;
960 sp->status = statusNobody;
961 sp->typeflag = 'U';
962 strncpy(sp->comment, add_buffer, LINE);
963 sp->pid = -1;
964 sp->exitstatus = 0;
965 sp->observer = 0;
966 metawindow();
967 }
968
add_key(W_Event * data)969 static int add_key(W_Event *data)
970 {
971 if (data->key == '\r') {
972 add_commit();
973 add_init();
974 add_redraw();
975 } else if (data->key == 21) {
976 add_init();
977 add_redraw();
978 } else if (data->key == 8 || data->key == '\177') {
979 if (add_offset > 0) {
980 add_buffer[add_offset-1] = '\0';
981 add_offset--;
982 add_redraw();
983 }
984 } else if (add_offset < (LINE-1)) {
985 add_buffer[add_offset+1] = '\0';
986 add_buffer[add_offset] = data->key;
987 add_offset++;
988 add_redraw();
989 }
990 return 0;
991 }
992
metawindow()993 void metawindow()
994 /* Show the meta server menu window */
995 {
996 int i, height;
997 char *header;
998 static int lastHeight = 0;
999
1000 if (!metaWin) {
1001 height = 350 + metaHeight * (W_Textheight + 8) + 4 * (metaHeight - 1);
1002 metaWin = W_MakeWindow("Netrek Server List", 0, 0, 716, height, NULL, 2,
1003 foreColor);
1004 W_SetBackgroundImage(metaWin, "Misc/map_back.png");
1005 logo = W_ReadImage(metaWin, "netrek-green-white-300px.png");
1006 metaList = W_MakeMenu("metalist", 50, 200, LINE, metaHeight, metaWin, 1);
1007 lastHeight = metaHeight;
1008 make_help();
1009 } else {
1010 if (metaHeight > lastHeight) {
1011 W_ReinitMenu(metaList, LINE, metaHeight);
1012 W_ResizeMenu(metaList, LINE, metaHeight);
1013 lastHeight = metaHeight;
1014 }
1015 // FIXME: handle metaList growing beyond metaWin
1016 }
1017
1018 header = "Server Players Type Age";
1019 W_WriteText(metaList, 0, 0, W_Cyan, header, -1, 0);
1020
1021 for (i = 0; i < metaHeight; i++) redraw(i);
1022
1023 /* Give the window the right name */
1024 W_RenameWindow(metaWin, metaWindowName);
1025
1026 /* Add additional options */
1027 W_WriteText(metaList, 0, metaHeight-B_REFRESH, W_Yellow,
1028 "Refresh (r)",
1029 -1, 0);
1030 add_redraw();
1031 W_WriteText(metaList, 0, metaHeight-B_HELP, W_Yellow,
1032 "Help & Tips (h)",
1033 -1, 0);
1034 W_WriteText(metaList, 0, metaHeight-B_QUIT, W_Yellow,
1035 "Quit (q)",
1036 -1, 0);
1037
1038 /* Map window */
1039 W_MapWindow(metaList);
1040 W_MapWindow(metaWin);
1041 }
1042
1043
metadone(void)1044 static void metadone(void)
1045 {
1046 W_UnmapWindow(metaList);
1047 W_DropImage(logo);
1048 W_UnmapWindow(metaWin);
1049 SaveMetasCache();
1050 free(serverlist);
1051 }
1052
1053
refresh()1054 static void refresh()
1055 {
1056 W_WriteText(metaList, 0, metaHeight-B_REFRESH, W_Red,
1057 "Refresh (in progress)", -1, 0);
1058 W_NextScreenShot(metaWin, 0, 0);
1059 W_Flush();
1060 ReadMetasSend();
1061 }
1062
1063
refresh_cyclic()1064 static void refresh_cyclic()
1065 {
1066 struct servers *sp;
1067 int i, interval = 30;
1068
1069 /* while we have a local player, chances are they want their network
1070 link for play, and their eyes are on the tactical, they don't need
1071 to know about other servers */
1072 for (i=0;i<num_servers;i++) {
1073 sp = serverlist + i;
1074 if (sp->pid != -1) {
1075 if (!sp->observer) return;
1076 interval = 90;
1077 }
1078 }
1079
1080 /* don't do until sufficient time has elapsed */
1081 if ((time(NULL) - last) > interval) {
1082 W_NextScreenShot(metaWin, 0, 0);
1083 ReadMetasSend();
1084 }
1085 }
1086
1087
choose(int way)1088 static void choose(int way)
1089 {
1090 int was;
1091 int lo = 0;
1092 int hi = num_servers - 1;
1093
1094 if (hi < 0) {
1095 return;
1096 }
1097
1098 was = chosen;
1099 if (chosen == -1) {
1100 chosen = lo;
1101 if (way > 0) chosen = lo;
1102 if (way < 0) chosen = hi;
1103 } else {
1104 if (way > 0) { chosen++; if (chosen > hi) chosen = hi; }
1105 if (way < 0) { chosen--; if (chosen < lo) chosen = lo; }
1106 }
1107 if (was != chosen) {
1108 if (was != -1) redraw(was);
1109 redraw(chosen);
1110 }
1111 }
1112
1113
chose(int which,unsigned char key)1114 static int chose(int which, unsigned char key)
1115 {
1116 struct servers *sp;
1117 pid_t pid;
1118
1119 if (which != chosen) {
1120 int was;
1121 was = chosen;
1122 chosen = which;
1123 if (was != -1) redraw(was);
1124 redraw(chosen);
1125 }
1126
1127 sp = serverlist + which;
1128 xtrekPort = sp->port;
1129 if (key == W_RBUTTON) { /* Guess at an observer port */
1130 xtrekPort++;
1131 fprintf(stderr,
1132 "you chose to observe on %s, guessing port %d\n",
1133 sp->address, xtrekPort);
1134 }
1135 serverName = strdup(sp->address);
1136
1137 sp->pid = -1;
1138 sp->exitstatus = EXIT_UNKNOWN;
1139 fprintf(stderr, "you chose server %s port %d\n", serverName, xtrekPort);
1140
1141 if ((pid = fork()) == 0) {
1142 char *args[16];
1143 int argc = 0;
1144
1145 setpgid(0, 0);
1146 args[argc++] = program;
1147 args[argc++] = "--server";
1148 args[argc++] = serverName;
1149 if (xtrekPort != DEFAULT_PORT) {
1150 char port[32];
1151 sprintf(port, "%d", xtrekPort);
1152 args[argc++] = "--port";
1153 args[argc++] = strdup(port);
1154 }
1155
1156 if (key == W_MBUTTON)
1157 args[argc++] = "--fast-guest";
1158
1159 args[argc++] = NULL;
1160
1161 execvp(program, args);
1162 perror("execvp");
1163 _exit(1);
1164 }
1165
1166 /* we are the parent, did the fork fail? */
1167 if (pid < 0) {
1168 perror("fork");
1169 sp->exitstatus = EXIT_FORK_FAILURE;
1170 } else {
1171 sp->pid = pid;
1172 sp->observer = (key == W_RBUTTON);
1173 }
1174
1175 redraw(which);
1176 W_Flush();
1177 return 0;
1178 }
1179
1180
button(W_Event * data)1181 static int button(W_Event *data)
1182 {
1183 if ((data->y > 0) && (data->y <= num_servers)) { /* click on server */
1184 return chose(data->y - 1, data->key);
1185 }
1186 if (data->y == (metaHeight-B_REFRESH)) { /* refresh */
1187 refresh();
1188 } else if (data->y == metaHeight-B_HELP) { /* help */
1189 toggle_help();
1190 } else if (data->y == metaHeight-B_QUIT) { /* quit */
1191 metadone();
1192 terminate(0);
1193 }
1194 return 0;
1195 }
1196
1197
key(W_Event * data)1198 static int key(W_Event *data)
1199 {
1200 if (data->y == (metaHeight-B_ADD)) return add_key(data);
1201 if (data->key == 113 || data->key == 196) { /* q or ^d */
1202 metadone();
1203 terminate(0);
1204 } else if (data->key == 114 || data->key == 210) { /* r or ^r */
1205 refresh();
1206 } else if (data->key == W_Key_Up) {
1207 choose(-1);
1208 } else if (data->key == W_Key_Down) {
1209 choose(1);
1210 } else if (data->key == '\r' || data->key == ' ') { /* enter or space */
1211 if (chosen != -1) return chose(chosen, W_LBUTTON);
1212 } else if (data->key == 'g') { /* g, for guest */
1213 if (chosen != -1) return chose(chosen, W_MBUTTON);
1214 } else if (data->key == 'o') { /* o, for observe */
1215 if (chosen != -1) return chose(chosen, W_RBUTTON);
1216 } else if (data->key == 'h') {
1217 toggle_help();
1218 } else {
1219 return button(data);
1220 }
1221 return 0;
1222 }
1223
1224
1225 static int metareap_needed = 0;
1226
1227 /*! @brief Check if a child process, a playing client, has terminated.
1228 @details Attempts a no hang wait on each active client process in
1229 the server list and clears the pid entry if a child has
1230 terminated.
1231 @return activity count, number of processes seen to have terminated. */
metareap(void)1232 static int metareap(void)
1233 {
1234 struct servers *sp;
1235 int i, status, activity;
1236 pid_t pid;
1237
1238 metareap_needed = 0;
1239 activity = 0;
1240 for (i=0;i<num_servers;i++) {
1241 sp = serverlist + i;
1242 if (sp->pid != -1) {
1243 pid = waitpid(sp->pid, &status, WNOHANG);
1244 if (pid == sp->pid) {
1245 sp->pid = -1;
1246 if (WIFEXITED(status)) {
1247 activity++;
1248 sp->exitstatus = WEXITSTATUS(status);
1249 }
1250 redraw(i);
1251 W_Flush();
1252 }
1253 }
1254 }
1255 return activity;
1256 }
1257
1258
1259 /*! @brief child death signal handler
1260 @details does nothing much, but needs to exist to ensure that
1261 select(2) is given EINTR when a child terminates, otherwise we
1262 will not note a termination until after the select times out.
1263 */
sigchld(int ignored)1264 void sigchld(int ignored)
1265 {
1266 metareap_needed++;
1267 }
1268
1269
metainput(void)1270 void metainput(void)
1271 /* Wait for actions in the meta-server window.
1272 *
1273 * This is really the meta-server window's own little input() function. It is
1274 * needed so we don't have to use all the bull in the main input(). Plus to
1275 * use it I'd have to call mapAll() first and the client would read in the
1276 * default server and then call it up before I can select a server. */
1277 {
1278 W_Event data;
1279
1280 (void) SIGNAL(SIGCHLD, sigchld);
1281 while (W_IsMapped(metaWin)) {
1282 while (1) {
1283 W_Flush();
1284 if (W_EventsPending()) break;
1285 if (ReadMetasRecv(W_Socket()) || metareap_needed) {
1286 metareap();
1287 metaHeight = num_servers + N_OVERHEAD;
1288 metawindow();
1289 W_Flush();
1290 }
1291 refresh_cyclic();
1292 }
1293 W_NextEvent(&data);
1294 switch ((int) data.type) {
1295 case W_EV_KEY:
1296 if (data.Window == metaList || data.Window == metaWin)
1297 if (key(&data)) return;
1298 if (data.Window == metaHelpWin) hide_help();
1299 break;
1300 case W_EV_BUTTON:
1301 if (data.Window == metaList)
1302 if (button(&data)) return;
1303 if (data.Window == metaHelpWin) hide_help();
1304 if (data.Window == metaWin && data.y < 200)
1305 W_NextScreenShot(metaWin, 0, 0);
1306 break;
1307 case W_EV_EXPOSE:
1308 if (data.Window == metaHelpWin) expo_help();
1309 if (data.Window == metaWin) {
1310 W_DrawScreenShot(metaWin, 0, 0);
1311 W_DrawImage(200, 9, logo);
1312 }
1313 break;
1314 case W_EV_CLOSED:
1315 if (data.Window == metaWin) {
1316 fprintf(stderr, "you quit, by closing the server list window\n");
1317 terminate(0);
1318 }
1319 break;
1320 default:
1321 break;
1322 }
1323 }
1324 }
1325
1326 #endif
1327