summaryrefslogtreecommitdiffstats
path: root/adventure
diff options
context:
space:
mode:
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;
}