From 51f33549bfb1594cd9ddc085b4f8ef124582516e Mon Sep 17 00:00:00 2001 From: dholland Date: Mon, 25 May 2009 08:33:57 +0000 Subject: Clean up scorefile handling. This was writing time_t to disk. Worse, the time_t member was at an unaligned offset in the structure in question, so after the time_t change the structure layout depended on compiler-inserted structure padding. This gives three legacy formats: one with 32-bit time, one with 64-bit time, and one with 64-bit time and 4 bytes of structure padding. And of course the file didn't have a header or version coding or anything. The new code writes a structure of well-defined size that should not receive unexpected padding, and gives the file a header and version number. It reads that format and any of the three legacy formats, figuring out which one it's dealing with by inspecting the file contents. For good measure, it also now handles opposite-endian files, doesn't bail out unceremoniously unless necessary, and won't croak if the file is corrupt and e.g. contains unterminated strings. (Was it worth going to this length? Maybe not. But it didn't seem right to just leave it, and it's not clear where to stop halfway.) --- tetris/scores.c | 555 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- tetris/scores.h | 37 +++- 2 files changed, 556 insertions(+), 36 deletions(-) (limited to 'tetris') diff --git a/tetris/scores.c b/tetris/scores.c index 0ebd03cf..a62892c1 100644 --- a/tetris/scores.c +++ b/tetris/scores.c @@ -1,4 +1,4 @@ -/* $NetBSD: scores.c,v 1.15 2009/05/25 04:33:53 dholland Exp $ */ +/* $NetBSD: scores.c,v 1.16 2009/05/25 08:33:57 dholland Exp $ */ /*- * Copyright (c) 1992, 1993 @@ -76,30 +76,311 @@ static struct highscore scores[NUMSPOTS]; static int checkscores(struct highscore *, int); 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 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