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