1/*	$NetBSD: save.c,v 1.14 2021/05/02 12:50:46 rillig Exp $	*/
2
3/*
4 * Copyright (c) 1988, 1993
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * This code is derived from software contributed to Berkeley by
8 * Timothy C. Stoehr.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 * 3. Neither the name of the University nor the names of its contributors
19 *    may be used to endorse or promote products derived from this software
20 *    without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
33 */
34
35#include <sys/cdefs.h>
36#ifndef lint
37#if 0
38static char sccsid[] = "@(#)save.c	8.1 (Berkeley) 5/31/93";
39#else
40__RCSID("$NetBSD: save.c,v 1.14 2021/05/02 12:50:46 rillig Exp $");
41#endif
42#endif /* not lint */
43
44/*
45 * save.c
46 *
47 * This source herein may be modified and/or distributed by anybody who
48 * so desires, with the following restrictions:
49 *    1.)  No portion of this notice shall be removed.
50 *    2.)  Credit shall not be taken for the creation of this source.
51 *    3.)  This code is not to be traded, sold, or used for personal
52 *         gain or profit.
53 *
54 */
55
56#include <stdio.h>
57#include "rogue.h"
58
59static boolean	has_been_touched(const struct rogue_time *,
60			const struct rogue_time *);
61static void	r_read(FILE *, void *, size_t);
62static void	r_write(FILE *, const void *, size_t);
63static void	read_pack(object *, FILE *, boolean);
64static void	read_string(char *, FILE *, size_t);
65static void	rw_dungeon(FILE *, boolean);
66static void	rw_id(struct id *, FILE *, int, boolean);
67static void	rw_rooms(FILE *, boolean);
68static void	write_pack(const object *, FILE *);
69static void	write_string(char *, FILE *);
70
71static short write_failed = 0;
72
73char *save_file = NULL;
74
75void
76save_game(void)
77{
78	char fname[64];
79
80	if (!get_input_line("file name?", save_file, fname, sizeof(fname),
81			"game not saved", 0, 1)) {
82		return;
83	}
84	check_message();
85	messagef(0, "%s", fname);
86	save_into_file(fname);
87}
88
89void
90save_into_file(const char *sfile)
91{
92	FILE *fp;
93	int file_id;
94	char *name_buffer;
95	size_t len;
96	char *hptr;
97	struct rogue_time rt_buf;
98
99	if (sfile[0] == '~') {
100		if ((hptr = md_getenv("HOME")) != NULL) {
101			len = strlen(hptr) + strlen(sfile);
102			name_buffer = md_malloc(len);
103			if (name_buffer == NULL) {
104				messagef(0,
105					"out of memory for save file name");
106				sfile = error_file;
107			} else {
108				(void)strcpy(name_buffer, hptr);
109				(void)strcat(name_buffer, sfile+1);
110				sfile = name_buffer;
111			}
112			/*
113			 * Note: name_buffer gets leaked. But it's small,
114			 * and in the common case we're about to exit.
115			 */
116		}
117	}
118	if (((fp = fopen(sfile, "w")) == NULL) ||
119	    ((file_id = md_get_file_id(sfile)) == -1)) {
120		if (fp)
121			fclose(fp);
122		messagef(0, "problem accessing the save file");
123		return;
124	}
125	md_ignore_signals();
126	write_failed = 0;
127	(void)xxx(1);
128	r_write(fp, &detect_monster, sizeof(detect_monster));
129	r_write(fp, &cur_level, sizeof(cur_level));
130	r_write(fp, &max_level, sizeof(max_level));
131	write_string(hunger_str, fp);
132	write_string(login_name, fp);
133	r_write(fp, &party_room, sizeof(party_room));
134	write_pack(&level_monsters, fp);
135	write_pack(&level_objects, fp);
136	r_write(fp, &file_id, sizeof(file_id));
137	rw_dungeon(fp, 1);
138	r_write(fp, &foods, sizeof(foods));
139	r_write(fp, &rogue, sizeof(fighter));
140	write_pack(&rogue.pack, fp);
141	rw_id(id_potions, fp, POTIONS, 1);
142	rw_id(id_scrolls, fp, SCROLS, 1);
143	rw_id(id_wands, fp, WANDS, 1);
144	rw_id(id_rings, fp, RINGS, 1);
145	r_write(fp, traps, (MAX_TRAPS * sizeof(trap)));
146	r_write(fp, is_wood, (WANDS * sizeof(boolean)));
147	r_write(fp, &cur_room, sizeof(cur_room));
148	rw_rooms(fp, 1);
149	r_write(fp, &being_held, sizeof(being_held));
150	r_write(fp, &bear_trap, sizeof(bear_trap));
151	r_write(fp, &halluc, sizeof(halluc));
152	r_write(fp, &blind, sizeof(blind));
153	r_write(fp, &confused, sizeof(confused));
154	r_write(fp, &levitate, sizeof(levitate));
155	r_write(fp, &haste_self, sizeof(haste_self));
156	r_write(fp, &see_invisible, sizeof(see_invisible));
157	r_write(fp, &detect_monster, sizeof(detect_monster));
158	r_write(fp, &wizard, sizeof(wizard));
159	r_write(fp, &score_only, sizeof(score_only));
160	r_write(fp, &m_moves, sizeof(m_moves));
161	md_gct(&rt_buf);
162	rt_buf.second += 10;		/* allow for some processing time */
163	r_write(fp, &rt_buf, sizeof(rt_buf));
164	fclose(fp);
165
166	if (write_failed) {
167		(void)md_df(sfile);	/* delete file */
168	} else {
169		clean_up("");
170	}
171}
172
173void
174restore(const char *fname)
175{
176	FILE *fp;
177	struct rogue_time saved_time, mod_time;
178	char buf[4];
179	char tbuf[MAX_OPT_LEN];
180	int new_file_id, saved_file_id;
181
182	fp = NULL;
183	if (((new_file_id = md_get_file_id(fname)) == -1) ||
184	    ((fp = fopen(fname, "r")) == NULL)) {
185		clean_up("cannot open file");
186	}
187	if (md_link_count(fname) > 1) {
188		clean_up("file has link");
189	}
190	(void)xxx(1);
191	r_read(fp, &detect_monster, sizeof(detect_monster));
192	r_read(fp, &cur_level, sizeof(cur_level));
193	r_read(fp, &max_level, sizeof(max_level));
194	read_string(hunger_str, fp, sizeof hunger_str);
195
196	(void)strlcpy(tbuf, login_name, sizeof tbuf);
197	read_string(login_name, fp, sizeof login_name);
198	if (strcmp(tbuf, login_name)) {
199		clean_up("you're not the original player");
200	}
201
202	r_read(fp, &party_room, sizeof(party_room));
203	read_pack(&level_monsters, fp, 0);
204	read_pack(&level_objects, fp, 0);
205	r_read(fp, &saved_file_id, sizeof(saved_file_id));
206	if (new_file_id != saved_file_id) {
207		clean_up("sorry, saved game is not in the same file");
208	}
209	rw_dungeon(fp, 0);
210	r_read(fp, &foods, sizeof(foods));
211	r_read(fp, &rogue, sizeof(fighter));
212	read_pack(&rogue.pack, fp, 1);
213	rw_id(id_potions, fp, POTIONS, 0);
214	rw_id(id_scrolls, fp, SCROLS, 0);
215	rw_id(id_wands, fp, WANDS, 0);
216	rw_id(id_rings, fp, RINGS, 0);
217	r_read(fp, traps, (MAX_TRAPS * sizeof(trap)));
218	r_read(fp, is_wood, (WANDS * sizeof(boolean)));
219	r_read(fp, &cur_room, sizeof(cur_room));
220	rw_rooms(fp, 0);
221	r_read(fp, &being_held, sizeof(being_held));
222	r_read(fp, &bear_trap, sizeof(bear_trap));
223	r_read(fp, &halluc, sizeof(halluc));
224	r_read(fp, &blind, sizeof(blind));
225	r_read(fp, &confused, sizeof(confused));
226	r_read(fp, &levitate, sizeof(levitate));
227	r_read(fp, &haste_self, sizeof(haste_self));
228	r_read(fp, &see_invisible, sizeof(see_invisible));
229	r_read(fp, &detect_monster, sizeof(detect_monster));
230	r_read(fp, &wizard, sizeof(wizard));
231	r_read(fp, &score_only, sizeof(score_only));
232	r_read(fp, &m_moves, sizeof(m_moves));
233	r_read(fp, &saved_time, sizeof(saved_time));
234
235	if (fread(buf, 1, 1, fp) > 0) {
236		clear();
237		clean_up("extra characters in file");
238	}
239
240	md_gfmt(fname, &mod_time);	/* get file modification time */
241
242	if (has_been_touched(&saved_time, &mod_time)) {
243		clear();
244		clean_up("sorry, file has been touched");
245	}
246	if ((!wizard) && !md_df(fname)) {
247		clean_up("cannot delete file");
248	}
249	msg_cleared = 0;
250	ring_stats(0);
251	fclose(fp);
252}
253
254static void
255write_pack(const object *pack, FILE *fp)
256{
257	object t;
258
259	while ((pack = pack->next_object) != NULL) {
260		r_write(fp, pack, sizeof(object));
261	}
262	t.ichar = t.what_is = 0;
263	r_write(fp, &t, sizeof(object));
264}
265
266static void
267read_pack(object *pack, FILE *fp, boolean is_rogue)
268{
269	object read_obj, *new_obj;
270
271	for (;;) {
272		r_read(fp, &read_obj, sizeof(object));
273		if (read_obj.ichar == 0) {
274			pack->next_object = NULL;
275			break;
276		}
277		new_obj = alloc_object();
278		*new_obj = read_obj;
279		if (is_rogue) {
280			if (new_obj->in_use_flags & BEING_WORN) {
281				do_wear(new_obj);
282			} else if (new_obj->in_use_flags & BEING_WIELDED) {
283				do_wield(new_obj);
284			} else if (new_obj->in_use_flags & (ON_EITHER_HAND)) {
285				do_put_on(new_obj,
286					((new_obj->in_use_flags & ON_LEFT_HAND) ? 1 : 0));
287			}
288		}
289		pack->next_object = new_obj;
290		pack = new_obj;
291	}
292}
293
294static void
295rw_dungeon(FILE *fp, boolean rw)
296{
297	short i, j;
298	char buf[DCOLS];
299
300	for (i = 0; i < DROWS; i++) {
301		if (rw) {
302			r_write(fp, dungeon[i], (DCOLS * sizeof(dungeon[0][0])));
303			for (j = 0; j < DCOLS; j++) {
304				buf[j] = mvinch(i, j);
305			}
306			r_write(fp, buf, DCOLS);
307		} else {
308			r_read(fp, dungeon[i], (DCOLS * sizeof(dungeon[0][0])));
309			r_read(fp, buf, DCOLS);
310			for (j = 0; j < DCOLS; j++) {
311				mvaddch(i, j, buf[j]);
312			}
313		}
314	}
315}
316
317static void
318rw_id(struct id id_table[], FILE *fp, int n, boolean wr)
319{
320	int i;
321
322	for (i = 0; i < n; i++) {
323		if (wr) {
324			r_write(fp, &id_table[i].value, sizeof(short));
325			r_write(fp, &id_table[i].id_status,
326				sizeof(unsigned short));
327			write_string(id_table[i].title, fp);
328		} else {
329			r_read(fp, &id_table[i].value, sizeof(short));
330			r_read(fp, &id_table[i].id_status,
331				sizeof(unsigned short));
332			read_string(id_table[i].title, fp, MAX_ID_TITLE_LEN);
333		}
334	}
335}
336
337static void
338write_string(char *s, FILE *fp)
339{
340	short n;
341
342	n = strlen(s) + 1;
343	xxxx(s, n);
344	r_write(fp, &n, sizeof(short));
345	r_write(fp, s, n);
346}
347
348static void
349read_string(char *s, FILE *fp, size_t len)
350{
351	short n;
352
353	r_read(fp, &n, sizeof(short));
354	if (n<=0 || (size_t)(unsigned short)n > len) {
355		clean_up("read_string: corrupt game file");
356	}
357	r_read(fp, s, n);
358	xxxx(s, n);
359	/* ensure null termination */
360	s[n-1] = 0;
361}
362
363static void
364rw_rooms(FILE *fp, boolean rw)
365{
366	short i;
367
368	for (i = 0; i < MAXROOMS; i++) {
369		rw ? r_write(fp, (rooms + i), sizeof(room)) :
370			r_read(fp, (rooms + i), sizeof(room));
371	}
372}
373
374static void
375r_read(FILE *fp, void *buf, size_t n)
376{
377	if (fread(buf, 1, n, fp) != n) {
378		clean_up("fread() failed, don't know why");
379	}
380}
381
382static void
383r_write(FILE *fp, const void *buf, size_t n)
384{
385	if (!write_failed) {
386		if (fwrite(buf, 1, n, fp) != n) {
387			messagef(0, "write() failed, don't know why");
388			sound_bell();
389			write_failed = 1;
390		}
391	}
392}
393
394static boolean
395has_been_touched(const struct rogue_time *saved_time,
396		 const struct rogue_time *mod_time)
397{
398	if (saved_time->year < mod_time->year) {
399		return(1);
400	} else if (saved_time->year > mod_time->year) {
401		return(0);
402	}
403	if (saved_time->month < mod_time->month) {
404		return(1);
405	} else if (saved_time->month > mod_time->month) {
406		return(0);
407	}
408	if (saved_time->day < mod_time->day) {
409		return(1);
410	} else if (saved_time->day > mod_time->day) {
411		return(0);
412	}
413	if (saved_time->hour < mod_time->hour) {
414		return(1);
415	} else if (saved_time->hour > mod_time->hour) {
416		return(0);
417	}
418	if (saved_time->minute < mod_time->minute) {
419		return(1);
420	} else if (saved_time->minute > mod_time->minute) {
421		return(0);
422	}
423	if (saved_time->second < mod_time->second) {
424		return(1);
425	}
426	return(0);
427}
428