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