rnd-20100316-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   GravityFlag = LInfo.InitialGravity;
252   FreezeZonks = LInfo.InitialFreezeZonks;
253
254 #if 1
255   /* this is set by main game tape code to native random generator directly */
256 #else
257
258 #if 1
259   printf("::: file.c: copyInternalEngineVars_SP(): RandomSeed = LInfo.DemoRandomSeed\n");
260 #endif
261
262   RandomSeed = LInfo.DemoRandomSeed;
263
264 #endif
265
266   LevelLoaded = True;
267 }
268
269 static void LoadNativeLevelFromFileStream_SP(FILE *file, int width, int height,
270                                              boolean demo_available)
271 {
272   LevelInfoType *header = &native_sp_level.header;
273   int i, x, y;
274
275   /* for details of the Supaplex level format, see Herman Perk's Supaplex
276      documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
277
278   native_sp_level.width  = width;
279   native_sp_level.height = height;
280
281   /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
282   for (y = 0; y < native_sp_level.height; y++)
283     for (x = 0; x < native_sp_level.width; x++)
284       native_sp_level.playfield[x][y] = getFile8Bit(file);
285
286   /* read level header (96 bytes) */
287
288   ReadUnusedBytesFromFile(file, 4);     /* (not used by Supaplex engine) */
289
290   /* initial gravity: 1 == "on", anything else (0) == "off" */
291   header->InitialGravity = getFile8Bit(file);
292
293   /* SpeedFixVersion XOR 0x20 */
294   header->Version = getFile8Bit(file);
295
296   /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
297   for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
298     header->LevelTitle[i] = getFile8Bit(file);
299
300   /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
301   header->InitialFreezeZonks = getFile8Bit(file);
302
303   /* number of infotrons needed; 0 means that Supaplex will count the total
304      amount of infotrons in the level and use the low byte of that number
305      (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
306   header->InfotronsNeeded = getFile8Bit(file);
307
308   /* number of special ("gravity") port entries below (maximum 10 allowed) */
309   header->SpecialPortCount = getFile8Bit(file);
310
311 #if 0
312   printf("::: num_special_ports == %d\n", header->SpecialPortCount);
313 #endif
314
315   /* database of properties of up to 10 special ports (6 bytes per port) */
316   for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
317   {
318     SpecialPortType *port = &header->SpecialPort[i];
319
320     /* high and low byte of the location of a special port; if (x, y) are the
321        coordinates of a port in the field and (0, 0) is the top-left corner,
322        the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
323        of what may be expected: Supaplex works with a game field in memory
324        which is 2 bytes per tile) */
325     port->PortLocation = getFile16BitBE(file);          /* yes, big endian */
326
327 #if 0
328     {
329       int port_x = (port->PortLocation / 2) % SP_PLAYFIELD_WIDTH;
330       int port_y = (port->PortLocation / 2) / SP_PLAYFIELD_WIDTH;
331
332       printf("::: %d: port_location == %d => (%d, %d)\n",
333              i, port->PortLocation, port_x, port_y);
334     }
335 #endif
336
337     /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
338     port->Gravity = getFile8Bit(file);
339
340     /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
341     port->FreezeZonks = getFile8Bit(file);
342
343     /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
344     port->FreezeEnemies = getFile8Bit(file);
345
346     ReadUnusedBytesFromFile(file, 1);   /* (not used by Supaplex engine) */
347   }
348
349   /* SpeedByte XOR Highbyte(RandomSeed) */
350   header->SpeedByte = getFile8Bit(file);
351
352   /* CheckSum XOR SpeedByte */
353   header->CheckSumByte = getFile8Bit(file);
354
355   /* random seed used for recorded demos */
356   header->DemoRandomSeed = getFile16BitLE(file);        /* yes, little endian */
357   // header->DemoRandomSeed = getFile16BitBE(file);     /* !!! TEST ONLY !!! */
358
359 #if 0
360   printf("::: file.c: DemoRandomSeed == %d\n", header->DemoRandomSeed);
361 #endif
362
363   /* auto-determine number of infotrons if it was stored as "0" -- see above */
364   if (header->InfotronsNeeded == 0)
365   {
366     for (x = 0; x < native_sp_level.width; x++)
367       for (y = 0; y < native_sp_level.height; y++)
368         if (native_sp_level.playfield[x][y] == fiInfotron)
369           header->InfotronsNeeded++;
370
371     header->InfotronsNeeded &= 0xff;    /* only use low byte -- see above */
372   }
373
374   /* read raw level header bytes (96 bytes) */
375
376   fseek(file, -(SP_HEADER_SIZE), SEEK_CUR);     /* rewind file */
377   for (i = 0; i < SP_HEADER_SIZE; i++)
378     native_sp_level.header_raw_bytes[i] = fgetc(file);
379
380   /* also load demo tape, if available (only in single level files) */
381
382   if (demo_available)
383   {
384     int level_nr = getFile8Bit(file);
385
386     level_nr &= 0x7f;                   /* clear highest bit */
387     level_nr = (level_nr < 1   ? 1   :
388                 level_nr > 111 ? 111 : level_nr);
389
390     native_sp_level.demo.level_nr = level_nr;
391
392     for (i = 0; i < SP_MAX_TAPE_LEN && !feof(file); i++)
393     {
394       native_sp_level.demo.data[i] = getFile8Bit(file);
395
396       if (native_sp_level.demo.data[i] == 0xff) /* "end of demo" byte */
397       {
398         i++;
399
400         break;
401       }
402     }
403
404     native_sp_level.demo.length = i;
405     native_sp_level.demo.is_available = (native_sp_level.demo.length > 0);
406   }
407 }
408
409 boolean LoadNativeLevel_SP(char *filename, int level_pos)
410 {
411   FILE *file;
412   int i, l, x, y;
413   char name_first, name_last;
414   struct LevelInfo_SP multipart_level;
415   int multipart_xpos, multipart_ypos;
416   boolean is_multipart_level;
417   boolean is_first_part;
418   boolean reading_multipart_level = FALSE;
419   boolean use_empty_level = FALSE;
420   LevelInfoType *header = &native_sp_level.header;
421   boolean is_single_level_file = (strSuffixLower(filename, ".sp") ||
422                                   strSuffixLower(filename, ".mpx"));
423   boolean demo_available = is_single_level_file;
424   boolean is_mpx_file = strSuffixLower(filename, ".mpx");
425   int file_seek_pos = level_pos * SP_STD_LEVEL_SIZE;
426   int level_width  = SP_STD_PLAYFIELD_WIDTH;
427   int level_height = SP_STD_PLAYFIELD_HEIGHT;
428
429   /* always start with reliable default values */
430   setLevelInfoToDefaults_SP();
431   copyInternalEngineVars_SP();
432
433   if (!(file = fopen(filename, MODE_READ)))
434   {
435     Error(ERR_WARN, "cannot open file '%s' -- using empty level", filename);
436
437     return FALSE;
438   }
439
440   if (is_mpx_file)
441   {
442     char mpx_chunk_name[4 + 1];
443     int mpx_version;
444     int mpx_level_count;
445     LevelDescriptor *mpx_level_desc;
446
447     getFileChunkBE(file, mpx_chunk_name, NULL);
448
449     if (!strEqual(mpx_chunk_name, "MPX "))
450     {
451       Error(ERR_WARN, "cannot find MPX ID in file '%s' -- using empty level",
452             filename);
453
454       return FALSE;
455     }
456
457     mpx_version = getFile16BitLE(file);
458
459     if (mpx_version != 1)
460     {
461       Error(ERR_WARN, "unknown MPX version in file '%s' -- using empty level",
462             filename);
463
464       return FALSE;
465     }
466
467     mpx_level_count = getFile16BitLE(file);
468
469     if (mpx_level_count < 1)
470     {
471       Error(ERR_WARN, "no MPX levels found in file '%s' -- using empty level",
472             filename);
473
474       return FALSE;
475     }
476
477     if (level_pos >= mpx_level_count)
478     {
479       Error(ERR_WARN, "MPX level not found in file '%s' -- using empty level",
480             filename);
481
482       return FALSE;
483     }
484
485     mpx_level_desc = checked_calloc(mpx_level_count * sizeof(LevelDescriptor));
486
487     for (i = 0; i < mpx_level_count; i++)
488     {
489       LevelDescriptor *ldesc = &mpx_level_desc[i];
490
491       ldesc->Width  = getFile16BitLE(file);
492       ldesc->Height = getFile16BitLE(file);
493       ldesc->OffSet = getFile32BitLE(file);     /* starts with 1, not with 0 */
494       ldesc->Size   = getFile32BitLE(file);
495     }
496
497     level_width  = mpx_level_desc[level_pos].Width;
498     level_height = mpx_level_desc[level_pos].Height;
499
500     file_seek_pos = mpx_level_desc[level_pos].OffSet - 1;
501   }
502
503   /* position file stream to the requested level (in case of level package) */
504   if (fseek(file, file_seek_pos, SEEK_SET) != 0)
505   {
506     Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
507
508     return FALSE;
509   }
510
511   /* there exist Supaplex level package files with multi-part levels which
512      can be detected as follows: instead of leading and trailing dashes ('-')
513      to pad the level name, they have leading and trailing numbers which are
514      the x and y coordinations of the current part of the multi-part level;
515      if there are '?' characters instead of numbers on the left or right side
516      of the level name, the multi-part level consists of only horizontal or
517      vertical parts */
518
519   for (l = level_pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
520   {
521     LoadNativeLevelFromFileStream_SP(file, level_width, level_height,
522                                      demo_available);
523
524     /* check if this level is a part of a bigger multi-part level */
525
526     if (is_single_level_file)
527       break;
528
529     name_first = header->LevelTitle[0];
530     name_last  = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
531
532     is_multipart_level =
533       ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
534        (name_last  == '?' || (name_last  >= '0' && name_last  <= '9')));
535
536     is_first_part =
537       ((name_first == '?' || name_first == '1') &&
538        (name_last  == '?' || name_last  == '1'));
539
540     if (is_multipart_level)
541     {
542       /* correct leading multipart level meta information in level name */
543       for (i = 0;
544            i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first;
545            i++)
546         header->LevelTitle[i] = '-';
547
548       /* correct trailing multipart level meta information in level name */
549       for (i = SP_LEVEL_NAME_LEN - 1;
550            i >= 0 && header->LevelTitle[i] == name_last;
551            i--)
552         header->LevelTitle[i] = '-';
553     }
554
555     /* ---------- check for normal single level ---------- */
556
557     if (!reading_multipart_level && !is_multipart_level)
558     {
559       /* the current level is simply a normal single-part level, and we are
560          not reading a multi-part level yet, so return the level as it is */
561
562       break;
563     }
564
565     /* ---------- check for empty level (unused multi-part) ---------- */
566
567     if (!reading_multipart_level && is_multipart_level && !is_first_part)
568     {
569       /* this is a part of a multi-part level, but not the first part
570          (and we are not already reading parts of a multi-part level);
571          in this case, use an empty level instead of the single part */
572
573       use_empty_level = TRUE;
574
575       break;
576     }
577
578     /* ---------- check for finished multi-part level ---------- */
579
580     if (reading_multipart_level &&
581         (!is_multipart_level ||
582          !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
583                     SP_LEVEL_NAME_LEN)))
584     {
585       /* we are already reading parts of a multi-part level, but this level is
586          either not a multi-part level, or a part of a different multi-part
587          level; in both cases, the multi-part level seems to be complete */
588
589       break;
590     }
591
592     /* ---------- here we have one part of a multi-part level ---------- */
593
594     reading_multipart_level = TRUE;
595
596     if (is_first_part)  /* start with first part of new multi-part level */
597     {
598       /* copy level info structure from first part */
599       multipart_level = native_sp_level;
600
601       /* clear playfield of new multi-part level */
602       for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
603         for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
604           multipart_level.playfield[x][y] = fiSpace;
605     }
606
607     if (name_first == '?')
608       name_first = '1';
609     if (name_last == '?')
610       name_last = '1';
611
612     multipart_xpos = (int)(name_first - '0');
613     multipart_ypos = (int)(name_last  - '0');
614
615 #if 0
616     printf("----------> part (%d/%d) of multi-part level '%s'\n",
617            multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
618 #endif
619
620     if (multipart_xpos * SP_STD_PLAYFIELD_WIDTH  > SP_MAX_PLAYFIELD_WIDTH ||
621         multipart_ypos * SP_STD_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
622     {
623       Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
624
625       break;
626     }
627
628     multipart_level.width  = MAX(multipart_level.width,
629                                  multipart_xpos * SP_STD_PLAYFIELD_WIDTH);
630     multipart_level.height = MAX(multipart_level.height,
631                                  multipart_ypos * SP_STD_PLAYFIELD_HEIGHT);
632
633     /* copy level part at the right position of multi-part level */
634     for (x = 0; x < SP_STD_PLAYFIELD_WIDTH; x++)
635     {
636       for (y = 0; y < SP_STD_PLAYFIELD_HEIGHT; y++)
637       {
638         int start_x = (multipart_xpos - 1) * SP_STD_PLAYFIELD_WIDTH;
639         int start_y = (multipart_ypos - 1) * SP_STD_PLAYFIELD_HEIGHT;
640
641         multipart_level.playfield[start_x + x][start_y + y] =
642           native_sp_level.playfield[x][y];
643       }
644     }
645   }
646
647   fclose(file);
648
649   if (use_empty_level)
650   {
651     setLevelInfoToDefaults_SP();
652
653     Error(ERR_WARN, "single part of multi-part level -- using empty level");
654   }
655
656   if (reading_multipart_level)
657     native_sp_level = multipart_level;
658
659   copyInternalEngineVars_SP();
660
661   return TRUE;
662 }
663
664 void SaveNativeLevel_SP(char *filename)
665 {
666   LevelInfoType *header = &native_sp_level.header;
667   FILE *file;
668   int i, x, y;
669
670   if (!(file = fopen(filename, MODE_WRITE)))
671   {
672     Error(ERR_WARN, "cannot save native level file '%s'", filename);
673
674     return;
675   }
676
677   /* write level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
678   for (y = 0; y < native_sp_level.height; y++)
679     for (x = 0; x < native_sp_level.width; x++)
680       putFile8Bit(file, native_sp_level.playfield[x][y]);
681
682   /* write level header (96 bytes) */
683
684   WriteUnusedBytesToFile(file, 4);
685
686   putFile8Bit(file, header->InitialGravity);
687   putFile8Bit(file, header->Version);
688
689   for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
690     putFile8Bit(file, header->LevelTitle[i]);
691
692   putFile8Bit(file, header->InitialFreezeZonks);
693   putFile8Bit(file, header->InfotronsNeeded);
694   putFile8Bit(file, header->SpecialPortCount);
695
696   for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
697   {
698     SpecialPortType *port = &header->SpecialPort[i];
699
700     putFile16BitBE(file, port->PortLocation);
701     putFile8Bit(file, port->Gravity);
702     putFile8Bit(file, port->FreezeZonks);
703     putFile8Bit(file, port->FreezeEnemies);
704
705     WriteUnusedBytesToFile(file, 1);
706   }
707
708   putFile8Bit(file, header->SpeedByte);
709   putFile8Bit(file, header->CheckSumByte);
710   putFile16BitLE(file, header->DemoRandomSeed);
711
712   /* also save demo tape, if available */
713
714   if (native_sp_level.demo.is_available)
715   {
716     putFile8Bit(file, native_sp_level.demo.level_nr);
717
718     for (i = 0; i < native_sp_level.demo.length; i++)
719       putFile8Bit(file, native_sp_level.demo.data[i]);
720   }
721
722   fclose(file);
723 }