1 /*
2  * enigma/savefile.c - provide routines that load and save a user's
3  * progress details and saved positions, plus a routine to load a
4  * move sequence from a file for replay.
5  *
6  * Copyright 2000 Simon Tatham. All rights reserved.
7  *
8  * Enigma is licensed under the MIT licence. See the file LICENCE for
9  * details.
10  *
11  * - we are all amf -
12  */
13 
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 
21 #include "enigma.h"
22 
savepos_load(levelset * set,char * user,int savenum)23 gamestate *savepos_load(levelset *set, char *user, int savenum) {
24     FILE *fp;
25     char buf[FILENAME_MAX+10];
26     char fname[FILENAME_MAX];
27     gamestate *state;
28     int nlines, levnum;
29     int i, j;
30     int seqpos = 0;
31 
32     fname[sizeof(fname)-1] = '\0';
33     strncpy(fname, SAVEDIR, sizeof(fname));
34     strncpy(fname + strlen(fname), set->name, sizeof(fname)-strlen(fname));
35     strncpy(fname + strlen(fname), ".", sizeof(fname)-strlen(fname));
36     strncpy(fname + strlen(fname), user, sizeof(fname)-strlen(fname));
37     sprintf(buf, ".%d", savenum+1);
38     strncpy(fname + strlen(fname), buf, sizeof(fname)-strlen(fname));
39     if (fname[sizeof(fname)-1] != '\0') {
40 	/* filename length overflow */
41 	return NULL;
42     }
43 
44     fp = fopen(fname, "r");
45     if (!fp) {
46 	/* can't open save file */
47 	return NULL;
48     }
49 
50     state = NULL;
51     nlines = 0;
52 
53     while (fgets(buf, sizeof(buf), fp)) {
54 	if (buf[strlen(buf)-1] != '\n') {
55 	    /* line length overflow in save file */
56 	    return NULL;
57 	}
58 	buf[strcspn(buf, "\r\n")] = '\0';
59 	if (state == NULL && !ishdr(buf, "Level: ")) {
60 	    /* Level: line not first in save file */
61 	    return NULL;
62 	}
63 	if (ishdr(buf, "Level: ")) {
64 	    levnum = atoi(buf+7);
65 	    if (levnum <= 0 || levnum > set->nlevels) {
66 		/* This save references a nonexistent level number. */
67 		return NULL;
68 	    }
69 	    state = gamestate_new(set->levels[levnum-1]->width,
70 				  set->levels[levnum-1]->height,
71 				  set->levels[levnum-1]->flags);
72 	    state->levnum = levnum;
73 	    state->title = set->levels[levnum-1]->title;
74 	    state->status = PLAYING;
75 	} else if (ishdr(buf, "Moves: ")) {
76 	    state->movenum = atoi(buf + 7);
77 	    state->sequence_size = state->movenum;
78 	    state->sequence = smalloc(state->sequence_size);
79 	    seqpos = 0;
80 	    memset(state->sequence, '?', state->sequence_size);
81 	} else if (ishdr(buf, "MoveData: ")) {
82 	    char *p = buf + 10;
83 	    int len;
84 	    len = strlen(p);
85 	    if (len > state->sequence_size - seqpos)
86 		len = state->sequence_size - seqpos;
87 	    if (len != 0) {
88 		memcpy(state->sequence + seqpos, p, len);
89 		seqpos += len;
90 	    }
91 	} else if (ishdr(buf, "Gold: ")) {
92 	    state->gold_got = atoi(buf + 6);
93 	} else if (ishdr(buf, "TotalGold: ")) {
94 	    state->gold_total = atoi(buf + 11);
95 	} else if (ishdr(buf, "Map: ")) {
96 	    if (state->leveldata == NULL) {
97 		/* map before size in save file */
98 		return NULL;
99 	    }
100 	    if ((int)strlen(buf + 5) != state->width) {
101 		/* wrong length map line in save file */
102 		return NULL;
103 	    }
104 	    if (nlines >= state->height) {
105 		/* too many map lines in save file */
106 		return NULL;
107 	    }
108 	    memcpy(state->leveldata + state->width * nlines,
109 		   buf + 5, state->width);
110 	    nlines++;
111 	} else {
112 	    /* unrecognised keyword in save file */
113 	    return NULL;
114 	}
115     }
116     if (nlines < state->height) {
117 	/* not enough map lines in save file */
118 	return NULL;
119     }
120 
121     fclose(fp);
122 
123     /*
124      * Find the player.
125      */
126     for (j = 0; j < state->height; j++) {
127 	for (i = 0; i < state->width; i++) {
128 	    if (state->leveldata[j*state->width+i] == '@') {
129 		state->player_x = i;
130 		state->player_y = j;
131 	    }
132 	}
133     }
134 
135     return state;
136 }
137 
savepos_del(levelset * set,char * user,int savenum)138 void savepos_del(levelset *set, char *user, int savenum) {
139     char buf[FILENAME_MAX+10];
140     char fname[FILENAME_MAX];
141 
142     fname[sizeof(fname)-1] = '\0';
143     strncpy(fname, SAVEDIR, sizeof(fname));
144     strncpy(fname + strlen(fname), set->name, sizeof(fname)-strlen(fname));
145     strncpy(fname + strlen(fname), ".", sizeof(fname)-strlen(fname));
146     strncpy(fname + strlen(fname), user, sizeof(fname)-strlen(fname));
147     sprintf(buf, ".%d", savenum+1);
148     strncpy(fname + strlen(fname), buf, sizeof(fname)-strlen(fname));
149     if (fname[sizeof(fname)-1] != '\0') {
150 	/* file name length overflow */
151 	return;
152     }
153     remove(fname);
154 }
155 
savepos_save(levelset * set,char * user,int savenum,gamestate * state)156 void savepos_save(levelset *set, char *user, int savenum, gamestate *state) {
157     FILE *fp;
158     char buf[FILENAME_MAX+10];
159     char fname[FILENAME_MAX];
160     int i;
161 
162     fname[sizeof(fname)-1] = '\0';
163     strncpy(fname, SAVEDIR, sizeof(fname));
164     strncpy(fname + strlen(fname), set->name, sizeof(fname)-strlen(fname));
165     strncpy(fname + strlen(fname), ".", sizeof(fname)-strlen(fname));
166     strncpy(fname + strlen(fname), user, sizeof(fname)-strlen(fname));
167     sprintf(buf, ".%d", savenum+1);
168     strncpy(fname + strlen(fname), buf, sizeof(fname)-strlen(fname));
169     if (fname[sizeof(fname)-1] != '\0') {
170 	/* File name length overflow */
171 	return;
172     }
173 
174     /* For writing this file we want umask 077, so we get file mode 0600. */
175     umask(077);
176     fp = fopen(fname, "w");
177     if (!fp) {
178 	/* unable to write save file */
179 	return;
180     }
181 #ifndef _WIN32
182     /* Now let's be very sure the file mode came out right. */
183     fchmod(fileno(fp), 0600);
184 #endif
185     fprintf(fp, "Level: %d\nMoves: %d\nGold: %d\nTotalGold: %d\n",
186 	    state->levnum, state->movenum, state->gold_got,
187 	    state->gold_total);
188     for (i = 0; i < state->height; i++) {
189 	fprintf(fp, "Map: %.*s\n", state->width,
190 		state->leveldata + i * state->width);
191     }
192     for (i = 0; i < state->movenum; i += 50) {
193 	int len = 50;
194 	if (len > state->movenum - i)
195 	    len = state->movenum - i;
196 	fprintf(fp, "MoveData: %.*s\n", len, state->sequence+i);
197     }
198 
199     fclose(fp);
200 }
201 
progress_load(levelset * set,char * user)202 progress progress_load(levelset *set, char *user) {
203     FILE *fp;
204     char buf[FILENAME_MAX+10];
205     char fname[FILENAME_MAX];
206     progress p;
207 
208     p.levnum = 0;
209     p.date = -1;
210 
211     fname[sizeof(fname)-1] = '\0';
212     strncpy(fname, SAVEDIR, sizeof(fname));
213     strncpy(fname + strlen(fname), set->name, sizeof(fname)-strlen(fname));
214     strncpy(fname + strlen(fname), ".", sizeof(fname)-strlen(fname));
215     strncpy(fname + strlen(fname), user, sizeof(fname)-strlen(fname));
216     strncpy(fname + strlen(fname), ".progress", sizeof(fname)-strlen(fname));
217     if (fname[sizeof(fname)-1] != '\0') {
218 	/* file name length overflow */
219 	return p;
220     }
221 
222     fp = fopen(fname, "r");
223     if (!fp) {
224 	/* unable to read progress file */
225 	return p;
226     }
227 
228     while (fgets(buf, sizeof(buf), fp)) {
229 	if (buf[strlen(buf)-1] != '\n') {
230 	    /* line length overflow in save file */
231 	    return p;
232 	}
233 	buf[strcspn(buf, "\r\n")] = '\0';
234 	if (ishdr(buf, "Level: ")) {
235 	    p.levnum = atoi(buf+7);
236 	} else if (ishdr(buf, "Date: ")) {
237 	    p.date = parse_date(buf+6);
238 	} else {
239 	    /* unrecognised keyword in progress file */
240 	    return p;
241 	}
242     }
243 
244     fclose(fp);
245 
246     return p;
247 }
248 
progress_save(levelset * set,char * user,progress p)249 void progress_save(levelset *set, char *user, progress p) {
250     FILE *fp;
251     char fname[FILENAME_MAX];
252     char datebuf[40];
253 
254     fname[sizeof(fname)-1] = '\0';
255     strncpy(fname, SAVEDIR, sizeof(fname));
256     strncpy(fname + strlen(fname), set->name, sizeof(fname)-strlen(fname));
257     strncpy(fname + strlen(fname), ".", sizeof(fname)-strlen(fname));
258     strncpy(fname + strlen(fname), user, sizeof(fname)-strlen(fname));
259     strncpy(fname + strlen(fname), ".progress", sizeof(fname)-strlen(fname));
260     if (fname[sizeof(fname)-1] != '\0') {
261 	/* file name length overflow */
262 	return;
263     }
264 
265     /* For writing this file we want umask 037, so we get file mode 0640. */
266     umask(037);
267     fp = fopen(fname, "w");
268     if (!fp) {
269 	/* unable to write progress file */
270 	return;
271     }
272 #ifndef _WIN32
273     /* Now let's be very sure the file mode came out right. */
274     fchmod(fileno(fp), 0640);
275 #endif
276     fmt_date(datebuf, p.date);
277     fprintf(fp, "Level: %d\nDate: %s\n", p.levnum, datebuf);
278 
279     fclose(fp);
280 }
281 
sequence_load(char * fname)282 char *sequence_load(char *fname) {
283     FILE *fp;
284     char *p;
285     int len, size;
286     int c;
287 
288     p = NULL;
289     len = size = 0;
290 
291     fp = fopen(fname, "r");
292     if (!fp)
293 	return NULL;
294     while (1) {
295 	c = fgetc(fp);
296 	if (c == EOF)
297 	    break;
298 	if (c == 'L')
299 	    c = 'h';
300 	if (c == 'D')
301 	    c = 'j';
302 	if (c == 'U')
303 	    c = 'k';
304 	if (c == 'R')
305 	    c = 'l';
306 	if (c == 'h' || c == 'j' || c == 'k' || c == 'l' || c == 'x') {
307 	    if (len >= size) {
308 		size = len + 256;
309 		p = (p ? realloc(p, size) : malloc(size));
310 		if (!p) {
311 		    fclose(fp);
312 		    return NULL;
313 		}
314 	    }
315 	    p[len++] = c;
316 	}
317     }
318     p[len] = '\0';
319     fclose(fp);
320     return p;
321 }
322 
sequence_save(char * fname,gamestate * state)323 void sequence_save(char *fname, gamestate *state) {
324     FILE *fp;
325     fp = fopen(fname, "w");
326     if (fp) {
327 	fwrite(state->sequence, 1, state->movenum, fp);
328 	fputc('\n', fp);
329 	fclose(fp);
330     }
331 }
332