summaryrefslogtreecommitdiffstats
path: root/adventure
diff options
context:
space:
mode:
authordholland <dholland@NetBSD.org>2012-01-07 22:23:16 +0000
committerdholland <dholland@NetBSD.org>2012-01-07 22:23:16 +0000
commitc7181d82c389743f54a91288f6c871b280f1cf71 (patch)
tree3d705f06efad6df144db9867109870700c69376a /adventure
parent7af3623528af2b5ce6d4f0a0341cf9e78b5dc28f (diff)
downloadbsdgames-darwin-c7181d82c389743f54a91288f6c871b280f1cf71.tar.gz
bsdgames-darwin-c7181d82c389743f54a91288f6c871b280f1cf71.tar.zst
bsdgames-darwin-c7181d82c389743f54a91288f6c871b280f1cf71.zip
Redo save file handling. The old save files were unportable, had no
magic number or versioning, relied on random(3) never changing to a different implementation, and were also saving pointers to disk and reading them back again. It *looks* as if the pointers thus loaded were reset before being used, but it's not particularly clear as the main loop of this thing is goto-based FORTRAN translated lightly to C. I've changed the logic to null these pointers instead of saving and loading them, and things seem to still work. The new save files have a header, support versioning, write only sized types in network byte order, and for the toy encryption to discourage cheating do something self-contained instead of using random(3) as a stream cipher. Because between the original import from 4.4 until earlier today trying to save would result in SIGSEGV on most platforms, it's unlikely anyone has a save file, but just in case (since the pointer issue appears to be nonlethal) I've kept compat code for old save files.
Diffstat (limited to 'adventure')
-rw-r--r--adventure/save.c758
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;
}