]> git.cameronkatri.com Git - bsdgames-darwin.git/blobdiff - tetris/scores.c
include sys/time.h earlier (rather than implicitly via fcntl.h) so that
[bsdgames-darwin.git] / tetris / scores.c
index e7e6bb590ad88e1cb36944e161bd305f780bfe47..7d5fca8605580244d2da2fa15bffb86522458740 100644 (file)
@@ -1,3 +1,5 @@
+/*     $NetBSD: scores.c,v 1.20 2011/01/05 15:48:00 wiz Exp $  */
+
 /*-
  * Copyright (c) 1992, 1993
  *     The Regents of the University of California.  All rights reserved.
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
- * 3. All advertising materials mentioning features or use of this software
- *    must display the following acknowledgement:
- *     This product includes software developed by the University of
- *     California, Berkeley and its contributors.
- * 4. Neither the name of the University nor the names of its contributors
+ * 3. Neither the name of the University nor the names of its contributors
  *    may be used to endorse or promote products derived from this software
  *    without specific prior written permission.
  *
  *
  * Major whacks since then.
  */
+#include <err.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <pwd.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/stat.h>
 #include <time.h>
+#include <term.h>
 #include <unistd.h>
 
-/*
- * XXX - need a <termcap.h>
- */
-int    tputs __P((const char *, int, int (*)(int)));
-
 #include "pathnames.h"
 #include "screen.h"
 #include "scores.h"
 #include "tetris.h"
 
