1 
2 #include "main_sp.h"
3 #include "global.h"
4 
5 
6 /* ------------------------------------------------------------------------- */
7 /* functions for loading Supaplex level                                      */
8 /* ------------------------------------------------------------------------- */
9 
setTapeInfoToDefaults_SP()10 void setTapeInfoToDefaults_SP()
11 {
12   native_sp_level.demo.is_available = FALSE;
13   native_sp_level.demo.length = 0;
14 }
15 
setLevelInfoToDefaults_SP()16 void setLevelInfoToDefaults_SP()
17 {
18   LevelInfoType *header = &native_sp_level.header;
19   char *empty_title = "-------- EMPTY --------";
20   int i, x, y;
21 
22   native_sp_level.game_sp = &game_sp;
23 
24   native_sp_level.width  = SP_STD_PLAYFIELD_WIDTH;
25   native_sp_level.height = SP_STD_PLAYFIELD_HEIGHT;
26 
27   for (x = 0; x < native_sp_level.width; x++)
28     for (y = 0; y < native_sp_level.height; y++)
29       native_sp_level.playfield[x][y] = fiSpace;
30 
31   /* copy string (without terminating '\0' character!) */
32   for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
33     header->LevelTitle[i] = empty_title[i];
34 
35   header->InitialGravity = 0;
36   header->Version = 0;
37   header->InitialFreezeZonks = 0;
38   header->InfotronsNeeded = 0;
39   header->SpecialPortCount = 0;
40   header->SpeedByte = 0;
41   header->CheckSumByte = 0;
42   header->DemoRandomSeed = 0;
43 
44   for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
45   {
46     SpecialPortType *port = &header->SpecialPort[i];
47 
48     port->PortLocation = 0;
49     port->Gravity = 0;
50     port->FreezeZonks = 0;
51     port->FreezeEnemies = 0;
52   }
53 
54   /* set raw header bytes (used for subsequent buffer zone) to "hardware" */
55   for (i = 0; i < SP_HEADER_SIZE; i++)
56     native_sp_level.header_raw_bytes[i] = 0x20;
57 
58   setTapeInfoToDefaults_SP();
59 }
60 
copyInternalEngineVars_SP()61 void copyInternalEngineVars_SP()
62 {
63   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 
LoadNativeLevelFromFileStream_SP(FILE * file,int width,int height,boolean demo_available)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 
LoadNativeLevel_SP(char * filename,int level_pos)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 
SaveNativeLevel_SP(char * filename)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 }
661