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