1 //----------------------------------------------------------------------------
2 //  EDGE New SaveGame Handling (Chunks)
3 //----------------------------------------------------------------------------
4 //
5 //  Copyright (c) 1999-2008  The EDGE Team.
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 //----------------------------------------------------------------------------
18 //
19 // See the file "docs/save_sys.txt" for a complete description of the
20 // new savegame system.
21 //
22 // -AJA- 2000/07/13: Wrote this file.
23 //
24 
25 #include "i_defs.h"
26 
27 #include <zlib.h>
28 
29 #include "epi/math_crc.h"
30 
31 #include "sv_chunk.h"
32 #include "z_zone.h"
33 
34 
35 #define DEBUG_GETBYTE  0
36 #define DEBUG_PUTBYTE  0
37 #define DEBUG_COMPRESS 0
38 
39 
40 #define XOR_STRING  "EDGE!"
41 #define XOR_LEN     5
42 
43 
44 #define STRING_MARKER   0xAA
45 #define NULLSTR_MARKER  0xDE
46 
47 #define EDGESAVE_MAGIC   "EdgeSave"
48 #define FIRST_CHUNK_OFS  16L
49 
50 
51 int savegame_version = 0;
52 
53 static int last_error = 0;
54 
55 
56 // maximum size that compressing could give (worst-case scenario)
57 #define MAX_COMP_SIZE(orig)  (compressBound(orig) + 4)
58 
59 
60 // The chunk stack will never get any deeper than this
61 #define MAX_CHUNK_DEPTH  16
62 
63 typedef struct chunk_s
64 {
65 	char s_mark[6];
66 	char e_mark[6];
67 
68 	// read/write data.  When reading, this is only allocated/freed for
69 	// top level chunks (depth 0), lower chunks just point inside their
70 	// parent's data.  When writing, all chunks are allocated (and grow
71 	// bigger as needed).  Note: `end' is the byte _after_ the last one.
72 
73 	unsigned char *start;
74 	unsigned char *end;
75 	unsigned char *pos;
76 }
77 chunk_t;
78 
79 static chunk_t chunk_stack[MAX_CHUNK_DEPTH];
80 static int chunk_stack_size = 0;
81 
82 static FILE *current_fp = NULL;
83 static epi::crc32_c current_crc;
84 
85 
CheckMagic(void)86 static bool CheckMagic(void)
87 {
88 	int i;
89 	int len = strlen(EDGESAVE_MAGIC);
90 
91 	for (i=0; i < len; i++)
92 		if (SV_GetByte() != EDGESAVE_MAGIC[i])
93 			return false;
94 
95 	return true;
96 }
97 
PutMagic(void)98 static void PutMagic(void)
99 {
100 	int i;
101 	int len = strlen(EDGESAVE_MAGIC);
102 
103 	for (i=0; i < len; i++)
104 		SV_PutByte(EDGESAVE_MAGIC[i]);
105 }
106 
PutPadding(void)107 static void PutPadding(void)
108 {
109 	SV_PutByte(0x1A);
110 	SV_PutByte(0x0D);
111 	SV_PutByte(0x0A);
112 	SV_PutByte(0x00);
113 }
114 
VerifyMarker(const char * id)115 static inline bool VerifyMarker(const char *id)
116 {
117 	return isalnum(id[0]) && isalnum(id[1]) &&
118 		isalnum(id[2]) && isalnum(id[3]);
119 }
120 
121 
SV_ChunkInit(void)122 void SV_ChunkInit(void)
123 {
124 	/* ZLib doesn't need to be initialised */
125 }
126 
127 
SV_ChunkShutdown(void)128 void SV_ChunkShutdown(void)
129 {
130 	// nothing to do
131 }
132 
133 
SV_GetError(void)134 int SV_GetError(void)
135 {
136 	int result = last_error;
137 	last_error = 0;
138 
139 	return result;
140 }
141 
142 
143 //----------------------------------------------------------------------------
144 //  READING PRIMITIVES
145 //----------------------------------------------------------------------------
146 
SV_OpenReadFile(const char * filename)147 bool SV_OpenReadFile(const char *filename)
148 {
149 	L_WriteDebug("Opening savegame file (R): %s\n", filename);
150 
151 	chunk_stack_size = 0;
152 	last_error = 0;
153 
154 	current_crc.Reset();
155 
156 	current_fp = fopen(filename, "rb");
157 
158 	if (! current_fp)
159 		return false;
160 
161 	return true;
162 }
163 
SV_CloseReadFile(void)164 bool SV_CloseReadFile(void)
165 {
166 	SYS_ASSERT(current_fp);
167 
168 	if (chunk_stack_size > 0)
169 		I_Error("SV_CloseReadFile: Too many Pushes (missing Pop somewhere).\n");
170 
171 	fclose(current_fp);
172 
173 	if (last_error)
174 		I_Warning("LOADGAME: Error(s) occurred during reading.\n");
175 
176 	return true;
177 }
178 
179 //
180 // Sets the version field, which is BCD, with the patch level in the
181 // two least significant digits.
182 //
SV_VerifyHeader(int * version)183 bool SV_VerifyHeader(int *version)
184 {
185 	// check header
186 
187 	if (! CheckMagic())
188 	{
189 		I_Warning("LOADGAME: Bad magic in savegame file\n");
190 		return false;
191 	}
192 
193 	// skip padding
194 	SV_GetByte();
195 	SV_GetByte();
196 	SV_GetByte();
197 	SV_GetByte();
198 
199 	(*version) = SV_GetInt();
200 
201 	savegame_version = (*version);
202 
203 	if (last_error)
204 	{
205 		I_Warning("LOADGAME: Bad header in savegame file\n");
206 		return false;
207 	}
208 
209 	if (savegame_version < 0x13401)
210 	{
211 		I_Printf("LOADGAME: Savegame is too old (0x%05x < 0x%05x)\n",
212 		         savegame_version, 0x13401);
213 		return false;
214 	}
215 
216 	return true;
217 }
218 
SV_VerifyContents(void)219 bool SV_VerifyContents(void)
220 {
221 	SYS_ASSERT(current_fp);
222 	SYS_ASSERT(chunk_stack_size == 0);
223 
224 	// skip top-level chunks until end...
225 	for (;;)
226 	{
227 		unsigned int orig_len;
228 		unsigned int file_len;
229 
230 		char start_marker[6];
231 
232 		SV_GetMarker(start_marker);
233 
234 		if (! VerifyMarker(start_marker))
235 		{
236 			I_Warning("LOADGAME: Verify failed: Invalid start marker: "
237 				"%02X %02X %02X %02X\n", start_marker[0], start_marker[1],
238 				start_marker[2], start_marker[3]);
239 			return false;
240 		}
241 
242 		if (strcmp(start_marker, DATA_END_MARKER) == 0)
243 			break;
244 
245 		// read chunk length
246 		file_len = SV_GetInt();
247 
248 		// read original, uncompressed size
249 		orig_len = SV_GetInt();
250 
251 		if ((orig_len & 3) != 0 || file_len > MAX_COMP_SIZE(orig_len))
252 		{
253 			I_Warning("LOADGAME: Verify failed: Chunk has bad size: "
254 				"(file=%d orig=%d)\n", file_len, orig_len);
255 			return false;
256 		}
257 
258 		// skip data bytes (merely compute the CRC)
259 		for (; (file_len > 0) && !last_error; file_len--)
260 			SV_GetByte();
261 
262 		// run out of data ?
263 		if (last_error)
264 		{
265 			I_Warning("LOADGAME: Verify failed: Chunk corrupt or "
266 				"File truncated.\n");
267 			return false;
268 		}
269 	}
270 
271 	// check trailer
272 	if (! CheckMagic())
273 	{
274 		I_Warning("LOADGAME: Verify failed: Bad trailer.\n");
275 		return false;
276 	}
277 
278 	// CRC is now computed
279 
280 	epi::crc32_c final_crc(current_crc);
281 
282 	u32_t read_crc = SV_GetInt();
283 
284 	if (read_crc != final_crc.crc)
285 	{
286 		I_Warning("LOADGAME: Verify failed: Bad CRC: %08X != %08X\n",
287 			current_crc.crc, read_crc);
288 		return false;
289 	}
290 
291 	// Move file pointer back to beginning
292 	fseek(current_fp, FIRST_CHUNK_OFS, SEEK_SET);
293 	clearerr(current_fp);
294 
295 	return true;
296 }
297 
SV_GetByte(void)298 unsigned char SV_GetByte(void)
299 {
300 	chunk_t *cur;
301 	unsigned char result;
302 
303 	if (last_error)
304 		return 0;
305 
306 	// read directly from file when no chunks are on the stack
307 	if (chunk_stack_size == 0)
308 	{
309 		int c = fgetc(current_fp);
310 
311 		if (c == EOF)
312 		{
313 			I_Error("LOADGAME: Corrupt Savegame (reached EOF).\n");
314 			last_error = 1;
315 			return 0;
316 		}
317 
318 		current_crc += (byte) c;
319 
320 #if (DEBUG_GETBYTE)
321 		{
322 			static int pos=0; pos++;
323 			L_WriteDebug("%08X: %02X \n", ftell(current_fp), c);
324 //			L_WriteDebug("0.%02X%s", result, ((pos % 10)==0) ? "\n" : " ");
325 		}
326 #endif
327 
328 		return (unsigned char) c;
329 	}
330 
331 	cur = &chunk_stack[chunk_stack_size - 1];
332 
333 	SYS_ASSERT(cur->start);
334 	SYS_ASSERT(cur->pos >= cur->start);
335 	SYS_ASSERT(cur->pos <= cur->end);
336 
337 	if (cur->pos == cur->end)
338 	{
339 		I_Error("LOADGAME: Corrupt Savegame (reached end of [%s] chunk).\n", cur->s_mark);
340 		last_error = 2;
341 		return 0;
342 	}
343 
344 	result = cur->pos[0];
345 	cur->pos++;
346 
347 #if (DEBUG_GETBYTE)
348 	{
349 		static int pos=0; pos++;
350 		L_WriteDebug("%d.%02X%s", chunk_stack_size, result, ((pos % 10)==0) ? "\n" : " ");
351 	}
352 #endif
353 
354 	return result;
355 }
356 
SV_PushReadChunk(const char * id)357 bool SV_PushReadChunk(const char *id)
358 {
359 	chunk_t *cur;
360 	unsigned int file_len;
361 
362 	if (chunk_stack_size >= MAX_CHUNK_DEPTH)
363 		I_Error("SV_PushReadChunk: Too many Pushes (missing Pop somewhere).\n");
364 
365 	// read chunk length
366 	file_len = SV_GetInt();
367 
368 	// create new chunk_t
369 	cur = &chunk_stack[chunk_stack_size];
370 
371 	strcpy(cur->s_mark, id);
372 	strcpy(cur->e_mark, id);
373 	strupr(cur->e_mark);
374 
375 	// top level chunk ?
376 	if (chunk_stack_size == 0)
377 	{
378 		unsigned int i;
379 
380 		unsigned int orig_len;
381 		unsigned int decomp_len;
382 
383 		// read uncompressed size
384 		orig_len = SV_GetInt();
385 
386 		SYS_ASSERT(file_len <= MAX_COMP_SIZE(orig_len));
387 
388 		byte *file_data = new byte[file_len+1];
389 
390 		for (i=0; (i < file_len) && !last_error; i++)
391 			file_data[i] = SV_GetByte();
392 
393 		SYS_ASSERT(!last_error);
394 
395 		cur->start = new byte[orig_len+1];
396 		cur->end = cur->start + orig_len;
397 
398 		// decompress data
399 		decomp_len = orig_len;
400 
401 		if (orig_len == file_len)
402 		{
403 			// no compression
404 			memcpy(cur->start, file_data, file_len);
405 			decomp_len = file_len;
406 		}
407 		else // use ZLIB
408 		{
409 			SYS_ASSERT(file_len > 0);
410 			SYS_ASSERT(file_len < orig_len);
411 
412 			uLongf out_len = orig_len;
413 
414 			int res = uncompress(cur->start, &out_len,
415 					file_data, file_len);
416 
417 			if (res != Z_OK)
418 				I_Error("LOADGAME: ReadChunk [%s] failed: ZLIB uncompress error.\n", id);
419 
420 			decomp_len = (unsigned int)out_len;
421 		}
422 
423 		SYS_ASSERT(decomp_len == orig_len);
424 
425 		delete[] file_data;
426 	}
427 	else
428 	{
429 		chunk_t *parent = &chunk_stack[chunk_stack_size - 1];
430 
431 		cur->start = parent->pos;
432 		cur->end = cur->start + file_len;
433 
434 		// skip data in parent
435 		parent->pos += file_len;
436 
437 		SYS_ASSERT(parent->pos >= parent->start);
438 		SYS_ASSERT(parent->pos <= parent->end);
439 	}
440 
441 	cur->pos = cur->start;
442 
443 	// let the SV_GetByte routine (etc) see the new chunk
444 	chunk_stack_size++;
445 	return true;
446 }
447 
SV_PopReadChunk(void)448 bool SV_PopReadChunk(void)
449 {
450 	chunk_t *cur;
451 
452 	if (chunk_stack_size == 0)
453 		I_Error("SV_PopReadChunk: Too many Pops (missing Push somewhere).\n");
454 
455 	cur = &chunk_stack[chunk_stack_size - 1];
456 
457 	if (chunk_stack_size == 1)
458 	{
459 		// free the data
460 		delete[] cur->start;
461 	}
462 
463 	cur->start = cur->pos = cur->end = NULL;
464 	chunk_stack_size--;
465 
466 	return true;
467 }
468 
SV_RemainingChunkSize(void)469 int SV_RemainingChunkSize(void)
470 {
471 	chunk_t *cur;
472 
473 	SYS_ASSERT(chunk_stack_size > 0);
474 
475 	cur = &chunk_stack[chunk_stack_size - 1];
476 
477 	SYS_ASSERT(cur->pos >= cur->start);
478 	SYS_ASSERT(cur->pos <= cur->end);
479 
480 	return (cur->end - cur->pos);
481 }
482 
SV_SkipReadChunk(const char * id)483 bool SV_SkipReadChunk(const char *id)
484 {
485 	if (! SV_PushReadChunk(id))
486 		return false;
487 
488 	return SV_PopReadChunk();
489 }
490 
491 
492 //----------------------------------------------------------------------------
493 //  WRITING PRIMITIVES
494 //----------------------------------------------------------------------------
495 
SV_OpenWriteFile(const char * filename,int version)496 bool SV_OpenWriteFile(const char *filename, int version)
497 {
498 	L_WriteDebug("Opening savegame file (W): %s\n", filename);
499 
500 	chunk_stack_size = 0;
501 	last_error = 0;
502 
503 	savegame_version = version;
504 
505 	current_crc.Reset();
506 
507 	current_fp = fopen(filename, "wb");
508 
509 	if (! current_fp)
510 	{
511 		I_Warning("SAVEGAME: Couldn't open file: %s\n", filename);
512 		return false;
513 	}
514 
515 	// write header
516 
517 	PutMagic();
518 	PutPadding();
519 	SV_PutInt(version);
520 
521 	return true;
522 }
523 
SV_CloseWriteFile(void)524 bool SV_CloseWriteFile(void)
525 {
526 	SYS_ASSERT(current_fp);
527 
528 	if (chunk_stack_size != 0)
529 		I_Error("SV_CloseWriteFile: Too many Pushes (missing Pop somewhere).\n");
530 
531 	// write trailer
532 
533 	SV_PutMarker(DATA_END_MARKER);
534 	PutMagic();
535 
536 	epi::crc32_c final_crc(current_crc);
537 
538 	SV_PutInt(final_crc.crc);
539 
540 	if (last_error)
541 		I_Warning("SAVEGAME: Error(s) occurred during writing.\n");
542 
543 	fclose(current_fp);
544 
545 	return true;
546 }
547 
SV_PushWriteChunk(const char * id)548 bool SV_PushWriteChunk(const char *id)
549 {
550 	chunk_t *cur;
551 
552 	if (chunk_stack_size >= MAX_CHUNK_DEPTH)
553 		I_Error("SV_PushWriteChunk: Too many Pushes (missing Pop somewhere).\n");
554 
555 	// create new chunk_t
556 	cur = &chunk_stack[chunk_stack_size];
557 	chunk_stack_size++;
558 
559 	strcpy(cur->s_mark, id);
560 	strcpy(cur->e_mark, id);
561 	strupr(cur->e_mark);
562 
563 	// create initial buffer
564 	cur->start = new byte[1024];
565 	cur->pos   = cur->start;
566 	cur->end   = cur->start + 1024;
567 
568 	return true;
569 }
570 
SV_PopWriteChunk(void)571 bool SV_PopWriteChunk(void)
572 {
573 	int i;
574 	chunk_t *cur;
575 	int len;
576 
577 	if (chunk_stack_size == 0)
578 		I_Error("SV_PopWriteChunk: Too many Pops (missing Push somewhere).\n");
579 
580 	cur = &chunk_stack[chunk_stack_size - 1];
581 
582 	SYS_ASSERT(cur->start);
583 	SYS_ASSERT(cur->pos >= cur->start);
584 	SYS_ASSERT(cur->pos <= cur->end);
585 
586 	len = cur->pos - cur->start;
587 
588 	// pad chunk to multiple of 4 characters
589 	for (; len & 3; len++)
590 		SV_PutByte(0);
591 
592 	// decrement stack size, so future PutBytes go where they should
593 	chunk_stack_size--;
594 
595 	// firstly, write out marker
596 	SV_PutMarker(cur->s_mark);
597 
598 	// write out data.  For top-level chunks, compress it.
599 
600 	if (chunk_stack_size == 0)
601 	{
602 		uLongf out_len = MAX_COMP_SIZE(len);
603 
604 		byte *out_buf = new byte[out_len+1];
605 
606 		int res = compress2(out_buf, &out_len, cur->start, len, Z_BEST_SPEED);
607 
608 		if (res != Z_OK || (int)out_len >= len)
609 		{
610 #if (DEBUG_COMPRESS)
611 			L_WriteDebug("WriteChunk UNCOMPRESSED (res %d != %d, out_len %d >= %d)\n",
612 					res, Z_OK, (int)out_len, len);
613 #endif
614 			// compression failed, so write uncompressed
615 			memcpy(out_buf, cur->start, len);
616 			out_len = len;
617 		}
618 #if (DEBUG_COMPRESS)
619 		else
620 		{
621 			L_WriteDebug("WriteChunk compress (res %d == %d, out_len %d < %d)\n",
622 					res, Z_OK, (int)out_len, len);
623 		}
624 #endif
625 
626 		SYS_ASSERT((int)out_len <= (int)MAX_COMP_SIZE(len));
627 
628 		// write compressed length
629 		SV_PutInt((int)out_len);
630 
631 		// write original length to parent
632 		SV_PutInt(len);
633 
634 		for (i=0; i < (int)out_len && !last_error; i++)
635 			SV_PutByte(out_buf[i]);
636 
637 		SYS_ASSERT(!last_error);
638 
639 		delete[] out_buf;
640 	}
641 	else
642 	{
643 		// write chunk length to parent
644 		SV_PutInt(len);
645 
646 		// FIXME: optimise this (transfer data directly into parent)
647 		for (i=0; i < len; i++)
648 			SV_PutByte(cur->start[i]);
649 	}
650 
651 	// all done, free stuff
652 	delete[] cur->start;
653 
654 	cur->start = cur->pos = cur->end = NULL;
655 	return true;
656 }
657 
SV_PutByte(unsigned char value)658 void SV_PutByte(unsigned char value)
659 {
660 	chunk_t *cur;
661 
662 #if (DEBUG_PUTBYTE)
663 	{
664 		static int pos=0; pos++;
665 		L_WriteDebug("%d.%02x%s", chunk_stack_size, value,
666 			((pos % 10)==0) ? "\n" : " ");
667 	}
668 #endif
669 
670 	if (last_error)
671 		return;
672 
673 	// write directly to the file when chunk stack is empty
674 	if (chunk_stack_size == 0)
675 	{
676 		fputc(value, current_fp);
677 
678 		if (ferror(current_fp))
679 		{
680 			I_Warning("SAVEGAME: Write error occurred !\n");
681 			last_error = 3;
682 			return;
683 		}
684 
685 		current_crc += (byte) value;
686 		return;
687 	}
688 
689 	cur = &chunk_stack[chunk_stack_size - 1];
690 
691 	SYS_ASSERT(cur->start);
692 	SYS_ASSERT(cur->pos >= cur->start);
693 	SYS_ASSERT(cur->pos <= cur->end);
694 
695 	// space left in chunk ?  If not, resize it.
696 	if (cur->pos == cur->end)
697 	{
698 		int old_len = (cur->end - cur->start);
699 		int new_len = old_len * 2;
700 		int pos_idx = (cur->pos - cur->start);
701 
702 		byte *new_start = new byte[new_len];
703 		memcpy(new_start, cur->start, old_len);
704 
705 		delete[] cur->start;
706 		cur->start = new_start;
707 
708 		cur->end = cur->start + new_len;
709 		cur->pos = cur->start + pos_idx;
710 	}
711 
712 	*(cur->pos++) = value;
713 }
714 
715 
716 //----------------------------------------------------------------------------
717 
718 //
719 //  BASIC DATATYPES
720 //
721 
SV_PutShort(unsigned short value)722 void SV_PutShort(unsigned short value)
723 {
724 	SV_PutByte(value & 0xff);
725 	SV_PutByte(value >> 8);
726 }
727 
SV_PutInt(unsigned int value)728 void SV_PutInt(unsigned int value)
729 {
730 	SV_PutShort(value & 0xffff);
731 	SV_PutShort(value >> 16);
732 }
733 
SV_GetShort(void)734 unsigned short SV_GetShort(void)
735 {
736 	// -ACB- 2004/02/08 Force the order of execution; otherwise
737 	// compilr optimisations may reverse the order of execution
738 	byte b1 = SV_GetByte();
739 	byte b2 = SV_GetByte();
740 	return b1 | (b2 << 8);
741 }
742 
SV_GetInt(void)743 unsigned int SV_GetInt(void)
744 {
745 	// -ACB- 2004/02/08 Force the order of execution; otherwise
746 	// compiler optimisations may reverse the order of execution
747 	unsigned short s1 = SV_GetShort();
748 	unsigned short s2 = SV_GetShort();
749 	return s1 | (s2 << 16);
750 }
751 
752 
753 //----------------------------------------------------------------------------
754 
755 //
756 //  ANGLES
757 //
758 
759 
SV_PutAngle(angle_t value)760 void SV_PutAngle(angle_t value)
761 {
762 	SV_PutInt((unsigned int) value);
763 }
764 
SV_GetAngle(void)765 angle_t SV_GetAngle(void)
766 {
767 	return (angle_t) SV_GetInt();
768 }
769 
770 
771 //----------------------------------------------------------------------------
772 
773 //
774 //  FLOATING POINT
775 //
776 
SV_PutFloat(float value)777 void SV_PutFloat(float value)
778 {
779 	int exp;
780 	int mant;
781 	bool neg;
782 
783 	neg = (value < 0.0f);
784 	if (neg) value = -value;
785 
786 	mant = (int) ldexp(frexp(value, &exp), 30);
787 
788 	SV_PutShort(256 + exp);
789 	SV_PutInt((unsigned int) (neg ? -mant : mant));
790 }
791 
SV_GetFloat(void)792 float SV_GetFloat(void)
793 {
794 	int exp;
795 	int mant;
796 
797 	exp = SV_GetShort() - 256;
798 	mant = (int) SV_GetInt();
799 
800 	return (float)ldexp((float) mant, -30 + exp);
801 }
802 
803 
804 //----------------------------------------------------------------------------
805 
806 //
807 //  STRINGS & MARKERS
808 //
809 
SV_PutString(const char * str)810 void SV_PutString(const char *str)
811 {
812 	if (str == NULL)
813 	{
814 		SV_PutByte(NULLSTR_MARKER);
815 		return;
816 	}
817 
818 	SV_PutByte(STRING_MARKER);
819 	SV_PutShort(strlen(str));
820 
821 	for (; *str; str++)
822 		SV_PutByte(*str);
823 }
824 
SV_PutMarker(const char * id)825 void SV_PutMarker(const char *id)
826 {
827 	int i;
828 
829 	SYS_ASSERT(id);
830 	SYS_ASSERT(strlen(id) == 4);
831 
832 	for (i=0; i < 4; i++)
833 		SV_PutByte((unsigned char) id[i]);
834 }
835 
SV_GetString(void)836 const char *SV_GetString(void)
837 {
838 	int type = SV_GetByte();
839 
840 	if (type == NULLSTR_MARKER)
841 		return NULL;
842 
843 	if (type != STRING_MARKER)
844 		I_Error("Corrupt savegame (invalid string).\n");
845 
846 	int len = SV_GetShort();
847 
848 	char *result = new char[len + 1];
849 	result[len] = 0;
850 
851 	for (int i = 0; i < len; i++)
852 		result[i] = (char) SV_GetByte();
853 
854 	return result;
855 }
856 
SV_DupString(const char * old)857 const char *SV_DupString(const char *old)
858 {
859 	if (! old)
860 		return NULL;
861 
862 	char *result = new char[strlen(old) + 1];
863 
864 	strcpy(result, old);
865 
866 	return result;
867 }
868 
SV_FreeString(const char * str)869 void SV_FreeString(const char *str)
870 {
871 	if (str)
872 		delete[] str;
873 }
874 
SV_GetMarker(char id[5])875 bool SV_GetMarker(char id[5])
876 {
877 	int i;
878 
879 	for (i=0; i < 4; i++)
880 		id[i] = (char) SV_GetByte();
881 
882 	id[4] = 0;
883 
884 	return VerifyMarker(id);
885 }
886 
887 
888 //--- editor settings ---
889 // vi:ts=4:sw=4:noexpandtab
890