rnd-20131217-1-src
[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 #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
171 #if 1
172
173 static void LoadNativeLevelFromFileStream_SP(File *file, int width, int height,
174                                              boolean demo_available)
175 {
176   LevelInfoType *header = &native_sp_level.header;
177   int i, x, y;
178
179   /* for details of the Supaplex level format, see Herman Perk's Supaplex
180      documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
181
182   native_sp_level.width  = MIN(width,  SP_MAX_PLAYFIELD_WIDTH);
183   native_sp_level.height = MIN(height, SP_MAX_PLAYFIELD_HEIGHT);
184
185   /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
186   /* (MPX levels may have non-standard playfield size -- check max. size) */
187   for (y = 0; y < height; y++)
188   {
189     for (x = 0; x < width; x++)
190     {
191       byte element = getFile8Bit(file);
192
193       if (x < SP_MAX_PLAYFIELD_WIDTH &&
194           y < SP_MAX_PLAYFIELD_HEIGHT)
195         native_sp_level.playfield[x][y] = element;
196     }
197   }
198
199   /* read level header (96 bytes) */
200
201   ReadUnusedBytesFromFile(file, 4);     /* (not used by Supaplex engine) */
202
203   /* initial gravity: 1 == "on", anything else (0) == "off" */
204   header->InitialGravity = getFile8Bit(file);
205
206   /* SpeedFixVersion XOR 0x20 */
207   header->Version = getFile8Bit(file);
208
209   /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
210   for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
211     header->LevelTitle[i] = getFile8Bit(file);
212
213   /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
214   header->InitialFreezeZonks = getFile8Bit(file);
215
216   /* number of infotrons needed; 0 means that Supaplex will count the total
217      amount of infotrons in the level and use the low byte of that number
218      (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
219   header->InfotronsNeeded = getFile8Bit(file);
220
221   /* number of special ("gravity") port entries below (maximum 10 allowed) */
222   header->SpecialPortCount = getFile8Bit(file);
223
224   /* database of properties of up to 10 special ports (6 bytes per port) */
225   for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
226   {
227     SpecialPortType *port = &header->SpecialPort[i];
228
229     /* high and low byte of the location of a special port; if (x, y) are the
230        coordinates of a port in the field and (0, 0) is the top-left corner,
231        the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
232        of what may be expected: Supaplex works with a game field in memory
233        which is 2 bytes per tile) */
234     port->PortLocation = getFile16BitBE(file);          /* yes, big endian */
235
236     /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
237     port->Gravity = getFile8Bit(file);
238
239     /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
240     port->FreezeZonks = getFile8Bit(file);
241
242     /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
243     port->FreezeEnemies = getFile8Bit(file);
244
245     ReadUnusedBytesFromFile(file, 1);   /* (not used by Supaplex engine) */
246   }
247
248   /* SpeedByte XOR Highbyte(RandomSeed) */
249   header->SpeedByte = getFile8Bit(file);
250
251   /* CheckSum XOR SpeedByte */
252   header->CheckSumByte = getFile8Bit(file);
253
254   /* random seed used for recorded demos */
255   header->DemoRandomSeed = getFile16BitLE(file);        /* yes, little endian */
256
257   /* auto-determine number of infotrons if it was stored as "0" -- see above */
258   if (header->InfotronsNeeded == 0)
259   {
260     for (x = 0; x < native_sp_level.width; x++)
261       for (y = 0; y < native_sp_level.height; y++)
262         if (native_sp_level.playfield[x][y] == fiInfotron)
263           header->InfotronsNeeded++;
264
265     header->InfotronsNeeded &= 0xff;    /* only use low byte -- see above */
266   }
267
268   /* read raw level header bytes (96 bytes) */
269
270   seekFile(file, -(SP_HEADER_SIZE), SEEK_CUR);  /* rewind file */
271   for (i = 0; i < SP_HEADER_SIZE; i++)
272     native_sp_level.header_raw_bytes[i] = getByteFromFile(file);
273
274   /* also load demo tape, if available (only in single level files) */
275
276   if (demo_available)
277   {
278     int level_nr = getFile8Bit(file);
279
280     level_nr &= 0x7f;                   /* clear highest bit */
281     level_nr = (level_nr < 1   ? 1   :
282                 level_nr > 111 ? 111 : level_nr);
283
284     native_sp_level.demo.level_nr = level_nr;
285
286     for (i = 0; i < SP_MAX_TAPE_LEN && !checkEndOfFile(file); i++)
287     {
288       native_sp_level.demo.data[i] = getFile8Bit(file);
289
290       if (native_sp_level.demo.data[i] == 0xff) /* "end of demo" byte */
291       {
292         i++;
293
294         break;
295       }
296     }
297
298     native_sp_level.demo.length = i;
299     native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
300   }
301 }
302
303 #else
304
305 static void LoadNativeLevelFromFileStream_SP(FILE *file, int width, int height,
306                                              boolean demo_available)
307 {
308   LevelInfoType *header = &native_sp_level.header;
309   int i, x, y;
310
311   /* for details of the Supaplex level format, see Herman Perk's Supaplex
312      documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
313
314   native_sp_level.width  = MIN(width,  SP_MAX_PLAYFIELD_WIDTH);
315   native_sp_level.height = MIN(height, SP_MAX_PLAYFIELD_HEIGHT);
316
317   /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
318   /* (MPX levels may have non-standard playfield size -- check max. size) */
319   for (y = 0; y < height; y++)
320   {
321     for (x = 0; x < width; x++)
322     {
323       byte element = getFile8Bit(file);
324
325       if (x < SP_MAX_PLAYFIELD_WIDTH &&
326           y < SP_MAX_PLAYFIELD_HEIGHT)
327         native_sp_level.playfield[x][y] = element;
328     }
329   }
330
331   /* read level header (96 bytes) */
332
333   ReadUnusedBytesFromFile(file, 4);     /* (not used by Supaplex engine) */
334
335   /* initial gravity: 1 == "on", anything else (0) == "off" */
336   header->InitialGravity = getFile8Bit(file);
337
338   /* SpeedFixVersion XOR 0x20 */
339   header->Version = getFile8Bit(file);
340
341   /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
342   for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
343     header->LevelTitle[i] = getFile8Bit(file);
344
345   /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
346   header->InitialFreezeZonks = getFile8Bit(file);
347
348   /* number of infotrons needed; 0 means that Supaplex will count the total
349      amount of infotrons in the level and use the low byte of that number
350      (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
351   header->InfotronsNeeded = getFile8Bit(file);
352
353   /* number of special ("gravity") port entries below (maximum 10 allowed) */
354   header->SpecialPortCount = getFile8Bit(file);
355
356   /* database of properties of up to 10 special ports (6 bytes per port) */
357   for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
358   {
359     SpecialPortType *port = &header->SpecialPort[i];
360
361     /* high and low byte of the location of a special port; if (x, y) are the
362        coordinates of a port in the field and (0, 0) is the top-left corner,
363        the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
364        of what may be expected: Supaplex works with a game field in memory
365        which is 2 bytes per tile) */
366     port->PortLocation = getFile16BitBE(file);          /* yes, big endian */
367
368     /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
369     port->Gravity = getFile8Bit(file);
370
371     /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
372     port->FreezeZonks = getFile8Bit(file);
373
374     /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
375     port->FreezeEnemies = getFile8Bit(file);
376
377     ReadUnusedBytesFromFile(file, 1);   /* (not used by Supaplex engine) */
378   }
379
380   /* SpeedByte XOR Highbyte(RandomSeed) */
381   header->SpeedByte = getFile8Bit(file);
382
383   /* CheckSum XOR SpeedByte */
384   header->CheckSumByte = getFile8Bit(file);
385
386   /* random seed used for recorded demos */
387   header->DemoRandomSeed = getFile16BitLE(file);        /* yes, little endian */
388
389   /* auto-determine number of infotrons if it was stored as "0" -- see above */
390   if (header->InfotronsNeeded == 0)
391   {
392     for (x = 0; x < native_sp_level.width; x++)
393       for (y = 0; y < native_sp_level.height; y++)
394         if (native_sp_level.playfield[x][y] == fiInfotron)
395           header->InfotronsNeeded++;
396
397     header->InfotronsNeeded &= 0xff;    /* only use low byte -- see above */
398   }
399
400   /* read raw level header bytes (96 bytes) */
401
402   fseek(file, -(SP_HEADER_SIZE), SEEK_CUR);     /* rewind file */
403   for (i = 0; i < SP_HEADER_SIZE; i++)
404     native_sp_level.header_raw_bytes[i] = fgetc(file);
405
406   /* also load demo tape, if available (only in single level files) */
407
408   if (demo_available)
409   {
410     int level_nr = getFile8Bit(file);
411
412     level_nr &= 0x7f;                   /* clear highest bit */
413     level_nr = (level_nr < 1   ? 1   :
414                 level_nr > 111 ? 111 : level_nr);
415
416     native_sp_level.demo.level_nr = level_nr;
417
418     for (i = 0; i < SP_MAX_TAPE_LEN && !feof(file); i++)
419     {
420       native_sp_level.demo.data[i] = getFile8Bit(file);
421
422       if (native_sp_level.demo.data[i] == 0xff) /* "end of demo" byte */
423       {
424         i++;
425
426         break;
427       }
428     }
429
430     native_sp_level.demo.length = i;
431     native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
432   }
433 }
434
435 #endif
436
437 #if 1
438
439 boolean LoadNativeLevel_SP(char *filename, int level_pos,
440                            boolean level_info_only)
441 {
442   File *file;
443   int i, l, x, y;
444   char name_first, name_last;
445   struct LevelInfo_SP multipart_level;
446   int multipart_xpos, multipart_ypos;
447   boolean is_multipart_level;
448   boolean is_first_part;
449   boolean reading_multipart_level = FALSE;
450   boolean use_empty_level = FALSE;
451   LevelInfoType *header = &native_sp_level.header;
452   boolean is_single_level_file = (strSuffixLower(filename, ".sp") ||
453                                   strSuffixLower(filename, ".mpx"));
454   boolean demo_available = is_single_level_file;
455   boolean is_mpx_file = strSuffixLower(filename, ".mpx");
456   int file_seek_pos = level_pos * SP_STD_LEVEL_SIZE;
457   int level_width  = SP_STD_PLAYFIELD_WIDTH;
458   int level_height = SP_STD_PLAYFIELD_HEIGHT;
459
460   /* always start with reliable default values */
461   setLevelInfoToDefaults_SP();
462   copyInternalEngineVars_SP();
463
464   if (!(file = openFile(filename, MODE_READ)))
465   {
466     if (!level_info_only)
467       Error(ERR_WARN, "cannot open file '%s' -- using empty level", filename);
468
469     return FALSE;
470   }
471
472   if (is_mpx_file)
473   {
474     char mpx_chunk_name[4 + 1];
475     int mpx_version;
476     int mpx_level_count;
477     LevelDescriptor *mpx_level_desc;
478
479     getFileChunkBE(file, mpx_chunk_name, NULL);
480
481     if (!strEqual(mpx_chunk_name, "MPX "))
482     {
483       Error(ERR_WARN, "cannot find MPX ID in file '%s' -- using empty level",
484             filename);
485
486       return FALSE;
487     }
488
489     mpx_version = getFile16BitLE(file);
490
491     if (mpx_version != 1)
492     {
493       Error(ERR_WARN, "unknown MPX version in file '%s' -- using empty level",
494             filename);
495
496       return FALSE;
497     }
498
499     mpx_level_count = getFile16BitLE(file);
500
501     if (mpx_level_count < 1)
502     {
503       Error(ERR_WARN, "no MPX levels found in file '%s' -- using empty level",
504             filename);
505
506       return FALSE;
507     }
508
509     if (level_pos >= mpx_level_count)
510     {
511       Error(ERR_WARN, "MPX level not found in file '%s' -- using empty level",
512             filename);
513
514       return FALSE;
515     }
516
517     mpx_level_desc = checked_calloc(mpx_level_count * sizeof(LevelDescriptor));
518
519     for (i = 0; i < mpx_level_count; i++)
520     {
521       LevelDescriptor *ldesc = &mpx_level_desc[i];
522
523       ldesc->Width  = getFile16BitLE(file);
524       ldesc->Height = getFile16BitLE(file);
525       ldesc->OffSet = getFile32BitLE(file);     /* starts with 1, not with 0 */
526       ldesc->Size   = getFile32BitLE(file);
527     }
528
529     level_width  = mpx_level_desc[level_pos].Width;
530     level_height = mpx_level_desc[level_pos].Height;
531
532     file_seek_pos = mpx_level_desc[level_pos].OffSet - 1;
533   }
534
535   /* position file stream to the requested level (in case of level package) */
536   if (seekFile(file, file_seek_pos, SEEK_SET) != 0)
537   {
538     Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
539
540     return FALSE;
541   }
542
543   /* there exist Supaplex level package files with multi-part levels which
544      can be detected as follows: instead of leading and trailing dashes ('-')
545      to pad the level name, they have leading and trailing numbers which are
546      the x and y coordinations of the current part of the multi-part level;
547      if there are '?' characters instead of numbers on the left or right side
548      of the level name, the multi-part level consists of only horizontal or
549      vertical parts */
550
551   for (l = level_pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
552   {
553     LoadNativeLevelFromFileStream_SP(file, level_width, level_height,
554                                      demo_available);
555
556     /* check if this level is a part of a bigger multi-part level */
557
558     if (is_single_level_file)
559       break;
560
561     name_first = header->LevelTitle[0];
562     name_last  = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
563
564     is_multipart_level =
565       ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
566        (name_last  == '?' || (name_last  >= '0' && name_last  <= '9')));
567
568     is_first_part =
569       ((name_first == '?' || name_first == '1') &&
570        (name_last  == '?' || name_last  == '1'));
571
572     if (is_multipart_level)
573     {
574       /* correct leading multipart level meta information in level name */
575       for (i = 0;
576            i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first;
577            i++)
578         header->LevelTitle[i] = '-';
579
580       /* correct trailing multipart level meta information in level name */
581       for (i = SP_LEVEL_NAME_LEN - 1;
582            i >= 0 && header->LevelTitle[i] == name_last;
583            i--)
584         header->LevelTitle[i] = '-';
585     }
586
587     /* ---------- check for normal single level ---------- */
588
589     if (!reading_multipart_level && !is_multipart_level)
590     {
591       /* the current level is simply a normal single-part level, and we are
592          not reading a multi-part level yet, so return the level as it is */
593
594       break;
595     }
596
597     /* ---------- check for empty level (unused multi-part) ---------- */
598
599     if (!reading_multipart_level && is_multipart_level && !is_first_part)
600     {
601       /* this is a part of a multi-part level, but not the first part
602          (and we are not already reading parts of a multi-part level);
603          in this case, use an empty level instead of the single part */
604
605       use_empty_level = TRUE;
606
607       break;
608     }
609
610     /* ---------- check for finished multi-part level ---------- */
611
612     if (reading_multipart_level &&
613         (!is_multipart_level ||
614          !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
615                     SP_LEVEL_NAME_LEN)))
616     {
617       /* we are already reading parts of a multi-part level, but this level is
618          either not a multi-part level, or a part of a different multi-part
619          level; in both cases, the multi-part level seems to be complete */
620
621       break;
622     }
623
624     /* ---------- here we have one part of a multi-part level ---------- */
625
626     reading_multipart_level = TRUE;
627
628     if (is_first_part)  /* start with first part of new multi-part level */
629     {
630       /* copy level info structure from first part */
631       multipart_level = native_sp_level;
632
633       /* clear playfield of new multi-part level */
634       for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
635         for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
636           multipart_level.playfield[x][y] = fiSpace;
637     }
638
639     if (name_first == '?')
640       name_first = '1';
641     if (name_last == '?')
642       name_last = '1';
643
644     multipart_xpos = (int)(name_first - '0');
645     multipart_ypos = (int)(name_last  - '0');
646
647 #if 0
648     printf("----------> part (%d/%d) of multi-part level '%s'\n",
649            multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
650 #endif
651
652     if (multipart_xpos * SP_STD_PLAYFIELD_WIDTH  > SP_MAX_PLAYFIELD_WIDTH ||
653         multipart_ypos * SP_STD_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
654     {
655       Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
656
657       break;
658     }
659
660     multipart_level.width  = MAX(multipart_level.width,
661                                  multipart_xpos * SP_STD_PLAYFIELD_WIDTH);
662     multipart_level.height = MAX(multipart_level.height,
663                                  multipart_ypos * SP_STD_PLAYFIELD_HEIGHT);
664
665     /* copy level part at the right position of multi-part level */
666     for (x = 0; x < SP_STD_PLAYFIELD_WIDTH; x++)
667     {
668       for (y = 0; y < SP_STD_PLAYFIELD_HEIGHT; y++)
669       {
670         int start_x = (multipart_xpos - 1) * SP_STD_PLAYFIELD_WIDTH;
671         int start_y = (multipart_ypos - 1) * SP_STD_PLAYFIELD_HEIGHT;
672
673         multipart_level.playfield[start_x + x][start_y + y] =
674           native_sp_level.playfield[x][y];
675       }
676     }
677   }
678
679   closeFile(file);
680
681   if (use_empty_level)
682   {
683     setLevelInfoToDefaults_SP();
684
685     Error(ERR_WARN, "single part of multi-part level -- using empty level");
686   }
687
688   if (reading_multipart_level)
689     native_sp_level = multipart_level;
690
691   copyInternalEngineVars_SP();
692
693   return TRUE;
694 }
695
696 #else
697
698 boolean LoadNativeLevel_SP(char *filename, int level_pos,
699                            boolean level_info_only)
700 {
701   FILE *file;
702   int i, l, x, y;
703   char name_first, name_last;
704   struct LevelInfo_SP multipart_level;
705   int multipart_xpos, multipart_ypos;
706   boolean is_multipart_level;
707   boolean is_first_part;
708   boolean reading_multipart_level = FALSE;
709   boolean use_empty_level = FALSE;
710   LevelInfoType *header = &native_sp_level.header;
711   boolean is_single_level_file = (strSuffixLower(filename, ".sp") ||
712                                   strSuffixLower(filename, ".mpx"));
713   boolean demo_available = is_single_level_file;
714   boolean is_mpx_file = strSuffixLower(filename, ".mpx");
715   int file_seek_pos = level_pos * SP_STD_LEVEL_SIZE;
716   int level_width  = SP_STD_PLAYFIELD_WIDTH;
717   int level_height = SP_STD_PLAYFIELD_HEIGHT;
718
719   /* always start with reliable default values */
720   setLevelInfoToDefaults_SP();
721   copyInternalEngineVars_SP();
722
723   if (!(file = fopen(filename, MODE_READ)))
724   {
725     if (!level_info_only)
726       Error(ERR_WARN, "cannot open file '%s' -- using empty level", filename);
727
728     return FALSE;
729   }
730
731   if (is_mpx_file)
732   {
733     char mpx_chunk_name[4 + 1];
734     int mpx_version;
735     int mpx_level_count;
736     LevelDescriptor *mpx_level_desc;
737
738     getFileChunkBE(file, mpx_chunk_name, NULL);
739
740     if (!strEqual(mpx_chunk_name, "MPX "))
741     {
742       Error(ERR_WARN, "cannot find MPX ID in file '%s' -- using empty level",
743             filename);
744
745       return FALSE;
746     }
747
748     mpx_version = getFile16BitLE(file);
749
750     if (mpx_version != 1)
751     {
752       Error(ERR_WARN, "unknown MPX version in file '%s' -- using empty level",
753             filename);
754
755       return FALSE;
756     }
757
758     mpx_level_count = getFile16BitLE(file);
759
760     if (mpx_level_count < 1)
761     {
762       Error(ERR_WARN, "no MPX levels found in file '%s' -- using empty level",
763             filename);
764
765       return FALSE;
766     }
767
768     if (level_pos >= mpx_level_count)
769     {
770       Error(ERR_WARN, "MPX level not found in file '%s' -- using empty level",
771             filename);
772
773       return FALSE;
774     }
775
776     mpx_level_desc = checked_calloc(mpx_level_count * sizeof(LevelDescriptor));
777
778     for (i = 0; i < mpx_level_count; i++)
779     {
780       LevelDescriptor *ldesc = &mpx_level_desc[i];
781
782       ldesc->Width  = getFile16BitLE(file);
783       ldesc->Height = getFile16BitLE(file);
784       ldesc->OffSet = getFile32BitLE(file);     /* starts with 1, not with 0 */
785       ldesc->Size   = getFile32BitLE(file);
786     }
787
788     level_width  = mpx_level_desc[level_pos].Width;
789     level_height = mpx_level_desc[level_pos].Height;
790
791     file_seek_pos = mpx_level_desc[level_pos].OffSet - 1;
792   }
793
794   /* position file stream to the requested level (in case of level package) */
795   if (fseek(file, file_seek_pos, SEEK_SET) != 0)
796   {
797     Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
798
799     return FALSE;
800   }
801
802   /* there exist Supaplex level package files with multi-part levels which
803      can be detected as follows: instead of leading and trailing dashes ('-')
804      to pad the level name, they have leading and trailing numbers which are
805      the x and y coordinations of the current part of the multi-part level;
806      if there are '?' characters instead of numbers on the left or right side
807      of the level name, the multi-part level consists of only horizontal or
808      vertical parts */
809
810   for (l = level_pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
811   {
812     LoadNativeLevelFromFileStream_SP(file, level_width, level_height,
813                                      demo_available);
814
815     /* check if this level is a part of a bigger multi-part level */
816
817     if (is_single_level_file)
818       break;
819
820     name_first = header->LevelTitle[0];
821     name_last  = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
822
823     is_multipart_level =
824       ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
825        (name_last  == '?' || (name_last  >= '0' && name_last  <= '9')));
826
827     is_first_part =
828       ((name_first == '?' || name_first == '1') &&
829        (name_last  == '?' || name_last  == '1'));
830
831     if (is_multipart_level)
832     {
833       /* correct leading multipart level meta information in level name */
834       for (i = 0;
835            i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first;
836            i++)
837         header->LevelTitle[i] = '-';
838
839       /* correct trailing multipart level meta information in level name */
840       for (i = SP_LEVEL_NAME_LEN - 1;
841            i >= 0 && header->LevelTitle[i] == name_last;
842            i--)
843         header->LevelTitle[i] = '-';
844     }
845
846     /* ---------- check for normal single level ---------- */
847
848     if (!reading_multipart_level && !is_multipart_level)
849     {
850       /* the current level is simply a normal single-part level, and we are
851          not reading a multi-part level yet, so return the level as it is */
852
853       break;
854     }
855
856     /* ---------- check for empty level (unused multi-part) ---------- */
857
858     if (!reading_multipart_level && is_multipart_level && !is_first_part)
859     {
860       /* this is a part of a multi-part level, but not the first part
861          (and we are not already reading parts of a multi-part level);
862          in this case, use an empty level instead of the single part */
863
864       use_empty_level = TRUE;
865
866       break;
867     }
868
869     /* ---------- check for finished multi-part level ---------- */
870
871     if (reading_multipart_level &&
872         (!is_multipart_level ||
873          !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
874                     SP_LEVEL_NAME_LEN)))
875     {
876       /* we are already reading parts of a multi-part level, but this level is
877          either not a multi-part level, or a part of a different multi-part
878          level; in both cases, the multi-part level seems to be complete */
879
880       break;
881     }
882
883     /* ---------- here we have one part of a multi-part level ---------- */
884
885     reading_multipart_level = TRUE;
886
887     if (is_first_part)  /* start with first part of new multi-part level */
888     {
889       /* copy level info structure from first part */
890       multipart_level = native_sp_level;
891
892       /* clear playfield of new multi-part level */
893       for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
894         for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
895           multipart_level.playfield[x][y] = fiSpace;
896     }
897
898     if (name_first == '?')
899       name_first = '1';
900     if (name_last == '?')
901       name_last = '1';
902
903     multipart_xpos = (int)(name_first - '0');
904     multipart_ypos = (int)(name_last  - '0');
905
906 #if 0
907     printf("----------> part (%d/%d) of multi-part level '%s'\n",
908            multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
909 #endif
910
911     if (multipart_xpos * SP_STD_PLAYFIELD_WIDTH  > SP_MAX_PLAYFIELD_WIDTH ||
912         multipart_ypos * SP_STD_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
913     {
914       Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
915
916       break;
917     }
918
919     multipart_level.width  = MAX(multipart_level.width,
920                                  multipart_xpos * SP_STD_PLAYFIELD_WIDTH);
921     multipart_level.height = MAX(multipart_level.height,
922                                  multipart_ypos * SP_STD_PLAYFIELD_HEIGHT);
923
924     /* copy level part at the right position of multi-part level */
925     for (x = 0; x < SP_STD_PLAYFIELD_WIDTH; x++)
926     {
927       for (y = 0; y < SP_STD_PLAYFIELD_HEIGHT; y++)
928       {
929         int start_x = (multipart_xpos - 1) * SP_STD_PLAYFIELD_WIDTH;
930         int start_y = (multipart_ypos - 1) * SP_STD_PLAYFIELD_HEIGHT;
931
932         multipart_level.playfield[start_x + x][start_y + y] =
933           native_sp_level.playfield[x][y];
934       }
935     }
936   }
937
938   fclose(file);
939
940   if (use_empty_level)
941   {
942     setLevelInfoToDefaults_SP();
943
944     Error(ERR_WARN, "single part of multi-part level -- using empty level");
945   }
946
947   if (reading_multipart_level)
948     native_sp_level = multipart_level;
949
950   copyInternalEngineVars_SP();
951
952   return TRUE;
953 }
954
955 #endif
956
957 void SaveNativeLevel_SP(char *filename)
958 {
959   LevelInfoType *header = &native_sp_level.header;
960   FILE *file;
961   int i, x, y;
962
963   if (!(file = fopen(filename, MODE_WRITE)))
964   {
965     Error(ERR_WARN, "cannot save native level file '%s'", filename);
966
967     return;
968   }
969
970   /* write level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
971   for (y = 0; y < native_sp_level.height; y++)
972     for (x = 0; x < native_sp_level.width; x++)
973       putFile8Bit(file, native_sp_level.playfield[x][y]);
974
975   /* write level header (96 bytes) */
976
977   WriteUnusedBytesToFile(file, 4);
978
979   putFile8Bit(file, header->InitialGravity);
980   putFile8Bit(file, header->Version);
981
982   for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
983     putFile8Bit(file, header->LevelTitle[i]);
984
985   putFile8Bit(file, header->InitialFreezeZonks);
986   putFile8Bit(file, header->InfotronsNeeded);
987   putFile8Bit(file, header->SpecialPortCount);
988
989   for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
990   {
991     SpecialPortType *port = &header->SpecialPort[i];
992
993     putFile16BitBE(file, port->PortLocation);
994     putFile8Bit(file, port->Gravity);
995     putFile8Bit(file, port->FreezeZonks);
996     putFile8Bit(file, port->FreezeEnemies);
997
998     WriteUnusedBytesToFile(file, 1);
999   }
1000
1001   putFile8Bit(file, header->SpeedByte);
1002   putFile8Bit(file, header->CheckSumByte);
1003   putFile16BitLE(file, header->DemoRandomSeed);
1004
1005   /* also save demo tape, if available */
1006
1007   if (native_sp_level.demo.is_available)
1008   {
1009     putFile8Bit(file, native_sp_level.demo.level_nr);
1010
1011     for (i = 0; i < native_sp_level.demo.length; i++)
1012       putFile8Bit(file, native_sp_level.demo.data[i]);
1013   }
1014
1015   fclose(file);
1016 }