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