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