1 
2 #include "main_sp.h"
3 #include "global.h"
4 
5 
6 /* ------------------------------------------------------------------------- */
7 /* functions for loading Supaplex level                                      */
8 /* ------------------------------------------------------------------------- */
9 
setTapeInfoToDefaults_SP()10 void setTapeInfoToDefaults_SP()
11 {
12   native_sp_level.demo.is_available = FALSE;
13   native_sp_level.demo.length = 0;
14 }
15 
setLevelInfoToDefaults_SP()16 void setLevelInfoToDefaults_SP()
17 {
18   LevelInfoType *header = &native_sp_level.header;
19   char *empty_title = "-------- EMPTY --------";
20   int i, x, y;
21 
22   native_sp_level.game_sp = &game_sp;
23 
24   native_sp_level.width  = SP_STD_PLAYFIELD_WIDTH;
25   native_sp_level.height = SP_STD_PLAYFIELD_HEIGHT;
26 
27   for (x = 0; x < native_sp_level.width; x++)
28     for (y = 0; y < native_sp_level.height; y++)
29       native_sp_level.playfield[x][y] = fiSpace;
30 
31   /* copy string (without terminating '\0' character!) */
32   for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
33     header->LevelTitle[i] = empty_title[i];
34 
35   header->InitialGravity = 0;
36   header->Version = 0;
37   header->InitialFreezeZonks = 0;
38   header->InfotronsNeeded = 0;
39   header->SpecialPortCount = 0;
40   header->SpeedByte = 0;
41   header->CheckSumByte = 0;
42   header->DemoRandomSeed = 0;
43 
44   for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
45   {
46     SpecialPortType *port = &header->SpecialPort[i];
47 
48     port->PortLocation = 0;
49     port->Gravity = 0;
50     port->FreezeZonks = 0;
51     port->FreezeEnemies = 0;
52   }
53 
54   /* set raw header bytes (used for subsequent buffer zone) to "hardware" */
55   for (i = 0; i < SP_HEADER_SIZE; i++)
56     native_sp_level.header_raw_bytes[i] = 0x20;
57 
58   setTapeInfoToDefaults_SP();
59 }
60 
copyInternalEngineVars_SP()61 void copyInternalEngineVars_SP()
62 {
63   int count;
64   int i, x, y;
65 
66   LInfo = native_sp_level.header;
67 
68   FieldWidth  = native_sp_level.width;
69   FieldHeight = native_sp_level.height;
70   HeaderSize = 96;
71 
72   FieldMax = (FieldWidth * FieldHeight) + HeaderSize - 1;
73   LevelMax = (FieldWidth * FieldHeight) - 1;
74 
75 #if 0
76   /* (add one byte for the level number stored as first byte of demo data) */
77   FileMax = FieldMax + native_sp_level.demo.length + 1;
78 #endif
79 
80 #if 0
81   PlayField8 = REDIM_1D(sizeof(byte), 0, FileMax);
82   DisPlayField = REDIM_1D(sizeof(byte), 0, FieldMax);
83   PlayField16 = REDIM_1D(sizeof(int), -game_sp.preceding_buffer_size, FieldMax);
84 #endif
85 
86   /* initialize preceding playfield buffer */
87   for (i = -game_sp.preceding_buffer_size; i < 0; i++)
88     PlayField16[i] = 0;
89 
90   /* initialize preceding playfield buffer */
91   for (i = -SP_MAX_PLAYFIELD_WIDTH; i < 0; i++)
92     PlayField8[i] = 0;
93 
94   count = 0;
95   for (i = 0; game_sp.preceding_buffer[i] != NULL; i++)
96   {
97     char *s = game_sp.preceding_buffer[i];
98     boolean hi_byte = FALSE;	/* little endian data => start with low byte */
99 
100     while (s[0] != '\0' && s[1] != '\0')
101     {
102       int hi_nibble = s[0] - (s[0] > '9' ? 'a' - 10 : '0');
103       int lo_nibble = s[1] - (s[1] > '9' ? 'a' - 10 : '0');
104       int byte = (hi_nibble << 4) | lo_nibble;
105 
106       if (hi_byte)
107 	byte <<= 8;
108 
109       PlayField16[-game_sp.preceding_buffer_size + count] |= byte;
110 
111       if (hi_byte)
112 	count++;
113 
114       hi_byte = !hi_byte;
115 
116       s += 2;
117 
118       while (*s == ' ')
119 	s++;
120     }
121   }
122 
123   count = 0;
124   for (y = 0; y < native_sp_level.height; y++)
125     for (x = 0; x < native_sp_level.width; x++)
126       PlayField8[count++] = native_sp_level.playfield[x][y];
127 
128   /* add raw header bytes to subsequent playfield buffer zone */
129   for (i = 0; i < SP_HEADER_SIZE; i++)
130     PlayField8[count++] = native_sp_level.header_raw_bytes[i];
131 
132   for (i = 0; i < count; i++)
133   {
134     PlayField16[i] = PlayField8[i];
135     DisPlayField[i] = PlayField8[i];
136     PlayField8[i] = 0;
137   }
138 
139   if (native_sp_level.demo.is_available)
140   {
141     DemoAvailable = True;
142 
143 #if 0
144     /* !!! NEVER USED !!! */
145     PlayField8[FieldMax + 1] = native_sp_level.demo.level_nr;
146 
147     /* !!! NEVER USED !!! */
148     for (i = 0; i < native_sp_level.demo.length; i++)
149       PlayField8[FieldMax + 2 + i] = native_sp_level.demo.data[i];
150 #endif
151   }
152 
153 #if 0
154   AnimationPosTable = REDIM_1D(sizeof(int), 0, LevelMax - 2 * FieldWidth);
155   AnimationSubTable = REDIM_1D(sizeof(byte), 0, LevelMax - 2 * FieldWidth);
156   TerminalState = REDIM_1D(sizeof(byte), 0, FieldMax);
157 #endif
158 
159   GravityFlag = LInfo.InitialGravity;
160   FreezeZonks = LInfo.InitialFreezeZonks;
161 
162 #if 1
163   /* this is set by main game tape code to native random generator directly */
164 #else
165   RandomSeed = LInfo.DemoRandomSeed;
166 #endif
167 
168   LevelLoaded = True;
169 }
170 
LoadNativeLevelFromFileStream_SP(FILE * file,int width,int height,boolean demo_available)171 static void LoadNativeLevelFromFileStream_SP(FILE *file, int width, int height,
172 					     boolean demo_available)
173 {
174   LevelInfoType *header = &native_sp_level.header;
175   int i, x, y;
176 
177   /* for details of the Supaplex level format, see Herman Perk's Supaplex
178      documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
179 
180   native_sp_level.width  = MIN(width,  SP_MAX_PLAYFIELD_WIDTH);
181   native_sp_level.height = MIN(height, SP_MAX_PLAYFIELD_HEIGHT);
182 
183   /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
184   /* (MPX levels may have non-standard playfield size -- check max. size) */
185   for (y = 0; y < height; y++)
186   {
187     for (x = 0; x < width; x++)
188     {
189       byte element = getFile8Bit(file);
190 
191       if (x < SP_MAX_PLAYFIELD_WIDTH &&
192 	  y < SP_MAX_PLAYFIELD_HEIGHT)
193 	native_sp_level.playfield[x][y] = element;
194     }
195   }
196 
197   /* read level header (96 bytes) */
198 
199   ReadUnusedBytesFromFile(file, 4);	/* (not used by Supaplex engine) */
200 
201   /* initial gravity: 1 == "on", anything else (0) == "off" */
202   header->InitialGravity = getFile8Bit(file);
203 
204   /* SpeedFixVersion XOR 0x20 */
205   header->Version = getFile8Bit(file);
206 
207   /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
208   for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
209     header->LevelTitle[i] = getFile8Bit(file);
210 
211   /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
212   header->InitialFreezeZonks = getFile8Bit(file);
213 
214   /* number of infotrons needed; 0 means that Supaplex will count the total
215      amount of infotrons in the level and use the low byte of that number
216      (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
217   header->InfotronsNeeded = getFile8Bit(file);
218 
219   /* number of special ("gravity") port entries below (maximum 10 allowed) */
220   header->SpecialPortCount = getFile8Bit(file);
221 
222   /* database of properties of up to 10 special ports (6 bytes per port) */
223   for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
224   {
225     SpecialPortType *port = &header->SpecialPort[i];
226 
227     /* high and low byte of the location of a special port; if (x, y) are the
228        coordinates of a port in the field and (0, 0) is the top-left corner,
229        the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
230        of what may be expected: Supaplex works with a game field in memory
231        which is 2 bytes per tile) */
232     port->PortLocation = getFile16BitBE(file);		/* yes, big endian */
233 
234     /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
235     port->Gravity = getFile8Bit(file);
236 
237     /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
238     port->FreezeZonks = getFile8Bit(file);
239 
240     /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
241     port->FreezeEnemies = getFile8Bit(file);
242 
243     ReadUnusedBytesFromFile(file, 1);	/* (not used by Supaplex engine) */
244   }
245 
246   /* SpeedByte XOR Highbyte(RandomSeed) */
247   header->SpeedByte = getFile8Bit(file);
248 
249   /* CheckSum XOR SpeedByte */
250   header->CheckSumByte = getFile8Bit(file);
251 
252   /* random seed used for recorded demos */
253   header->DemoRandomSeed = getFile16BitLE(file);	/* yes, little endian */
254 
255   /* auto-determine number of infotrons if it was stored as "0" -- see above */
256   if (header->InfotronsNeeded == 0)
257   {
258     for (x = 0; x < native_sp_level.width; x++)
259       for (y = 0; y < native_sp_level.height; y++)
260 	if (native_sp_level.playfield[x][y] == fiInfotron)
261 	  header->InfotronsNeeded++;
262 
263     header->InfotronsNeeded &= 0xff;	/* only use low byte -- see above */
264   }
265 
266   /* read raw level header bytes (96 bytes) */
267 
268   fseek(file, -(SP_HEADER_SIZE), SEEK_CUR);	/* rewind file */
269   for (i = 0; i < SP_HEADER_SIZE; i++)
270     native_sp_level.header_raw_bytes[i] = fgetc(file);
271 
272   /* also load demo tape, if available (only in single level files) */
273 
274   if (demo_available)
275   {
276     int level_nr = getFile8Bit(file);
277 
278     level_nr &= 0x7f;			/* clear highest bit */
279     level_nr = (level_nr < 1   ? 1   :
280 		level_nr > 111 ? 111 : level_nr);
281 
282     native_sp_level.demo.level_nr = level_nr;
283 
284     for (i = 0; i < SP_MAX_TAPE_LEN && !feof(file); i++)
285     {
286       native_sp_level.demo.data[i] = getFile8Bit(file);
287 
288       if (native_sp_level.demo.data[i] == 0xff)	/* "end of demo" byte */
289       {
290 	i++;
291 
292 	break;
293       }
294     }
295 
296     native_sp_level.demo.length = i;
297     native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
298   }
299 }
300 
LoadNativeLevel_SP(char * filename,int level_pos,boolean level_info_only)301 boolean LoadNativeLevel_SP(char *filename, int level_pos,
302 			   boolean level_info_only)
303 {
304   FILE *file;
305   int i, l, x, y;
306   char name_first, name_last;
307   struct LevelInfo_SP multipart_level;
308   int multipart_xpos, multipart_ypos;
309   boolean is_multipart_level;
310   boolean is_first_part;
311   boolean reading_multipart_level = FALSE;
312   boolean use_empty_level = FALSE;
313   LevelInfoType *header = &native_sp_level.header;
314   boolean is_single_level_file = (strSuffixLower(filename, ".sp") ||
315 				  strSuffixLower(filename, ".mpx"));
316   boolean demo_available = is_single_level_file;
317   boolean is_mpx_file = strSuffixLower(filename, ".mpx");
318   int file_seek_pos = level_pos * SP_STD_LEVEL_SIZE;
319   int level_width  = SP_STD_PLAYFIELD_WIDTH;
320   int level_height = SP_STD_PLAYFIELD_HEIGHT;
321 
322   /* always start with reliable default values */
323   setLevelInfoToDefaults_SP();
324   copyInternalEngineVars_SP();
325 
326   if (!(file = fopen(filename, MODE_READ)))
327   {
328     if (!level_info_only)
329       Error(ERR_WARN, "cannot open file '%s' -- using empty level", filename);
330 
331     return FALSE;
332   }
333 
334   if (is_mpx_file)
335   {
336     char mpx_chunk_name[4 + 1];
337     int mpx_version;
338     int mpx_level_count;
339     LevelDescriptor *mpx_level_desc;
340 
341     getFileChunkBE(file, mpx_chunk_name, NULL);
342 
343     if (!strEqual(mpx_chunk_name, "MPX "))
344     {
345       Error(ERR_WARN, "cannot find MPX ID in file '%s' -- using empty level",
346 	    filename);
347 
348       return FALSE;
349     }
350 
351     mpx_version = getFile16BitLE(file);
352 
353     if (mpx_version != 1)
354     {
355       Error(ERR_WARN, "unknown MPX version in file '%s' -- using empty level",
356 	    filename);
357 
358       return FALSE;
359     }
360 
361     mpx_level_count = getFile16BitLE(file);
362 
363     if (mpx_level_count < 1)
364     {
365       Error(ERR_WARN, "no MPX levels found in file '%s' -- using empty level",
366 	    filename);
367 
368       return FALSE;
369     }
370 
371     if (level_pos >= mpx_level_count)
372     {
373       Error(ERR_WARN, "MPX level not found in file '%s' -- using empty level",
374 	    filename);
375 
376       return FALSE;
377     }
378 
379     mpx_level_desc = checked_calloc(mpx_level_count * sizeof(LevelDescriptor));
380 
381     for (i = 0; i < mpx_level_count; i++)
382     {
383       LevelDescriptor *ldesc = &mpx_level_desc[i];
384 
385       ldesc->Width  = getFile16BitLE(file);
386       ldesc->Height = getFile16BitLE(file);
387       ldesc->OffSet = getFile32BitLE(file);	/* starts with 1, not with 0 */
388       ldesc->Size   = getFile32BitLE(file);
389     }
390 
391     level_width  = mpx_level_desc[level_pos].Width;
392     level_height = mpx_level_desc[level_pos].Height;
393 
394     file_seek_pos = mpx_level_desc[level_pos].OffSet - 1;
395   }
396 
397   /* position file stream to the requested level (in case of level package) */
398   if (fseek(file, file_seek_pos, SEEK_SET) != 0)
399   {
400     Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
401 
402     return FALSE;
403   }
404 
405   /* there exist Supaplex level package files with multi-part levels which
406      can be detected as follows: instead of leading and trailing dashes ('-')
407      to pad the level name, they have leading and trailing numbers which are
408      the x and y coordinations of the current part of the multi-part level;
409      if there are '?' characters instead of numbers on the left or right side
410      of the level name, the multi-part level consists of only horizontal or
411      vertical parts */
412 
413   for (l = level_pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
414   {
415     LoadNativeLevelFromFileStream_SP(file, level_width, level_height,
416 				     demo_available);
417 
418     /* check if this level is a part of a bigger multi-part level */
419 
420     if (is_single_level_file)
421       break;
422 
423     name_first = header->LevelTitle[0];
424     name_last  = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
425 
426     is_multipart_level =
427       ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
428        (name_last  == '?' || (name_last  >= '0' && name_last  <= '9')));
429 
430     is_first_part =
431       ((name_first == '?' || name_first == '1') &&
432        (name_last  == '?' || name_last  == '1'));
433 
434     if (is_multipart_level)
435     {
436       /* correct leading multipart level meta information in level name */
437       for (i = 0;
438 	   i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first;
439 	   i++)
440 	header->LevelTitle[i] = '-';
441 
442       /* correct trailing multipart level meta information in level name */
443       for (i = SP_LEVEL_NAME_LEN - 1;
444 	   i >= 0 && header->LevelTitle[i] == name_last;
445 	   i--)
446 	header->LevelTitle[i] = '-';
447     }
448 
449     /* ---------- check for normal single level ---------- */
450 
451     if (!reading_multipart_level && !is_multipart_level)
452     {
453       /* the current level is simply a normal single-part level, and we are
454 	 not reading a multi-part level yet, so return the level as it is */
455 
456       break;
457     }
458 
459     /* ---------- check for empty level (unused multi-part) ---------- */
460 
461     if (!reading_multipart_level && is_multipart_level && !is_first_part)
462     {
463       /* this is a part of a multi-part level, but not the first part
464 	 (and we are not already reading parts of a multi-part level);
465 	 in this case, use an empty level instead of the single part */
466 
467       use_empty_level = TRUE;
468 
469       break;
470     }
471 
472     /* ---------- check for finished multi-part level ---------- */
473 
474     if (reading_multipart_level &&
475 	(!is_multipart_level ||
476 	 !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
477 		    SP_LEVEL_NAME_LEN)))
478     {
479       /* we are already reading parts of a multi-part level, but this level is
480 	 either not a multi-part level, or a part of a different multi-part
481 	 level; in both cases, the multi-part level seems to be complete */
482 
483       break;
484     }
485 
486     /* ---------- here we have one part of a multi-part level ---------- */
487 
488     reading_multipart_level = TRUE;
489 
490     if (is_first_part)	/* start with first part of new multi-part level */
491     {
492       /* copy level info structure from first part */
493       multipart_level = native_sp_level;
494 
495       /* clear playfield of new multi-part level */
496       for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
497 	for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
498 	  multipart_level.playfield[x][y] = fiSpace;
499     }
500 
501     if (name_first == '?')
502       name_first = '1';
503     if (name_last == '?')
504       name_last = '1';
505 
506     multipart_xpos = (int)(name_first - '0');
507     multipart_ypos = (int)(name_last  - '0');
508 
509 #if 0
510     printf("----------> part (%d/%d) of multi-part level '%s'\n",
511 	   multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
512 #endif
513 
514     if (multipart_xpos * SP_STD_PLAYFIELD_WIDTH  > SP_MAX_PLAYFIELD_WIDTH ||
515 	multipart_ypos * SP_STD_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
516     {
517       Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
518 
519       break;
520     }
521 
522     multipart_level.width  = MAX(multipart_level.width,
523 				 multipart_xpos * SP_STD_PLAYFIELD_WIDTH);
524     multipart_level.height = MAX(multipart_level.height,
525 				 multipart_ypos * SP_STD_PLAYFIELD_HEIGHT);
526 
527     /* copy level part at the right position of multi-part level */
528     for (x = 0; x < SP_STD_PLAYFIELD_WIDTH; x++)
529     {
530       for (y = 0; y < SP_STD_PLAYFIELD_HEIGHT; y++)
531       {
532 	int start_x = (multipart_xpos - 1) * SP_STD_PLAYFIELD_WIDTH;
533 	int start_y = (multipart_ypos - 1) * SP_STD_PLAYFIELD_HEIGHT;
534 
535 	multipart_level.playfield[start_x + x][start_y + y] =
536 	  native_sp_level.playfield[x][y];
537       }
538     }
539   }
540 
541   fclose(file);
542 
543   if (use_empty_level)
544   {
545     setLevelInfoToDefaults_SP();
546 
547     Error(ERR_WARN, "single part of multi-part level -- using empty level");
548   }
549 
550   if (reading_multipart_level)
551     native_sp_level = multipart_level;
552 
553   copyInternalEngineVars_SP();
554 
555   return TRUE;
556 }
557 
SaveNativeLevel_SP(char * filename)558 void SaveNativeLevel_SP(char *filename)
559 {
560   LevelInfoType *header = &native_sp_level.header;
561   FILE *file;
562   int i, x, y;
563 
564   if (!(file = fopen(filename, MODE_WRITE)))
565   {
566     Error(ERR_WARN, "cannot save native level file '%s'", filename);
567 
568     return;
569   }
570 
571   /* write level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
572   for (y = 0; y < native_sp_level.height; y++)
573     for (x = 0; x < native_sp_level.width; x++)
574       putFile8Bit(file, native_sp_level.playfield[x][y]);
575 
576   /* write level header (96 bytes) */
577 
578   WriteUnusedBytesToFile(file, 4);
579 
580   putFile8Bit(file, header->InitialGravity);
581   putFile8Bit(file, header->Version);
582 
583   for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
584     putFile8Bit(file, header->LevelTitle[i]);
585 
586   putFile8Bit(file, header->InitialFreezeZonks);
587   putFile8Bit(file, header->InfotronsNeeded);
588   putFile8Bit(file, header->SpecialPortCount);
589 
590   for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
591   {
592     SpecialPortType *port = &header->SpecialPort[i];
593 
594     putFile16BitBE(file, port->PortLocation);
595     putFile8Bit(file, port->Gravity);
596     putFile8Bit(file, port->FreezeZonks);
597     putFile8Bit(file, port->FreezeEnemies);
598 
599     WriteUnusedBytesToFile(file, 1);
600   }
601 
602   putFile8Bit(file, header->SpeedByte);
603   putFile8Bit(file, header->CheckSumByte);
604   putFile16BitLE(file, header->DemoRandomSeed);
605 
606   /* also save demo tape, if available */
607 
608   if (native_sp_level.demo.is_available)
609   {
610     putFile8Bit(file, native_sp_level.demo.level_nr);
611 
612     for (i = 0; i < native_sp_level.demo.length; i++)
613       putFile8Bit(file, native_sp_level.demo.data[i]);
614   }
615 
616   fclose(file);
617 }
618