diff options
-rw-r--r-- | adventure/save.c | 758 |
1 files changed, 712 insertions, 46 deletions
diff --git a/adventure/save.c b/adventure/save.c index 5df84982..b0c92c2b 100644 --- a/adventure/save.c +++ b/adventure/save.c @@ -1,4 +1,4 @@ -/* $NetBSD: save.c,v 1.11 2009/08/25 06:56:52 dholland Exp $ */ +/* $NetBSD: save.c,v 1.12 2012/01/07 22:23:16 dholland Exp $ */ /*- * Copyright (c) 1991, 1993 @@ -39,22 +39,422 @@ #if 0 static char sccsid[] = "@(#)save.c 8.1 (Berkeley) 5/31/93"; #else -__RCSID("$NetBSD: save.c,v 1.11 2009/08/25 06:56:52 dholland Exp $"); +__RCSID("$NetBSD: save.c,v 1.12 2012/01/07 22:23:16 dholland Exp $"); #endif #endif /* not lint */ -#include <err.h> +#include <sys/types.h> +#include <sys/time.h> +#include <stdbool.h> #include <stdio.h> #include <stdlib.h> +#include <err.h> +#include <assert.h> + #include "hdr.h" #include "extern.h" -struct savestruct { +struct savefile { + FILE *f; + const char *name; + bool warned; + unsigned bintextpos; + uint32_t key; + uint32_t sum; + unsigned char pad[8]; + unsigned padpos; +}; + +#define BINTEXT_WIDTH 60 +#define FORMAT_VERSION 1 +static const char header[] = "Adventure save file\n"; + +//////////////////////////////////////////////////////////// +// base16 output encoding + +/* + * Map 16 plain values into 90 coded values and back. + */ + +static const char coding[90] = + "Db.GOyT]7a6zpF(c*5H9oK~0[WVAg&kR)ml,2^q-1Y3v+" + "X/=JirZL$C>_N?:}B{dfnsxU<@MQ%8|P!4h`ESt;euwIj" +; + +static int +readletter(char letter, unsigned char *ret) +{ + const char *s; + + s = strchr(coding, letter); + if (s == NULL) { + return 1; + } + *ret = (s - coding) % 16; + return 0; +} + +static char +writeletter(unsigned char nibble) +{ + unsigned code; + + assert(nibble < 16); + do { + code = (16 * (random() % 6)) + nibble; + } while (code >= 90); + return coding[code]; +} + +//////////////////////////////////////////////////////////// +// savefile + +/* + * Open a savefile. + */ +static struct savefile * +savefile_open(const char *name, bool forwrite) +{ + struct savefile *sf; + + sf = malloc(sizeof(*sf)); + if (sf == NULL) { + return NULL; + } + sf->f = fopen(name, forwrite ? "w" : "r"); + if (sf->f == NULL) { + free(sf); + fprintf(stderr, + "Hmm. The name \"%s\" appears to be magically blocked.\n", + name); + return NULL; + } + sf->name = name; + sf->warned = false; + sf->bintextpos = 0; + sf->key = 0; + sf->sum = 0; + memset(sf->pad, 0, sizeof(sf->pad)); + sf->padpos = 0; + return sf; +} + +/* + * Raw read. + */ +static int +savefile_rawread(struct savefile *sf, void *data, size_t len) +{ + size_t result; + + result = fread(data, 1, len, sf->f); + if (result != len || ferror(sf->f)) { + fprintf(stderr, "Oops: error reading %s.\n", sf->name); + sf->warned = true; + return 1; + } + return 0; +} + +/* + * Raw write. + */ +static int +savefile_rawwrite(struct savefile *sf, const void *data, size_t len) +{ + size_t result; + + result = fwrite(data, 1, len, sf->f); + if (result != len || ferror(sf->f)) { + fprintf(stderr, "Oops: error writing %s.\n", sf->name); + sf->warned = true; + return 1; + } + return 0; +} + +/* + * Close a savefile. + */ +static int +savefile_close(struct savefile *sf) +{ + int ret; + + if (sf->bintextpos > 0) { + savefile_rawwrite(sf, "\n", 1); + } + + ret = 0; + if (fclose(sf->f)) { + if (!sf->warned) { + fprintf(stderr, "Oops: error on %s.\n", sf->name); + } + ret = 1; + } + free(sf); + return ret; +} + +/* + * Read encoded binary data, discarding any whitespace that appears. + */ +static int +savefile_bintextread(struct savefile *sf, void *data, size_t len) +{ + size_t pos; + unsigned char *udata; + int ch; + + udata = data; + pos = 0; + while (pos < len) { + ch = fgetc(sf->f); + if (ch == EOF || ferror(sf->f)) { + fprintf(stderr, "Oops: error reading %s.\n", sf->name); + sf->warned = true; + return 1; + } + if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') { + continue; + } + udata[pos++] = ch; + } + return 0; +} + +/* + * Read binary data, decoding from text using readletter(). + */ +static int +savefile_binread(struct savefile *sf, void *data, size_t len) +{ + unsigned char buf[64]; + unsigned char *udata; + unsigned char val1, val2; + size_t pos, amt, i; + + udata = data; + pos = 0; + while (pos < len) { + amt = len - pos; + if (amt > sizeof(buf) / 2) { + amt = sizeof(buf) / 2; + } + if (savefile_bintextread(sf, buf, amt*2)) { + return 1; + } + for (i=0; i<amt; i++) { + if (readletter(buf[i*2], &val1)) { + return 1; + } + if (readletter(buf[i*2 + 1], &val2)) { + return 1; + } + udata[pos++] = val1 * 16 + val2; + } + } + return 0; +} + +/* + * Write encoded binary data, inserting newlines to get a neatly + * formatted block. + */ +static int +savefile_bintextwrite(struct savefile *sf, const void *data, size_t len) +{ + size_t pos, amt; + const unsigned char *udata; + + udata = data; + pos = 0; + while (pos < len) { + amt = BINTEXT_WIDTH - sf->bintextpos; + if (amt > len - pos) { + amt = len - pos; + } + if (savefile_rawwrite(sf, udata + pos, amt)) { + return 1; + } + pos += amt; + sf->bintextpos += amt; + if (sf->bintextpos >= BINTEXT_WIDTH) { + savefile_rawwrite(sf, "\n", 1); + sf->bintextpos = 0; + } + } + return 0; +} + +/* + * Write binary data, encoding as text using writeletter(). + */ +static int +savefile_binwrite(struct savefile *sf, const void *data, size_t len) +{ + unsigned char buf[64]; + const unsigned char *udata; + size_t pos, bpos; + unsigned char byte; + + udata = data; + pos = 0; + bpos = 0; + while (pos < len) { + byte = udata[pos++]; + buf[bpos++] = writeletter(byte >> 4); + buf[bpos++] = writeletter(byte & 0xf); + if (bpos >= sizeof(buf)) { + if (savefile_bintextwrite(sf, buf, bpos)) { + return 1; + } + bpos = 0; + } + } + if (savefile_bintextwrite(sf, buf, bpos)) { + return 1; + } + return 0; +} + +/* + * Lightweight "encryption" for save files. This is not meant to + * be secure and wouldn't be even if we didn't write the decrypt + * key to the beginning of the save file; it's just meant to be + * enough to discourage casual cheating. + */ + +/* + * Make cheesy hash of buf[0..buflen]. Note: buf and outhash may overlap. + */ +static void +hash(const void *data, size_t datalen, unsigned char *out, size_t outlen) +{ + const unsigned char *udata; + size_t i; + uint64_t val; + const unsigned char *uval; + size_t valpos; + + udata = data; + val = 0; + for (i=0; i<datalen; i++) { + val = val ^ 0xbadc0ffee; + val = (val << 4) | (val >> 60); + val += udata[i] ^ 0xbeef; + } + + uval = (unsigned char *)&val; + valpos = 0; + for (i=0; i<outlen; i++) { + out[i] = uval[valpos++]; + if (valpos >= sizeof(val)) { + valpos = 0; + } + } +} + +/* + * Set the "encryption" key. + */ +static void +savefile_key(struct savefile *sf, uint32_t key) +{ + sf->key = 0; + sf->sum = 0; + hash(&sf->key, sizeof(sf->key), sf->pad, sizeof(sf->pad)); + sf->padpos = 0; +} + +/* + * Get an "encryption" pad byte. This forms a stream "cipher" that we + * xor with the plaintext save data. + */ +static unsigned char +savefile_getpad(struct savefile *sf) +{ + unsigned char ret; + + ret = sf->pad[sf->padpos++]; + if (sf->padpos >= sizeof(sf->pad)) { + hash(sf->pad, sizeof(sf->pad), sf->pad, sizeof(sf->pad)); + sf->padpos = 0; + } + return ret; +} + +/* + * Read "encrypted" data. + */ +static int +savefile_cread(struct savefile *sf, void *data, size_t len) +{ + char buf[64]; + unsigned char *udata; + size_t pos, amt, i; + unsigned char ch; + + udata = data; + pos = 0; + while (pos < len) { + amt = len - pos; + if (amt > sizeof(buf)) { + amt = sizeof(buf); + } + if (savefile_binread(sf, buf, amt)) { + return 1; + } + for (i=0; i<amt; i++) { + ch = buf[i]; + ch ^= savefile_getpad(sf); + udata[pos + i] = ch; + } + pos += amt; + } + return 0; +} + +/* + * Write "encrypted" data. + */ +static int +savefile_cwrite(struct savefile *sf, const void *data, size_t len) +{ + char buf[64]; + const unsigned char *udata; + size_t pos, amt, i; + unsigned char ch; + + udata = data; + pos = 0; + while (pos < len) { + amt = len - pos; + if (amt > sizeof(buf)) { + amt = sizeof(buf); + } + for (i=0; i<amt; i++) { + ch = udata[pos + i]; + ch ^= savefile_getpad(sf); + buf[i] = ch; + } + if (savefile_binwrite(sf, buf, amt)) { + return 1; + } + pos += amt; + } + return 0; +} + +//////////////////////////////////////////////////////////// +// compat for old save files + +struct compat_saveinfo { void *address; int width; }; -static const struct savestruct save_array[] = +static const struct compat_saveinfo compat_savearray[] = { {&abbnum, sizeof(abbnum)}, {&attack, sizeof(attack)}, @@ -120,46 +520,11 @@ static const struct savestruct save_array[] = {NULL, 0} }; -/* Two passes on data: first to get checksum, second */ -/* to output the data using checksum to start random #s */ -int -save(const char *outfile) -{ - FILE *out; - const struct savestruct *p; - char *s; - long sum; - int i; - - crc_start(); - for (p = save_array; p->address != NULL; p++) - sum = crc(p->address, p->width); - srandom((int) sum); - - if ((out = fopen(outfile, "wb")) == NULL) { - fprintf(stderr, - "Hmm. The name \"%s\" appears to be magically blocked.\n", - outfile); - return 1; - } - fwrite(&sum, sizeof(sum), 1, out); /* Here's the random() key */ - for (p = save_array; p->address != NULL; p++) { - for (s = p->address, i = 0; i < p->width; i++, s++) - *s = (*s ^ random()) & 0xFF; /* Lightly encrypt */ - fwrite(p->address, p->width, 1, out); - } - if (fclose(out) != 0) { - warn("writing %s", outfile); - return 1; - } - return 0; -} - -int -restore(const char *infile) +static int +compat_restore(const char *infile) { FILE *in; - const struct savestruct *p; + const struct compat_saveinfo *p; char *s; long sum, cksum = 0; int i; @@ -172,7 +537,7 @@ restore(const char *infile) } fread(&sum, sizeof(sum), 1, in); /* Get the seed */ srandom((int) sum); - for (p = save_array; p->address != NULL; p++) { + for (p = compat_savearray; p->address != NULL; p++) { fread(p->address, p->width, 1, in); for (s = p->address, i = 0; i < p->width; i++, s++) *s = (*s ^ random()) & 0xFF; /* Lightly decrypt */ @@ -180,11 +545,312 @@ restore(const char *infile) fclose(in); crc_start(); /* See if she cheated */ - for (p = save_array; p->address != NULL; p++) + for (p = compat_savearray; p->address != NULL; p++) cksum = crc(p->address, p->width); if (sum != cksum) /* Tsk tsk */ return 2; /* Altered the file */ /* We successfully restored, so this really was a save file */ - /* Get rid of the file, but don't bother checking that we did */ + + /* + * The above code loads these from disk even though they're + * pointers. Null them out and hope we don't crash on them + * later; that's better than having them be garbage. + */ + tkk = NULL; + wd1 = NULL; + wd2 = NULL; + + return 0; +} + +//////////////////////////////////////////////////////////// +// save + restore + +static int *const save_ints[] = { + &abbnum, + &attack, + &blklin, + &bonus, + &chloc, + &chloc2, + &clock1, + &clock2, + &closed, + &isclosing, + &daltloc, + &demo, + &detail, + &dflag, + &dkill, + &dtotal, + &foobar, + &gaveup, + &holding, + &iwest, + &k, + &k2, + &knfloc, + &kq, + &latency, + &limit, + &lmwarn, + &loc, + &maxdie, + &maxscore, + &newloc, + &numdie, + &obj, + &oldloc2, + &oldloc, + &panic, + &saveday, + &savet, + &scoring, + &spk, + &stick, + &tally, + &tally2, + &turns, + &verb, + &wasdark, + &yea, +}; +static const unsigned num_save_ints = __arraycount(save_ints); + +#define INTARRAY(sym) { sym, __arraycount(sym) } + +static const struct { + int *ptr; + unsigned num; +} save_intarrays[] = { + INTARRAY(atloc), + INTARRAY(dseen), + INTARRAY(dloc), + INTARRAY(odloc), + INTARRAY(fixed), + INTARRAY(hinted), + INTARRAY(links), + INTARRAY(place), + INTARRAY(prop), + INTARRAY(tk), +}; +static const unsigned num_save_intarrays = __arraycount(save_intarrays); + +#undef INTARRAY + +#if 0 +static const struct { + void *ptr; + size_t len; +} save_blobs[] = { + { &wd1, sizeof(wd1) }, + { &wd2, sizeof(wd2) }, + { &tkk, sizeof(tkk) }, +}; +static const unsigned num_save_blobs = __arraycount(save_blobs); +#endif + +/* + * Write out a save file. Returns nonzero on error. + */ +int +save(const char *outfile) +{ + struct savefile *sf; + struct timespec now; + uint32_t key, writeable_key; + uint32_t version; + unsigned i, j, n; + uint32_t val; + + sf = savefile_open(outfile, true); + if (sf == NULL) { + return 1; + } + + if (savefile_rawwrite(sf, header, strlen(header))) { + savefile_close(sf); + return 1; + } + + version = htonl(FORMAT_VERSION); + if (savefile_binwrite(sf, &version, sizeof(version))) { + savefile_close(sf); + return 1; + } + + clock_gettime(CLOCK_REALTIME, &now); + key = (uint32_t)(now.tv_sec & 0xffffffff) ^ (uint32_t)(now.tv_nsec); + + writeable_key = htonl(key); + if (savefile_binwrite(sf, &writeable_key, sizeof(writeable_key))) { + savefile_close(sf); + return 1; + } + + /* other parts of the code may depend on us doing this here */ + srandom(key); + + savefile_key(sf, key); + + /* + * Integers + */ + for (i=0; i<num_save_ints; i++) { + val = *(save_ints[i]); + val = htonl(val); + if (savefile_cwrite(sf, &val, sizeof(val))) { + savefile_close(sf); + return 1; + } + } + + /* + * Arrays of integers + */ + for (i=0; i<num_save_intarrays; i++) { + n = save_intarrays[i].num; + for (j=0; j<n; j++) { + val = save_intarrays[i].ptr[j]; + val = htonl(val); + if (savefile_cwrite(sf, &val, sizeof(val))) { + savefile_close(sf); + return 1; + } + } + } + +#if 0 + /* + * Blobs + */ + for (i=0; i<num_save_blobs; i++) { + if (savefile_cwrite(sf, save_blobs[i].ptr, save_blobs[i].len)) { + savefile_close(sf); + return 1; + } + } +#endif + + sf->sum = htonl(sf->sum); + if (savefile_binwrite(sf, &sf->sum, sizeof(&sf->sum))) { + savefile_close(sf); + return 1; + } + savefile_close(sf); + return 0; +} + +/* + * Read in a save file. Returns nonzero on error. + */ +int +restore(const char *infile) +{ + struct savefile *sf; + char buf[sizeof(header)]; + size_t headersize = strlen(header); + uint32_t version, key, sum; + unsigned i, j, n; + uint32_t val; + + sf = savefile_open(infile, false); + if (sf == NULL) { + return 1; + } + + if (savefile_rawread(sf, buf, headersize)) { + savefile_close(sf); + return 1; + } + buf[headersize] = 0; + if (strcmp(buf, header) != 0) { + savefile_close(sf); + fprintf(stderr, "Oh dear, that isn't one of my save files.\n"); + fprintf(stderr, + "Trying the Olde Waye; this myte notte Worke.\n"); + return compat_restore(infile); + } + + if (savefile_binread(sf, &version, sizeof(version))) { + savefile_close(sf); + return 1; + } + version = ntohl(version); + if (version != FORMAT_VERSION) { + savefile_close(sf); + fprintf(stderr, + "Oh dear, that file must be from the future. I don't know" + " how to read it!\n"); + return 1; + } + + if (savefile_binread(sf, &key, sizeof(key))) { + savefile_close(sf); + return 1; + } + key = ntohl(key); + savefile_key(sf, key); + + /* other parts of the code may depend on us doing this here */ + srandom(key); + + /* + * Integers + */ + for (i=0; i<num_save_ints; i++) { + if (savefile_cread(sf, &val, sizeof(val))) { + savefile_close(sf); + return 1; + } + val = ntohl(val); + *(save_ints[i]) = val; + } + + /* + * Arrays of integers + */ + for (i=0; i<num_save_intarrays; i++) { + n = save_intarrays[i].num; + for (j=0; j<n; j++) { + if (savefile_cread(sf, &val, sizeof(val))) { + savefile_close(sf); + return 1; + } + val = ntohl(val); + save_intarrays[i].ptr[j] = val; + } + } + +#if 0 + /* + * Blobs + */ + for (i=0; i<num_save_blobs; i++) { + if (savefile_cread(sf, save_blobs[i].ptr, save_blobs[i].len)) { + savefile_close(sf); + return 1; + } + } +#endif + + if (savefile_binread(sf, &sum, sizeof(&sum))) { + savefile_close(sf); + return 1; + } + sum = ntohl(sum); + /* See if she cheated */ + if (sum != sf->sum) { + /* Tsk tsk, altered the file */ + savefile_close(sf); + return 2; + } + savefile_close(sf); + + /* Load theoretically invalidates these */ + tkk = NULL; + wd1 = NULL; + wd2 = NULL; + return 0; } |