1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 
24 #include "glk/agt/agility.h"
25 #include "glk/agt/interp.h"
26 #include "glk/agt/exec.h"
27 
28 namespace Glk {
29 namespace AGT {
30 
31 #define SAVE_UNDO
32 #define DEBUG_SAVE_SIZE 0
33 
34 long state_size;
35 
36 
37 /*-------------------------------------------------------------------*/
38 /*  INITIALISATION ROUTINES  */
39 /* These initialize all of the values that can be derived from */
40 /*   other data in the game file or that are reset when a game */
41 /*   is restored */
42 /* See parser.c for the interpreter's main initialisation routines */
43 
init_vals(void)44 void init_vals(void)
45 /* Compute quantities that can be deduced from existing data */
46 {
47 	int i;
48 
49 	quitflag = winflag = deadflag = endflag = 0;
50 	cmd_saveable = 0;
51 	last_he = last_she = last_it = 0;
52 	totwt = totsize = 0;
53 	for (i = 0; i <= maxroom - first_room; i++)
54 		room[i].contents = 0;
55 	player_contents = player_worn = 0;
56 	for (i = 0; i <= maxnoun - first_noun; i++) {
57 		if (player_has(i + first_noun)) totwt += noun[i].weight;
58 		if (noun[i].location == 1) totsize += noun[i].size;
59 		noun[i].something_pos_near_noun = 0;
60 		noun[i].contents = noun[i].next = 0;
61 	}
62 	for (i = 0; i <= maxcreat - first_creat; i++)
63 		creature[i].contents = creature[i].next = 0;
64 	for (i = 0; i <= maxnoun - first_noun; i++) {
65 		add_object(noun[i].location, i + first_noun);
66 		if (noun[i].nearby_noun >= first_noun &&
67 		        noun[i].nearby_noun <= maxnoun)
68 			noun[noun[i].nearby_noun - first_noun].something_pos_near_noun = 1;
69 	}
70 	for (i = 0; i <= maxcreat - first_creat; i++)
71 		add_object(creature[i].location, i + first_creat);
72 	objscore = 0; /* Will need to recompute this ... */
73 }
74 
75 
76 
77 
78 /*-------------------------------------------------------------------*/
79 /*  ROUTINES TO SAVE/RESTORE THE GAME STATE */
80 /* These are used by RESTART and UNDO as well as SAVE and RESTORE */
81 
82 /* Game State format: */
83 /* The first two bytes indicate the length of the block (unsigned).*/
84 /* The next two bytes indicate the game file somehow (so we don't try to */
85 /* restore to a different game). */
86 /* After this comes the game information itself. */
87 /* All values are still little-endian (that is, LSB first) */
88 
89 /* These are the macros for writing game information to the state block */
90 /* There is no difference between signed and unsigned when storing them;
91  there will be problems when recovering them again. */
92 
93 #define g(ft,var) {ft,DT_DEFAULT,&var,0}
94 #define r(ft,str,f) {ft,DT_DEFAULT,NULL,offsetof(str,f)}
95 #define dptype   {FT_DESCPTR,DT_DESCPTR,NULL,0}
96 
97 static file_info fi_savehead[] = {
98 	g(FT_INT16, loc), g(FT_INT32, tscore), g(FT_INT16, turncnt),
99 	g(FT_BYTE, statusmode),
100 	g(FT_BOOL, first_visit_flag), g(FT_BOOL, newlife_flag),
101 	g(FT_BOOL, room_firstdesc), g(FT_BOOL, verboseflag),
102 	g(FT_BOOL, notify_flag), g(FT_BOOL, listexit_flag),
103 	g(FT_BOOL, menu_mode), g(FT_BOOL, sound_on),
104 	g(FT_BOOL, agt_answer), g(FT_INT32, agt_number),
105 	g(FT_INT16, curr_time), g(FT_INT16, curr_lives),
106 	g(FT_INT16, delta_time),
107 	endrec
108 };
109 
110 static file_info fi_saveroom[] = {
111 	dptype,
112 	r(FT_BOOL, room_rec, seen),
113 	r(FT_BOOL, room_rec, locked_door),
114 	r(FT_INT16, room_rec, oclass),
115 	r(FT_INT16, room_rec, points),
116 	r(FT_INT16, room_rec, light),
117 	r(FT_PATHARRAY, room_rec, path),
118 	r(FT_UINT32, room_rec, flag_noun_bits),
119 	endrec
120 };
121 
122 static file_info fi_savenoun[] = {
123 	dptype,
124 	r(FT_INT16, noun_rec, location),
125 	r(FT_INT16, noun_rec, nearby_noun),
126 	r(FT_INT16, noun_rec, num_shots),
127 	r(FT_INT16, noun_rec, initdesc),
128 	r(FT_INT16, noun_rec, oclass),
129 	r(FT_INT16, noun_rec, points),
130 	r(FT_INT16, noun_rec, weight),
131 	r(FT_INT16, noun_rec, size),
132 	r(FT_BOOL, noun_rec, on),
133 	r(FT_BOOL, noun_rec, open),
134 	r(FT_BOOL, noun_rec, locked),
135 	r(FT_BOOL, noun_rec, movable),
136 	r(FT_BOOL, noun_rec, seen),
137 	r(FT_WORD, noun_rec, pos_prep),
138 	r(FT_WORD, noun_rec, pos_name),
139 	endrec
140 };
141 
142 static file_info fi_savecreat[] = {
143 	dptype,
144 	r(FT_INT16, creat_rec, location),
145 	r(FT_INT16, creat_rec, counter),
146 	r(FT_INT16, creat_rec, timecounter),
147 	r(FT_INT16, creat_rec, initdesc),
148 	r(FT_INT16, creat_rec, oclass),
149 	r(FT_INT16, creat_rec, points),
150 	r(FT_BOOL, creat_rec, groupmemb),
151 	r(FT_BOOL, creat_rec, hostile),
152 	r(FT_BOOL, creat_rec, seen),
153 	endrec
154 };
155 
156 static file_info fi_saveustr[] = {
157 	{FT_TLINE, DT_DEFAULT, NULL, 0},
158 	endrec
159 };
160 
161 
162 
getstate(uchar * gs)163 uchar *getstate(uchar *gs)
164 /* Returns block containing game state.
165   If gs!=NULL, uses that space as a buffer;
166   if gs==NULL, we malloc a new block and return it */
167 {
168 	rbool new_block; /* True if we allocate a new block */
169 	long bp;
170 
171 	if (gs == NULL) {
172 		rm_trap = 0; /* Don't exit on out-of-memory condition */
173 		gs = (uchar *)rmalloc(state_size); /* This should be enough. */
174 		rm_trap = 1;
175 		if (gs == NULL) /* This is why we set rm_trap to 0 before calling rmalloc */
176 			return NULL;
177 		new_block = 1;
178 	} else new_block = 0;
179 
180 	/* First two bytes reserved for block size, which we don't know yet.*/
181 	gs[4] = game_sig & 0xFF;
182 	gs[5] = (game_sig >> 8) & 0xFF;
183 
184 	tscore -= objscore;  /* Only include "permanent" part of score;
185 			objscore we can recompute on RESTORE */
186 
187 	/* Need to setup here */
188 	set_internal_buffer(gs);
189 	fi_saveroom[0].ptr = room_ptr;
190 	fi_savenoun[0].ptr = noun_ptr;
191 	fi_savecreat[0].ptr = creat_ptr;
192 
193 	bp = 6;
194 	bp += write_globalrec(fi_savehead, bp);
195 	bp += write_recblock(flag, FT_BYTE, FLAG_NUM + 1, bp);
196 	bp += write_recblock(agt_counter, FT_INT16, CNT_NUM + 1, bp);
197 	bp += write_recblock(agt_var, FT_INT32, VAR_NUM + 1, bp);
198 	bp += write_recarray(room, sizeof(room_rec), rangefix(maxroom - first_room + 1),
199 	                     fi_saveroom, bp);
200 	bp += write_recarray(noun, sizeof(noun_rec), rangefix(maxnoun - first_noun + 1),
201 	                     fi_savenoun, bp);
202 	bp += write_recarray(creature, sizeof(creat_rec),
203 	                     rangefix(maxcreat - first_creat + 1),
204 	                     fi_savecreat, bp);
205 	if (userstr != NULL)
206 		bp += write_recarray(userstr, sizeof(tline), MAX_USTR, fi_saveustr, bp);
207 	if (objflag != NULL)
208 		bp += write_recblock(objflag, FT_BYTE, objextsize(0), bp);
209 	if (objprop != NULL)
210 		bp += write_recblock(objprop, FT_INT32, objextsize(1), bp);
211 	set_internal_buffer(NULL);
212 	gs[0] = bp & 0xFF;
213 	gs[1] = (bp >> 8) & 0xFF;
214 	gs[2] = (bp >> 16) & 0xFF;
215 	gs[3] = (bp >> 24) & 0x7F; /* Don't trust top bit */
216 	if (new_block)
217 		gs = (uchar *)rrealloc(gs, bp);
218 	tscore += objscore;
219 	return gs;
220 }
221 
222 
223 
putstate(uchar * gs)224 void putstate(uchar *gs) { /* Restores games state. */
225 	long size, bp, numrec, i;
226 
227 
228 	size = gs[0] + (((long)gs[1]) << 8) + (((long)gs[2]) << 16) + (((long)gs[3]) << 24);
229 	if (size != state_size) {
230 		writeln("Size difference in save files!");
231 		agt_delay(3);
232 		return;
233 	}
234 	if (gs[4] + (((long)gs[5]) << 8) != game_sig) {
235 		writestr("This appears to be a save file for a different game. Is this"
236 		         " from an earlier chapter in a multi-part game such as"
237 		         " Klaustrophobia");
238 		if (yesno("?"))
239 			skip_descr = 1; /* We don't want to overwrite the descriptions
240 			   with the pointers from the save file. */
241 		else {
242 			writestr("Do you want to try using it anyhow (WARNING: This could"
243 			         " crash the interpreter)");
244 			if (!(yesno("?"))) {
245 				writeln("Command cancelled!");
246 				agt_delay(3);
247 				return;
248 			}
249 		}
250 	}
251 
252 
253 	/* setup... */
254 	set_internal_buffer(gs);
255 	fi_saveroom[0].ptr = room_ptr;
256 	fi_savenoun[0].ptr = noun_ptr;
257 	fi_savecreat[0].ptr = creat_ptr;
258 	bp = 6;
259 
260 	read_globalrec(fi_savehead, 0, bp, 0);
261 	bp += compute_recsize(fi_savehead);
262 	read_recblock(flag, FT_BYTE, FLAG_NUM + 1, bp, 0);
263 	bp += ft_leng[FT_BYTE] * (FLAG_NUM + 1);
264 	read_recblock(agt_counter, FT_INT16, CNT_NUM + 1, bp, 0);
265 	bp += ft_leng[FT_INT16] * (CNT_NUM + 1);
266 	read_recblock(agt_var, FT_INT32, VAR_NUM + 1, bp, 0);
267 	bp += ft_leng[FT_INT32] * (VAR_NUM + 1);
268 
269 	numrec = rangefix(maxroom - first_room + 1);
270 	read_recarray(room, sizeof(room_rec), numrec, fi_saveroom, 0, bp, 0);
271 	bp += compute_recsize(fi_saveroom) * numrec;
272 	numrec = rangefix(maxnoun - first_noun + 1);
273 	read_recarray(noun, sizeof(noun_rec), numrec, fi_savenoun, 0, bp, 0);
274 	bp += compute_recsize(fi_savenoun) * numrec;
275 	numrec = rangefix(maxcreat - first_creat + 1);
276 	read_recarray(creature, sizeof(creat_rec), numrec, fi_savecreat, 0, bp, 0);
277 	bp += compute_recsize(fi_savecreat) * numrec;
278 	if (userstr != NULL) {
279 		read_recarray(userstr, sizeof(tline), MAX_USTR, fi_saveustr, 0, bp, 0);
280 		bp += ft_leng[FT_TLINE] * MAX_USTR;
281 	}
282 	if (objflag != NULL) {
283 		i = objextsize(0);
284 		read_recblock(objflag, FT_BYTE, i, bp, 0);
285 		bp += ft_leng[FT_BYTE] * i;
286 	}
287 	if (objprop != NULL) {
288 		i = objextsize(1);
289 		read_recblock(objprop, FT_INT32, i, bp, 0);
290 		bp += ft_leng[FT_INT32] * i;
291 	}
292 	set_internal_buffer(NULL);
293 
294 	if (skip_descr)   /* Need to "fix" position information. This is a hack. */
295 		/* Basically, this sets the position of each object to its default */
296 		/* The problem here is that the usual position info is invalid-- we've
297 		   changed games, and hence dictionaries */
298 		for (i = 0; i < maxnoun - first_noun; i++) {
299 			if (noun[i].position != NULL && noun[i].position[0] != 0)
300 				noun[i].pos_prep = -1;
301 			else noun[i].pos_prep = 0;
302 		}
303 	else   /* Rebuild position information */
304 		for (i = 0; i < maxnoun - first_noun; i++)
305 			if (noun[i].pos_prep == -1)
306 				noun[i].position = noun[i].initpos;
307 			else
308 				noun[i].position = NULL;
309 
310 	init_vals();
311 	skip_descr = 0; /* If we set this to 1, restore it to its original state */
312 	/* Now do some simple consistancy checking on major variables */
313 	if (loc > maxroom || loc < 0 || turncnt < 0 ||
314 	        curr_lives < 0 || curr_lives > max_lives) {
315 		error("Error: Save file inconsistent.");
316 	}
317 }
318 
init_state_sys(void)319 void init_state_sys(void)
320 /* Initializes the state saving mechanisms */
321 /* Mainly it just computes the size of a state block */
322 {
323 	state_size = compute_recsize(fi_savehead)
324 	             + compute_recsize(fi_saveroom) * rangefix(maxroom - first_room + 1)
325 	             + compute_recsize(fi_savenoun) * rangefix(maxnoun - first_noun + 1)
326 	             + compute_recsize(fi_savecreat) * rangefix(maxcreat - first_creat + 1)
327 	             + ft_leng[FT_BYTE] * (FLAG_NUM + 1)
328 	             + ft_leng[FT_INT16] * (CNT_NUM + 1)
329 	             + ft_leng[FT_INT32] * (VAR_NUM + 1)
330 	             + ft_leng[FT_BYTE] * objextsize(0)
331 	             + ft_leng[FT_INT32] * objextsize(1)
332 	             + 6;  /* Six bytes in header */
333 	if (userstr != NULL) state_size += ft_leng[FT_TLINE] * MAX_USTR;
334 }
335 
336 
337 /*-------------------------------------------------------------------*/
338 /*  SAVE FILE ROUTINES    */
339 
savegame(Common::WriteStream * savefile)340 extern Common::Error savegame(Common::WriteStream *savefile) {
341 	uchar *gs;
342 	long size;
343 
344 #ifndef UNDO_SAVE
345 	gs = getstate(NULL);
346 #else
347 	gs = undo_state;
348 #endif
349 	if (gs == NULL) {
350 		writeln("Insufficiant memory to support SAVE.");
351 		return Common::kWritingFailed;
352 	}
353 
354 	if (!filevalid(savefile, fSAV)) {
355 		writeln("That is not a valid save file.");
356 		return Common::kWritingFailed;
357 	}
358 	size = gs[0] + (((long)gs[1]) << 8) + (((long)gs[2]) << 16) + (((long)gs[3]) << 24);
359 	bool result = binwrite(savefile, gs, size, 1, 0);
360 #ifndef UNDO_SAVE
361 	rfree(gs);
362 #endif
363 	if (!result) {
364 		warning("Error writing save file.");
365 		return Common::kWritingFailed;
366 	} else {
367 		return Common::kNoError;
368 	}
369 }
370 
371 /* 1=success, 0=failure */
loadgame(Common::SeekableReadStream * loadfile)372 Common::Error loadgame(Common::SeekableReadStream *loadfile) {
373 	long size;
374 	uchar *gs;
375 	const char *errstr;
376 
377 	if (!filevalid(loadfile, fSAV)) {
378 		warning("Unable to open file.");
379 		return Common::kReadingFailed;
380 	}
381 	size = binsize(loadfile);
382 	if (size == -1) {
383 		warning("Could not access file.");
384 		return Common::kReadingFailed;
385 	}
386 
387 	gs = (uchar *)rmalloc(size);
388 	if (!binread(loadfile, gs, size, 1, &errstr)) {
389 		warning("Error reading file.");
390 		rfree(gs);
391 		return Common::kReadingFailed;
392 	}
393 
394 	if (size != gs[0] + (((long)gs[1]) << 8) + (((long)gs[2]) << 16) + (((long)gs[3]) << 24)) {
395 		if (size == gs[0] + (((long)gs[1]) << 8)) {
396 			/* Old save file format; patch to look like new format */
397 			gs = (uchar *)rrealloc(gs, size + 2);
398 			memmove(gs + 4, gs + 2, size - 2);
399 			gs[2] = gs[3] = 0;
400 		} else {
401 			warning("Save file corrupted or invalid.");
402 			rfree(gs);
403 			return Common::kReadingFailed;
404 		}
405 	}
406 
407 	putstate(gs);
408 	rfree(gs);
409 	set_statline();
410 	look_room();
411 	return Common::kNoError;
412 }
413 
restart_game(void)414 void restart_game(void) {
415 	putstate(restart_state);
416 	agt_clrscr();
417 	set_statline();
418 	do_look = do_autoverb = 1;
419 	if (intro_ptr.size > 0) {
420 		print_descr(intro_ptr, 1);
421 		wait_return();
422 		agt_clrscr();
423 	}
424 	newroom();
425 }
426 
427 } // End of namespace AGT
428 } // End of namespace Glk
429