1 /*
2  * XPilot NG, a multiplayer space war game.
3  *
4  * Copyright (C) 1999-2004 by
5  *
6  *      Marcus Sundberg      <mackan@stacken.kth.se>
7  *      Kristian S�derblom   <kps@users.sourceforge.net>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  */
23 
24 #include "xpserver.h"
25 
26 /* MAX_SCORES = how many players we remember */
27 #define MAX_SCORES 300
28 
29 static bool Rank_parse_rankfile(FILE *file);
30 
31 /* Score data */
32 static ranknode_t ranknodes[MAX_SCORES];
33 
34 typedef struct rank {
35     int ind;
36     double ratio;
37 } rank_t;
38 
39 static rank_t rank_base[MAX_SCORES];
40 
41 static double sc_table[MAX_SCORES];
42 static double kr_table[MAX_SCORES];
43 static double kd_table[MAX_SCORES];
44 static double hf_table[MAX_SCORES];
45 
46 static bool playerstag = false;
47 static int num_players = 0;
48 static int rank_entries = 0;
49 
rank_cmp(const void * p1,const void * p2)50 static int rank_cmp(const void *p1, const void *p2)
51 {
52     const rank_t *r1, *r2;
53 
54     r1 = (const rank_t *)p1;
55     r2 = (const rank_t *)p2;
56 
57     /*
58      * Function qsort(3) normally sorts array elements into ascending order.
59      * We want a descending order (greatest ratio first), that's why we
60      * tell qsort a greater ratio is less than a lesser ratio.
61      */
62     if (r1->ratio < r2->ratio)
63 	return 1;
64     if (r1->ratio > r2->ratio)
65 	return -1;
66     return 0;
67 }
68 
rank_showtime(const time_t t)69 static char *rank_showtime(const time_t t)
70 {
71     static char buf[80];
72 
73     strftime(buf, sizeof(buf), "%d\xA0%b\xA0%Y\xA0%H:%M\xA0UTC",
74 	gmtime(&t));
75     return buf;
76 }
77 
78 /*
79  * Encode 'str' for XML (xml set to nonzero) or HTML (xml set to zero).
80  */
encode(const char * str,int xml)81 static char *encode(const char *str, int xml)
82 {
83     static char result[MAX_CHARS];
84     char c;
85 
86     result[0] = '\0';
87     while ((c = *str++) != '\0') {
88 	if (c == '<')
89 	    strlcat(result, "&lt;", sizeof(result));
90 	else if (c == '>')
91 	    strlcat(result, "&gt;", sizeof(result));
92 	else if (c == '&')
93 	    strlcat(result, "&amp;", sizeof(result));
94 	else if (c == '\'' && xml)
95 	    strlcat(result, "&apos;", sizeof(result));
96 	else if (c == '"')
97 	    strlcat(result, "&quot;", sizeof(result));
98 	else {
99 	    char tmp[2];
100 
101 	    sprintf(tmp, "%c", c);
102 	    strlcat(result, tmp, sizeof(result));
103 	}
104     }
105 
106     return result;
107 }
108 
109 /* Here's where we calculate the ranks. Figure it out yourselves! */
SortRankings(void)110 static void SortRankings(void)
111 {
112     double lowSC = 0.0, highSC = 0.0;
113     double lowKD = 0.0, highKD = 0.0;
114     double lowKR = 0.0, highKR = 0.0;
115     double lowHF = 0.0, highHF = 0.0;
116     bool foundFirst = false;
117     int k;
118 
119     /* Ok, there are two loops: the first one calculates the scores and
120        records lowest and highest scores. The second loop combines the
121        scores into a rank. I cannot do it in one loop since I need to
122        know low- and highmarks for each score before I can calculate the
123        rank. */
124     for (k = 0; k < MAX_SCORES; k++) {
125 	ranknode_t *rank = &ranknodes[k];
126 	double attenuation, kills, sc, kd, kr, hf;
127 
128 	if (strlen(rank->name) == 0)
129 	    continue;
130 
131 	/* The attenuation affects players with less than 300 rounds. */
132 	attenuation = (rank->rounds < 300) ?
133 	    ((double) rank->rounds / 300.0) : 1.0;
134 
135 	kills = rank->kills;
136 	sc = (double) rank->score * attenuation;
137 	kd = ((rank->deaths != 0) ?
138 	      (kills / (double) rank->deaths) :
139 	      (kills)) * attenuation;
140 	kr = ((rank->rounds != 0) ?
141 	      (kills / (double) rank->rounds) :
142 	      (kills)) * attenuation;
143 	hf = ((rank->ballsLost != 0) ?
144 	      ((double) rank->ballsCashed /
145 	       (double) rank->ballsLost) :
146 	      (double) rank->ballsCashed) * attenuation;
147 
148 	sc_table[k] = sc;
149 	kd_table[k] = kd;
150 	kr_table[k] = kr;
151 	hf_table[k] = hf;
152 
153 	if (!foundFirst) {
154 	    lowSC = highSC = sc;
155 	    lowKD = highKD = kd;
156 	    lowKR = highKR = kr;
157 	    lowHF = highHF = hf;
158 	    foundFirst = true;
159 	} else {
160 	    if (sc > highSC)
161 		highSC = sc;
162 	    else if (sc < lowSC)
163 		lowSC = sc;
164 
165 	    if (kd > highKD)
166 		highKD = kd;
167 	    else if (kd < lowKD)
168 		lowKD = kd;
169 
170 	    if (kr > highKR)
171 		highKR = kr;
172 	    else if (kr < lowKR)
173 		lowKR = kr;
174 
175 	    if (hf > highHF)
176 		highHF = hf;
177 	    else if (hf < lowHF)
178 		lowHF = hf;
179 	}
180     }
181 
182     /* Normalize */
183     highSC -= lowSC;
184     highKD -= lowKD;
185     highKR -= lowKR;
186     highHF -= lowHF;
187 
188 
189     {
190 	const double factorSC = (highSC != 0.0) ? (100.0 / highSC) : 0.0;
191 	const double factorKD = (highKD != 0.0) ? (100.0 / highKD) : 0.0;
192 	const double factorKR = (highKR != 0.0) ? (100.0 / highKR) : 0.0;
193 	const double factorHF = (highHF != 0.0) ? (100.0 / highHF) : 0.0;
194 	int i;
195 
196 	rank_entries = 0;
197 	for (i = 0; i < MAX_SCORES; i++) {
198 	    ranknode_t *rank = &ranknodes[i];
199 	    double sc, kd, kr, hf, rsc, rkd, rkr, rhf;
200 
201 	    rank_base[i].ind = i;
202 	    if (strlen(rank->name) == 0) {
203 		rank_base[i].ratio = -1;
204 		continue;
205 	    }
206 	    rank_entries++;
207 
208 	    sc = sc_table[i];
209 	    kd = kd_table[i];
210 	    kr = kr_table[i];
211 	    hf = hf_table[i];
212 
213 	    rsc = (sc - lowSC) * factorSC;
214 	    rkd = (kd - lowKD) * factorKD;
215 	    rkr = (kr - lowKR) * factorKR;
216 	    rhf = (hf - lowHF) * factorHF;
217 
218 	    rank_base[i].ratio = 0.20 * rsc + 0.30 * rkd + 0.30 * rkr + 0.20 * rhf;
219 
220 	    /* KHS: maximum survived time serves as factor */
221 	    if(options.survivalScore != 0.0){
222 		rank_base[i].ratio=rank->max_survival_time;
223 	    }
224 	}
225 
226 
227 	/* And finally we sort the ranks, wheee! */
228 	qsort(rank_base, MAX_SCORES, sizeof(rank_t), rank_cmp);
229     }
230 }
231 
Rank_get_logout_message(ranknode_t * rank)232 static const char *Rank_get_logout_message(ranknode_t *rank)
233 {
234     static char msg[MSG_LEN];
235     player_t *pl;
236 
237     assert(strlen(rank->name) > 0);
238     pl = Get_player_by_name(rank->name, NULL, NULL);
239     if (pl) {
240 	if (Player_is_paused(pl))
241 	    snprintf(msg, sizeof(msg), "paused");
242 	else
243 	    snprintf(msg, sizeof(msg), "playing");
244     }
245     else
246 	snprintf(msg, sizeof(msg), "%s", rank_showtime(rank->timestamp));
247 
248     return msg;
249 }
250 
251 /* Sort the ranks and save them to the webpage. */
Rank_write_webpage(void)252 void Rank_write_webpage(void)
253 {
254     static const char stdcss[] =
255 	"  <style type=\"text/css\">\n"
256 	"    body {\n"
257 	"      font-family: sans-serif;\n"
258 	"      color: #000000;\n"
259 	"      background-color: #ffffff;\n"
260 	"    }\n"
261 	"    table {\n"
262 	"      font-size: small;\n"
263 	"      border-collapse: collapse;\n"
264 	"      border-spacing: 0;\n"
265 	"    }\n"
266 	"    tr.odd {\n"
267 	"      color: #000000;\n"
268 	"      background-color: #d0d8e0;\n"
269 	"    }\n"
270 	"    tr.even {\n"
271 	"      color: #000000;\n"
272 	"      background-color: #e0e8f0;\n"
273 	"    }\n"
274 	"    th, td {\n"
275 	"      padding: 0.2em 0.5em;\n"
276 	"    }\n"
277 	"    th {\n"
278 	"      color: #000000;\n"
279 	"      background-color: #ffffff;\n"
280 	"      border: solid #808890;\n"
281 	"      border-width: 1px 0 1px 0;\n"
282 	"      font-weight: bold;\n"
283 	"    }\n"
284 	"    td.player {\n"
285 	"      font-weight: bold;\n"
286 	"    }\n"
287 	"    a:link {\n"
288 	"      color: #0000c0;\n"
289 	"      background-color: #ffffff;\n"
290 	"    }\n"
291 	"    a:visited {\n"
292 	"      color: #c000c0;\n"
293 	"      background-color: #ffffff;\n"
294 	"    }\n"
295 	"  </style>\n";
296 
297     char *filename;
298     FILE *file;
299     int i;
300 
301     SortRankings();
302 
303     filename = options.rankWebpageFileName;
304     if (!filename)
305 	return;
306 
307     file = fopen(filename, "w");
308     if (!file) {
309 	error("Couldn't open ranking file \"%s\" for writing", filename);
310 	return;
311     }
312 
313     fprintf(file,
314 	"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\"\n"
315 	"    \"http://www.w3.org/TR/HTML4/strict.dtd\">\n"
316 	"<html lang=\"en\">\n"
317 	"<head>\n"
318 	"  <title>%s @ %s</title>\n"
319 	"  <meta http-equiv=\"Content-Type\" "
320 	    "content=\"text/html; charset=ISO-8859-1\">\n",
321 	options.mapName, Server.host);
322 
323     if (options.rankWebpageCSS != NULL)
324 	fprintf(file,
325 	    "  <link rel=\"StyleSheet\" type=\"text/css\" href=\"%s\" />\n",
326 	    options.rankWebpageCSS);
327     else
328 	fprintf(file, "%s", stdcss);
329 
330     fprintf(file,
331 	"</head>\n"
332 	"\n"
333 	"<body>\n"
334 	"  <h1>%s @ %s</h1>\n"  /* mapname @ servername */
335 	"\n"
336 	"  <p>\n"
337 	"    <a href=\"http://xpilot.sourceforge.net/rank-info.html\">"
338 	    "How does the ranking work?</a>\n"
339 	"  </p>\n"
340 	"\n"
341 	"  <table>\n",
342 	options.mapName, Server.host);
343 
344     for (i = 0; i < MAX_SCORES; i++) {
345 	ranknode_t *rank = &ranknodes[rank_base[i].ind];
346 
347 	if (strlen(rank->name) == 0)
348 	    continue;
349 
350 	if (i % 20 == 0)
351 	    fprintf(file,
352 		"    <tr>\n"
353 		"      <th class=\"rank\" align=\"left\">Rank</th>\n"
354 		"      <th class=\"player\" align=\"left\">Player</th>\n"
355 		"      <th class=\"score\" align=\"left\">Score</th>\n"
356 		"      <th class=\"kills\" align=\"left\">Kills</th>\n"
357 		"      <th class=\"deaths\" align=\"left\">Deaths</th>\n"
358 		"      <th class=\"rounds\" align=\"left\">Rounds</th>\n"
359 		"      <th class=\"shots\" align=\"left\">Shots</th>\n"
360 		"      <th class=\"deadliest\" align=\"left\">Deadliest</th>\n"
361 		"      <th class=\"balls\" align=\"left\">Balls</th>\n"
362 		"      <th class=\"ratio\" align=\"left\">Ratio</th>\n"
363 		"      <th class=\"user\" align=\"right\">User</th>\n"
364 		"      <th class=\"host\" align=\"left\">Host</th>\n"
365 		"      <th class=\"logout\" align=\"left\">Logout</th>\n"
366 		"    </tr>\n");
367 
368 	fprintf(file,
369 	    "    <tr class=\"%s\">\n"
370 	    "      <td class=\"rank\" align=\"right\">%d</td>\n"
371 	    "      <td class=\"player\" align=\"left\">%s</td>\n",
372 	    i % 2 == 0 ? "odd" : "even",  /* sic */
373 	    i + 1,
374 	    encode(rank->name, 0));
375 
376 	fprintf(file,
377 	    "      <td class=\"score\" align=\"right\">%.1f</td>\n"
378 	    "      <td class=\"kills\" align=\"right\">%u</td>\n"
379 	    "      <td class=\"deaths\" align=\"right\">%u</td>\n"
380 	    "      <td class=\"rounds\" align=\"right\">%u</td>\n"
381 	    "      <td class=\"shots\" align=\"right\">%u</td>\n"
382 	    "      <td class=\"deadliest\" align=\"right\">%u</td>\n"
383 	    "      <td class=\"balls\" align=\"left\">%u/%u/%u/%u/%.2f</td>\n"
384 	    "      <td class=\"ratio\" align=\"right\">%.2f</td>\n"
385 	    "      <td class=\"user\" align=\"right\">%s</td>\n",
386 	    rank->score,
387 	    rank->kills, rank->deaths,
388 	    rank->rounds, rank->shots,
389 	    rank->deadliest,
390 	    rank->ballsCashed, rank->ballsSaved,
391 	    rank->ballsWon, rank->ballsLost,
392 	    rank->bestball,
393 	    rank_base[i].ratio,
394 	    encode(rank->user, 0));
395 
396 	fprintf(file,
397 		"      <td class=\"host\" align=\"left\">%s</td>\n"
398 		"      <td class=\"logout\" align=\"left\">%s</td>\n"
399 		"    </tr>\n",
400 		encode(rank->host, 0),
401 		Rank_get_logout_message(rank));
402     }
403 
404     fprintf(file,
405 	"  </table>\n"
406 	"\n"
407 	"  <p>\n"
408 	"    <em>Explanation for ballstats</em>:<br>\n"
409 	"    The numbers are c/s/w/l/b, where<br>\n"
410 	"    c = The number of enemy balls you have cashed.<br>\n"
411 	"    s = The number of your own balls you have returned.<br>\n"
412 	"    w = The number of enemy balls your team has cashed.<br>\n"
413 	"    l = The number of your own balls you have lost.<br>\n"
414 	"    b = The fastest ballrun you have made.<br>\n"
415 	"  </p>\n"
416 	"\n"
417 	"  <p>\n"
418 	"    Page generated by " PACKAGE_STRING " on %s\n"
419 	"  </p>\n"
420 	"</body>\n"
421 	"</html>\n",
422 	rank_showtime(time(NULL)));
423 
424     fclose(file);
425 }
426 
427 
Rank_get_stats(const char * name,char * buf,size_t size)428 bool Rank_get_stats(const char *name, char *buf, size_t size)
429 {
430     ranknode_t *r = Rank_get_by_name(name);
431 
432     if (r == NULL)
433 	return false;
434 
435     snprintf(buf, size,
436 	     "%-15s  SC: %7.1f  K/D: %5d/%5d  R: %4d  SH: %6d  Dl: %d "
437 	     "B: %d/%d/%d/%d/%.2f TM: %.2f",
438 	     r->name, r->score, r->kills, r->deaths, r->rounds, r->shots, r->deadliest,
439 	     r->ballsCashed, r->ballsSaved, r->ballsWon, r->ballsLost,
440 	     r->bestball,r->max_survival_time);
441 
442     return true;
443 }
444 
445 
446 /* Send a line with the ranks of the current players to the game. */
Rank_show_ranks(void)447 void Rank_show_ranks(void)
448 {
449     char msg[MSG_LEN];
450     char tmpbuf[MSG_LEN];
451     int i, num = 0, numranks = 0;
452 
453     snprintf(msg, sizeof(msg), "Ranks: ");
454 
455     for (i = 0; i < MAX_SCORES; i++) {
456 	ranknode_t *rank = &ranknodes[rank_base[i].ind];
457 
458 	if (strlen(rank->name) > 0)
459 	    numranks++;
460 
461 	if (rank->pl != NULL) {
462 	    if (num > 0)
463 		strlcat(msg, ", ", sizeof(msg));
464 	    snprintf(tmpbuf, sizeof(tmpbuf), "%s [%d]", rank->name, i + 1);
465 	    strlcat(msg, tmpbuf, sizeof(msg));
466 	    num++;
467 	}
468     }
469 
470     strlcat(msg, ".", sizeof(msg));
471     Set_message(msg);
472 
473     /* show a few best ranks */
474     snprintf(msg, sizeof(msg), " < Top %d ranks: ",
475 	     numranks < 3 ? numranks : 3);
476     num = 0;
477     for (i = 0; i < MAX_SCORES; i++) {
478 	ranknode_t *rank = &ranknodes[rank_base[i].ind];
479 
480 	if (strlen(rank->name) == 0)
481 	    continue;
482 
483 	if (num > 0)
484 	    strlcat(msg, ", ", sizeof(msg));
485 	snprintf(tmpbuf, sizeof(tmpbuf),
486 		 (options.survivalScore == 0.0) ?
487 		 "%d. %s (%.2f)" : "%d. %s (%.1f)",
488 		 num + 1, rank->name, rank_base[i].ratio);
489 	strlcat(msg, tmpbuf, sizeof(msg));
490 	num++;
491 	if (num > 2)
492 	    break;
493     }
494 
495     strlcat(msg, " >", sizeof(msg));
496     Set_message(msg);
497 
498     return;
499 }
500 
Init_ranknode(ranknode_t * rank,const char * name,const char * user,const char * host)501 static void Init_ranknode(ranknode_t *rank,
502 			  const char *name, const char *user, const char *host)
503 {
504     memset(rank, 0, sizeof(ranknode_t));
505     strlcpy(rank->name, name, sizeof(rank->name));
506     strlcpy(rank->user, user, sizeof(rank->user));
507     strlcpy(rank->host, host, sizeof(rank->host));
508 }
509 
510 
Rank_get_by_name(const char * name)511 ranknode_t *Rank_get_by_name(const char *name)
512 {
513     int i;
514     player_t *pl;
515 
516     assert(name != NULL);
517 
518     for (i = 0; i < MAX_SCORES; i++) {
519 	ranknode_t *rank = &ranknodes[i];
520 
521 	if (!strcasecmp(name, rank->name))
522 	    return rank;
523     }
524 
525     /*
526      * If name doesn't equal any nick in the ranking list (ignoring case),
527      * let's see if it could be an abbreviation of the nick of some player
528      * who is currently playing.
529      */
530     pl = Get_player_by_name(name, NULL, NULL);
531     if (pl && pl->rank)
532 	return pl->rank;
533 
534     return NULL;
535 }
536 
537 /* Read scores from disk, and zero-initialize the ones that are not used.
538    Call this on startup. */
Rank_init_saved_scores(void)539 void Rank_init_saved_scores(void)
540 {
541     int i;
542     FILE *file;
543 
544     for (i = 0; i < MAX_SCORES; i++) {
545 	ranknode_t *rank = &ranknodes[i];
546 
547 	memset(rank, 0, sizeof(ranknode_t));
548     }
549 
550     if (getenv("XPILOTSCOREFILE")) {
551 	warn("Environment variable XPILOTSCOREFILE is obsolete.");
552 	warn("Use server option rankFileName instead.");
553     }
554 
555     if (getenv("XPILOTRANKINGPAGE")) {
556 	warn("Environment variable XPILOTRANKINGPAGE is obsolete.");
557 	warn("Use server option rankWebpageFileName instead.");
558     }
559 
560     if (getenv("XPILOTNOJSRANKINGPAGE")) {
561 	warn("Environment variable XPILOTNOJSRANKINGPAGE is obsolete.");
562 	warn("Use server option rankWebpageFileName instead.");
563     }
564 
565     if (!options.rankFileName)
566 	return;
567 
568     file = fopen(options.rankFileName, "r");
569     if (!file) {
570 	if (errno != ENOENT)
571 	    error("Couldn't open rank file \"%s\"", options.rankFileName);
572 	return;
573     }
574 
575     Rank_parse_rankfile(file);
576 
577     fclose(file);
578 
579     xpprintf("%s Rank file with %d entries opened successfully.\n",
580 	     showtime(), num_players);
581 }
582 
583 /*
584  * A player has logged in. Find his info or create new info by kicking
585  * the player who hasn't played for the longest time.
586  */
Rank_get_saved_score(player_t * pl)587 void Rank_get_saved_score(player_t * pl)
588 {
589     ranknode_t *rank, *unused = NULL;
590     int i;
591 
592     updateScores = true;
593 
594     for (i = 0; i < MAX_SCORES; i++) {
595 	rank = &ranknodes[i];
596 	if (!strcasecmp(pl->name, rank->name)) {
597 	    if (rank->pl == NULL) {
598 		/* Ok, found it. */
599 		rank->pl = pl;
600 		Player_set_score(pl,rank->score);
601 		pl->rank = rank;
602 	    } else {
603 		/* That ranknode is already in use by another player! */
604 		Player_set_score(pl,0);
605 		pl->rank = NULL;
606 	    }
607 	    return;
608 	}
609     }
610 
611     /* find unused rank node */
612     for (i = 0; i < MAX_SCORES; i++) {
613 	rank = &ranknodes[i];
614 
615 	if (strlen(rank->name) == 0) {
616 	    unused = rank;
617 	    /*warn("found unused node %d", i);*/
618 	    break;
619 	}
620     }
621 
622     /*
623      * If all entries are in use, use the least-recently-used node
624      * of the bottom half of the list.
625      */
626     if (!unused) {
627 	for (i = MAX_SCORES / 2; i < MAX_SCORES; i++) {
628 	    rank = &ranknodes[rank_base[i].ind];
629 
630 	    /*warn("i is %d, index is %d, timestamp is %u",
631 	      i, rank_base[i].ind, rank->timestamp); */
632 
633 	    if (!unused || rank->timestamp < unused->timestamp)
634 		unused = rank;
635 	}
636     }
637 
638     rank = unused;
639     /*warn("timestamp of lru node = %u", rank->timestamp);*/
640 
641     Init_ranknode(rank, pl->name, pl->username, pl->hostname);
642     rank->pl = pl;
643     rank->timestamp = time(NULL);
644     Player_set_score(pl,0);
645     pl->rank = rank;
646 }
647 
648 /* A player has quit, save his info and mark him as not playing. */
Rank_save_score(player_t * pl)649 void Rank_save_score(player_t * pl)
650 {
651     ranknode_t *rank = pl->rank;
652 
653     rank->score =  Get_Score(pl);
654     rank->pl = NULL;
655     rank->timestamp = time(NULL);
656 }
657 
658 /* Save the scores to disk (not the webpage). */
Rank_write_rankfile(void)659 void Rank_write_rankfile(void)
660 {
661     FILE *file = NULL;
662     char tmp_file[PATH_MAX];
663     int i;
664 
665     if (!options.rankFileName)
666 	return;
667 
668     snprintf(tmp_file, sizeof(tmp_file), "%s-new", options.rankFileName);
669 
670     file = fopen(tmp_file, "w");
671     if (file == NULL) {
672 	error("Open temporary file \"%s\"", tmp_file);
673 	goto failed;
674     }
675 
676     if (fprintf(file,
677 		"<?xml version=\"1.0\"?>\n"
678 		"<XPilotNGRank version=\"1.0\">\n"
679 		"<Players>\n") < 0)
680 	goto writefailed;
681 
682 
683     for (i = 0; i < rank_entries; i++) {
684 	ranknode_t *rank = &ranknodes[rank_base[i].ind];
685 
686 	if (strlen(rank->name) == 0)
687 	    continue;
688 
689 	if (fprintf(file, "<Player "
690 		    "name=\"%s\" ", encode(rank->name, 1)) < 0)
691 	    goto writefailed;
692 
693 	if (fprintf(file,
694 		    "user=\"%s\" ", encode(rank->user, 1)) < 0)
695 	    goto writefailed;
696 
697 	if (fprintf(file,
698 		    "host=\"%s\" ", encode(rank->host, 1)) < 0)
699 	    goto writefailed;
700 
701 	if (rank->score != 0.0
702 	    && fprintf(file, "score=\"%.2f\" ", rank->score) < 0)
703 	    goto writefailed;
704 
705 	if (rank->kills > 0
706 	    && fprintf(file, "kills=\"%d\" ", rank->kills) < 0)
707 	    goto writefailed;
708 
709 	if (rank->deaths > 0
710 	    && fprintf(file, "deaths=\"%d\" ", rank->deaths) < 0)
711 	    goto writefailed;
712 
713 	if (rank->rounds > 0
714 	    && fprintf(file, "rounds=\"%d\" ", rank->rounds) < 0)
715 	    goto writefailed;
716 
717 	if (rank->shots > 0
718 	    && fprintf(file, "shots=\"%d\" ", rank->shots) < 0)
719 	    goto writefailed;
720 
721 	if (rank->deadliest > 0
722 	    && fprintf(file, "deadliest=\"%d\" ", rank->deadliest) < 0)
723 	    goto writefailed;
724 
725 	if (rank->ballsCashed > 0
726 	    && fprintf(file, "ballscashed=\"%d\" ", rank->ballsCashed) < 0)
727 	    goto writefailed;
728 
729 	if (rank->ballsSaved > 0
730 	    && fprintf(file, "ballssaved=\"%d\" ", rank->ballsSaved) < 0)
731 	    goto writefailed;
732 
733 	if (rank->ballsWon > 0
734 	    && fprintf(file, "ballswon=\"%d\" ", rank->ballsWon) < 0)
735 	    goto writefailed;
736 
737 	if (rank->ballsLost > 0
738 	    && fprintf(file, "ballslost=\"%d\" ", rank->ballsLost) < 0)
739 	    goto writefailed;
740 
741 	if (rank->bestball > 0
742 	    && fprintf(file, "bestball=\"%.2f\" ", rank->bestball) < 0)
743 	    goto writefailed;
744 
745         if (rank->max_survival_time > 0
746             && fprintf(file, "max_survival_time=\"%.2f\" ",
747             rank->max_survival_time) < 0)
748 	    goto writefailed;
749 
750 	if (fprintf(file, "timestamp=\"%u\" ", (unsigned)rank->timestamp) < 0)
751 	    goto writefailed;
752 
753 	if (fprintf(file, "/>\n") < 0)
754 	    goto writefailed;
755     }
756 
757     if (fprintf(file,
758 		"</Players>\n"
759 		"</XPilotNGRank>\n") < 0)
760 	goto writefailed;
761 
762 
763     if (fclose(file) != 0) {
764 	error("Close temporary file \"%s\"", tmp_file);
765 	goto failed;
766     }
767     file = NULL;
768 
769     /* Overwrite old rank file. */
770     if (rename(tmp_file, options.rankFileName) < 0) {
771 	error("Rename \"%s\" to \"%s\"", tmp_file, options.rankFileName);
772 	goto failed;
773     }
774 
775     remove(tmp_file);
776 
777     /*xpprintf("%s Rank file with %d entries written successfully.\n",
778       showtime(), rank_entries);*/
779 
780     return;
781 
782  writefailed:
783     error("Write temporary file \"%s\"", tmp_file);
784 
785  failed:
786 
787     if (file) {
788 	fclose(file);
789 	remove(tmp_file);
790     }
791     warn("Couldn't save ranking data to file \"%s\".", options.rankFileName);
792 
793     return;
794 }
795 
796 
tagstart(void * data,const char * el,const char ** attr)797 static void tagstart(void *data, const char *el, const char **attr)
798 {
799     static bool xptag = false;
800 
801     UNUSED_PARAM(data);
802 
803     if (!strcasecmp(el, "XPilotNGRank")) {
804 	double version = -1;
805 
806 	while (*attr) {
807 	    if (!strcasecmp(*attr, "version"))
808 		version = atof(*(attr + 1));
809 	    attr += 2;
810 	}
811 	if (version > 1.0) {
812 	    warn("Rank file has newer version than this server recognizes.");
813 	    warn("The file might use unsupported features.");
814 	}
815 	xptag = true;
816 	return;
817     }
818 
819     if (!xptag) {
820 	fatal("This doesn't look like a rank file "
821 	      " (XPilotNGRank must be first tag).");
822 	return; /* not reached */
823     }
824 
825     if (!strcasecmp(el, "Player")) {
826 	ranknode_t *rank;
827 
828 	if (!playerstag)
829 	    fatal("Player tag in rank file without Players.");
830 
831 	rank = &ranknodes[num_players++];
832 	memset(rank, 0, sizeof(ranknode_t));
833 
834 	while (*attr) {
835 	    if (!strcasecmp(*attr, "name"))
836 		strlcpy(rank->name, *(attr + 1), sizeof(rank->name));
837 	    if (!strcasecmp(*attr, "user"))
838 		strlcpy(rank->user, *(attr + 1), sizeof(rank->user));
839 	    if (!strcasecmp(*attr, "host"))
840 		strlcpy(rank->host, *(attr + 1), sizeof(rank->host));
841 	    if (!strcasecmp(*attr, "score"))
842 		rank->score = atof(*(attr + 1));
843 	    if (!strcasecmp(*attr, "kills"))
844 		rank->kills = atoi(*(attr + 1));
845 	    if (!strcasecmp(*attr, "deaths"))
846 		rank->deaths = atoi(*(attr + 1));
847 	    if (!strcasecmp(*attr, "rounds"))
848 		rank->rounds = atoi(*(attr + 1));
849 	    if (!strcasecmp(*attr, "shots"))
850 		rank->shots = atoi(*(attr + 1));
851 	    if (!strcasecmp(*attr, "deadliest"))
852 		rank->deadliest = atoi(*(attr + 1));
853 	    if (!strcasecmp(*attr, "ballssaved"))
854 		rank->ballsSaved = atoi(*(attr + 1));
855 	    if (!strcasecmp(*attr, "ballslost"))
856 		rank->ballsLost = atoi(*(attr + 1));
857 	    if (!strcasecmp(*attr, "ballswon"))
858 		rank->ballsWon = atoi(*(attr + 1));
859 	    if (!strcasecmp(*attr, "ballscashed"))
860 		rank->ballsCashed = atoi(*(attr + 1));
861 	    if (!strcasecmp(*attr, "bestball"))
862 		rank->bestball = atof(*(attr + 1));
863 	    if (!strcasecmp(*attr, "max_survival_time"))
864 	        rank->max_survival_time = atof(*(attr + 1));
865 	    if (!strcasecmp(*attr, "timestamp"))
866 		rank->timestamp = atoi(*(attr + 1));
867 
868 	    attr += 2;
869 	}
870 
871 	return;
872     }
873 
874     if (!strcasecmp(el, "Players")) {
875 	playerstag = true;
876 	return;
877     }
878 
879     warn("Unknown tag in rank file: \"%s\"", el);
880     return;
881 }
882 
tagend(void * data,const char * el)883 static void tagend(void *data, const char *el)
884 {
885     UNUSED_PARAM(data);
886 
887     if (!strcasecmp(el, "Players"))
888 	playerstag = false;
889 
890     return;
891 }
892 
Rank_parse_rankfile(FILE * file)893 static bool Rank_parse_rankfile(FILE *file)
894 {
895     char buf[8192];
896     int len, fd;
897     XML_Parser p = XML_ParserCreate(NULL);
898 
899     fd = fileno(file);
900     if (fd == -1)
901 	return false;
902 
903     if (!p) {
904 	warn("Creating Expat instance for ranking file parsing failed.\n");
905 	return false;
906     }
907     XML_SetElementHandler(p, tagstart, tagend);
908     do {
909 	len = read(fd, buf, sizeof(buf));
910 	if (len < 0) {
911 	    error("Error reading rankfile!");
912 	    return false;
913 	}
914 	if (!XML_Parse(p, buf, len, !len)) {
915 	    warn("Parse error reading rankfile at line %d:\n%s\n",
916 		  XML_GetCurrentLineNumber(p),
917 		  XML_ErrorString(XML_GetErrorCode(p)));
918 	    return false;
919 	}
920     } while (len);
921     return true;
922 }
923