1 /*
2 BStone: A Source port of
3 Blake Stone: Aliens of Gold and Blake Stone: Planet Strike
4
5 Copyright (c) 1992-2013 Apogee Entertainment, LLC
6 Copyright (c) 2013-2015 Boris I. Bendovsky (bibendovsky@hotmail.com)
7
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the
20 Free Software Foundation, Inc.,
21 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 */
23
24
25 /*
26 =============================================================================
27
28 Id Software Caching Manager
29 ---------------------------
30
31 Must be started BEFORE the memory manager, because it needs to get the headers
32 loaded into the data segment
33
34 =============================================================================
35 */
36
37
38 #include "id_heads.h"
39
40
41 /*
42 =============================================================================
43
44 GLOBAL VARIABLES
45
46 =============================================================================
47 */
48
49 uint16_t rlew_tag;
50
51 int16_t mapon;
52
53 uint16_t* mapsegs[MAPPLANES];
54 MapHeaderSegments mapheaderseg;
55 AudioSegments audiosegs;
56 GrSegments grsegs;
57
58 GrNeeded grneeded;
59 uint8_t ca_levelbit, ca_levelnum;
60
61 int16_t profilehandle, debughandle;
62
63 int NUM_EPISODES = 0;
64 int MAPS_PER_EPISODE = 0;
65 int MAPS_WITH_STATS = 0;
66
67 int NUMMAPS = 0;
68
69 bool is_aog_full();
70 bool is_aog_sw();
71 bool is_ps();
72
73
74 std::string audioname = "AUDIO.";
75
76 /*
77 =============================================================================
78
79 LOCAL VARIABLES
80
81 =============================================================================
82 */
83
84 extern int32_t CGAhead;
85 extern int32_t EGAhead;
86 extern uint8_t CGAdict;
87 extern uint8_t EGAdict;
88 extern uint8_t maphead;
89 extern uint8_t mapdict;
90 extern uint8_t audiohead;
91 extern uint8_t audiodict;
92
93
94 std::string extension; // Need a string, not constant to change cache files
95 std::string gheadname = "VGAHEAD.";
96 std::string gfilename = "VGAGRAPH.";
97 std::string gdictname = "VGADICT.";
98 std::string mheadname = "MAPHEAD.";
99 std::string mfilename = "MAPTEMP.";
100 std::string aheadname = "AUDIOHED.";
101 std::string afilename = "AUDIOT.";
102
103 void CA_CannotOpen(
104 const std::string& string);
105
106 int32_t* grstarts; // array of offsets in egagraph, -1 for sparse
107 int32_t* audiostarts; // array of offsets in audio / audiot
108
109 #ifdef GRHEADERLINKED
110 huffnode* grhuffman;
111 #else
112 huffnode grhuffman[255];
113 #endif
114
115 #ifdef AUDIOHEADERLINKED
116 huffnode* audiohuffman;
117 #else
118 huffnode audiohuffman[255];
119 #endif
120
121 bstone::FileStream grhandle; // handle to EGAGRAPH
122 bstone::FileStream maphandle; // handle to MAPTEMP / GAMEMAPS
123 bstone::FileStream audiohandle; // handle to AUDIOT / AUDIO
124
125 int32_t chunkcomplen;
126 int32_t chunkexplen;
127
128 bool old_is_sound_enabled;
129
130 static Buffer ca_buffer;
131
132 static const int BUFFERSIZE = 0x10000;
133
134
135 // BBi
136 int ca_gr_last_expanded_size;
137 int map_compressed_size = 0;
138 std::string map_sha1_string;
139
140 void CAL_CarmackExpand(
141 uint16_t* source,
142 uint16_t* dest,
143 uint16_t length);
144
145
146 #ifdef THREEBYTEGRSTARTS
GRFILEPOS(int16_t c)147 int32_t GRFILEPOS(
148 int16_t c)
149 {
150 int32_t value;
151 int16_t offset;
152
153 offset = c * 3;
154
155 value = *(int32_t*)(((uint8_t*)grstarts) + offset);
156
157 value &= 0x00ffffffl;
158
159 if (value == 0xffffffl) {
160 value = -1;
161 }
162
163 return value;
164 }
165 #else
166 #define GRFILEPOS(c) (grstarts[c])
167 #endif
168
OpenGrFile()169 void OpenGrFile()
170 {
171 auto fname = ::data_dir + ::gfilename + ::extension;
172
173 ::grhandle.open(fname);
174
175 if (!::grhandle.is_open()) {
176 ::CA_CannotOpen(fname);
177 }
178 }
179
CloseGrFile()180 void CloseGrFile()
181 {
182 ::grhandle.close();
183 }
184
OpenMapFile()185 void OpenMapFile()
186 {
187 std::string fname;
188
189 #ifdef CARMACIZED
190 strcpy(fname, "GAMEMAPS.");
191 strcat(fname, extension);
192
193 if ((maphandle = open(fname,
194 O_RDONLY | O_BINARY, S_IREAD)) == -1)
195 {
196 CA_CannotOpen(fname);
197 }
198 #else
199 fname = ::data_dir + ::mfilename + ::extension;
200
201 ::maphandle.open(fname);
202
203 if (!::maphandle.is_open()) {
204 ::CA_CannotOpen(fname);
205 }
206 #endif
207 }
208
CloseMapFile()209 void CloseMapFile()
210 {
211 ::maphandle.close();
212 }
213
OpenAudioFile()214 void OpenAudioFile()
215 {
216 std::string fname;
217
218 #ifndef AUDIOHEADERLINKED
219 fname = ::data_dir + ::afilename + ::extension;
220
221 ::audiohandle.open(fname);
222
223 if (!::audiohandle.is_open()) {
224 ::CA_CannotOpen(fname);
225 }
226 #else
227 if ((audiohandle = open("AUDIO."EXTENSION,
228 O_RDONLY | O_BINARY, S_IREAD)) == -1)
229 {
230 CA_ERROR(SETUPAUDIO_CANT_OPEN);
231 }
232 #endif
233 }
234
CloseAudioFile()235 void CloseAudioFile()
236 {
237 ::audiohandle.close();
238 }
239
240 /*
241 ============================
242 =
243 = CAL_GetGrChunkLength
244 =
245 = Gets the length of an explicit length chunk (not tiles)
246 = The file pointer is positioned so the compressed data can be read in next.
247 =
248 ============================
249 */
CAL_GetGrChunkLength(int16_t chunk)250 void CAL_GetGrChunkLength(
251 int16_t chunk)
252 {
253 grhandle.set_position(GRFILEPOS(chunk));
254 grhandle.read(&chunkexplen, sizeof(chunkexplen));
255
256 chunkcomplen = GRFILEPOS(chunk + 1) - GRFILEPOS(chunk) - 4;
257 }
258
259
260 /*
261 ============================================================================
262
263 COMPRESSION routines, see JHUFF.C for more
264
265 ============================================================================
266 */
267
268 /*
269 ======================
270 =
271 = CAL_HuffExpand
272 =
273 = Length is the length of the EXPANDED data
274 = If screenhack, the data is decompressed in four planes directly
275 = to the screen
276 =
277 ======================
278 */
CAL_HuffExpand(uint8_t * source,uint8_t * destination,int32_t length,huffnode * hufftable)279 void CAL_HuffExpand(
280 uint8_t* source,
281 uint8_t* destination,
282 int32_t length,
283 huffnode* hufftable)
284 {
285 uint8_t val = *source++;
286 uint8_t mask = 1;
287 uint16_t nodeval;
288
289 huffnode* headptr = &hufftable[254]; // head node is always node 254
290
291 uint8_t* dst = destination;
292 uint8_t* end = dst + length;
293
294 huffnode* huffptr = headptr;
295
296 while (dst < end) {
297 if ((val & mask) == 0) {
298 nodeval = huffptr->bit0;
299 } else {
300 nodeval = huffptr->bit1;
301 }
302
303 if (mask == 0x80) {
304 val = *source++;
305 mask = 1;
306 } else {
307 mask <<= 1;
308 }
309
310 if (nodeval < 256) {
311 dst[0] = static_cast<uint8_t>(nodeval);
312 ++dst;
313 huffptr = headptr;
314 } else {
315 huffptr = &hufftable[nodeval - 256];
316 }
317 }
318 }
319
ca_huff_expand_on_screen(uint8_t * source,huffnode * hufftable)320 void ca_huff_expand_on_screen(
321 uint8_t* source,
322 huffnode* hufftable)
323 {
324 uint8_t val = *source++;
325 uint8_t mask = 1;
326 uint16_t nodeval;
327
328 huffnode* headptr = &hufftable[254]; // head node is always node 254
329 huffnode* huffptr = headptr;
330
331 for (int p = 0; p < 4; ++p) {
332 int x = p;
333 int y = 0;
334
335 while (y < ::vga_ref_height) {
336 if ((val & mask) == 0) {
337 nodeval = huffptr->bit0;
338 } else {
339 nodeval = huffptr->bit1;
340 }
341
342 if (mask == 0x80) {
343 val = *source++;
344 mask = 1;
345 } else {
346 mask <<= 1;
347 }
348
349 if (nodeval < 256) {
350 VL_Plot(x, y, static_cast<uint8_t>(nodeval));
351 huffptr = headptr;
352
353 x += 4;
354
355 if (x >= ::vga_ref_width) {
356 x = p;
357 ++y;
358 }
359 } else {
360 huffptr = &hufftable[nodeval - 256];
361 }
362 }
363 }
364 }
365
366 #ifdef CARMACIZED
367 /*
368 ======================
369 =
370 = CAL_CarmackExpand
371 =
372 = Length is the length of the EXPANDED data
373 =
374 ======================
375 */
CAL_CarmackExpand(uint16_t * source,uint16_t * dest,uint16_t length)376 void CAL_CarmackExpand(
377 uint16_t* source,
378 uint16_t* dest,
379 uint16_t length)
380 {
381 #define NEARTAG 0xa7
382 #define FARTAG 0xa8
383
384 uint16_t ch, chhigh, count, offset;
385 uint16_t* copyptr, * inptr, * outptr;
386
387 length /= 2;
388
389 inptr = source;
390 outptr = dest;
391
392 while (length) {
393 ch = *inptr++;
394 chhigh = ch >> 8;
395 if (chhigh == NEARTAG) {
396 count = ch & 0xff;
397 if (!count) { // have to insert a word containing the tag byte
398 ch |= *((uint8_t*)inptr)++;
399 *outptr++ = ch;
400 length--;
401 } else {
402 offset = *((uint8_t*)inptr)++;
403 copyptr = outptr - offset;
404 length -= count;
405 while (count--) {
406 *outptr++ = *copyptr++;
407 }
408 }
409 } else if (chhigh == FARTAG) {
410 count = ch & 0xff;
411 if (!count) { // have to insert a word containing the tag byte
412 ch |= *((uint8_t*)inptr)++;
413 *outptr++ = ch;
414 length--;
415 } else {
416 offset = *inptr++;
417 copyptr = dest + offset;
418 length -= count;
419 while (count--) {
420 *outptr++ = *copyptr++;
421 }
422 }
423 } else {
424 *outptr++ = ch;
425 length--;
426 }
427 }
428 }
429
430 #endif
431
432 /*
433 ======================
434 =
435 = CA_RLEWexpand
436 = length is EXPANDED length
437 =
438 ======================
439 */
CA_RLEWexpand(uint16_t * source,uint16_t * dest,int32_t length,uint16_t rlewtag)440 void CA_RLEWexpand(
441 uint16_t* source,
442 uint16_t* dest,
443 int32_t length,
444 uint16_t rlewtag)
445 {
446 uint16_t i;
447 uint16_t value;
448 uint16_t count;
449 const uint16_t* end = &dest[length / 2];
450
451 do {
452 value = *source++;
453
454 if (value != rlewtag) {
455 *dest++ = value;
456 } else {
457 count = *source++;
458 value = *source++;
459
460 for (i = 0; i < count; ++i) {
461 *dest++ = value;
462 }
463 }
464 } while (dest < end);
465 }
466
467 /*
468 =============================================================================
469
470 CACHE MANAGER ROUTINES
471
472 =============================================================================
473 */
474
475
476 /*
477 ======================
478 =
479 = CA_Shutdown
480 =
481 = Closes all files
482 =
483 ======================
484 */
CA_Shutdown()485 void CA_Shutdown()
486 {
487 #ifdef PROFILE
488 if (profilehandle != -1) {
489 close(profilehandle);
490 profilehandle = -1;
491 }
492 #endif
493
494 CloseMapFile();
495 CloseGrFile();
496 CloseAudioFile();
497 }
498
499 /*
500 ======================
501 =
502 = CA_Startup
503 =
504 = Open all files and load in headers
505 =
506 ======================
507 */
CA_Startup()508 void CA_Startup()
509 {
510 #ifdef PROFILE
511 unlink("PROFILE.TXT");
512 profilehandle = open("PROFILE.TXT", O_CREAT | O_WRONLY | O_TEXT);
513 #endif
514
515 CAL_SetupMapFile();
516 CAL_SetupGrFile();
517 CAL_SetupAudioFile();
518
519 mapon = -1;
520 ca_levelbit = 1;
521 ca_levelnum = 0;
522
523 ::ca_buffer.reserve(BUFFERSIZE);
524 }
525
526 /*
527 ======================
528 =
529 = CA_CacheAudioChunk
530 =
531 ======================
532 */
CA_CacheAudioChunk(int16_t chunk)533 void CA_CacheAudioChunk(
534 int16_t chunk)
535 {
536 int32_t pos;
537 int32_t compressed;
538 #ifdef AUDIOHEADERLINKED
539 int32_t expanded;
540 memptr bigbufferseg;
541 uint8_t* source;
542 #endif
543
544 if (audiosegs[chunk]) {
545 return; // allready in memory
546 }
547
548 //
549 // load the chunk into a buffer, either the miscbuffer if it fits, or allocate
550 // a larger buffer
551 //
552 pos = audiostarts[chunk];
553 compressed = audiostarts[chunk + 1] - pos;
554
555 OpenAudioFile();
556
557 audiohandle.set_position(pos);
558
559 #ifndef AUDIOHEADERLINKED
560
561 audiosegs[chunk] = new uint8_t[compressed];
562 audiohandle.read(audiosegs[chunk], compressed);
563
564 #else
565
566 if (compressed <= BUFFERSIZE) {
567 CA_FarRead(audiohandle, bufferseg, compressed);
568 source = bufferseg;
569 } else {
570 MM_GetPtr(&bigbufferseg, compressed);
571 if (mmerror) {
572 CloseAudioFile();
573 return;
574 }
575 MM_SetLock(&bigbufferseg, true);
576 CA_FarRead(audiohandle, bigbufferseg, compressed);
577 source = bigbufferseg;
578 }
579
580 expanded = *(int32_t*)source;
581 source += 4; // skip over length
582 MM_GetPtr(&(memptr)audiosegs[chunk], expanded);
583 if (mmerror) {
584 goto done;
585 }
586 CAL_HuffExpand(source, audiosegs[chunk], expanded, audiohuffman, false);
587
588 done:
589 if (compressed > BUFFERSIZE) {
590 MM_FreePtr(&bigbufferseg);
591 }
592 #endif
593
594 CloseAudioFile();
595 }
596
597 /*
598 ======================
599 =
600 = CA_LoadAllSounds
601 =
602 = Purges all sounds, then loads all new ones (mode switch)
603 =
604 ======================
605 */
CA_LoadAllSounds()606 void CA_LoadAllSounds()
607 {
608 int16_t start = 0;
609
610 if (::old_is_sound_enabled) {
611 start = STARTADLIBSOUNDS;
612 }
613
614 if (::sd_is_sound_enabled) {
615 start = STARTADLIBSOUNDS;
616 } else {
617 return;
618 }
619
620 for (auto i = 0; i < NUMSOUNDS; ++i, ++start) {
621 ::CA_CacheAudioChunk(start);
622 }
623
624 ::old_is_sound_enabled = ::sd_is_sound_enabled;
625 }
626
627 // ===========================================================================
628
629
630 /*
631 ======================
632 =
633 = CAL_ExpandGrChunk
634 =
635 = Does whatever is needed with a pointer to a compressed chunk
636 =
637 ======================
638 */
639
CAL_ExpandGrChunk(int16_t chunk,uint8_t * source)640 void CAL_ExpandGrChunk(
641 int16_t chunk,
642 uint8_t* source)
643 {
644 int32_t expanded;
645
646 if (chunk >= STARTTILE8 && chunk < STARTEXTERNS) {
647 //
648 // expanded sizes of tile8/16/32 are implicit
649 //
650
651 const int BLOCK = 64;
652 const int MASKBLOCK = 128;
653
654 if (chunk < STARTTILE8M) { // tile 8s are all in one chunk!
655 expanded = BLOCK * NUMTILE8;
656 } else if (chunk < STARTTILE16) {
657 expanded = MASKBLOCK * NUMTILE8M;
658 } else if (chunk < STARTTILE16M) { // all other tiles are one/chunk
659 expanded = BLOCK * 4;
660 } else if (chunk < STARTTILE32) {
661 expanded = MASKBLOCK * 4;
662 } else if (chunk < STARTTILE32M) {
663 expanded = BLOCK * 16;
664 } else {
665 expanded = MASKBLOCK * 16;
666 }
667 } else {
668 //
669 // everything else has an explicit size longword
670 //
671 expanded = bstone::Endian::le(*reinterpret_cast<int32_t*>(source));
672 source += 4; // skip over length
673 }
674
675 //
676 // allocate final space, decompress it, and free bigbuffer
677 // Sprites need to have shifts made and various other junk
678 //
679 grsegs[chunk] = new char[expanded];
680
681 CAL_HuffExpand(source, static_cast<uint8_t*>(grsegs[chunk]), expanded, grhuffman);
682
683 ca_gr_last_expanded_size = expanded;
684 }
685
686 /*
687 ======================
688 =
689 = CA_CacheGrChunk
690 =
691 = Makes sure a given chunk is in memory, loadiing it if needed
692 =
693 ======================
694 */
CA_CacheGrChunk(int16_t chunk)695 void CA_CacheGrChunk(
696 int16_t chunk)
697 {
698 int32_t pos, compressed;
699 uint8_t* source;
700 int16_t next;
701
702 grneeded[chunk] |= ca_levelbit; // make sure it doesn't get removed
703 if (grsegs[chunk]) {
704 return; // allready in memory
705
706 }
707 //
708 // load the chunk into a buffer, either the miscbuffer if it fits, or allocate
709 // a larger buffer
710 //
711 pos = GRFILEPOS(chunk);
712 if (pos < 0) { // $FFFFFFFF start is a sparse tile
713 return;
714 }
715
716 next = chunk + 1;
717 while (GRFILEPOS(next) == -1) { // skip past any sparse tiles
718 next++;
719 }
720
721 compressed = GRFILEPOS(next) - pos;
722
723
724 ::grhandle.set_position(pos);
725
726 ::ca_buffer.resize(compressed);
727 ::grhandle.read(::ca_buffer.data(), compressed);
728 source = ::ca_buffer.data();
729
730 ::CAL_ExpandGrChunk(chunk, source);
731 }
732
733
734 /*
735 ======================
736 =
737 = CA_CacheScreen
738 =
739 = Decompresses a chunk from disk straight onto the screen
740 =
741 ======================
742 */
CA_CacheScreen(int16_t chunk)743 void CA_CacheScreen(
744 int16_t chunk)
745 {
746 int32_t pos, compressed;
747 uint8_t* source;
748 int16_t next;
749
750
751 //
752 // load the chunk into a buffer
753 //
754 pos = GRFILEPOS(chunk);
755 next = chunk + 1;
756 while (GRFILEPOS(next) == -1) { // skip past any sparse tiles
757 next++;
758 }
759 compressed = GRFILEPOS(next) - pos;
760
761 grhandle.set_position(pos);
762
763 ::ca_buffer.resize(compressed);
764 grhandle.read(::ca_buffer.data(), compressed);
765 source = ::ca_buffer.data();
766
767 source += 4; // skip over length
768
769 //
770 // allocate final space, decompress it, and free bigbuffer
771 // Sprites need to have shifts made and various other junk
772 //
773 ca_huff_expand_on_screen(source, grhuffman);
774 }
775
776 /*
777 ======================
778 =
779 = CA_CacheMap
780 =
781 = WOLF: This is specialized for a 64*64 map size
782 =
783 ======================
784 */
CA_CacheMap(int16_t mapnum)785 void CA_CacheMap(
786 int16_t mapnum)
787 {
788 int32_t pos, compressed;
789 int16_t plane;
790 uint16_t** dest;
791 uint16_t size;
792 uint16_t* source;
793 #ifdef CARMACIZED
794 memptr buffer2seg;
795 int32_t expanded;
796 #endif
797
798 mapon = mapnum;
799
800 OpenMapFile();
801
802 // BBi
803 bstone::Sha1 map_sha1;
804 ::map_compressed_size = 0;
805
806 //
807 // load the planes into the allready allocated buffers
808 //
809 size = MAPSIZE * MAPSIZE * MAPPLANES;
810
811 for (plane = 0; plane < MAPPLANES; plane++) {
812 pos = mapheaderseg[mapnum]->planestart[plane];
813 compressed = mapheaderseg[mapnum]->planelength[plane];
814
815 dest = &mapsegs[plane];
816
817 maphandle.set_position(pos);
818 ::ca_buffer.resize(compressed);
819 source = reinterpret_cast<uint16_t*>(::ca_buffer.data());
820
821 maphandle.read(source, compressed);
822
823 // BBi
824 ::map_compressed_size += compressed;
825 map_sha1.process(source, compressed);
826
827 #ifdef CARMACIZED
828 //
829 // unhuffman, then unRLEW
830 // The huffman'd chunk has a two byte expanded length first
831 // The resulting RLEW chunk also does, even though it's not really
832 // needed
833 //
834 expanded = *source;
835 source++;
836 MM_GetPtr(&buffer2seg, expanded);
837 CAL_CarmackExpand(source, (uint16_t*)buffer2seg, expanded);
838 CA_RLEWexpand(((uint16_t*)buffer2seg) + 1, *dest, size,
839 ((mapfiletype*)tinf)->RLEWtag);
840 MM_FreePtr(&buffer2seg);
841
842 #else
843 //
844 // unRLEW, skipping expanded length
845 //
846 CA_RLEWexpand(source + 1, *dest, size,
847 rlew_tag);
848 #endif
849 }
850
851 CloseMapFile();
852
853 // BBi
854 map_sha1.finish();
855 ::map_sha1_string = map_sha1.get_digest_string();
856 }
857
858 /*
859 ======================
860 =
861 = CA_UpLevel
862 =
863 = Goes up a bit level in the needed lists and clears it out.
864 = Everything is made purgable
865 =
866 ======================
867 */
CA_UpLevel()868 void CA_UpLevel()
869 {
870 if (ca_levelnum == 7) {
871 ::Quit("Up past level 7.");
872 }
873
874 ca_levelbit <<= 1;
875 ca_levelnum++;
876 }
877
878 /*
879 ======================
880 =
881 = CA_DownLevel
882 =
883 = Goes down a bit level in the needed lists and recaches
884 = everything from the lower level
885 =
886 ======================
887 */
CA_DownLevel()888 void CA_DownLevel()
889 {
890 if (!ca_levelnum) {
891 ::Quit("Down past level 0.");
892 }
893
894 ca_levelbit >>= 1;
895 ca_levelnum--;
896 CA_CacheMarks();
897 }
898
CA_CacheMarks()899 void CA_CacheMarks()
900 {
901 const int MAXEMPTYREAD = 1024;
902
903 int16_t i;
904 int16_t next;
905 int16_t numcache;
906 int32_t pos;
907 int32_t endpos;
908 int32_t nextpos;
909 int32_t nextendpos;
910 int32_t compressed;
911 int32_t bufferstart;
912 int32_t bufferend; // file position of general buffer
913 uint8_t* source;
914
915 numcache = 0;
916 //
917 // go through and make everything not needed purgable
918 //
919 for (i = 0; i < NUMCHUNKS; i++) {
920 if (grneeded[i] & ca_levelbit) {
921 if (grsegs[i]) { // its allready in memory, make
922 } else {
923 numcache++;
924 }
925 }
926 }
927
928 if (!numcache) { // nothing to cache!
929 return;
930 }
931
932
933 //
934 // go through and load in anything still needed
935 //
936 bufferstart = bufferend = 0; // nothing good in buffer now
937
938 for (i = 0; i < NUMCHUNKS; i++) {
939 if ((grneeded[i] & ca_levelbit) && !grsegs[i]) {
940 pos = GRFILEPOS(i);
941 if (pos < 0) {
942 continue;
943 }
944
945 next = i + 1;
946 while (GRFILEPOS(next) == -1) { // skip past any sparse tiles
947 next++;
948 }
949
950 compressed = GRFILEPOS(next) - pos;
951 endpos = pos + compressed;
952
953 if (bufferstart <= pos && bufferend >= endpos) {
954 // data is allready in buffer
955 source = ::ca_buffer.data() + (pos - bufferstart);
956 } else {
957 // load buffer with a new block from disk
958 // try to get as many of the needed blocks in as possible
959 while (next < NUMCHUNKS) {
960 while (next < NUMCHUNKS &&
961 !(grneeded[next] & ca_levelbit && !grsegs[next]))
962 {
963 ++next;
964 }
965
966 if (next == NUMCHUNKS) {
967 continue;
968 }
969
970 nextpos = GRFILEPOS(next);
971
972 while (GRFILEPOS(++next) == -1) {
973 // skip past any sparse tiles
974 }
975
976 nextendpos = GRFILEPOS(next);
977
978 if ((nextpos - endpos) <= MAXEMPTYREAD
979 && (nextendpos - pos) <= BUFFERSIZE)
980 {
981 endpos = nextendpos;
982 } else {
983 next = NUMCHUNKS; // read pos to posend
984 }
985 }
986
987 grhandle.set_position(pos);
988 ::ca_buffer.resize(endpos - pos);
989 grhandle.read(::ca_buffer.data(), endpos - pos);
990 bufferstart = pos;
991 bufferend = endpos;
992 source = ::ca_buffer.data();
993 }
994
995 CAL_ExpandGrChunk(i, source);
996 }
997 }
998 }
999
CA_CannotOpen(const std::string & string)1000 void CA_CannotOpen(
1001 const std::string& string)
1002 {
1003 ::Quit("Can't open " + string + "!\n");
1004 }
1005
UNCACHEGRCHUNK(int chunk)1006 void UNCACHEGRCHUNK(
1007 int chunk)
1008 {
1009 delete [] static_cast<char*>(grsegs[chunk]);
1010 grsegs[chunk] = nullptr;
1011
1012 grneeded[chunk] &= ~ca_levelbit;
1013 }
1014
ca_load_script(int chunk_id,bool strip_xx)1015 std::string ca_load_script(
1016 int chunk_id,
1017 bool strip_xx)
1018 {
1019 ::CA_CacheGrChunk(static_cast<int16_t>(chunk_id));
1020
1021 const char* script = static_cast<const char*>(grsegs[chunk_id]);
1022
1023 int length = 0;
1024
1025 for (int i = 0; script[i] != '\x1A'; ++i) {
1026 if (script[i] == '^' && script[i + 1] == 'X' && script[i + 2] == 'X') {
1027 length = i + 3;
1028 }
1029 }
1030
1031 if (length == 0) {
1032 ::Quit("Invalid script.");
1033 }
1034
1035 if (strip_xx) {
1036 length -= 3;
1037 }
1038
1039 return std::string(script, length);
1040 }
1041
initialize_ca_constants()1042 void initialize_ca_constants()
1043 {
1044 if (::is_aog_full()) {
1045 NUM_EPISODES = 6;
1046 MAPS_PER_EPISODE = 15;
1047 MAPS_WITH_STATS = 11;
1048 } else if (::is_aog_sw()) {
1049 NUM_EPISODES = 1;
1050 MAPS_PER_EPISODE = 15;
1051 MAPS_WITH_STATS = 11;
1052 } else if (::is_ps()) {
1053 NUM_EPISODES = 1;
1054 MAPS_PER_EPISODE = 25;
1055 MAPS_WITH_STATS = 20;
1056 }
1057
1058 NUMMAPS = NUM_EPISODES * MAPS_PER_EPISODE;
1059
1060 mapheaderseg.resize(NUMMAPS);
1061 }
1062