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