+/*
+ * Allow updating the high scores unless we're built as part of /rescue.
+ */
+#ifndef RESCUEDIR
+#define ALLOW_SCORE_UPDATES
+#endif
+
 /*
  * Within this code, we can hang onto one extra "high score", leaving
  * room for our current score (whether or not it is high).
@@ -78,88 +81,584 @@ static int nscores;
 static int gotscores;
 static struct highscore scores[NUMSPOTS];
 
-static int checkscores __P((struct highscore *, int));
-static int cmpscores __P((const void *, const void *));
-static void getscores __P((FILE **));
-static void printem __P((int, int, struct highscore *, int, const char *));
-static char *thisuser __P((void));
+static int checkscores(struct highscore *, int);
+static int cmpscores(const void *, const void *);
+static void getscores(int *);
+static void printem(int, int, struct highscore *, int, const char *);
+static char *thisuser(void);
+
+/* contents chosen to be a highly illegal username */
+static const char hsh_magic_val[HSH_MAGIC_SIZE] = "//:\0\0://";
+
+#define HSH_ENDIAN_NATIVE      0x12345678
+#define HSH_ENDIAN_OPP         0x78563412
+
+/* current file format version */
+#define HSH_VERSION            1
+
+/* codes for scorefile_probe return */
+#define SCOREFILE_ERROR                (-1)
+#define SCOREFILE_CURRENT      0       /* 40-byte */
+#define SCOREFILE_CURRENT_OPP  1       /* 40-byte, opposite-endian */
+#define SCOREFILE_599          2       /* 36-byte */
+#define SCOREFILE_599_OPP      3       /* 36-byte, opposite-endian */
+#define SCOREFILE_50           4       /* 32-byte */
+#define SCOREFILE_50_OPP       5       /* 32-byte, opposite-endian */
+
+/*
+ * Check (or guess) what kind of score file contents we have.
+ */
+static int
+scorefile_probe(int sd)
+{
+       struct stat st;
+       int t1, t2, t3, tx;
+       ssize_t result;
+       uint32_t numbers[3], offset56, offset60, offset64;
+
+       if (fstat(sd, &st) < 0) {
+               warn("Score file %s: fstat", _PATH_SCOREFILE);
+               return -1;
+       }
+
+       t1 = st.st_size % sizeof(struct highscore_ondisk) == 0;
+       t2 = st.st_size % sizeof(struct highscore_ondisk_599) == 0;
+       t3 = st.st_size % sizeof(struct highscore_ondisk_50) == 0;
+       tx = t1 + t2 + t3;
+       if (tx == 1) {
+               /* Size matches exact number of one kind of records */
+               if (t1) {
+                       return SCOREFILE_CURRENT;
+               } else if (t2) {
+                       return SCOREFILE_599;
+               } else {
+                       return SCOREFILE_50;
+               }
+       } else if (tx == 0) {
+               /* Size matches nothing, pick most likely as default */
+               goto wildguess;
+       }
+
+       /*
+        * File size is multiple of more than one structure size.
+        * (For example, 288 bytes could be 9*hso50 or 8*hso599.)
+        * Read the file and see if we can figure out what's going
+        * on. This is the layout of the first two records:
+        *
+        *   offset     hso / current   hso_599         hso_50
+        *              (40-byte)       (36-byte)       (32-byte)
+        *
+        *   0          name #0         name #0         name #0
+         *   4            :               :               :
+         *   8            :               :               :
+         *   12           :               :               :
+         *   16           :               :               :
+        *   20         score #0        score #0        score #0
+        *   24         level #0        level #0        level #0
+        *   28          (pad)          time #0         time #0
+        *   32         time #0                         name #1
+        *   36                         name #1           :
+         *   40         name #1           :               :
+         *   44           :               :               :
+         *   48           :               :               :
+        *   52           :               :             score #1
+        *   56           :             score #1        level #1
+        *   60         score #1        level #1        time #1
+        *   64         level #1        time #1         name #2
+        *   68          (pad)            :               :
+        *   72         time #1         name #2           :
+        *   76           :               :               :
+        *   80                  --- end ---
+        *
+        * There are a number of things we could check here, but the
+        * most effective test is based on the following restrictions:
+        *
+        *    - The level must be between 1 and 9 (inclusive)
+        *    - All times must be after 1985 and are before 2038,
+        *      so the high word must be 0 and the low word may not be
+        *      a small value.
+        *    - Integer values of 0 or 1-9 cannot be the beginning of
+        *      a login name string.
+        *    - Values of 1-9 are probably not a score.
+        *
+        * So we read the three words at offsets 56, 60, and 64, and
+        * poke at the values to try to figure things...
+        */
+
+       if (lseek(sd, 56, SEEK_SET) < 0) {
+               warn("Score file %s: lseek", _PATH_SCOREFILE);
+               return -1;
+       }
+       result = read(sd, &numbers, sizeof(numbers));
+       if (result < 0) {
+               warn("Score file %s: read", _PATH_SCOREFILE);
+               return -1;
+       }
+       if ((size_t)result != sizeof(numbers)) {
+               /*
+                * The smallest file whose size divides by more than
+                * one of the sizes is substantially larger than 64,
+                * so this should *never* happen.
+                */
+               warnx("Score file %s: Unexpected EOF", _PATH_SCOREFILE);
+               return -1;
+       }
+
+       offset56 = numbers[0];
+       offset60 = numbers[1];
+       offset64 = numbers[2];
+
+       if (offset64 >= MINLEVEL && offset64 <= MAXLEVEL) {
+               /* 40-byte structure */
+               return SCOREFILE_CURRENT;
+       } else if (offset60 >= MINLEVEL && offset60 <= MAXLEVEL) {
+               /* 36-byte structure */
+               return SCOREFILE_599;
+       } else if (offset56 >= MINLEVEL && offset56 <= MAXLEVEL) {
+               /* 32-byte structure */
+               return SCOREFILE_50;
+       }
+
+       /* None was a valid level; try opposite endian */
+       offset64 = bswap32(offset64);
+       offset60 = bswap32(offset60);
+       offset56 = bswap32(offset56);
+
+       if (offset64 >= MINLEVEL && offset64 <= MAXLEVEL) {
+               /* 40-byte structure */
+               return SCOREFILE_CURRENT_OPP;
+       } else if (offset60 >= MINLEVEL && offset60 <= MAXLEVEL) {
+               /* 36-byte structure */
+               return SCOREFILE_599_OPP;
+       } else if (offset56 >= MINLEVEL && offset56 <= MAXLEVEL) {
+               /* 32-byte structure */
+               return SCOREFILE_50_OPP;
+       }
+
+       /* That didn't work either, dunno what's going on */
+ wildguess:
+       warnx("Score file %s is likely corrupt", _PATH_SCOREFILE);
+       if (sizeof(void *) == 8 && sizeof(time_t) == 8) {
+               return SCOREFILE_CURRENT;
+       } else if (sizeof(time_t) == 8) {
+               return SCOREFILE_599;
+       } else {
+               return SCOREFILE_50;
+       }
+}
+
+/*
+ * Copy a string safely, making sure it's null-terminated.
+ */
+static void
+readname(char *to, size_t maxto, const char *from, size_t maxfrom)
+{
+       size_t amt;
+
+       amt = maxto < maxfrom ? maxto : maxfrom;
+       memcpy(to, from, amt);
+       to[maxto-1] = '\0';
+}
+
+/*
+ * Copy integers, byte-swapping if desired.
+ */
+static int32_t
+read32(int32_t val, int doflip)
+{
+       if (doflip) {
+               val = bswap32(val);
+       }
+       return val;
+}
+
+static int64_t
+read64(int64_t val, int doflip)
+{
+       if (doflip) {
+               val = bswap64(val);
+       }
+       return val;
+}
+
+/*
+ * Read up to MAXHISCORES scorefile_ondisk entries.
+ */
+static int
+readscores(int sd, int doflip)
+{
+       struct highscore_ondisk buf[MAXHISCORES];
+       ssize_t result;
+       int i;
+
+       result = read(sd, buf, sizeof(buf));
+       if (result < 0) {
+               warn("Score file %s: read", _PATH_SCOREFILE);
+               return -1;
+       }
+       nscores = result / sizeof(buf[0]);
+
+       for (i=0; i<nscores; i++) {
+               readname(scores[i].hs_name, sizeof(scores[i].hs_name),
+                        buf[i].hso_name, sizeof(buf[i].hso_name));
+               scores[i].hs_score = read32(buf[i].hso_score, doflip);
+               scores[i].hs_level = read32(buf[i].hso_level, doflip);
+               scores[i].hs_time = read64(buf[i].hso_time, doflip);
+       }
+       return 0;
+}
+
+/*
+ * Read up to MAXHISCORES scorefile_ondisk_599 entries.
+ */
+static int
+readscores599(int sd, int doflip)
+{
+       struct highscore_ondisk_599 buf[MAXHISCORES];
+       ssize_t result;
+       int i;
+
+       result = read(sd, buf, sizeof(buf));
+       if (result < 0) {
+               warn("Score file %s: read", _PATH_SCOREFILE);
+               return -1;
+       }
+       nscores = result / sizeof(buf[0]);
+
+       for (i=0; i<nscores; i++) {
+               readname(scores[i].hs_name, sizeof(scores[i].hs_name),
+                        buf[i].hso599_name, sizeof(buf[i].hso599_name));
+               scores[i].hs_score = read32(buf[i].hso599_score, doflip);
+               scores[i].hs_level = read32(buf[i].hso599_level, doflip);
+               /*
+                * Don't bother pasting the time together into a
+                * 64-bit value; just take whichever half is nonzero.
+                */
+               scores[i].hs_time =
+                       read32(buf[i].hso599_time[buf[i].hso599_time[0] == 0],
+                              doflip);
+       }
+       return 0;
+}
+
+/*
+ * Read up to MAXHISCORES scorefile_ondisk_50 entries.
+ */
+static int
+readscores50(int sd, int doflip)
+{
+       struct highscore_ondisk_50 buf[MAXHISCORES];
+       ssize_t result;
+       int i;
+
+       result = read(sd, buf, sizeof(buf));
+       if (result < 0) {
+               warn("Score file %s: read", _PATH_SCOREFILE);
+               return -1;
+       }
+       nscores = result / sizeof(buf[0]);
+
+       for (i=0; i<nscores; i++) {
+               readname(scores[i].hs_name, sizeof(scores[i].hs_name),
+                        buf[i].hso50_name, sizeof(buf[i].hso50_name));
+               scores[i].hs_score = read32(buf[i].hso50_score, doflip);
+               scores[i].hs_level = read32(buf[i].hso50_level, doflip);
+               scores[i].hs_time = read32(buf[i].hso50_time, doflip);
+       }
+       return 0;
+}
 
 /*
  * Read the score file.  Can be called from savescore (before showscores)
  * or showscores (if savescore will not be called).  If the given pointer
- * is not NULL, sets *fpp to an open file pointer that corresponds to a
+ * is not NULL, sets *fdp to an open file handle that corresponds to a
  * read/write score file that is locked with LOCK_EX.  Otherwise, the
  * file is locked with LOCK_SH for the read and closed before return.
- *
- * Note, we assume closing the stdio file releases the lock.
  */
 static void
