1 /*        $NetBSD: scores.c,v 1.26 2021/05/02 12:50:46 rillig Exp $   */
2 
3 /*-
4  * Copyright (c) 1992, 1993
5  *        The Regents of the University of California.  All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * Chris Torek and Darren F. Provine.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. Neither the name of the University nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  *
34  *        @(#)scores.c        8.1 (Berkeley) 5/31/93
35  */
36 
37 /*
38  * Score code for Tetris, by Darren Provine (kilroy@gboro.glassboro.edu)
39  * modified 22 January 1992, to limit the number of entries any one
40  * person has.
41  *
42  * Major whacks since then.
43  */
44 #include <err.h>
45 #include <errno.h>
46 #include <fcntl.h>
47 #include <pwd.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <sys/stat.h>
52 #include <time.h>
53 #include <term.h>
54 #include <unistd.h>
55 
56 #include "pathnames.h"
57 #include "screen.h"
58 #include "scores.h"
59 #include "tetris.h"
60 
61 /*
62  * Allow updating the high scores unless we're built as part of /rescue.
63  */
64 #ifndef RESCUEDIR
65 #define ALLOW_SCORE_UPDATES
66 #endif
67 
68 /*
69  * Within this code, we can hang onto one extra "high score", leaving
70  * room for our current score (whether or not it is high).
71  *
72  * We also sometimes keep tabs on the "highest" score on each level.
73  * As long as the scores are kept sorted, this is simply the first one at
74  * that level.
75  */
76 #define NUMSPOTS (MAXHISCORES + 1)
77 #define   NLEVELS (MAXLEVEL + 1)
78 
79 static time_t now;
80 static int nscores;
81 static int gotscores;
82 static struct highscore scores[NUMSPOTS];
83 
84 static int checkscores(struct highscore *, int);
85 static int cmpscores(const void *, const void *);
86 static void getscores(int *);
87 static void printem(int, int, struct highscore *, int, const char *);
88 static char *thisuser(void);
89 
90 /* contents chosen to be a highly illegal username */
91 static const char hsh_magic_val[HSH_MAGIC_SIZE] = "//:\0\0://";
92 
93 #define HSH_ENDIAN_NATIVE     0x12345678
94 #define HSH_ENDIAN_OPP                  0x78563412
95 
96 /* current file format version */
97 #define HSH_VERSION           1
98 
99 /* codes for scorefile_probe return */
100 #define SCOREFILE_ERROR                 (-1)
101 #define SCOREFILE_CURRENT     0         /* 40-byte */
102 #define SCOREFILE_CURRENT_OPP 1         /* 40-byte, opposite-endian */
103 #define SCOREFILE_599                   2         /* 36-byte */
104 #define SCOREFILE_599_OPP     3         /* 36-byte, opposite-endian */
105 #define SCOREFILE_50                    4         /* 32-byte */
106 #define SCOREFILE_50_OPP      5         /* 32-byte, opposite-endian */
107 
108 /*
109  * Check (or guess) what kind of score file contents we have.
110  */
111 static int
scorefile_probe(int sd)112 scorefile_probe(int sd)
113 {
114           struct stat st;
115           int t1, t2, t3, tx;
116           ssize_t result;
117           uint32_t numbers[3], offset56, offset60, offset64;
118 
119           if (fstat(sd, &st) < 0) {
120                     warn("Score file %s: fstat", _PATH_SCOREFILE);
121                     return -1;
122           }
123 
124           t1 = st.st_size % sizeof(struct highscore_ondisk) == 0;
125           t2 = st.st_size % sizeof(struct highscore_ondisk_599) == 0;
126           t3 = st.st_size % sizeof(struct highscore_ondisk_50) == 0;
127           tx = t1 + t2 + t3;
128           if (tx == 1) {
129                     /* Size matches exact number of one kind of records */
130                     if (t1) {
131                               return SCOREFILE_CURRENT;
132                     } else if (t2) {
133                               return SCOREFILE_599;
134                     } else {
135                               return SCOREFILE_50;
136                     }
137           } else if (tx == 0) {
138                     /* Size matches nothing, pick most likely as default */
139                     goto wildguess;
140           }
141 
142           /*
143            * File size is multiple of more than one structure size.
144            * (For example, 288 bytes could be 9*hso50 or 8*hso599.)
145            * Read the file and see if we can figure out what's going
146            * on. This is the layout of the first two records:
147            *
148            *   offset     hso / current   hso_599         hso_50
149            *              (40-byte)       (36-byte)       (32-byte)
150            *
151            *   0          name #0         name #0         name #0
152          *   4            :               :               :
153          *   8            :               :               :
154          *   12           :               :               :
155          *   16           :               :               :
156            *   20         score #0        score #0        score #0
157            *   24         level #0        level #0        level #0
158            *   28          (pad)          time #0         time #0
159            *   32         time #0                         name #1
160            *   36                         name #1           :
161          *   40         name #1           :               :
162          *   44           :               :               :
163          *   48           :               :               :
164            *   52           :               :             score #1
165            *   56           :             score #1        level #1
166            *   60         score #1        level #1        time #1
167            *   64         level #1        time #1         name #2
168            *   68          (pad)            :               :
169            *   72         time #1         name #2           :
170            *   76           :               :               :
171            *   80                  --- end ---
172            *
173            * There are a number of things we could check here, but the
174            * most effective test is based on the following restrictions:
175            *
176            *    - The level must be between 1 and 9 (inclusive)
177            *    - All times must be after 1985 and are before 2038,
178            *      so the high word must be 0 and the low word may not be
179            *      a small value.
180            *    - Integer values of 0 or 1-9 cannot be the beginning of
181            *      a login name string.
182            *    - Values of 1-9 are probably not a score.
183            *
184            * So we read the three words at offsets 56, 60, and 64, and
185            * poke at the values to try to figure things...
186            */
187 
188           if (lseek(sd, 56, SEEK_SET) < 0) {
189                     warn("Score file %s: lseek", _PATH_SCOREFILE);
190                     return -1;
191           }
192           result = read(sd, &numbers, sizeof(numbers));
193           if (result < 0) {
194                     warn("Score file %s: read", _PATH_SCOREFILE);
195                     return -1;
196           }
197           if ((size_t)result != sizeof(numbers)) {
198                     /*
199                      * The smallest file whose size divides by more than
200                      * one of the sizes is substantially larger than 64,
201                      * so this should *never* happen.
202                      */
203                     warnx("Score file %s: Unexpected EOF", _PATH_SCOREFILE);
204                     return -1;
205           }
206 
207           offset56 = numbers[0];
208           offset60 = numbers[1];
209           offset64 = numbers[2];
210 
211           if (offset64 >= MINLEVEL && offset64 <= MAXLEVEL) {
212                     /* 40-byte structure */
213                     return SCOREFILE_CURRENT;
214           } else if (offset60 >= MINLEVEL && offset60 <= MAXLEVEL) {
215                     /* 36-byte structure */
216                     return SCOREFILE_599;
217           } else if (offset56 >= MINLEVEL && offset56 <= MAXLEVEL) {
218                     /* 32-byte structure */
219                     return SCOREFILE_50;
220           }
221 
222           /* None was a valid level; try opposite endian */
223           offset64 = bswap32(offset64);
224           offset60 = bswap32(offset60);
225           offset56 = bswap32(offset56);
226 
227           if (offset64 >= MINLEVEL && offset64 <= MAXLEVEL) {
228                     /* 40-byte structure */
229                     return SCOREFILE_CURRENT_OPP;
230           } else if (offset60 >= MINLEVEL && offset60 <= MAXLEVEL) {
231                     /* 36-byte structure */
232                     return SCOREFILE_599_OPP;
233           } else if (offset56 >= MINLEVEL && offset56 <= MAXLEVEL) {
234                     /* 32-byte structure */
235                     return SCOREFILE_50_OPP;
236           }
237 
238           /* That didn't work either, dunno what's going on */
239  wildguess:
240           warnx("Score file %s is likely corrupt", _PATH_SCOREFILE);
241           if (sizeof(void *) == 8 && sizeof(time_t) == 8) {
242                     return SCOREFILE_CURRENT;
243           } else if (sizeof(time_t) == 8) {
244                     return SCOREFILE_599;
245           } else {
246                     return SCOREFILE_50;
247           }
248 }
249 
250 /*
251  * Copy a string safely, making sure it's null-terminated.
252  */
253 static void
readname(char * to,size_t maxto,const char * from,size_t maxfrom)254 readname(char *to, size_t maxto, const char *from, size_t maxfrom)
255 {
256           size_t amt;
257 
258           amt = maxto < maxfrom ? maxto : maxfrom;
259           memcpy(to, from, amt);
260           to[maxto-1] = '\0';
261 }
262 
263 /*
264  * Copy integers, byte-swapping if desired.
265  */
266 static int32_t
read32(int32_t val,int doflip)267 read32(int32_t val, int doflip)
268 {
269           if (doflip) {
270                     val = bswap32(val);
271           }
272           return val;
273 }
274 
275 static int64_t
read64(int64_t val,int doflip)276 read64(int64_t val, int doflip)
277 {
278           if (doflip) {
279                     val = bswap64(val);
280           }
281           return val;
282 }
283 
284 /*
285  * Read up to MAXHISCORES scorefile_ondisk entries.
286  */
287 static int
readscores(int sd,int doflip)288 readscores(int sd, int doflip)
289 {
290           struct highscore_ondisk buf[MAXHISCORES];
291           ssize_t result;
292           int i;
293 
294           result = read(sd, buf, sizeof(buf));
295           if (result < 0) {
296                     warn("Score file %s: read", _PATH_SCOREFILE);
297                     return -1;
298           }
299           nscores = result / sizeof(buf[0]);
300 
301           for (i=0; i<nscores; i++) {
302                     readname(scores[i].hs_name, sizeof(scores[i].hs_name),
303                                buf[i].hso_name, sizeof(buf[i].hso_name));
304                     scores[i].hs_score = read32(buf[i].hso_score, doflip);
305                     scores[i].hs_level = read32(buf[i].hso_level, doflip);
306                     scores[i].hs_time = read64(buf[i].hso_time, doflip);
307           }
308           return 0;
309 }
310 
311 /*
312  * Read up to MAXHISCORES scorefile_ondisk_599 entries.
313  */
314 static int
readscores599(int sd,int doflip)315 readscores599(int sd, int doflip)
316 {
317           struct highscore_ondisk_599 buf[MAXHISCORES];
318           ssize_t result;
319           int i;
320 
321           result = read(sd, buf, sizeof(buf));
322           if (result < 0) {
323                     warn("Score file %s: read", _PATH_SCOREFILE);
324                     return -1;
325           }
326           nscores = result / sizeof(buf[0]);
327 
328           for (i=0; i<nscores; i++) {
329                     readname(scores[i].hs_name, sizeof(scores[i].hs_name),
330                                buf[i].hso599_name, sizeof(buf[i].hso599_name));
331                     scores[i].hs_score = read32(buf[i].hso599_score, doflip);
332                     scores[i].hs_level = read32(buf[i].hso599_level, doflip);
333                     /*
334                      * Don't bother pasting the time together into a
335                      * 64-bit value; just take whichever half is nonzero.
336                      */
337                     scores[i].hs_time =
338                               read32(buf[i].hso599_time[buf[i].hso599_time[0] == 0],
339                                      doflip);
340           }
341           return 0;
342 }
343 
344 /*
345  * Read up to MAXHISCORES scorefile_ondisk_50 entries.
346  */
347 static int
readscores50(int sd,int doflip)348 readscores50(int sd, int doflip)
349 {
350           struct highscore_ondisk_50 buf[MAXHISCORES];
351           ssize_t result;
352           int i;
353 
354           result = read(sd, buf, sizeof(buf));
355           if (result < 0) {
356                     warn("Score file %s: read", _PATH_SCOREFILE);
357                     return -1;
358           }
359           nscores = result / sizeof(buf[0]);
360 
361           for (i=0; i<nscores; i++) {
362                     readname(scores[i].hs_name, sizeof(scores[i].hs_name),
363                                buf[i].hso50_name, sizeof(buf[i].hso50_name));
364                     scores[i].hs_score = read32(buf[i].hso50_score, doflip);
365                     scores[i].hs_level = read32(buf[i].hso50_level, doflip);
366                     scores[i].hs_time = read32(buf[i].hso50_time, doflip);
367           }
368           return 0;
369 }
370 
371 /*
372  * Read the score file.  Can be called from savescore (before showscores)
373  * or showscores (if savescore will not be called).  If the given pointer
374  * is not NULL, sets *fdp to an open file handle that corresponds to a
375  * read/write score file that is locked with LOCK_EX.  Otherwise, the
376  * file is locked with LOCK_SH for the read and closed before return.
377  */
378 static void
getscores(int * fdp)379 getscores(int *fdp)
380 {
381           struct highscore_header header;
382           int sd, mint, lck;
383           mode_t mask;
384           const char *human;
385           int doflip;
386           int serrno;
387           ssize_t result;
388 
389 #ifdef ALLOW_SCORE_UPDATES
390           if (fdp != NULL) {
391                     mint = O_RDWR | O_CREAT;
392                     human = "read/write";
393                     lck = LOCK_EX;
394           } else
395 #endif
396           {
397                     mint = O_RDONLY;
398                     human = "reading";
399                     lck = LOCK_SH;
400           }
401           setegid(egid);
402           mask = umask(S_IWOTH);
403           sd = open(_PATH_SCOREFILE, mint, 0666);
404           serrno = errno;
405           (void)umask(mask);
406           setegid(gid);
407           if (sd < 0) {
408                     /*
409                      * If the file simply isn't there because nobody's
410                      * played yet, and we aren't going to be trying to
411                      * update it, don't warn. Even if we are going to be
412                      * trying to write it, don't fail -- we can still show
413                      * the player the score they got.
414                      */
415                     errno = serrno;
416                     if (fdp != NULL || errno != ENOENT) {
417                               warn("Cannot open %s for %s", _PATH_SCOREFILE, human);
418                     }
419                     goto fail;
420           }
421 
422           /*
423            * Grab a lock.
424            * XXX: failure here should probably be more fatal than this.
425            */
426           if (flock(sd, lck))
427                     warn("warning: score file %s cannot be locked",
428                         _PATH_SCOREFILE);
429 
430           /*
431            * The current format (since -current of 20090525) is
432            *
433            *    struct highscore_header
434            *    up to MAXHIGHSCORES x struct highscore_ondisk
435            *
436            * Before this, there is no header, and the contents
437            * might be any of three formats:
438            *
439            *    highscore_ondisk       (64-bit machines with 64-bit time_t)
440            *    highscore_ondisk_599   (32-bit machines with 64-bit time_t)
441            *    highscore_ondisk_50    (32-bit machines with 32-bit time_t)
442            *
443            * The first two appear in 5.99 between the time_t change and
444            * 20090525, depending on whether the compiler inserts
445            * structure padding before an unaligned 64-bit time_t. The
446            * last appears in 5.0 and earlier.
447            *
448            * Any or all of these might also appear on other OSes where
449            * this code has been ported.
450            *
451            * Since the old file has no header, we will have to guess
452            * which of these formats it has.
453            */
454 
455           /*
456            * First, look for a header.
457            */
458           result = read(sd, &header, sizeof(header));
459           if (result < 0) {
460                     warn("Score file %s: read", _PATH_SCOREFILE);
461                     goto sdfail;
462           }
463           if (result != 0 && (size_t)result != sizeof(header)) {
464                     warnx("Score file %s: read: unexpected EOF", _PATH_SCOREFILE);
465                     /*
466                      * File is hopelessly corrupt, might as well truncate it
467                      * and start over with empty scores.
468                      */
469                     if (lseek(sd, 0, SEEK_SET) < 0) {
470                               /* ? */
471                               warn("Score file %s: lseek", _PATH_SCOREFILE);
472                               goto sdfail;
473                     }
474                     if (ftruncate(sd, 0) == 0) {
475                               result = 0;
476                     } else {
477                               goto sdfail;
478                     }
479           }
480 
481           if (result == 0) {
482                     /* Empty file; that just means there are no scores. */
483                     nscores = 0;
484           } else {
485                     /*
486                      * Is what we read a header, or the first 16 bytes of
487                      * a score entry? hsh_magic_val is chosen to be
488                      * something that is extremely unlikely to appear in
489                      * hs_name[].
490                      */
491                     if (!memcmp(header.hsh_magic, hsh_magic_val, HSH_MAGIC_SIZE)) {
492                               /* Yes, we have a header. */
493 
494                               if (header.hsh_endiantag == HSH_ENDIAN_NATIVE) {
495                                         /* native endian */
496                                         doflip = 0;
497                               } else if (header.hsh_endiantag == HSH_ENDIAN_OPP) {
498                                         doflip = 1;
499                               } else {
500                                         warnx("Score file %s: Unknown endian tag %u",
501                                                   _PATH_SCOREFILE, header.hsh_endiantag);
502                                         goto sdfail;
503                               }
504 
505                               if (header.hsh_version != HSH_VERSION) {
506                                         warnx("Score file %s: Unknown version code %u",
507                                                   _PATH_SCOREFILE, header.hsh_version);
508                                         goto sdfail;
509                               }
510 
511                               if (readscores(sd, doflip) < 0) {
512                                         goto sdfail;
513                               }
514                     } else {
515                               /*
516                                * Ok, it wasn't a header. Try to figure out what
517                                * size records we have.
518                                */
519                               result = scorefile_probe(sd);
520                               if (lseek(sd, 0, SEEK_SET) < 0) {
521                                         warn("Score file %s: lseek", _PATH_SCOREFILE);
522                                         goto sdfail;
523                               }
524                               switch (result) {
525                               case SCOREFILE_CURRENT:
526                                         result = readscores(sd, 0 /* don't flip */);
527                                         break;
528                               case SCOREFILE_CURRENT_OPP:
529                                         result = readscores(sd, 1 /* do flip */);
530                                         break;
531                               case SCOREFILE_599:
532                                         result = readscores599(sd, 0 /* don't flip */);
533                                         break;
534                               case SCOREFILE_599_OPP:
535                                         result = readscores599(sd, 1 /* do flip */);
536                                         break;
537                               case SCOREFILE_50:
538                                         result = readscores50(sd, 0 /* don't flip */);
539                                         break;
540                               case SCOREFILE_50_OPP:
541                                         result = readscores50(sd, 1 /* do flip */);
542                                         break;
543                               default:
544                                         goto sdfail;
545                               }
546                               if (result < 0) {
547                                         goto sdfail;
548                               }
549                     }
550           }
551 
552 
553           if (fdp)
554                     *fdp = sd;
555           else
556                     close(sd);
557 
558           return;
559 
560 sdfail:
561           close(sd);
562  fail:
563           if (fdp != NULL) {
564                     *fdp = -1;
565           }
566           nscores = 0;
567 }
568 
569 #ifdef ALLOW_SCORE_UPDATES
570 /*
571  * Paranoid write wrapper; unlike fwrite() it preserves errno.
572  */
573 static int
dowrite(int sd,const void * vbuf,size_t len)574 dowrite(int sd, const void *vbuf, size_t len)
575 {
576           const char *buf = vbuf;
577           ssize_t result;
578           size_t done = 0;
579 
580           while (done < len) {
581                     result = write(sd, buf+done, len-done);
582                     if (result < 0) {
583                               if (errno == EINTR) {
584                                         continue;
585                               }
586                               return -1;
587                     }
588                     done += result;
589           }
590           return 0;
591 }
592 #endif /* ALLOW_SCORE_UPDATES */
593 
594 /*
595  * Write the score file out.
596  */
597 static void
putscores(int sd)598 putscores(int sd)
599 {
600 #ifdef ALLOW_SCORE_UPDATES
601           struct highscore_header header;
602           struct highscore_ondisk buf[MAXHISCORES] = {0};
603           int i;
604 
605           if (sd == -1) {
606                     return;
607           }
608 
609           memcpy(header.hsh_magic, hsh_magic_val, HSH_MAGIC_SIZE);
610           header.hsh_endiantag = HSH_ENDIAN_NATIVE;
611           header.hsh_version = HSH_VERSION;
612 
613           for (i=0; i<nscores; i++) {
614                     memcpy(buf[i].hso_name, scores[i].hs_name,
615                               sizeof(buf[i].hso_name));
616                     buf[i].hso_score = scores[i].hs_score;
617                     buf[i].hso_level = scores[i].hs_level;
618                     buf[i].hso_pad = 0xbaadf00d;
619                     buf[i].hso_time = scores[i].hs_time;
620           }
621 
622           if (lseek(sd, 0, SEEK_SET) < 0) {
623                     warn("Score file %s: lseek", _PATH_SCOREFILE);
624                     goto fail;
625           }
626           if (dowrite(sd, &header, sizeof(header)) < 0 ||
627               dowrite(sd, buf, sizeof(buf[0]) * nscores) < 0) {
628                     warn("Score file %s: write", _PATH_SCOREFILE);
629                     goto fail;
630           }
631           return;
632  fail:
633           warnx("high scores may be damaged");
634 #else
635           (void)sd;
636 #endif /* ALLOW_SCORE_UPDATES */
637 }
638 
639 /*
640  * Close the score file.
641  */
642 static void
closescores(int sd)643 closescores(int sd)
644 {
645           flock(sd, LOCK_UN);
646           close(sd);
647 }
648 
649 /*
650  * Read and update the scores file with the current reults.
651  */
652 void
savescore(int level)653 savescore(int level)
654 {
655           struct highscore *sp;
656           int i;
657           int change;
658           int sd;
659           const char *me;
660 
661           getscores(&sd);
662           gotscores = 1;
663           (void)time(&now);
664 
665           /*
666            * Allow at most one score per person per level -- see if we
667            * can replace an existing score, or (easiest) do nothing.
668            * Otherwise add new score at end (there is always room).
669            */
670           change = 0;
671           me = thisuser();
672           for (i = 0, sp = &scores[0]; i < nscores; i++, sp++) {
673                     if (sp->hs_level != level || strcmp(sp->hs_name, me) != 0)
674                               continue;
675                     if (score > sp->hs_score) {
676                               (void)printf("%s bettered %s %d score of %d!\n",
677                                   "\nYou", "your old level", level,
678                                   sp->hs_score * sp->hs_level);
679                               sp->hs_score = score;         /* new score */
680                               sp->hs_time = now;  /* and time */
681                               change = 1;
682                     } else if (score == sp->hs_score) {
683                               (void)printf("%s tied %s %d high score.\n",
684                                   "\nYou", "your old level", level);
685                               sp->hs_time = now;  /* renew it */
686                               change = 1;                   /* gotta rewrite, sigh */
687                     } /* else new score < old score: do nothing */
688                     break;
689           }
690           if (i >= nscores) {
691                     strcpy(sp->hs_name, me);
692                     sp->hs_level = level;
693                     sp->hs_score = score;
694                     sp->hs_time = now;
695                     nscores++;
696                     change = 1;
697           }
698 
699           if (change) {
700                     /*
701                      * Sort & clean the scores, then rewrite.
702                      */
703                     nscores = checkscores(scores, nscores);
704                     putscores(sd);
705           }
706           closescores(sd);
707 }
708 
709 /*
710  * Get login name, or if that fails, get something suitable.
711  * The result is always trimmed to fit in a score.
712  */
713 static char *
thisuser(void)714 thisuser(void)
715 {
716           const char *p;
717           struct passwd *pw;
718           size_t l;
719           static char u[sizeof(scores[0].hs_name)];
720 
721           if (u[0])
722                     return (u);
723           p = getlogin();
724           if (p == NULL || *p == '\0') {
725                     pw = getpwuid(getuid());
726                     if (pw != NULL)
727                               p = pw->pw_name;
728                     else
729                               p = "  ???";
730           }
731           l = strlen(p);
732           if (l >= sizeof(u))
733                     l = sizeof(u) - 1;
734           memcpy(u, p, l);
735           u[l] = '\0';
736           return (u);
737 }
738 
739 /*
740  * Score comparison function for qsort.
741  *
742  * If two scores are equal, the person who had the score first is
743  * listed first in the highscore file.
744  */
745 static int
cmpscores(const void * x,const void * y)746 cmpscores(const void *x, const void *y)
747 {
748           const struct highscore *a, *b;
749           long l;
750 
751           a = x;
752           b = y;
753           l = (long)b->hs_level * b->hs_score - (long)a->hs_level * a->hs_score;
754           if (l < 0)
755                     return (-1);
756           if (l > 0)
757                     return (1);
758           if (a->hs_time < b->hs_time)
759                     return (-1);
760           if (a->hs_time > b->hs_time)
761                     return (1);
762           return (0);
763 }
764 
765 /*
766  * If we've added a score to the file, we need to check the file and ensure
767  * that this player has only a few entries.  The number of entries is
768  * controlled by MAXSCORES, and is to ensure that the highscore file is not
769  * monopolised by just a few people.  People who no longer have accounts are
770  * only allowed the highest score.  Scores older than EXPIRATION seconds are
771  * removed, unless they are someone's personal best.
772  * Caveat:  the highest score on each level is always kept.
773  */
774 static int
checkscores(struct highscore * hs,int num)775 checkscores(struct highscore *hs, int num)
776 {
777           struct highscore *sp;
778           int i, j, k, numnames;
779           int levelfound[NLEVELS];
780           struct peruser {
781                     char *name;
782                     int times;
783           } count[NUMSPOTS];
784           struct peruser *pu;
785 
786           /*
787            * Sort so that highest totals come first.
788            *
789            * levelfound[i] becomes set when the first high score for that
790            * level is encountered.  By definition this is the highest score.
791            */
792           qsort((void *)hs, nscores, sizeof(*hs), cmpscores);
793           for (i = MINLEVEL; i < NLEVELS; i++)
794                     levelfound[i] = 0;
795           numnames = 0;
796           for (i = 0, sp = hs; i < num;) {
797                     /*
798                      * This is O(n^2), but do you think we care?
799                      */
800                     for (j = 0, pu = count; j < numnames; j++, pu++)
801                               if (strcmp(sp->hs_name, pu->name) == 0)
802                                         break;
803                     if (j == numnames) {
804                               /*
805                                * Add new user, set per-user count to 1.
806                                */
807                               pu->name = sp->hs_name;
808                               pu->times = 1;
809                               numnames++;
810                     } else {
811                               /*
812                                * Two ways to keep this score:
813                                * - Not too many (per user), still has acct, &
814                                *        score not dated; or
815                                * - High score on this level.
816                                */
817                               if ((pu->times < MAXSCORES &&
818                                    getpwnam(sp->hs_name) != NULL &&
819                                    sp->hs_time + EXPIRATION >= now) ||
820                                   levelfound[sp->hs_level] == 0)
821                                         pu->times++;
822                               else {
823                                         /*
824                                          * Delete this score, do not count it,
825                                          * do not pass go, do not collect $200.
826                                          */
827                                         num--;
828                                         for (k = i; k < num; k++)
829                                                   hs[k] = hs[k + 1];
830                                         continue;
831                               }
832                     }
833                     if (sp->hs_level < NLEVELS && sp->hs_level >= 0)
834                               levelfound[sp->hs_level] = 1;
835                     i++, sp++;
836           }
837           return (num > MAXHISCORES ? MAXHISCORES : num);
838 }
839 
840 /*
841  * Show current scores.  This must be called after savescore, if
842  * savescore is called at all, for two reasons:
843  * - Showscores munches the time field.
844  * - Even if that were not the case, a new score must be recorded
845  *   before it can be shown anyway.
846  */
847 void
showscores(int level)848 showscores(int level)
849 {
850           struct highscore *sp;
851           int i, n, c;
852           const char *me;
853           int levelfound[NLEVELS];
854 
855           if (!gotscores)
856                     getscores(NULL);
857           (void)printf("\n\t\t\t    Tetris High Scores\n");
858 
859           /*
860            * If level == 0, the person has not played a game but just asked for
861            * the high scores; we do not need to check for printing in highlight
862            * mode.  If SOstr is null, we can't do highlighting anyway.
863            */
864           me = level && enter_standout_mode ? thisuser() : NULL;
865 
866           /*
867            * Set times to 0 except for high score on each level.
868            */
869           for (i = MINLEVEL; i < NLEVELS; i++)
870                     levelfound[i] = 0;
871           for (i = 0, sp = scores; i < nscores; i++, sp++) {
872         if (sp->hs_level < NLEVELS && sp->hs_level >= 0) {
873                     if (levelfound[sp->hs_level])
874                               sp->hs_time = 0;
875                         else {
876                                   sp->hs_time = 1;
877                               levelfound[sp->hs_level] = 1;
878                         }
879         }
880           }
881 
882           /*
883            * Page each screenful of scores.
884            */
885           for (i = 0, sp = scores; i < nscores; sp += n) {
886                     n = 40;
887                     if (i + n > nscores)
888                               n = nscores - i;
889                     printem(level, i + 1, sp, n, me);
890                     if ((i += n) < nscores) {
891                               (void)printf("\nHit RETURN to continue.");
892                               (void)fflush(stdout);
893                               while ((c = getchar()) != '\n')
894                                         if (c == EOF)
895                                                   break;
896                               (void)printf("\n");
897                     }
898           }
899 }
900 
901 static void
printem(int level,int offset,struct highscore * hs,int n,const char * me)902 printem(int level, int offset, struct highscore *hs, int n, const char *me)
903 {
904           struct highscore *sp;
905           int nrows, row, col, item, i, highlight;
906           char buf[100];
907 #define   TITLE "Rank  Score   Name     (points/level)"
908 
909           /*
910            * This makes a nice two-column sort with headers, but it's a bit
911            * convoluted...
912            */
913           printf("%s   %s\n", TITLE, n > 1 ? TITLE : "");
914 
915           highlight = 0;
916           nrows = (n + 1) / 2;
917 
918           for (row = 0; row < nrows; row++) {
919                     for (col = 0; col < 2; col++) {
920                               item = col * nrows + row;
921                               if (item >= n) {
922                                         /*
923                                          * Can only occur on trailing columns.
924                                          */
925                                         (void)putchar('\n');
926                                         continue;
927                               }
928                               sp = &hs[item];
929                               (void)snprintf(buf, sizeof(buf),
930                                   "%3d%c %6d  %-11s (%6d on %d)",
931                                   item + offset, sp->hs_time ? '*' : ' ',
932                                   sp->hs_score * sp->hs_level,
933                                   sp->hs_name, sp->hs_score, sp->hs_level);
934                               /*
935                                * Highlight if appropriate.  This works because
936                                * we only get one score per level.
937                                */
938                               if (me != NULL &&
939                                   sp->hs_level == level &&
940                                   sp->hs_score == score &&
941                                   strcmp(sp->hs_name, me) == 0) {
942                                         putpad(enter_standout_mode);
943                                         highlight = 1;
944                               }
945                               (void)printf("%s", buf);
946                               if (highlight) {
947                                         putpad(exit_standout_mode);
948                                         highlight = 0;
949                               }
950 
951                               /* fill in spaces so column 1 lines up */
952                               if (col == 0)
953                                         for (i = 40 - strlen(buf); --i >= 0;)
954                                                   (void)putchar(' ');
955                               else /* col == 1 */
956                                         (void)putchar('\n');
957                     }
958           }
959 }
960