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