[BACK]Return to scores.c CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / src / games / tetris

Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.

Diff for /src/games/tetris/scores.c between version 1.15 and 1.16

version 1.15, 2009/05/25 04:33:53 version 1.16, 2009/05/25 08:33:57
Line 76  static struct highscore scores[NUMSPOTS]
Line 76  static struct highscore scores[NUMSPOTS]
   
 static int checkscores(struct highscore *, int);  static int checkscores(struct highscore *, int);
 static int cmpscores(const void *, const void *);  static int cmpscores(const void *, const void *);
 static void getscores(FILE **);  static void getscores(int *);
 static void printem(int, int, struct highscore *, int, const char *);  static void printem(int, int, struct highscore *, int, const char *);
 static char *thisuser(void);  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)   * Read the score file.  Can be called from savescore (before showscores)
  * or showscores (if savescore will not be called).  If the given pointer   * 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   * 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.   * 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  static void
 getscores(FILE **fpp)  getscores(int *fdp)
 {  {
           struct highscore_header header;
         int sd, mint, lck;          int sd, mint, lck;
         mode_t mask;          mode_t mask;
         const char *mstr, *human;          const char *mstr, *human;
         FILE *sf;          int doflip;
           int serrno;
           ssize_t result;
   
         if (fpp != NULL) {          if (fdp != NULL) {
                 mint = O_RDWR | O_CREAT;                  mint = O_RDWR | O_CREAT;
                 mstr = "r+";  
                 human = "read/write";                  human = "read/write";
                 lck = LOCK_EX;                  lck = LOCK_EX;
         } else {          } else {
Line 111  getscores(FILE **fpp)
Line 392  getscores(FILE **fpp)
         setegid(egid);          setegid(egid);
         mask = umask(S_IWOTH);          mask = umask(S_IWOTH);
         sd = open(_PATH_SCOREFILE, mint, 0666);          sd = open(_PATH_SCOREFILE, mint, 0666);
           serrno = errno;
         (void)umask(mask);          (void)umask(mask);
           setegid(gid);
         if (sd < 0) {          if (sd < 0) {
                 if (fpp == NULL) {                  /*
                         nscores = 0;                   * If the file simply isn't there because nobody's
                         setegid(gid);                   * played yet, and we aren't going to be trying to
                         return;                   * 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);
                 }                  }
                 err(1, "cannot open %s for %s", _PATH_SCOREFILE, human);                  goto fail;
         }  
         if ((sf = fdopen(sd, mstr)) == NULL) {  
                 err(1, "cannot fdopen %s for %s", _PATH_SCOREFILE, human);  
         }          }
         setegid(gid);  
   
         /*          /*
          * Grab a lock.           * Grab a lock.
            * XXX: failure here should probably be more fatal than this.
          */           */
         if (flock(sd, lck))          if (flock(sd, lck))
                 warn("warning: score file %s cannot be locked",                  warn("warning: score file %s cannot be locked",
                     _PATH_SCOREFILE);                      _PATH_SCOREFILE);
   
         nscores = fread(scores, sizeof(scores[0]), MAXHISCORES, sf);          /*
         if (ferror(sf)) {           * The current format (since -current of 20090525) is
                 err(1, "error reading %s", _PATH_SCOREFILE);           *
            *    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);
                   close(sd);
                   goto fail;
           }
           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 fail;
                   }
                   if (ftruncate(sd, 0) == 0) {
                           result = 0;
                   } else {
                           close(sd);
                           goto fail;
                   }
           }
   
           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 fail;
                           }
   
                           if (header.hsh_version != HSH_VERSION) {
                                   warnx("Score file %s: Unknown version code %u",
                                           _PATH_SCOREFILE, header.hsh_version);
                                   goto fail;
                           }
   
                           if (readscores(sd, doflip) < 0) {
                                   goto fail;
                           }
                   } 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 fail;
                           }
                           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 fail;
                           }
                           if (result < 0) {
                                   goto fail;
                           }
                   }
         }          }
   
   
         if (fpp)          if (fdp)
                 *fpp = sf;                  *fdp = sd;
         else          else
                 (void)fclose(sf);                  close(sd);
   
           return;
   
    fail:
           if (fdp != NULL) {
                   *fdp = -1;
           }
           nscores = 0;
   }
   
   /*
    * 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;
 }  }
   
   /*
    * Write the score file out.
    */
   static void
   putscores(int sd)
   {
           struct highscore_header header;
           struct highscore_ondisk buf[nscores];
           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");
   }
   
   /*
    * 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  void
 savescore(int level)  savescore(int level)
 {  {
         struct highscore *sp;          struct highscore *sp;
         int i;          int i;
         int change;          int change;
         FILE *sf;          int sd;
         const char *me;          const char *me;
   
         getscores(&sf);          getscores(&sd);
         gotscores = 1;          gotscores = 1;
         (void)time(&now);          (void)time(&now);
   
Line 195  savescore(int level)
Line 685  savescore(int level)
                  * Sort & clean the scores, then rewrite.                   * Sort & clean the scores, then rewrite.
                  */                   */
                 nscores = checkscores(scores, nscores);                  nscores = checkscores(scores, nscores);
                 rewind(sf);                  putscores(sd);
                 if (fwrite(scores, sizeof(*sp), nscores, sf) != (size_t)nscores ||  
                     fflush(sf) == EOF)  
                         warnx("error writing %s: %s -- %s",  
                             _PATH_SCOREFILE, strerror(errno),  
                             "high scores may be damaged");  
         }          }
         (void)fclose(sf);       /* releases lock */          closescores(sd);
 }  }
   
 /*  /*
Line 352  showscores(int level)
Line 837  showscores(int level)
         int levelfound[NLEVELS];          int levelfound[NLEVELS];
   
         if (!gotscores)          if (!gotscores)
                 getscores((FILE **)NULL);                  getscores(NULL);
         (void)printf("\n\t\t\t    Tetris High Scores\n");          (void)printf("\n\t\t\t    Tetris High Scores\n");
   
         /*          /*

Legend:
Removed from v.1.15  
changed lines
  Added in v.1.16

CVSweb <webmaster@jp.NetBSD.org>