X-Git-Url: https://git.cameronkatri.com/bsdgames-darwin.git/blobdiff_plain/ae9b67f6a8d26e5428a3cbefadaa66889d4c9137..08eca96e71d96ad1f8e9b888875ab5570f208d19:/adventure/save.c diff --git a/adventure/save.c b/adventure/save.c index 9f0e8d3a..c4054b46 100644 --- a/adventure/save.c +++ b/adventure/save.c @@ -1,4 +1,4 @@ -/* $NetBSD: save.c,v 1.6 1999/07/16 01:38:20 hubertf Exp $ */ +/* $NetBSD: save.c,v 1.14 2014/03/22 22:04:40 dholland Exp $ */ /*- * Copyright (c) 1991, 1993 @@ -17,11 +17,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by the University of - * California, Berkeley and its contributors. - * 4. Neither the name of the University nor the names of its contributors + * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * @@ -43,21 +39,427 @@ #if 0 static char sccsid[] = "@(#)save.c 8.1 (Berkeley) 5/31/93"; #else -__RCSID("$NetBSD: save.c,v 1.6 1999/07/16 01:38:20 hubertf Exp $"); +__RCSID("$NetBSD: save.c,v 1.14 2014/03/22 22:04:40 dholland Exp $"); #endif #endif /* not lint */ +#include +#include +#include #include #include +#include +#include + #include "hdr.h" #include "extern.h" -struct savestruct { +#define __arraycount(a) (sizeof(a) / sizeof(*(a))) + +struct savefile { + FILE *f; + const char *name; + bool warned; + size_t bintextpos; + uint32_t key; + struct crcstate crc; + unsigned char pad[8]; + unsigned padpos; +}; + +#define BINTEXT_WIDTH 60 +#define FORMAT_VERSION 2 +#define FORMAT_VERSION_NOSUM 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; + crc_start(&sf->crc); + 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; ibintextpos; + 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> 60); + val += udata[i] ^ 0xbeefU; + } + + uval = (unsigned char *)&val; + valpos = 0; + for (i=0; i= sizeof(val)) { + valpos = 0; + } + } +} + +/* + * Set the "encryption" key. + */ +static void +savefile_key(struct savefile *sf, uint32_t key) +{ + sf->key = 0; + crc_start(&sf->crc); + 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; icrc, data, len); + 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; icrc, data, len); + return 0; +} + +//////////////////////////////////////////////////////////// +// compat for old save files + +struct compat_saveinfo { void *address; - int width; + size_t width; }; -struct savestruct save_array[] = +static const struct compat_saveinfo compat_savearray[] = { {&abbnum, sizeof(abbnum)}, {&attack, sizeof(attack)}, @@ -68,8 +470,8 @@ struct savestruct save_array[] = {&clock1, sizeof(clock1)}, {&clock2, sizeof(clock2)}, {&closed, sizeof(closed)}, - {&closng, sizeof(closng)}, - {&daltlc, sizeof(daltlc)}, + {&isclosing, sizeof(isclosing)}, + {&daltloc, sizeof(daltloc)}, {&demo, sizeof(demo)}, {&detail, sizeof(detail)}, {&dflag, sizeof(dflag)}, @@ -77,27 +479,27 @@ struct savestruct save_array[] = {&dtotal, sizeof(dtotal)}, {&foobar, sizeof(foobar)}, {&gaveup, sizeof(gaveup)}, - {&holdng, sizeof(holdng)}, + {&holding, sizeof(holding)}, {&iwest, sizeof(iwest)}, {&k, sizeof(k)}, {&k2, sizeof(k2)}, {&knfloc, sizeof(knfloc)}, {&kq, sizeof(kq)}, - {&latncy, sizeof(latncy)}, + {&latency, sizeof(latency)}, {&limit, sizeof(limit)}, {&lmwarn, sizeof(lmwarn)}, {&loc, sizeof(loc)}, {&maxdie, sizeof(maxdie)}, - {&mxscor, sizeof(mxscor)}, + {&maxscore, sizeof(maxscore)}, {&newloc, sizeof(newloc)}, {&numdie, sizeof(numdie)}, {&obj, sizeof(obj)}, - {&oldlc2, sizeof(oldlc2)}, + {&oldloc2, sizeof(oldloc2)}, {&oldloc, sizeof(oldloc)}, {&panic, sizeof(panic)}, {&saveday, sizeof(saveday)}, {&savet, sizeof(savet)}, - {&scorng, sizeof(scorng)}, + {&scoring, sizeof(scoring)}, {&spk, sizeof(spk)}, {&stick, sizeof(stick)}, {&tally, sizeof(tally)}, @@ -107,7 +509,7 @@ struct savestruct save_array[] = {&verb, sizeof(verb)}, {&wd1, sizeof(wd1)}, {&wd2, sizeof(wd2)}, - {&wzdark, sizeof(wzdark)}, + {&wasdark, sizeof(wasdark)}, {&yea, sizeof(yea)}, {atloc, sizeof(atloc)}, {dloc, sizeof(dloc)}, @@ -123,48 +525,15 @@ struct savestruct save_array[] = {NULL, 0} }; -int -save(outfile) /* Two passes on data: first to get checksum, - * second */ - const char *outfile; /* to output the data using checksum to start - * random #s */ -{ - FILE *out; - 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); - } - fclose(out); - return 0; -} - -int -restore(infile) - const char *infile; +static int +compat_restore(const char *infile) { FILE *in; - struct savestruct *p; + const struct compat_saveinfo *p; char *s; long sum, cksum = 0; - int i; + size_t i; + struct crcstate crc; if ((in = fopen(infile, "rb")) == NULL) { fprintf(stderr, @@ -174,19 +543,328 @@ restore(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 */ } fclose(in); - crc_start(); /* See if she cheated */ - for (p = save_array; p->address != NULL; p++) - cksum = crc(p->address, p->width); + crc_start(&crc); /* See if she cheated */ + for (p = compat_savearray; p->address != NULL; p++) + crc_add(&crc, p->address, p->width); + cksum = crc_get(&crc); 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, sum; + + 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; icrc)); + if (savefile_binwrite(sf, &sum, sizeof(&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; + bool skipsum = false; + + 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); + switch (version) { + case FORMAT_VERSION: + break; + case FORMAT_VERSION_NOSUM: + skipsum = true; + break; + default: + 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; icrc)) { + /* 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; }