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