-getscores(fpp)
-       FILE **fpp;
+getscores(int *fdp)
 {
+       struct highscore_header header;
        int sd, mint, lck;
-       char *mstr, *human;
-       FILE *sf;
+       mode_t mask;
+       const char *mstr, *human;
+       int doflip;
+       int serrno;
+       ssize_t result;
 
-       if (fpp != NULL) {
+#ifdef ALLOW_SCORE_UPDATES
+       if (fdp != NULL) {
                mint = O_RDWR | O_CREAT;
-               mstr = "r+";
                human = "read/write";
                lck = LOCK_EX;
-       } else {
+       } else
+#endif
+       {
                mint = O_RDONLY;
                mstr = "r";
                human = "reading";
                lck = LOCK_SH;
        }
+       setegid(egid);
+       mask = umask(S_IWOTH);
        sd = open(_PATH_SCOREFILE, mint, 0666);
+       serrno = errno;
+       (void)umask(mask);
+       setegid(gid);
        if (sd < 0) {
-               if (fpp == NULL) {
-                       nscores = 0;
-                       return;
+               /*
+                * If the file simply isn't there because nobody's
+                * played yet, and we aren't going to be trying to
+                * update it, don't warn. Even if we are going to be
+                * trying to write it, don't fail -- we can still show
+                * the player the score they got.
+                */
+               if (fdp != NULL || errno != ENOENT) {
+                       warn("Cannot open %s for %s", _PATH_SCOREFILE, human);
                }
-               (void)fprintf(stderr, "tetris: cannot open %s for %s: %s\n",
-                   _PATH_SCOREFILE, human, strerror(errno));
-               exit(1);
-       }
-       if ((sf = fdopen(sd, mstr)) == NULL) {
-               (void)fprintf(stderr, "tetris: cannot fdopen %s for %s: %s\n",
-                   _PATH_SCOREFILE, human, strerror(errno));
-               exit(1);
+               goto fail;
        }
 
        /*
         * Grab a lock.
+        * XXX: failure here should probably be more fatal than this.
         */
        if (flock(sd, lck))
-               (void)fprintf(stderr,
-                   "tetris: warning: score file %s cannot be locked: %s\n",
-                   _PATH_SCOREFILE, strerror(errno));
+               warn("warning: score file %s cannot be locked",
+                   _PATH_SCOREFILE);
 
-       nscores = fread(scores, sizeof(scores[0]), MAXHISCORES, sf);
-       if (ferror(sf)) {
-               (void)fprintf(stderr, "tetris: error reading %s: %s\n",
-                   _PATH_SCOREFILE, strerror(errno));
-               exit(1);
+       /*
+        * The current format (since -current of 20090525) is
+        *
+        *    struct highscore_header
+        *    up to MAXHIGHSCORES x struct highscore_ondisk
+        *
+        * Before this, there is no header, and the contents
+        * might be any of three formats:
+        *
+        *    highscore_ondisk       (64-bit machines with 64-bit time_t)
+        *    highscore_ondisk_599   (32-bit machines with 64-bit time_t)
+        *    highscore_ondisk_50    (32-bit machines with 32-bit time_t)
+        *
+        * The first two appear in 5.99 between the time_t change and
+        * 20090525, depending on whether the compiler inserts
+        * structure padding before an unaligned 64-bit time_t. The
+        * last appears in 5.0 and earlier.
+        *
+        * Any or all of these might also appear on other OSes where
+        * this code has been ported.
+        *
+        * Since the old file has no header, we will have to guess
+        * which of these formats it has.
+        */
+
+       /*
+        * First, look for a header.
+        */
+       result = read(sd, &header, sizeof(header));
+       if (result < 0) {
+               warn("Score file %s: read", _PATH_SCOREFILE);
+               goto sdfail;
+       }
+       if (result != 0 && (size_t)result != sizeof(header)) {
+               warnx("Score file %s: read: unexpected EOF", _PATH_SCOREFILE);
+               /*
+                * File is hopelessly corrupt, might as well truncate it
+                * and start over with empty scores.
+                */
+               if (lseek(sd, 0, SEEK_SET) < 0) {
+                       /* ? */
+                       warn("Score file %s: lseek", _PATH_SCOREFILE);
+                       goto sdfail;
+               }
+               if (ftruncate(sd, 0) == 0) {
+                       result = 0;
+               } else {
+                       goto sdfail;
+               }
+       }
+
+       if (result == 0) {
+               /* Empty file; that just means there are no scores. */
+               nscores = 0;
+       } else {
+               /*
+                * Is what we read a header, or the first 16 bytes of
+                * a score entry? hsh_magic_val is chosen to be
+                * something that is extremely unlikely to appear in
+                * hs_name[].
+                */
+               if (!memcmp(header.hsh_magic, hsh_magic_val, HSH_MAGIC_SIZE)) {
+                       /* Yes, we have a header. */
+
+                       if (header.hsh_endiantag == HSH_ENDIAN_NATIVE) {
+                               /* native endian */
+                               doflip = 0;
+                       } else if (header.hsh_endiantag == HSH_ENDIAN_OPP) {
+                               doflip = 1;
+                       } else {
+                               warnx("Score file %s: Unknown endian tag %u",
+                                       _PATH_SCOREFILE, header.hsh_endiantag);
+                               goto sdfail;
+                       }
+
+                       if (header.hsh_version != HSH_VERSION) {
+                               warnx("Score file %s: Unknown version code %u",
+                                       _PATH_SCOREFILE, header.hsh_version);
+                               goto sdfail;
+                       }
+
+                       if (readscores(sd, doflip) < 0) {
+                               goto sdfail;
+                       }
+               } else {
+                       /*
+                        * Ok, it wasn't a header. Try to figure out what
+                        * size records we have.
+                        */
+                       result = scorefile_probe(sd);
+                       if (lseek(sd, 0, SEEK_SET) < 0) {
+                               warn("Score file %s: lseek", _PATH_SCOREFILE);
+                               goto sdfail;
+                       }
+                       switch (result) {
+                       case SCOREFILE_CURRENT:
+                               result = readscores(sd, 0 /* don't flip */);
+                               break;
+                       case SCOREFILE_CURRENT_OPP:
+                               result = readscores(sd, 1 /* do flip */);
+                               break;
+                       case SCOREFILE_599:
+                               result = readscores599(sd, 0 /* don't flip */);
+                               break;
+                       case SCOREFILE_599_OPP:
+                               result = readscores599(sd, 1 /* do flip */);
+                               break;
+                       case SCOREFILE_50:
+                               result = readscores50(sd, 0 /* don't flip */);
+                               break;
+                       case SCOREFILE_50_OPP:
+                               result = readscores50(sd, 1 /* do flip */);
+                               break;
+                       default:
+                               goto sdfail;
+                       }
+                       if (result < 0) {
+                               goto sdfail;
+                       }
+               }
        }
+       
 
-       if (fpp)
-               *fpp = sf;
+       if (fdp)
+               *fdp = sd;
        else
-               (void)fclose(sf);
+               close(sd);
+
+       return;
+
+sdfail:
+       close(sd);
+ fail:
+       if (fdp != NULL) {
+               *fdp = -1;
+       }
+       nscores = 0;
 }
 
+#ifdef ALLOW_SCORE_UPDATES
+/*
+ * Paranoid write wrapper; unlike fwrite() it preserves errno.
+ */
+static int
+dowrite(int sd, const void *vbuf, size_t len)
+{
+       const char *buf = vbuf;
+       ssize_t result;
+       size_t done = 0;
+
+       while (done < len) {
+               result = write(sd, buf+done, len-done);
+               if (result < 0) {
+                       if (errno == EINTR) {
+                               continue;
+                       }
+                       return -1;
+               }
+               done += result;
+       }
+       return 0;
+}
+#endif /* ALLOW_SCORE_UPDATES */
+
+/*
+ * Write the score file out.
+ */
+static void
+putscores(int sd)
+{
+#ifdef ALLOW_SCORE_UPDATES
+       struct highscore_header header;
+       struct highscore_ondisk buf[MAXHISCORES];
+       int i;
+
+       if (sd == -1) {
+               return;
+       }
+
+       memcpy(header.hsh_magic, hsh_magic_val, HSH_MAGIC_SIZE);
+       header.hsh_endiantag = HSH_ENDIAN_NATIVE;
+       header.hsh_version = HSH_VERSION;
+
+       for (i=0; i<nscores; i++) {
+               strncpy(buf[i].hso_name, scores[i].hs_name,
+                       sizeof(buf[i].hso_name));
+               buf[i].hso_score = scores[i].hs_score;
+               buf[i].hso_level = scores[i].hs_level;
+               buf[i].hso_pad = 0xbaadf00d;
+               buf[i].hso_time = scores[i].hs_time;
+       }
+
+       if (lseek(sd, 0, SEEK_SET) < 0) {
+               warn("Score file %s: lseek", _PATH_SCOREFILE);
+               goto fail;
+       }
+       if (dowrite(sd, &header, sizeof(header)) < 0 ||
+           dowrite(sd, buf, sizeof(buf[0]) * nscores) < 0) {
+               warn("Score file %s: write", _PATH_SCOREFILE);
+               goto fail;
+       }
+       return;
+ fail:
+       warnx("high scores may be damaged");
+#else
+       (void)sd;
+#endif /* ALLOW_SCORE_UPDATES */
+}
+
+/*
+ * Close the score file.
+ */
+static void
+closescores(int sd)
+{
+       flock(sd, LOCK_UN);
+       close(sd);
+}
+
+/*
+ * Read and update the scores file with the current reults.
+ */
 void
-savescore(level)
-       int level;
+savescore(int level)
 {
-       register struct highscore *sp;
-       register int i;
+       struct highscore *sp;
+       int i;
        int change;
-       FILE *sf;
+       int sd;
        const char *me;
 
-       getscores(&sf);
+       getscores(&sd);
        gotscores = 1;
        (void)time(&now);
 
@@ -202,15 +701,9 @@ savescore(level)
                 * Sort & clean the scores, then rewrite.
                 */
                nscores = checkscores(scores, nscores);
-               rewind(sf);
-               if (fwrite(scores, sizeof(*sp), nscores, sf) != nscores ||
-                   fflush(sf) == EOF)
-                       (void)fprintf(stderr,
-                           "tetris: error writing %s: %s -- %s\n",
-                           _PATH_SCOREFILE, strerror(errno),
-                           "high scores may be damaged");
-       }
-       (void)fclose(sf);       /* releases lock */
+               putscores(sd);
+       }
+       closescores(sd);
 }
 
 /*
@@ -218,11 +711,11 @@ savescore(level)
  * The result is always trimmed to fit in a score.
  */
 static char *
-thisuser()
+thisuser(void)
 {
-       register const char *p;
-       register struct passwd *pw;
-       register size_t l;
+       const char *p;
+       struct passwd *pw;
+       size_t l;
        static char u[sizeof(scores[0].hs_name)];
 
        if (u[0])
@@ -238,7 +731,7 @@ thisuser()
        l = strlen(p);
        if (l >= sizeof(u))
                l = sizeof(u) - 1;
-       bcopy(p, u, l);
+       memcpy(u, p, l);
        u[l] = '\0';
        return (u);
 }
@@ -250,11 +743,10 @@ thisuser()
  * listed first in the highscore file.
  */
 static int
-cmpscores(x, y)
-       const void *x, *y;
+cmpscores(const void *x, const void *y)
 {
-       register const struct highscore *a, *b;
-       register long l;
+       const struct highscore *a, *b;
+       long l;
 
        a = x;
        b = y;
@@ -280,18 +772,16 @@ cmpscores(x, y)
  * Caveat:  the highest score on each level is always kept.
  */
 static int
-checkscores(hs, num)
-       register struct highscore *hs;
-       int num;
+checkscores(struct highscore *hs, int num)
 {
-       register struct highscore *sp;
-       register int i, j, k, numnames;
+       struct highscore *sp;
+       int i, j, k, numnames;
        int levelfound[NLEVELS];
        struct peruser {
                char *name;
                int times;
        } count[NUMSPOTS];
-       register struct peruser *pu;
+       struct peruser *pu;
 
        /*
         * Sort so that highest totals come first.
@@ -340,7 +830,8 @@ checkscores(hs, num)
                                continue;
                        }
                }
-               levelfound[sp->hs_level] = 1;
+        if (sp->hs_level < NLEVELS && sp->hs_level >= 0)
+               levelfound[sp->hs_level] = 1;
                i++, sp++;
        }
        return (num > MAXHISCORES ? MAXHISCORES : num);
@@ -354,16 +845,15 @@ checkscores(hs, num)
  *   before it can be shown anyway.
  */
 void
-showscores(level)
-       int level;
+showscores(int level)
 {
-       register struct highscore *sp;
-       register int i, n, c;
+       struct highscore *sp;
+       int i, n, c;
        const char *me;
        int levelfound[NLEVELS];
 
        if (!gotscores)
-               getscores((FILE **)NULL);
+               getscores(NULL);
        (void)printf("\n\t\t\t    Tetris High Scores\n");
 
        /*
@@ -371,7 +861,7 @@ showscores(level)
         * the high scores; we do not need to check for printing in highlight
         * mode.  If SOstr is null, we can't do highlighting anyway.
         */
-       me = level && SOstr ? thisuser() : NULL;
+       me = level && enter_standout_mode ? thisuser() : NULL;
 
        /*
         * Set times to 0 except for high score on each level.
@@ -379,12 +869,14 @@ showscores(level)
        for (i = MINLEVEL; i < NLEVELS; i++)
                levelfound[i] = 0;
        for (i = 0, sp = scores; i < nscores; i++, sp++) {
-               if (levelfound[sp->hs_level])
-                       sp->hs_time = 0;
-               else {
-                       sp->hs_time = 1;
-                       levelfound[sp->hs_level] = 1;
-               }
+        if (sp->hs_level < NLEVELS && sp->hs_level >= 0) {
+               if (levelfound[sp->hs_level])
+                       sp->hs_time = 0;
+                   else {
+                           sp->hs_time = 1;
+                       levelfound[sp->hs_level] = 1;
+                   }
+        }
        }
 
        /*
@@ -407,13 +899,9 @@ showscores(level)
 }
 
 static void
-printem(level, offset, hs, n, me)
-       int level, offset;
-       register struct highscore *hs;
-       register int n;
-       const char *me;
+printem(int level, int offset, struct highscore *hs, int n, const char *me)
 {
-       register struct highscore *sp;
+       struct highscore *sp;
        int nrows, row, col, item, i, highlight;
        char buf[100];
 #define        TITLE "Rank  Score   Name     (points/level)"
@@ -437,10 +925,9 @@ printem(level, offset, hs, n, me)
                                (void)putchar('\n');
                                continue;
                        }
-                       (void)printf(item + offset < 10 ? "  " : " ");
                        sp = &hs[item];
-                       (void)sprintf(buf,
-                           "%d%c %6d  %-11s (%d on %d)",
+                       (void)snprintf(buf, sizeof(buf),
+                           "%3d%c %6d  %-11s (%6d on %d)",
                            item + offset, sp->hs_time ? '*' : ' ',
                            sp->hs_score * sp->hs_level,
                            sp->hs_name, sp->hs_score, sp->hs_level);
@@ -452,12 +939,12 @@ printem(level, offset, hs, n, me)
                            sp->hs_level == level &&
                            sp->hs_score == score &&
                            strcmp(sp->hs_name, me) == 0) {
-                               putpad(SOstr);
+                               putpad(enter_standout_mode);
                                highlight = 1;
                        }
                        (void)printf("%s", buf);
                        if (highlight) {
-                               putpad(SEstr);
+                               putpad(exit_standout_mode);
                                highlight = 0;
                        }