1 //
2 // Saved game.
3 //
4
5
6 #include "ojk_saved_game.h"
7 #include <algorithm>
8 #include <memory>
9 #include "ojk_saved_game_helper.h"
10 #include "qcommon/qcommon.h"
11 #include "server/server.h"
12
13
14 namespace ojk
15 {
16
17
SavedGame()18 SavedGame::SavedGame() :
19 error_message_(),
20 file_handle_(),
21 io_buffer_(),
22 saved_io_buffer_(),
23 io_buffer_offset_(),
24 saved_io_buffer_offset_(),
25 rle_buffer_(),
26 is_readable_(),
27 is_writable_(),
28 is_failed_()
29 {
30 }
31
~SavedGame()32 SavedGame::~SavedGame()
33 {
34 close();
35 }
36
open(const std::string & base_file_name)37 bool SavedGame::open(
38 const std::string& base_file_name)
39 {
40 close();
41
42
43 const std::string file_path = generate_path(
44 base_file_name);
45
46 bool is_succeed = true;
47
48 static_cast<void>(::FS_FOpenFileRead(
49 file_path.c_str(),
50 &file_handle_,
51 qtrue));
52
53 if (file_handle_ == 0)
54 {
55 is_succeed = false;
56
57 error_message_ =
58 S_COLOR_RED "Failed to open a saved game file: \"" +
59 file_path + "\".";
60
61 ::Com_DPrintf(
62 "%s\n",
63 error_message_.c_str());
64 }
65
66 if (is_succeed)
67 {
68 is_readable_ = true;
69 }
70
71
72 if (is_succeed)
73 {
74 SavedGameHelper saved_game(
75 this);
76
77 int sg_version = -1;
78
79 if (saved_game.try_read_chunk<int32_t>(
80 INT_ID('_', 'V', 'E', 'R'),
81 sg_version))
82 {
83 if (sg_version != iSAVEGAME_VERSION)
84 {
85 is_succeed = false;
86
87 ::Com_Printf(
88 S_COLOR_RED "File \"%s\" has version # %d (expecting %d)\n",
89 base_file_name.c_str(),
90 sg_version,
91 iSAVEGAME_VERSION);
92 }
93 }
94 else
95 {
96 is_succeed = false;
97
98 ::Com_Printf(
99 S_COLOR_RED "Failed to read a version.\n");
100 }
101 }
102
103 if (!is_succeed)
104 {
105 close();
106 }
107
108 return is_succeed;
109 }
110
create(const std::string & base_file_name)111 bool SavedGame::create(
112 const std::string& base_file_name)
113 {
114 close();
115
116
117 remove(
118 base_file_name);
119
120 const std::string file_path = generate_path(
121 base_file_name);
122
123 file_handle_ = ::FS_FOpenFileWrite(
124 file_path.c_str());
125
126 if (file_handle_ == 0)
127 {
128 const std::string error_message =
129 S_COLOR_RED "Failed to create a saved game file: \"" +
130 file_path + "\".";
131
132 ::Com_Printf(
133 "%s\n",
134 error_message.c_str());
135
136 return false;
137 }
138
139
140 is_writable_ = true;
141
142 const int sg_version = iSAVEGAME_VERSION;
143
144 SavedGameHelper sgsh(this);
145
146 sgsh.write_chunk<int32_t>(
147 INT_ID('_', 'V', 'E', 'R'),
148 sg_version);
149
150 if (is_failed())
151 {
152 close();
153 return false;
154 }
155
156 return true;
157 }
158
close()159 void SavedGame::close()
160 {
161 if (file_handle_ != 0)
162 {
163 ::FS_FCloseFile(file_handle_);
164 file_handle_ = 0;
165 }
166
167 clear_error();
168 reset_buffer();
169
170 saved_io_buffer_.clear();
171 saved_io_buffer_offset_ = 0;
172
173 rle_buffer_.clear();
174
175 is_readable_ = false;
176 is_writable_ = false;
177 }
178
read_chunk(const uint32_t chunk_id)179 bool SavedGame::read_chunk(
180 const uint32_t chunk_id)
181 {
182 if (is_failed_)
183 {
184 return false;
185 }
186
187 if (file_handle_ == 0)
188 {
189 is_failed_ = true;
190 error_message_ = "Not open or created.";
191 return false;
192 }
193
194 io_buffer_offset_ = 0;
195
196 const std::string chunk_id_string = get_chunk_id_string(
197 chunk_id);
198
199 ::Com_DPrintf(
200 "Attempting read of chunk %s\n",
201 chunk_id_string.c_str());
202
203 uint32_t loaded_chunk_id = 0;
204 uint32_t loaded_data_size = 0;
205
206 int loaded_chunk_size = ::FS_Read(
207 &loaded_chunk_id,
208 static_cast<int>(sizeof(loaded_chunk_id)),
209 file_handle_);
210
211 loaded_chunk_size += ::FS_Read(
212 &loaded_data_size,
213 static_cast<int>(sizeof(loaded_data_size)),
214 file_handle_);
215
216 const bool is_compressed = (static_cast<int32_t>(loaded_data_size) < 0);
217
218 if (is_compressed)
219 {
220 loaded_data_size = -static_cast<int32_t>(loaded_data_size);
221 }
222
223 // Make sure we are loading the correct chunk...
224 //
225 if (loaded_chunk_id != chunk_id)
226 {
227 is_failed_ = true;
228
229 const std::string loaded_chunk_id_string = get_chunk_id_string(
230 loaded_chunk_id);
231
232 error_message_ =
233 "Loaded chunk ID (" +
234 loaded_chunk_id_string +
235 ") does not match requested chunk ID (" +
236 chunk_id_string +
237 ").";
238
239 return false;
240 }
241
242 uint32_t loaded_checksum = 0;
243
244 #ifdef JK2_MODE
245 // Get checksum...
246 //
247 loaded_chunk_size += ::FS_Read(
248 &loaded_checksum,
249 static_cast<int>(sizeof(loaded_checksum)),
250 file_handle_);
251 #endif // JK2_MODE
252
253 // Load in data and magic number...
254 //
255 uint32_t compressed_size = 0;
256
257 if (is_compressed)
258 {
259 loaded_chunk_size += ::FS_Read(
260 &compressed_size,
261 static_cast<int>(sizeof(compressed_size)),
262 file_handle_);
263
264 rle_buffer_.resize(
265 compressed_size);
266
267 loaded_chunk_size += ::FS_Read(
268 rle_buffer_.data(),
269 compressed_size,
270 file_handle_);
271
272 io_buffer_.resize(
273 loaded_data_size);
274
275 decompress(
276 rle_buffer_,
277 io_buffer_);
278 }
279 else
280 {
281 io_buffer_.resize(
282 loaded_data_size);
283
284 loaded_chunk_size += ::FS_Read(
285 io_buffer_.data(),
286 loaded_data_size,
287 file_handle_);
288 }
289
290 #ifdef JK2_MODE
291 uint32_t loaded_magic_value = 0;
292
293 loaded_chunk_size += ::FS_Read(
294 &loaded_magic_value,
295 static_cast<int>(sizeof(loaded_magic_value)),
296 file_handle_);
297
298 if (loaded_magic_value != get_jo_magic_value())
299 {
300 is_failed_ = true;
301
302 error_message_ =
303 "Bad saved game magic for chunk " + chunk_id_string + ".";
304
305 return false;
306 }
307 #else
308 // Get checksum...
309 //
310 loaded_chunk_size += ::FS_Read(
311 &loaded_checksum,
312 static_cast<int>(sizeof(loaded_checksum)),
313 file_handle_);
314 #endif // JK2_MODE
315
316 // Make sure the checksums match...
317 //
318 const uint32_t checksum = ::Com_BlockChecksum(
319 io_buffer_.data(),
320 static_cast<int>(io_buffer_.size()));
321
322 if (loaded_checksum != checksum)
323 {
324 is_failed_ = true;
325
326 error_message_ =
327 "Failed checksum check for chunk " + chunk_id_string + ".";
328
329 return false;
330 }
331
332 // Make sure we didn't encounter any read errors...
333 std::size_t ref_chunk_size =
334 sizeof(loaded_chunk_id) +
335 sizeof(loaded_data_size) +
336 sizeof(loaded_checksum) +
337 (is_compressed ? sizeof(compressed_size) : 0) +
338 (is_compressed ? compressed_size : io_buffer_.size());
339
340 #ifdef JK2_MODE
341 ref_chunk_size += sizeof(loaded_magic_value);
342 #endif
343
344 if (loaded_chunk_size != static_cast<int>(ref_chunk_size))
345 {
346 is_failed_ = true;
347
348 error_message_ =
349 "Error during loading chunk " + chunk_id_string + ".";
350
351 return false;
352 }
353
354 return true;
355 }
356
is_all_data_read() const357 bool SavedGame::is_all_data_read() const
358 {
359 if (is_failed_)
360 {
361 return false;
362 }
363
364 if (file_handle_ == 0)
365 {
366 return false;
367 }
368
369 return io_buffer_.size() == io_buffer_offset_;
370 }
371
ensure_all_data_read()372 void SavedGame::ensure_all_data_read()
373 {
374 if (!is_all_data_read())
375 {
376 error_message_ = "Not all expected data read.";
377
378 throw_error();
379 }
380 }
381
write_chunk(const uint32_t chunk_id)382 bool SavedGame::write_chunk(
383 const uint32_t chunk_id)
384 {
385 if (is_failed_)
386 {
387 return false;
388 }
389
390 if (file_handle_ == 0)
391 {
392 is_failed_ = true;
393 error_message_ = "Not open or created.";
394 return false;
395 }
396
397
398 const std::string chunk_id_string = get_chunk_id_string(
399 chunk_id);
400
401 ::Com_DPrintf(
402 "Attempting write of chunk %s\n",
403 chunk_id_string.c_str());
404
405 if (::sv_testsave->integer != 0)
406 {
407 return true;
408 }
409
410 const int src_size = static_cast<int>(io_buffer_.size());
411
412 const uint32_t checksum = Com_BlockChecksum(
413 io_buffer_.data(),
414 src_size);
415
416 uint32_t saved_chunk_size = ::FS_Write(
417 &chunk_id,
418 static_cast<int>(sizeof(chunk_id)),
419 file_handle_);
420
421 int compressed_size = -1;
422
423 if (::sv_compress_saved_games->integer != 0)
424 {
425 compress(
426 io_buffer_,
427 rle_buffer_);
428
429 if (rle_buffer_.size() < io_buffer_.size())
430 {
431 compressed_size = static_cast<int>(rle_buffer_.size());
432 }
433 }
434
435 #ifdef JK2_MODE
436 const uint32_t magic_value = get_jo_magic_value();
437 #endif // JK2_MODE
438
439 if (compressed_size > 0)
440 {
441 const int size = -static_cast<int>(io_buffer_.size());
442
443 saved_chunk_size += ::FS_Write(
444 &size,
445 static_cast<int>(sizeof(size)),
446 file_handle_);
447
448 #ifdef JK2_MODE
449 saved_chunk_size += ::FS_Write(
450 &checksum,
451 static_cast<int>(sizeof(checksum)),
452 file_handle_);
453 #endif // JK2_MODE
454
455 saved_chunk_size += ::FS_Write(
456 &compressed_size,
457 static_cast<int>(sizeof(compressed_size)),
458 file_handle_);
459
460 saved_chunk_size += ::FS_Write(
461 rle_buffer_.data(),
462 compressed_size,
463 file_handle_);
464
465 #ifdef JK2_MODE
466 saved_chunk_size += ::FS_Write(
467 &magic_value,
468 static_cast<int>(sizeof(magic_value)),
469 file_handle_);
470 #else
471 saved_chunk_size += ::FS_Write(
472 &checksum,
473 static_cast<int>(sizeof(checksum)),
474 file_handle_);
475 #endif // JK2_MODE
476
477 std::size_t ref_chunk_size =
478 sizeof(chunk_id) +
479 sizeof(size) +
480 sizeof(checksum) +
481 sizeof(compressed_size) +
482 compressed_size;
483
484 #ifdef JK2_MODE
485 ref_chunk_size += sizeof(magic_value);
486 #endif // JK2_MODE
487
488 if (saved_chunk_size != ref_chunk_size)
489 {
490 is_failed_ = true;
491
492 error_message_ = "Failed to write " + chunk_id_string + " chunk.";
493
494 ::Com_Printf(
495 "%s%s\n",
496 S_COLOR_RED,
497 error_message_.c_str());
498
499 return false;
500 }
501 }
502 else
503 {
504 const uint32_t size = static_cast<uint32_t>(io_buffer_.size());
505
506 saved_chunk_size += ::FS_Write(
507 &size,
508 static_cast<int>(sizeof(size)),
509 file_handle_);
510
511 #ifdef JK2_MODE
512 saved_chunk_size += ::FS_Write(
513 &checksum,
514 static_cast<int>(sizeof(checksum)),
515 file_handle_);
516 #endif // JK2_MODE
517
518 saved_chunk_size += ::FS_Write(
519 io_buffer_.data(),
520 size,
521 file_handle_);
522
523 #ifdef JK2_MODE
524 saved_chunk_size += ::FS_Write(
525 &magic_value,
526 static_cast<int>(sizeof(magic_value)),
527 file_handle_);
528 #else
529 saved_chunk_size += ::FS_Write(
530 &checksum,
531 static_cast<int>(sizeof(checksum)),
532 file_handle_);
533 #endif // JK2_MODE
534
535 std::size_t ref_chunk_size =
536 sizeof(chunk_id) +
537 sizeof(size) +
538 sizeof(checksum) +
539 size;
540
541 #ifdef JK2_MODE
542 ref_chunk_size += sizeof(magic_value);
543 #endif // JK2_MODE
544
545 if (saved_chunk_size != ref_chunk_size)
546 {
547 is_failed_ = true;
548
549 error_message_ = "Failed to write " + chunk_id_string + " chunk.";
550
551 ::Com_Printf(
552 "%s%s\n",
553 S_COLOR_RED,
554 error_message_.c_str());
555
556 return false;
557 }
558 }
559
560 return true;
561 }
562
read(void * dst_data,int dst_size)563 bool SavedGame::read(
564 void* dst_data,
565 int dst_size)
566 {
567 if (is_failed_)
568 {
569 return false;
570 }
571
572 if (file_handle_ == 0)
573 {
574 is_failed_ = true;
575 error_message_ = "Not open or created.";
576 return false;
577 }
578
579 if (!dst_data)
580 {
581 is_failed_ = true;
582 error_message_ = "Null pointer.";
583 return false;
584 }
585
586 if (dst_size < 0)
587 {
588 is_failed_ = true;
589 error_message_ = "Negative size.";
590 return false;
591 }
592
593 if (!is_readable_)
594 {
595 is_failed_ = true;
596 error_message_ = "Not readable.";
597 return false;
598 }
599
600 if (dst_size == 0)
601 {
602 return true;
603 }
604
605 if ((io_buffer_offset_ + dst_size) > io_buffer_.size())
606 {
607 is_failed_ = true;
608 error_message_ = "Not enough data.";
609 return false;
610 }
611
612 std::uninitialized_copy_n(
613 &io_buffer_[io_buffer_offset_],
614 dst_size,
615 static_cast<uint8_t*>(dst_data));
616
617 io_buffer_offset_ += dst_size;
618
619 return true;
620 }
621
write(const void * src_data,int src_size)622 bool SavedGame::write(
623 const void* src_data,
624 int src_size)
625 {
626 if (is_failed_)
627 {
628 return false;
629 }
630
631 if (file_handle_ == 0)
632 {
633 is_failed_ = true;
634 error_message_ = "Not open or created.";
635 return false;
636 }
637
638 if (!src_data)
639 {
640 is_failed_ = true;
641 error_message_ = "Null pointer.";
642 return false;
643 }
644
645 if (src_size < 0)
646 {
647 is_failed_ = true;
648 error_message_ = "Negative size.";
649 return false;
650 }
651
652 if (!is_writable_)
653 {
654 is_failed_ = true;
655 error_message_ = "Not writable.";
656 return false;
657 }
658
659 if (src_size == 0)
660 {
661 return true;
662 }
663
664 const std::size_t new_buffer_size = io_buffer_offset_ + src_size;
665
666 io_buffer_.resize(
667 new_buffer_size);
668
669 std::uninitialized_copy_n(
670 static_cast<const uint8_t*>(src_data),
671 src_size,
672 &io_buffer_[io_buffer_offset_]);
673
674 io_buffer_offset_ = new_buffer_size;
675
676 return true;
677 }
678
is_failed() const679 bool SavedGame::is_failed() const
680 {
681 return is_failed_;
682 }
683
skip(int count)684 bool SavedGame::skip(
685 int count)
686 {
687 if (is_failed_)
688 {
689 return false;
690 }
691
692 if (file_handle_ == 0)
693 {
694 is_failed_ = true;
695 error_message_ = "Not open or created.";
696 return false;
697 }
698
699 if (!is_readable_ && !is_writable_)
700 {
701 is_failed_ = true;
702 error_message_ = "Not open or created.";
703 return false;
704 }
705
706 if (count < 0)
707 {
708 is_failed_ = true;
709 error_message_ = "Negative count.";
710 return false;
711 }
712
713 if (count == 0)
714 {
715 return true;
716 }
717
718 const std::size_t new_offset = io_buffer_offset_ + count;
719 const std::size_t buffer_size = io_buffer_.size();
720
721 if (new_offset > buffer_size)
722 {
723 if (is_readable_)
724 {
725 is_failed_ = true;
726 error_message_ = "Not enough data.";
727 return false;
728 }
729 else if (is_writable_)
730 {
731 if (new_offset > buffer_size)
732 {
733 io_buffer_.resize(
734 new_offset);
735 }
736 }
737 }
738
739 io_buffer_offset_ = new_offset;
740
741 return true;
742 }
743
save_buffer()744 void SavedGame::save_buffer()
745 {
746 saved_io_buffer_ = io_buffer_;
747 saved_io_buffer_offset_ = io_buffer_offset_;
748 }
749
load_buffer()750 void SavedGame::load_buffer()
751 {
752 io_buffer_ = saved_io_buffer_;
753 io_buffer_offset_ = saved_io_buffer_offset_;
754 }
755
get_buffer_data() const756 const void* SavedGame::get_buffer_data() const
757 {
758 return io_buffer_.data();
759 }
760
get_buffer_size() const761 int SavedGame::get_buffer_size() const
762 {
763 return static_cast<int>(io_buffer_.size());
764 }
765
rename(const std::string & old_base_file_name,const std::string & new_base_file_name)766 void SavedGame::rename(
767 const std::string& old_base_file_name,
768 const std::string& new_base_file_name)
769 {
770 const std::string old_path = generate_path(
771 old_base_file_name);
772
773 const std::string new_path = generate_path(
774 new_base_file_name);
775
776 const int rename_result = ::FS_MoveUserGenFile(
777 old_path.c_str(),
778 new_path.c_str());
779
780 if (rename_result == 0)
781 {
782 ::Com_Printf(
783 S_COLOR_RED "Error during savegame-rename."
784 " Check \"%s\" for write-protect or disk full!\n",
785 new_path.c_str());
786 }
787 }
788
remove(const std::string & base_file_name)789 void SavedGame::remove(
790 const std::string& base_file_name)
791 {
792 const std::string path = generate_path(
793 base_file_name);
794
795 ::FS_DeleteUserGenFile(
796 path.c_str());
797 }
798
get_instance()799 SavedGame& SavedGame::get_instance()
800 {
801 static SavedGame result;
802 return result;
803 }
804
clear_error()805 void SavedGame::clear_error()
806 {
807 is_failed_ = false;
808 error_message_.clear();
809 }
810
throw_error()811 void SavedGame::throw_error()
812 {
813 if (error_message_.empty())
814 {
815 error_message_ = "Generic error.";
816 }
817
818 error_message_ = "SG: " + error_message_;
819
820 ::Com_Error(
821 ERR_DROP,
822 "%s",
823 error_message_.c_str());
824 }
825
compress(const Buffer & src_buffer,Buffer & dst_buffer)826 void SavedGame::compress(
827 const Buffer& src_buffer,
828 Buffer& dst_buffer)
829 {
830 const int src_size = static_cast<int>(src_buffer.size());
831
832 dst_buffer.resize(2 * src_size);
833
834 int src_count = 0;
835 int dst_index = 0;
836
837 while (src_count < src_size)
838 {
839 int src_index = src_count;
840 uint8_t b = src_buffer[src_index++];
841
842 while (src_index < src_size &&
843 (src_index - src_count) < 127 &&
844 src_buffer[src_index] == b)
845 {
846 src_index += 1;
847 }
848
849 if ((src_index - src_count) == 1)
850 {
851 while (src_index < src_size &&
852 (src_index - src_count) < 127 && (
853 src_buffer[src_index] != src_buffer[src_index - 1] || (
854 src_index > 1 &&
855 src_buffer[src_index] != src_buffer[src_index - 2])))
856 {
857 src_index += 1;
858 }
859
860 while (src_index < src_size &&
861 src_buffer[src_index] == src_buffer[src_index - 1])
862 {
863 src_index -= 1;
864 }
865
866 dst_buffer[dst_index++] =
867 static_cast<uint8_t>(src_count - src_index);
868
869 for (int i = src_count; i < src_index; ++i)
870 {
871 dst_buffer[dst_index++] = src_buffer[i];
872 }
873 }
874 else
875 {
876 dst_buffer[dst_index++] =
877 static_cast<uint8_t>(src_index - src_count);
878
879 dst_buffer[dst_index++] = b;
880 }
881
882 src_count = src_index;
883 }
884
885 dst_buffer.resize(
886 dst_index);
887 }
888
decompress(const Buffer & src_buffer,Buffer & dst_buffer)889 void SavedGame::decompress(
890 const Buffer& src_buffer,
891 Buffer& dst_buffer)
892 {
893 int src_index = 0;
894 int dst_index = 0;
895
896 int remain_size = static_cast<int>(dst_buffer.size());
897
898 while (remain_size > 0)
899 {
900 int8_t count = static_cast<int8_t>(src_buffer[src_index++]);
901
902 if (count > 0)
903 {
904 std::uninitialized_fill_n(
905 &dst_buffer[dst_index],
906 count,
907 src_buffer[src_index++]);
908 }
909 else
910 {
911 if (count < 0)
912 {
913 count = -count;
914
915 std::uninitialized_copy_n(
916 &src_buffer[src_index],
917 count,
918 &dst_buffer[dst_index]);
919
920 src_index += count;
921 }
922 }
923
924 dst_index += count;
925 remain_size -= count;
926 }
927 }
928
generate_path(const std::string & base_file_name)929 std::string SavedGame::generate_path(
930 const std::string& base_file_name)
931 {
932 std::string normalized_file_name = base_file_name;
933
934 std::replace(
935 normalized_file_name.begin(),
936 normalized_file_name.end(),
937 '/',
938 '_');
939
940 return "saves/" + normalized_file_name + ".sav";
941 }
942
get_chunk_id_string(uint32_t chunk_id)943 std::string SavedGame::get_chunk_id_string(
944 uint32_t chunk_id)
945 {
946 std::string result(4, '\0');
947
948 result[0] = static_cast<char>((chunk_id >> 24) & 0xFF);
949 result[1] = static_cast<char>((chunk_id >> 16) & 0xFF);
950 result[2] = static_cast<char>((chunk_id >> 8) & 0xFF);
951 result[3] = static_cast<char>((chunk_id >> 0) & 0xFF);
952
953 return result;
954 }
955
reset_buffer()956 void SavedGame::reset_buffer()
957 {
958 io_buffer_.clear();
959 reset_buffer_offset();
960 }
961
reset_buffer_offset()962 void SavedGame::reset_buffer_offset()
963 {
964 io_buffer_offset_ = 0;
965 }
966
get_jo_magic_value()967 const uint32_t SavedGame::get_jo_magic_value()
968 {
969 return 0x1234ABCD;
970 }
971
972
973 } // ojk
974