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