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