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.
This commit is contained in:
dholland 2012-01-07 22:23:16 +00:00
parent 7f7d4934d7
commit c99eaa0a43
1 changed files with 712 additions and 46 deletions

View File

@ -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;
}