/* $NetBSD: save.c,v 1.15 2021/04/12 09:12:28 mrg Exp $ */ /*- * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * The game adventure was originally written in Fortran by Will Crowther * and Don Woods. It was later translated to C and enhanced by Jim * Gillogly. This code is derived from software contributed to Berkeley * by Jim Gillogly at The Rand Corporation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 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. 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. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #ifndef lint #if 0 static char sccsid[] = "@(#)save.c 8.1 (Berkeley) 5/31/93"; #else __RCSID("$NetBSD: save.c,v 1.15 2021/04/12 09:12:28 mrg Exp $"); #endif #endif /* not lint */ #include #include #include #include #include #include #include #include "hdr.h" #include "extern.h" 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[91] = "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; size_t width; }; static const struct compat_saveinfo compat_savearray[] = { {&abbnum, sizeof(abbnum)}, {&attack, sizeof(attack)}, {&blklin, sizeof(blklin)}, {&bonus, sizeof(bonus)}, {&chloc, sizeof(chloc)}, {&chloc2, sizeof(chloc2)}, {&clock1, sizeof(clock1)}, {&clock2, sizeof(clock2)}, {&closed, sizeof(closed)}, {&isclosing, sizeof(isclosing)}, {&daltloc, sizeof(daltloc)}, {&demo, sizeof(demo)}, {&detail, sizeof(detail)}, {&dflag, sizeof(dflag)}, {&dkill, sizeof(dkill)}, {&dtotal, sizeof(dtotal)}, {&foobar, sizeof(foobar)}, {&gaveup, sizeof(gaveup)}, {&holding, sizeof(holding)}, {&iwest, sizeof(iwest)}, {&k, sizeof(k)}, {&k2, sizeof(k2)}, {&knfloc, sizeof(knfloc)}, {&kq, sizeof(kq)}, {&latency, sizeof(latency)}, {&limit, sizeof(limit)}, {&lmwarn, sizeof(lmwarn)}, {&loc, sizeof(loc)}, {&maxdie, sizeof(maxdie)}, {&maxscore, sizeof(maxscore)}, {&newloc, sizeof(newloc)}, {&numdie, sizeof(numdie)}, {&obj, sizeof(obj)}, {&oldloc2, sizeof(oldloc2)}, {&oldloc, sizeof(oldloc)}, {&panic, sizeof(panic)}, {&saveday, sizeof(saveday)}, {&savet, sizeof(savet)}, {&scoring, sizeof(scoring)}, {&spk, sizeof(spk)}, {&stick, sizeof(stick)}, {&tally, sizeof(tally)}, {&tally2, sizeof(tally2)}, {&tkk, sizeof(tkk)}, {&turns, sizeof(turns)}, {&verb, sizeof(verb)}, {&wd1, sizeof(wd1)}, {&wd2, sizeof(wd2)}, {&wasdark, sizeof(wasdark)}, {&yea, sizeof(yea)}, {atloc, sizeof(atloc)}, {dloc, sizeof(dloc)}, {dseen, sizeof(dseen)}, {fixed, sizeof(fixed)}, {hinted, sizeof(hinted)}, {links, sizeof(links)}, {odloc, sizeof(odloc)}, {place, sizeof(place)}, {prop, sizeof(prop)}, {tk, sizeof(tk)}, {NULL, 0} }; static int compat_restore(const char *infile) { FILE *in; const struct compat_saveinfo *p; char *s; long sum, cksum = 0; size_t i; struct crcstate crc; if ((in = fopen(infile, "rb")) == NULL) { fprintf(stderr, "Hmm. The file \"%s\" appears to be magically blocked.\n", infile); return 1; } fread(&sum, sizeof(sum), 1, in); /* Get the seed */ srandom((int) sum); 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(&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 */ /* * 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; }