+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;
+}