rnd-20030502-2-src
[rocksndiamonds.git] / src / files.c
1 /***********************************************************
2 * Rocks'n'Diamonds -- McDuffin Strikes Back!               *
3 *----------------------------------------------------------*
4 * (c) 1995-2002 Artsoft Entertainment                      *
5 *               Holger Schemel                             *
6 *               Detmolder Strasse 189                      *
7 *               33604 Bielefeld                            *
8 *               Germany                                    *
9 *               e-mail: info@artsoft.org                   *
10 *----------------------------------------------------------*
11 * files.c                                                  *
12 ***********************************************************/
13
14 #include <ctype.h>
15 #include <sys/stat.h>
16
17 #include "libgame/libgame.h"
18
19 #include "files.h"
20 #include "init.h"
21 #include "tools.h"
22 #include "tape.h"
23
24
25 #define CHUNK_ID_LEN            4       /* IFF style chunk id length  */
26 #define CHUNK_SIZE_UNDEFINED    0       /* undefined chunk size == 0  */
27 #define CHUNK_SIZE_NONE         -1      /* do not write chunk size    */
28 #define FILE_VERS_CHUNK_SIZE    8       /* size of file version chunk */
29 #define LEVEL_HEADER_SIZE       80      /* size of level file header  */
30 #define LEVEL_HEADER_UNUSED     14      /* unused level header bytes  */
31 #define LEVEL_CHUNK_CNT2_SIZE   160     /* size of level CNT2 chunk   */
32 #define LEVEL_CHUNK_CNT2_UNUSED 11      /* unused CNT2 chunk bytes    */
33 #define TAPE_HEADER_SIZE        20      /* size of tape file header   */
34 #define TAPE_HEADER_UNUSED      3       /* unused tape header bytes   */
35
36 /* file identifier strings */
37 #define LEVEL_COOKIE_TMPL       "ROCKSNDIAMONDS_LEVEL_FILE_VERSION_x.x"
38 #define TAPE_COOKIE_TMPL        "ROCKSNDIAMONDS_TAPE_FILE_VERSION_x.x"
39 #define SCORE_COOKIE            "ROCKSNDIAMONDS_SCORE_FILE_VERSION_1.2"
40
41
42 /* ========================================================================= */
43 /* level file functions                                                      */
44 /* ========================================================================= */
45
46 static void setLevelInfoToDefaults()
47 {
48   int i, j, x, y;
49
50   level.file_version = FILE_VERSION_ACTUAL;
51   level.game_version = GAME_VERSION_ACTUAL;
52
53   level.encoding_16bit_field = FALSE;   /* default: only 8-bit elements */
54   level.encoding_16bit_yamyam = FALSE;  /* default: only 8-bit elements */
55   level.encoding_16bit_amoeba = FALSE;  /* default: only 8-bit elements */
56
57   lev_fieldx = level.fieldx = STD_LEV_FIELDX;
58   lev_fieldy = level.fieldy = STD_LEV_FIELDY;
59
60   for(x=0; x<MAX_LEV_FIELDX; x++)
61     for(y=0; y<MAX_LEV_FIELDY; y++)
62       Feld[x][y] = Ur[x][y] = EL_SAND;
63
64   level.time = 100;
65   level.gems_needed = 0;
66   level.amoeba_speed = 10;
67   level.time_magic_wall = 10;
68   level.time_wheel = 10;
69   level.time_light = 10;
70   level.time_timegate = 10;
71   level.amoeba_content = EL_DIAMOND;
72   level.double_speed = FALSE;
73   level.gravity = FALSE;
74   level.em_slippery_gems = FALSE;
75
76   for(i=0; i<MAX_LEVEL_NAME_LEN; i++)
77     level.name[i] = '\0';
78   for(i=0; i<MAX_LEVEL_AUTHOR_LEN; i++)
79     level.author[i] = '\0';
80
81   strcpy(level.name, NAMELESS_LEVEL_NAME);
82   strcpy(level.author, ANONYMOUS_NAME);
83
84   for(i=0; i<LEVEL_SCORE_ELEMENTS; i++)
85     level.score[i] = 10;
86
87   level.num_yam_contents = STD_ELEMENT_CONTENTS;
88   for(i=0; i<MAX_ELEMENT_CONTENTS; i++)
89     for(x=0; x<3; x++)
90       for(y=0; y<3; y++)
91         level.yam_content[i][x][y] =
92           (i < STD_ELEMENT_CONTENTS ? EL_ROCK : EL_EMPTY);
93
94   Feld[0][0] = Ur[0][0] = EL_PLAYER_1;
95   Feld[STD_LEV_FIELDX-1][STD_LEV_FIELDY-1] =
96     Ur[STD_LEV_FIELDX-1][STD_LEV_FIELDY-1] = EL_EXIT_CLOSED;
97
98   for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
99   {
100     level.custom_element[i].change.events = CE_BITMASK_DEFAULT;
101     level.custom_element[i].change.successor = EL_EMPTY_SPACE;
102     level.custom_element[i].change.delay_fixed = 0;
103     level.custom_element[i].change.delay_random = 0;
104
105     /* start with no properties at all */
106 #if 1
107     for (j=0; j < NUM_EP_BITFIELDS; j++)
108       Properties[EL_CUSTOM_START + i][j] = EP_BITMASK_DEFAULT;
109 #else
110     Properties[EL_CUSTOM_START + i][EP_BITFIELD_BASE] = EP_BITMASK_DEFAULT;
111 #endif
112   }
113
114   BorderElement = EL_STEELWALL;
115
116   level.no_level_file = FALSE;
117
118   if (leveldir_current == NULL)         /* only when dumping level */
119     return;
120
121   /* try to determine better author name than 'anonymous' */
122   if (strcmp(leveldir_current->author, ANONYMOUS_NAME) != 0)
123   {
124     strncpy(level.author, leveldir_current->author, MAX_LEVEL_AUTHOR_LEN);
125     level.author[MAX_LEVEL_AUTHOR_LEN] = '\0';
126   }
127   else
128   {
129     switch (LEVELCLASS(leveldir_current))
130     {
131       case LEVELCLASS_TUTORIAL:
132         strcpy(level.author, PROGRAM_AUTHOR_STRING);
133         break;
134
135       case LEVELCLASS_CONTRIBUTION:
136         strncpy(level.author, leveldir_current->name,MAX_LEVEL_AUTHOR_LEN);
137         level.author[MAX_LEVEL_AUTHOR_LEN] = '\0';
138         break;
139
140       case LEVELCLASS_USER:
141         strncpy(level.author, getRealName(), MAX_LEVEL_AUTHOR_LEN);
142         level.author[MAX_LEVEL_AUTHOR_LEN] = '\0';
143         break;
144
145       default:
146         /* keep default value */
147         break;
148     }
149   }
150 }
151
152 static int checkLevelElement(int element)
153 {
154   if (element >= NUM_FILE_ELEMENTS)
155   {
156     Error(ERR_WARN, "invalid level element %d", element);
157     element = EL_CHAR_QUESTION;
158   }
159   else if (element == EL_PLAYER_OBSOLETE)
160     element = EL_PLAYER_1;
161   else if (element == EL_KEY_OBSOLETE)
162     element = EL_KEY_1;
163
164   return element;
165 }
166
167 static int LoadLevel_VERS(FILE *file, int chunk_size, struct LevelInfo *level)
168 {
169   level->file_version = getFileVersion(file);
170   level->game_version = getFileVersion(file);
171
172   return chunk_size;
173 }
174
175 static int LoadLevel_HEAD(FILE *file, int chunk_size, struct LevelInfo *level)
176 {
177   int i, x, y;
178
179   lev_fieldx = level->fieldx = fgetc(file);
180   lev_fieldy = level->fieldy = fgetc(file);
181
182   level->time           = getFile16BitBE(file);
183   level->gems_needed    = getFile16BitBE(file);
184
185   for(i=0; i<MAX_LEVEL_NAME_LEN; i++)
186     level->name[i] = fgetc(file);
187   level->name[MAX_LEVEL_NAME_LEN] = 0;
188
189   for(i=0; i<LEVEL_SCORE_ELEMENTS; i++)
190     level->score[i] = fgetc(file);
191
192   level->num_yam_contents = STD_ELEMENT_CONTENTS;
193   for(i=0; i<STD_ELEMENT_CONTENTS; i++)
194     for(y=0; y<3; y++)
195       for(x=0; x<3; x++)
196         level->yam_content[i][x][y] = checkLevelElement(fgetc(file));
197
198   level->amoeba_speed           = fgetc(file);
199   level->time_magic_wall        = fgetc(file);
200   level->time_wheel             = fgetc(file);
201   level->amoeba_content         = checkLevelElement(fgetc(file));
202   level->double_speed           = (fgetc(file) == 1 ? TRUE : FALSE);
203   level->gravity                = (fgetc(file) == 1 ? TRUE : FALSE);
204   level->encoding_16bit_field   = (fgetc(file) == 1 ? TRUE : FALSE);
205   level->em_slippery_gems       = (fgetc(file) == 1 ? TRUE : FALSE);
206
207   ReadUnusedBytesFromFile(file, LEVEL_HEADER_UNUSED);
208
209   return chunk_size;
210 }
211
212 static int LoadLevel_AUTH(FILE *file, int chunk_size, struct LevelInfo *level)
213 {
214   int i;
215
216   for(i=0; i<MAX_LEVEL_AUTHOR_LEN; i++)
217     level->author[i] = fgetc(file);
218   level->author[MAX_LEVEL_NAME_LEN] = 0;
219
220   return chunk_size;
221 }
222
223 static int LoadLevel_BODY(FILE *file, int chunk_size, struct LevelInfo *level)
224 {
225   int x, y;
226   int chunk_size_expected = level->fieldx * level->fieldy;
227
228   /* Note: "chunk_size" was wrong before version 2.0 when elements are
229      stored with 16-bit encoding (and should be twice as big then).
230      Even worse, playfield data was stored 16-bit when only yamyam content
231      contained 16-bit elements and vice versa. */
232
233   if (level->encoding_16bit_field && level->file_version >= FILE_VERSION_2_0)
234     chunk_size_expected *= 2;
235
236   if (chunk_size_expected != chunk_size)
237   {
238     ReadUnusedBytesFromFile(file, chunk_size);
239     return chunk_size_expected;
240   }
241
242   for(y=0; y<level->fieldy; y++)
243     for(x=0; x<level->fieldx; x++)
244       Feld[x][y] = Ur[x][y] =
245         checkLevelElement(level->encoding_16bit_field ?
246                           getFile16BitBE(file) : fgetc(file));
247   return chunk_size;
248 }
249
250 static int LoadLevel_CONT(FILE *file, int chunk_size, struct LevelInfo *level)
251 {
252   int i, x, y;
253   int header_size = 4;
254   int content_size = MAX_ELEMENT_CONTENTS * 3 * 3;
255   int chunk_size_expected = header_size + content_size;
256
257   /* Note: "chunk_size" was wrong before version 2.0 when elements are
258      stored with 16-bit encoding (and should be twice as big then).
259      Even worse, playfield data was stored 16-bit when only yamyam content
260      contained 16-bit elements and vice versa. */
261
262   if (level->encoding_16bit_field && level->file_version >= FILE_VERSION_2_0)
263     chunk_size_expected += content_size;
264
265   if (chunk_size_expected != chunk_size)
266   {
267     ReadUnusedBytesFromFile(file, chunk_size);
268     return chunk_size_expected;
269   }
270
271   fgetc(file);
272   level->num_yam_contents = fgetc(file);
273   fgetc(file);
274   fgetc(file);
275
276   /* correct invalid number of content fields -- should never happen */
277   if (level->num_yam_contents < 1 ||
278       level->num_yam_contents > MAX_ELEMENT_CONTENTS)
279     level->num_yam_contents = STD_ELEMENT_CONTENTS;
280
281   for(i=0; i<MAX_ELEMENT_CONTENTS; i++)
282     for(y=0; y<3; y++)
283       for(x=0; x<3; x++)
284         level->yam_content[i][x][y] =
285           checkLevelElement(level->encoding_16bit_field ?
286                             getFile16BitBE(file) : fgetc(file));
287   return chunk_size;
288 }
289
290 static int LoadLevel_CNT2(FILE *file, int chunk_size, struct LevelInfo *level)
291 {
292   int i, x, y;
293   int element;
294   int num_contents, content_xsize, content_ysize;
295   int content_array[MAX_ELEMENT_CONTENTS][3][3];
296
297   element = checkLevelElement(getFile16BitBE(file));
298   num_contents = fgetc(file);
299   content_xsize = fgetc(file);
300   content_ysize = fgetc(file);
301   ReadUnusedBytesFromFile(file, LEVEL_CHUNK_CNT2_UNUSED);
302
303   for(i=0; i<MAX_ELEMENT_CONTENTS; i++)
304     for(y=0; y<3; y++)
305       for(x=0; x<3; x++)
306         content_array[i][x][y] = checkLevelElement(getFile16BitBE(file));
307
308   /* correct invalid number of content fields -- should never happen */
309   if (num_contents < 1 || num_contents > MAX_ELEMENT_CONTENTS)
310     num_contents = STD_ELEMENT_CONTENTS;
311
312   if (element == EL_YAMYAM)
313   {
314     level->num_yam_contents = num_contents;
315
316     for(i=0; i<num_contents; i++)
317       for(y=0; y<3; y++)
318         for(x=0; x<3; x++)
319           level->yam_content[i][x][y] = content_array[i][x][y];
320   }
321   else if (element == EL_BD_AMOEBA)
322   {
323     level->amoeba_content = content_array[0][0][0];
324   }
325   else
326   {
327     Error(ERR_WARN, "cannot load content for element '%d'", element);
328   }
329
330   return chunk_size;
331 }
332
333 static int LoadLevel_CUS1(FILE *file, int chunk_size, struct LevelInfo *level)
334 {
335   int num_changed_custom_elements = getFile16BitBE(file);
336   int chunk_size_expected = 2 + num_changed_custom_elements * 6;
337   int i;
338
339   if (chunk_size_expected != chunk_size)
340   {
341     ReadUnusedBytesFromFile(file, chunk_size - 2);
342     return chunk_size_expected;
343   }
344
345   for (i=0; i < num_changed_custom_elements; i++)
346   {
347     int element = getFile16BitBE(file);
348     int properties = getFile32BitBE(file);
349
350     if (IS_CUSTOM_ELEMENT(element))
351       Properties[element][EP_BITFIELD_BASE] = properties;
352     else
353       Error(ERR_WARN, "invalid custom element number %d", element);
354   }
355
356   return chunk_size;
357 }
358
359 static int LoadLevel_CUS2(FILE *file, int chunk_size, struct LevelInfo *level)
360 {
361   int num_changed_custom_elements = getFile16BitBE(file);
362   int chunk_size_expected = 2 + num_changed_custom_elements * 4;
363   int i;
364
365   if (chunk_size_expected != chunk_size)
366   {
367     ReadUnusedBytesFromFile(file, chunk_size - 2);
368     return chunk_size_expected;
369   }
370
371   for (i=0; i < num_changed_custom_elements; i++)
372   {
373     int element = getFile16BitBE(file);
374     int custom_element_successor = getFile16BitBE(file);
375     int i = element - EL_CUSTOM_START;
376
377     if (IS_CUSTOM_ELEMENT(element))
378       level->custom_element[i].change.successor = custom_element_successor;
379     else
380       Error(ERR_WARN, "invalid custom element number %d", element);
381   }
382
383   return chunk_size;
384 }
385
386 void LoadLevelFromFilename(char *filename)
387 {
388   char cookie[MAX_LINE_LEN];
389   char chunk_name[CHUNK_ID_LEN + 1];
390   int chunk_size;
391   FILE *file;
392
393   /* always start with reliable default values */
394   setLevelInfoToDefaults();
395
396   if (!(file = fopen(filename, MODE_READ)))
397   {
398     level.no_level_file = TRUE;
399
400     Error(ERR_WARN, "cannot read level '%s' - creating new level", filename);
401     return;
402   }
403
404   getFileChunkBE(file, chunk_name, NULL);
405   if (strcmp(chunk_name, "RND1") == 0)
406   {
407     getFile32BitBE(file);               /* not used */
408
409     getFileChunkBE(file, chunk_name, NULL);
410     if (strcmp(chunk_name, "CAVE") != 0)
411     {
412       Error(ERR_WARN, "unknown format of level file '%s'", filename);
413       fclose(file);
414       return;
415     }
416   }
417   else  /* check for pre-2.0 file format with cookie string */
418   {
419     strcpy(cookie, chunk_name);
420     fgets(&cookie[4], MAX_LINE_LEN - 4, file);
421     if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n')
422       cookie[strlen(cookie) - 1] = '\0';
423
424     if (!checkCookieString(cookie, LEVEL_COOKIE_TMPL))
425     {
426       Error(ERR_WARN, "unknown format of level file '%s'", filename);
427       fclose(file);
428       return;
429     }
430
431     if ((level.file_version = getFileVersionFromCookieString(cookie)) == -1)
432     {
433       Error(ERR_WARN, "unsupported version of level file '%s'", filename);
434       fclose(file);
435       return;
436     }
437
438     /* pre-2.0 level files have no game version, so use file version here */
439     level.game_version = level.file_version;
440   }
441
442   if (level.file_version < FILE_VERSION_1_2)
443   {
444     /* level files from versions before 1.2.0 without chunk structure */
445     LoadLevel_HEAD(file, LEVEL_HEADER_SIZE,           &level);
446     LoadLevel_BODY(file, level.fieldx * level.fieldy, &level);
447   }
448   else
449   {
450     static struct
451     {
452       char *name;
453       int size;
454       int (*loader)(FILE *, int, struct LevelInfo *);
455     }
456     chunk_info[] =
457     {
458       { "VERS", FILE_VERS_CHUNK_SIZE,   LoadLevel_VERS },
459       { "HEAD", LEVEL_HEADER_SIZE,      LoadLevel_HEAD },
460       { "AUTH", MAX_LEVEL_AUTHOR_LEN,   LoadLevel_AUTH },
461       { "BODY", -1,                     LoadLevel_BODY },
462       { "CONT", -1,                     LoadLevel_CONT },
463       { "CNT2", LEVEL_CHUNK_CNT2_SIZE,  LoadLevel_CNT2 },
464       { "CUS1", -1,                     LoadLevel_CUS1 },
465       { "CUS2", -1,                     LoadLevel_CUS2 },
466       {  NULL,  0,                      NULL }
467     };
468
469     while (getFileChunkBE(file, chunk_name, &chunk_size))
470     {
471       int i = 0;
472
473       while (chunk_info[i].name != NULL &&
474              strcmp(chunk_name, chunk_info[i].name) != 0)
475         i++;
476
477       if (chunk_info[i].name == NULL)
478       {
479         Error(ERR_WARN, "unknown chunk '%s' in level file '%s'",
480               chunk_name, filename);
481         ReadUnusedBytesFromFile(file, chunk_size);
482       }
483       else if (chunk_info[i].size != -1 &&
484                chunk_info[i].size != chunk_size)
485       {
486         Error(ERR_WARN, "wrong size (%d) of chunk '%s' in level file '%s'",
487               chunk_size, chunk_name, filename);
488         ReadUnusedBytesFromFile(file, chunk_size);
489       }
490       else
491       {
492         /* call function to load this level chunk */
493         int chunk_size_expected =
494           (chunk_info[i].loader)(file, chunk_size, &level);
495
496         /* the size of some chunks cannot be checked before reading other
497            chunks first (like "HEAD" and "BODY") that contain some header
498            information, so check them here */
499         if (chunk_size_expected != chunk_size)
500         {
501           Error(ERR_WARN, "wrong size (%d) of chunk '%s' in level file '%s'",
502                 chunk_size, chunk_name, filename);
503         }
504       }
505     }
506   }
507
508   fclose(file);
509
510   if (leveldir_current == NULL)         /* only when dumping level */
511     return;
512
513   if (IS_LEVELCLASS_CONTRIBUTION(leveldir_current) ||
514       IS_LEVELCLASS_USER(leveldir_current))
515   {
516     /* For user contributed and private levels, use the version of
517        the game engine the levels were created for.
518        Since 2.0.1, the game engine version is now directly stored
519        in the level file (chunk "VERS"), so there is no need anymore
520        to set the game version from the file version (except for old,
521        pre-2.0 levels, where the game version is still taken from the
522        file format version used to store the level -- see above). */
523
524     /* do some special adjustments to support older level versions */
525     if (level.file_version == FILE_VERSION_1_0)
526     {
527       Error(ERR_WARN, "level file '%s' has version number 1.0", filename);
528       Error(ERR_WARN, "using high speed movement for player");
529
530       /* player was faster than monsters in (pre-)1.0 levels */
531       level.double_speed = TRUE;
532     }
533
534     /* Default behaviour for EM style gems was "slippery" only in 2.0.1 */
535     if (level.game_version == VERSION_IDENT(2,0,1))
536       level.em_slippery_gems = TRUE;
537   }
538   else
539   {
540     /* Always use the latest version of the game engine for all but
541        user contributed and private levels; this allows for actual
542        corrections in the game engine to take effect for existing,
543        converted levels (from "classic" or other existing games) to
544        make the game emulation more accurate, while (hopefully) not
545        breaking existing levels created from other players. */
546
547     level.game_version = GAME_VERSION_ACTUAL;
548
549     /* Set special EM style gems behaviour: EM style gems slip down from
550        normal, steel and growing wall. As this is a more fundamental change,
551        it seems better to set the default behaviour to "off" (as it is more
552        natural) and make it configurable in the level editor (as a property
553        of gem style elements). Already existing converted levels (neither
554        private nor contributed levels) are changed to the new behaviour. */
555
556     if (level.file_version < FILE_VERSION_2_0)
557       level.em_slippery_gems = TRUE;
558   }
559
560   /* map some elements which have changed in newer versions */
561   if (level.game_version <= VERSION_IDENT(2,2,0))
562   {
563     int x, y;
564
565     /* map game font elements */
566     for(y=0; y<level.fieldy; y++)
567     {
568       for(x=0; x<level.fieldx; x++)
569       {
570         int element = Ur[x][y];
571
572         if (element == EL_CHAR('['))
573           element = EL_CHAR_AUMLAUT;
574         else if (element == EL_CHAR('\\'))
575           element = EL_CHAR_OUMLAUT;
576         else if (element == EL_CHAR(']'))
577           element = EL_CHAR_UUMLAUT;
578         else if (element == EL_CHAR('^'))
579           element = EL_CHAR_COPYRIGHT;
580
581         Feld[x][y] = Ur[x][y] = element;
582       }
583     }
584   }
585
586   /* determine border element for this level */
587   SetBorderElement();
588 }
589
590 void LoadLevel(int level_nr)
591 {
592   char *filename = getLevelFilename(level_nr);
593
594   LoadLevelFromFilename(filename);
595   InitElementPropertiesEngine(level.game_version);
596 }
597
598 static void SaveLevel_VERS(FILE *file, struct LevelInfo *level)
599 {
600   putFileVersion(file, level->file_version);
601   putFileVersion(file, level->game_version);
602 }
603
604 static void SaveLevel_HEAD(FILE *file, struct LevelInfo *level)
605 {
606   int i, x, y;
607
608   fputc(level->fieldx, file);
609   fputc(level->fieldy, file);
610
611   putFile16BitBE(file, level->time);
612   putFile16BitBE(file, level->gems_needed);
613
614   for(i=0; i<MAX_LEVEL_NAME_LEN; i++)
615     fputc(level->name[i], file);
616
617   for(i=0; i<LEVEL_SCORE_ELEMENTS; i++)
618     fputc(level->score[i], file);
619
620   for(i=0; i<STD_ELEMENT_CONTENTS; i++)
621     for(y=0; y<3; y++)
622       for(x=0; x<3; x++)
623         fputc((level->encoding_16bit_yamyam ? EL_EMPTY :
624                level->yam_content[i][x][y]),
625               file);
626   fputc(level->amoeba_speed, file);
627   fputc(level->time_magic_wall, file);
628   fputc(level->time_wheel, file);
629   fputc((level->encoding_16bit_amoeba ? EL_EMPTY : level->amoeba_content),
630         file);
631   fputc((level->double_speed ? 1 : 0), file);
632   fputc((level->gravity ? 1 : 0), file);
633   fputc((level->encoding_16bit_field ? 1 : 0), file);
634   fputc((level->em_slippery_gems ? 1 : 0), file);
635
636   WriteUnusedBytesToFile(file, LEVEL_HEADER_UNUSED);
637 }
638
639 static void SaveLevel_AUTH(FILE *file, struct LevelInfo *level)
640 {
641   int i;
642
643   for(i=0; i<MAX_LEVEL_AUTHOR_LEN; i++)
644     fputc(level->author[i], file);
645 }
646
647 static void SaveLevel_BODY(FILE *file, struct LevelInfo *level)
648 {
649   int x, y;
650
651   for(y=0; y<level->fieldy; y++) 
652     for(x=0; x<level->fieldx; x++) 
653       if (level->encoding_16bit_field)
654         putFile16BitBE(file, Ur[x][y]);
655       else
656         fputc(Ur[x][y], file);
657 }
658
659 #if 0
660 static void SaveLevel_CONT(FILE *file, struct LevelInfo *level)
661 {
662   int i, x, y;
663
664   fputc(EL_YAMYAM, file);
665   fputc(level->num_yam_contents, file);
666   fputc(0, file);
667   fputc(0, file);
668
669   for(i=0; i<MAX_ELEMENT_CONTENTS; i++)
670     for(y=0; y<3; y++)
671       for(x=0; x<3; x++)
672         if (level->encoding_16bit_field)
673           putFile16BitBE(file, level->yam_content[i][x][y]);
674         else
675           fputc(level->yam_content[i][x][y], file);
676 }
677 #endif
678
679 static void SaveLevel_CNT2(FILE *file, struct LevelInfo *level, int element)
680 {
681   int i, x, y;
682   int num_contents, content_xsize, content_ysize;
683   int content_array[MAX_ELEMENT_CONTENTS][3][3];
684
685   if (element == EL_YAMYAM)
686   {
687     num_contents = level->num_yam_contents;
688     content_xsize = 3;
689     content_ysize = 3;
690
691     for(i=0; i<MAX_ELEMENT_CONTENTS; i++)
692       for(y=0; y<3; y++)
693         for(x=0; x<3; x++)
694           content_array[i][x][y] = level->yam_content[i][x][y];
695   }
696   else if (element == EL_BD_AMOEBA)
697   {
698     num_contents = 1;
699     content_xsize = 1;
700     content_ysize = 1;
701
702     for(i=0; i<MAX_ELEMENT_CONTENTS; i++)
703       for(y=0; y<3; y++)
704         for(x=0; x<3; x++)
705           content_array[i][x][y] = EL_EMPTY;
706     content_array[0][0][0] = level->amoeba_content;
707   }
708   else
709   {
710     /* chunk header already written -- write empty chunk data */
711     WriteUnusedBytesToFile(file, LEVEL_CHUNK_CNT2_SIZE);
712
713     Error(ERR_WARN, "cannot save content for element '%d'", element);
714     return;
715   }
716
717   putFile16BitBE(file, element);
718   fputc(num_contents, file);
719   fputc(content_xsize, file);
720   fputc(content_ysize, file);
721
722   WriteUnusedBytesToFile(file, LEVEL_CHUNK_CNT2_UNUSED);
723
724   for(i=0; i<MAX_ELEMENT_CONTENTS; i++)
725     for(y=0; y<3; y++)
726       for(x=0; x<3; x++)
727         putFile16BitBE(file, content_array[i][x][y]);
728 }
729
730 static void SaveLevel_CUS1(FILE *file, struct LevelInfo *level,
731                            int num_changed_custom_elements)
732 {
733   int i, check = 0;
734
735   putFile16BitBE(file, num_changed_custom_elements);
736
737   for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
738   {
739     int element = EL_CUSTOM_START + i;
740
741     if (Properties[element][EP_BITFIELD_BASE] != EP_BITMASK_DEFAULT)
742     {
743       if (check < num_changed_custom_elements)
744       {
745         putFile16BitBE(file, element);
746         putFile32BitBE(file, Properties[element][EP_BITFIELD_BASE]);
747       }
748
749       check++;
750     }
751   }
752
753   if (check != num_changed_custom_elements)     /* should not happen */
754     Error(ERR_WARN, "inconsistent number of custom element properties");
755 }
756
757 static void SaveLevel_CUS2(FILE *file, struct LevelInfo *level,
758                            int num_changed_custom_elements)
759 {
760   int i, check = 0;
761
762   putFile16BitBE(file, num_changed_custom_elements);
763
764   for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
765   {
766     int element = EL_CUSTOM_START + i;
767
768     if (level->custom_element[i].change.successor != EL_EMPTY_SPACE)
769     {
770       if (check < num_changed_custom_elements)
771       {
772         putFile16BitBE(file, element);
773         putFile16BitBE(file, level->custom_element[i].change.successor);
774       }
775
776       check++;
777     }
778   }
779
780   if (check != num_changed_custom_elements)     /* should not happen */
781     Error(ERR_WARN, "inconsistent number of custom element successors");
782 }
783
784 void SaveLevel(int level_nr)
785 {
786   char *filename = getLevelFilename(level_nr);
787   int body_chunk_size;
788   int num_changed_custom_elements1 = 0;
789   int num_changed_custom_elements2 = 0;
790   int i, x, y;
791   FILE *file;
792
793   if (!(file = fopen(filename, MODE_WRITE)))
794   {
795     Error(ERR_WARN, "cannot save level file '%s'", filename);
796     return;
797   }
798
799   level.file_version = FILE_VERSION_ACTUAL;
800   level.game_version = GAME_VERSION_ACTUAL;
801
802   /* check level field for 16-bit elements */
803   level.encoding_16bit_field = FALSE;
804   for(y=0; y<level.fieldy; y++) 
805     for(x=0; x<level.fieldx; x++) 
806       if (Ur[x][y] > 255)
807         level.encoding_16bit_field = TRUE;
808
809   /* check yamyam content for 16-bit elements */
810   level.encoding_16bit_yamyam = FALSE;
811   for(i=0; i<level.num_yam_contents; i++)
812     for(y=0; y<3; y++)
813       for(x=0; x<3; x++)
814         if (level.yam_content[i][x][y] > 255)
815           level.encoding_16bit_yamyam = TRUE;
816
817   /* check amoeba content for 16-bit elements */
818   level.encoding_16bit_amoeba = FALSE;
819   if (level.amoeba_content > 255)
820     level.encoding_16bit_amoeba = TRUE;
821
822   /* calculate size of "BODY" chunk */
823   body_chunk_size =
824     level.fieldx * level.fieldy * (level.encoding_16bit_field ? 2 : 1);
825
826   /* check for non-standard custom elements and calculate "CUS1" chunk size */
827   for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
828     if (Properties[EL_CUSTOM_START +i][EP_BITFIELD_BASE] != EP_BITMASK_DEFAULT)
829       num_changed_custom_elements1++;
830
831   /* check for non-standard custom elements and calculate "CUS2" chunk size */
832   for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
833     if (level.custom_element[i].change.successor != EL_EMPTY_SPACE)
834       num_changed_custom_elements2++;
835
836   putFileChunkBE(file, "RND1", CHUNK_SIZE_UNDEFINED);
837   putFileChunkBE(file, "CAVE", CHUNK_SIZE_NONE);
838
839   putFileChunkBE(file, "VERS", FILE_VERS_CHUNK_SIZE);
840   SaveLevel_VERS(file, &level);
841
842   putFileChunkBE(file, "HEAD", LEVEL_HEADER_SIZE);
843   SaveLevel_HEAD(file, &level);
844
845   putFileChunkBE(file, "AUTH", MAX_LEVEL_AUTHOR_LEN);
846   SaveLevel_AUTH(file, &level);
847
848   putFileChunkBE(file, "BODY", body_chunk_size);
849   SaveLevel_BODY(file, &level);
850
851   if (level.encoding_16bit_yamyam ||
852       level.num_yam_contents != STD_ELEMENT_CONTENTS)
853   {
854     putFileChunkBE(file, "CNT2", LEVEL_CHUNK_CNT2_SIZE);
855     SaveLevel_CNT2(file, &level, EL_YAMYAM);
856   }
857
858   if (level.encoding_16bit_amoeba)
859   {
860     putFileChunkBE(file, "CNT2", LEVEL_CHUNK_CNT2_SIZE);
861     SaveLevel_CNT2(file, &level, EL_BD_AMOEBA);
862   }
863
864   if (num_changed_custom_elements1 > 0)
865   {
866     putFileChunkBE(file, "CUS1", 2 + num_changed_custom_elements1 * 6);
867     SaveLevel_CUS1(file, &level, num_changed_custom_elements1);
868   }
869
870   if (num_changed_custom_elements2 > 0)
871   {
872     putFileChunkBE(file, "CUS2", 2 + num_changed_custom_elements2 * 4);
873     SaveLevel_CUS2(file, &level, num_changed_custom_elements2);
874   }
875
876   fclose(file);
877
878   SetFilePermissions(filename, PERMS_PRIVATE);
879 }
880
881 void DumpLevel(struct LevelInfo *level)
882 {
883   printf_line("-", 79);
884   printf("Level xxx (file version %06d, game version %06d)\n",
885          level->file_version, level->game_version);
886   printf_line("-", 79);
887
888   printf("Level Author: '%s'\n", level->author);
889   printf("Level Title:  '%s'\n", level->name);
890   printf("\n");
891   printf("Playfield Size: %d x %d\n", level->fieldx, level->fieldy);
892   printf("\n");
893   printf("Level Time:  %d seconds\n", level->time);
894   printf("Gems needed: %d\n", level->gems_needed);
895   printf("\n");
896   printf("Time for Magic Wall: %d seconds\n", level->time_magic_wall);
897   printf("Time for Wheel:      %d seconds\n", level->time_wheel);
898   printf("Time for Light:      %d seconds\n", level->time_light);
899   printf("Time for Timegate:   %d seconds\n", level->time_timegate);
900   printf("\n");
901   printf("Amoeba Speed: %d\n", level->amoeba_speed);
902   printf("\n");
903   printf("Gravity:                %s\n", (level->gravity ? "yes" : "no"));
904   printf("Double Speed Movement:  %s\n", (level->double_speed ? "yes" : "no"));
905   printf("EM style slippery gems: %s\n", (level->em_slippery_gems ? "yes" : "no"));
906
907   printf_line("-", 79);
908 }
909
910
911 /* ========================================================================= */
912 /* tape file functions                                                       */
913 /* ========================================================================= */
914
915 static void setTapeInfoToDefaults()
916 {
917   int i;
918
919   /* always start with reliable default values (empty tape) */
920   TapeErase();
921
922   /* default values (also for pre-1.2 tapes) with only the first player */
923   tape.player_participates[0] = TRUE;
924   for(i=1; i<MAX_PLAYERS; i++)
925     tape.player_participates[i] = FALSE;
926
927   /* at least one (default: the first) player participates in every tape */
928   tape.num_participating_players = 1;
929
930   tape.level_nr = level_nr;
931   tape.counter = 0;
932   tape.changed = FALSE;
933
934   tape.recording = FALSE;
935   tape.playing = FALSE;
936   tape.pausing = FALSE;
937 }
938
939 static int LoadTape_VERS(FILE *file, int chunk_size, struct TapeInfo *tape)
940 {
941   tape->file_version = getFileVersion(file);
942   tape->game_version = getFileVersion(file);
943
944   return chunk_size;
945 }
946
947 static int LoadTape_HEAD(FILE *file, int chunk_size, struct TapeInfo *tape)
948 {
949   int i;
950
951   tape->random_seed = getFile32BitBE(file);
952   tape->date        = getFile32BitBE(file);
953   tape->length      = getFile32BitBE(file);
954
955   /* read header fields that are new since version 1.2 */
956   if (tape->file_version >= FILE_VERSION_1_2)
957   {
958     byte store_participating_players = fgetc(file);
959     int engine_version;
960
961     /* since version 1.2, tapes store which players participate in the tape */
962     tape->num_participating_players = 0;
963     for(i=0; i<MAX_PLAYERS; i++)
964     {
965       tape->player_participates[i] = FALSE;
966
967       if (store_participating_players & (1 << i))
968       {
969         tape->player_participates[i] = TRUE;
970         tape->num_participating_players++;
971       }
972     }
973
974     ReadUnusedBytesFromFile(file, TAPE_HEADER_UNUSED);
975
976     engine_version = getFileVersion(file);
977     if (engine_version > 0)
978       tape->engine_version = engine_version;
979   }
980
981   return chunk_size;
982 }
983
984 static int LoadTape_INFO(FILE *file, int chunk_size, struct TapeInfo *tape)
985 {
986   int level_identifier_size;
987   int i;
988
989   level_identifier_size = getFile16BitBE(file);
990
991   tape->level_identifier =
992     checked_realloc(tape->level_identifier, level_identifier_size);
993
994   for(i=0; i < level_identifier_size; i++)
995     tape->level_identifier[i] = fgetc(file);
996
997   tape->level_nr = getFile16BitBE(file);
998
999   chunk_size = 2 + level_identifier_size + 2;
1000
1001   return chunk_size;
1002 }
1003
1004 static int LoadTape_BODY(FILE *file, int chunk_size, struct TapeInfo *tape)
1005 {
1006   int i, j;
1007   int chunk_size_expected =
1008     (tape->num_participating_players + 1) * tape->length;
1009
1010   if (chunk_size_expected != chunk_size)
1011   {
1012     ReadUnusedBytesFromFile(file, chunk_size);
1013     return chunk_size_expected;
1014   }
1015
1016   for(i=0; i<tape->length; i++)
1017   {
1018     if (i >= MAX_TAPELEN)
1019       break;
1020
1021     for(j=0; j<MAX_PLAYERS; j++)
1022     {
1023       tape->pos[i].action[j] = MV_NO_MOVING;
1024
1025       if (tape->player_participates[j])
1026         tape->pos[i].action[j] = fgetc(file);
1027     }
1028
1029     tape->pos[i].delay = fgetc(file);
1030
1031     if (tape->file_version == FILE_VERSION_1_0)
1032     {
1033       /* eliminate possible diagonal moves in old tapes */
1034       /* this is only for backward compatibility */
1035
1036       byte joy_dir[4] = { JOY_LEFT, JOY_RIGHT, JOY_UP, JOY_DOWN };
1037       byte action = tape->pos[i].action[0];
1038       int k, num_moves = 0;
1039
1040       for (k=0; k<4; k++)
1041       {
1042         if (action & joy_dir[k])
1043         {
1044           tape->pos[i + num_moves].action[0] = joy_dir[k];
1045           if (num_moves > 0)
1046             tape->pos[i + num_moves].delay = 0;
1047           num_moves++;
1048         }
1049       }
1050
1051       if (num_moves > 1)
1052       {
1053         num_moves--;
1054         i += num_moves;
1055         tape->length += num_moves;
1056       }
1057     }
1058     else if (tape->file_version < FILE_VERSION_2_0)
1059     {
1060       /* convert pre-2.0 tapes to new tape format */
1061
1062       if (tape->pos[i].delay > 1)
1063       {
1064         /* action part */
1065         tape->pos[i + 1] = tape->pos[i];
1066         tape->pos[i + 1].delay = 1;
1067
1068         /* delay part */
1069         for(j=0; j<MAX_PLAYERS; j++)
1070           tape->pos[i].action[j] = MV_NO_MOVING;
1071         tape->pos[i].delay--;
1072
1073         i++;
1074         tape->length++;
1075       }
1076     }
1077
1078     if (feof(file))
1079       break;
1080   }
1081
1082   if (i != tape->length)
1083     chunk_size = (tape->num_participating_players + 1) * i;
1084
1085   return chunk_size;
1086 }
1087
1088 void LoadTapeFromFilename(char *filename)
1089 {
1090   char cookie[MAX_LINE_LEN];
1091   char chunk_name[CHUNK_ID_LEN + 1];
1092   FILE *file;
1093   int chunk_size;
1094
1095   /* always start with reliable default values */
1096   setTapeInfoToDefaults();
1097
1098   if (!(file = fopen(filename, MODE_READ)))
1099     return;
1100
1101   getFileChunkBE(file, chunk_name, NULL);
1102   if (strcmp(chunk_name, "RND1") == 0)
1103   {
1104     getFile32BitBE(file);               /* not used */
1105
1106     getFileChunkBE(file, chunk_name, NULL);
1107     if (strcmp(chunk_name, "TAPE") != 0)
1108     {
1109       Error(ERR_WARN, "unknown format of tape file '%s'", filename);
1110       fclose(file);
1111       return;
1112     }
1113   }
1114   else  /* check for pre-2.0 file format with cookie string */
1115   {
1116     strcpy(cookie, chunk_name);
1117     fgets(&cookie[4], MAX_LINE_LEN - 4, file);
1118     if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n')
1119       cookie[strlen(cookie) - 1] = '\0';
1120
1121     if (!checkCookieString(cookie, TAPE_COOKIE_TMPL))
1122     {
1123       Error(ERR_WARN, "unknown format of tape file '%s'", filename);
1124       fclose(file);
1125       return;
1126     }
1127
1128     if ((tape.file_version = getFileVersionFromCookieString(cookie)) == -1)
1129     {
1130       Error(ERR_WARN, "unsupported version of tape file '%s'", filename);
1131       fclose(file);
1132       return;
1133     }
1134
1135     /* pre-2.0 tape files have no game version, so use file version here */
1136     tape.game_version = tape.file_version;
1137   }
1138
1139   if (tape.file_version < FILE_VERSION_1_2)
1140   {
1141     /* tape files from versions before 1.2.0 without chunk structure */
1142     LoadTape_HEAD(file, TAPE_HEADER_SIZE, &tape);
1143     LoadTape_BODY(file, 2 * tape.length,  &tape);
1144   }
1145   else
1146   {
1147     static struct
1148     {
1149       char *name;
1150       int size;
1151       int (*loader)(FILE *, int, struct TapeInfo *);
1152     }
1153     chunk_info[] =
1154     {
1155       { "VERS", FILE_VERS_CHUNK_SIZE,   LoadTape_VERS },
1156       { "HEAD", TAPE_HEADER_SIZE,       LoadTape_HEAD },
1157       { "INFO", -1,                     LoadTape_INFO },
1158       { "BODY", -1,                     LoadTape_BODY },
1159       {  NULL,  0,                      NULL }
1160     };
1161
1162     while (getFileChunkBE(file, chunk_name, &chunk_size))
1163     {
1164       int i = 0;
1165
1166       while (chunk_info[i].name != NULL &&
1167              strcmp(chunk_name, chunk_info[i].name) != 0)
1168         i++;
1169
1170       if (chunk_info[i].name == NULL)
1171       {
1172         Error(ERR_WARN, "unknown chunk '%s' in tape file '%s'",
1173               chunk_name, filename);
1174         ReadUnusedBytesFromFile(file, chunk_size);
1175       }
1176       else if (chunk_info[i].size != -1 &&
1177                chunk_info[i].size != chunk_size)
1178       {
1179         Error(ERR_WARN, "wrong size (%d) of chunk '%s' in tape file '%s'",
1180               chunk_size, chunk_name, filename);
1181         ReadUnusedBytesFromFile(file, chunk_size);
1182       }
1183       else
1184       {
1185         /* call function to load this tape chunk */
1186         int chunk_size_expected =
1187           (chunk_info[i].loader)(file, chunk_size, &tape);
1188
1189         /* the size of some chunks cannot be checked before reading other
1190            chunks first (like "HEAD" and "BODY") that contain some header
1191            information, so check them here */
1192         if (chunk_size_expected != chunk_size)
1193         {
1194           Error(ERR_WARN, "wrong size (%d) of chunk '%s' in tape file '%s'",
1195                 chunk_size, chunk_name, filename);
1196         }
1197       }
1198     }
1199   }
1200
1201   fclose(file);
1202
1203   tape.length_seconds = GetTapeLength();
1204 }
1205
1206 void LoadTape(int level_nr)
1207 {
1208   char *filename = getTapeFilename(level_nr);
1209
1210   LoadTapeFromFilename(filename);
1211 }
1212
1213 static void SaveTape_VERS(FILE *file, struct TapeInfo *tape)
1214 {
1215   putFileVersion(file, tape->file_version);
1216   putFileVersion(file, tape->game_version);
1217 }
1218
1219 static void SaveTape_HEAD(FILE *file, struct TapeInfo *tape)
1220 {
1221   int i;
1222   byte store_participating_players = 0;
1223
1224   /* set bits for participating players for compact storage */
1225   for(i=0; i<MAX_PLAYERS; i++)
1226     if (tape->player_participates[i])
1227       store_participating_players |= (1 << i);
1228
1229   putFile32BitBE(file, tape->random_seed);
1230   putFile32BitBE(file, tape->date);
1231   putFile32BitBE(file, tape->length);
1232
1233   fputc(store_participating_players, file);
1234
1235   /* unused bytes not at the end here for 4-byte alignment of engine_version */
1236   WriteUnusedBytesToFile(file, TAPE_HEADER_UNUSED);
1237
1238   putFileVersion(file, tape->engine_version);
1239 }
1240
1241 static void SaveTape_INFO(FILE *file, struct TapeInfo *tape)
1242 {
1243   int level_identifier_size = strlen(tape->level_identifier) + 1;
1244   int i;
1245
1246   putFile16BitBE(file, level_identifier_size);
1247
1248   for(i=0; i < level_identifier_size; i++)
1249     fputc(tape->level_identifier[i], file);
1250
1251   putFile16BitBE(file, tape->level_nr);
1252 }
1253
1254 static void SaveTape_BODY(FILE *file, struct TapeInfo *tape)
1255 {
1256   int i, j;
1257
1258   for(i=0; i<tape->length; i++)
1259   {
1260     for(j=0; j<MAX_PLAYERS; j++)
1261       if (tape->player_participates[j])
1262         fputc(tape->pos[i].action[j], file);
1263
1264     fputc(tape->pos[i].delay, file);
1265   }
1266 }
1267
1268 void SaveTape(int level_nr)
1269 {
1270   char *filename = getTapeFilename(level_nr);
1271   FILE *file;
1272   boolean new_tape = TRUE;
1273   int num_participating_players = 0;
1274   int info_chunk_size;
1275   int body_chunk_size;
1276   int i;
1277
1278   InitTapeDirectory(leveldir_current->filename);
1279
1280   /* if a tape still exists, ask to overwrite it */
1281   if (access(filename, F_OK) == 0)
1282   {
1283     new_tape = FALSE;
1284     if (!Request("Replace old tape ?", REQ_ASK))
1285       return;
1286   }
1287
1288   if (!(file = fopen(filename, MODE_WRITE)))
1289   {
1290     Error(ERR_WARN, "cannot save level recording file '%s'", filename);
1291     return;
1292   }
1293
1294   tape.file_version = FILE_VERSION_ACTUAL;
1295   tape.game_version = GAME_VERSION_ACTUAL;
1296
1297   /* count number of participating players  */
1298   for(i=0; i<MAX_PLAYERS; i++)
1299     if (tape.player_participates[i])
1300       num_participating_players++;
1301
1302   info_chunk_size = 2 + (strlen(tape.level_identifier) + 1) + 2;
1303   body_chunk_size = (num_participating_players + 1) * tape.length;
1304
1305   putFileChunkBE(file, "RND1", CHUNK_SIZE_UNDEFINED);
1306   putFileChunkBE(file, "TAPE", CHUNK_SIZE_NONE);
1307
1308   putFileChunkBE(file, "VERS", FILE_VERS_CHUNK_SIZE);
1309   SaveTape_VERS(file, &tape);
1310
1311   putFileChunkBE(file, "HEAD", TAPE_HEADER_SIZE);
1312   SaveTape_HEAD(file, &tape);
1313
1314   putFileChunkBE(file, "INFO", info_chunk_size);
1315   SaveTape_INFO(file, &tape);
1316
1317   putFileChunkBE(file, "BODY", body_chunk_size);
1318   SaveTape_BODY(file, &tape);
1319
1320   fclose(file);
1321
1322   SetFilePermissions(filename, PERMS_PRIVATE);
1323
1324   tape.changed = FALSE;
1325
1326   if (new_tape)
1327     Request("tape saved !", REQ_CONFIRM);
1328 }
1329
1330 void DumpTape(struct TapeInfo *tape)
1331 {
1332   int i, j;
1333
1334   if (TAPE_IS_EMPTY(*tape))
1335   {
1336     Error(ERR_WARN, "no tape available for level %d", tape->level_nr);
1337     return;
1338   }
1339
1340   printf_line("-", 79);
1341   printf("Tape of Level %03d (file version %06d, game version %06d)\n",
1342          tape->level_nr, tape->file_version, tape->game_version);
1343   printf("Level series identifier: '%s'\n", tape->level_identifier);
1344   printf_line("-", 79);
1345
1346   for(i=0; i<tape->length; i++)
1347   {
1348     if (i >= MAX_TAPELEN)
1349       break;
1350
1351     printf("%03d: ", i);
1352
1353     for(j=0; j<MAX_PLAYERS; j++)
1354     {
1355       if (tape->player_participates[j])
1356       {
1357         int action = tape->pos[i].action[j];
1358
1359         printf("%d:%02x ", j, action);
1360         printf("[%c%c%c%c|%c%c] - ",
1361                (action & JOY_LEFT ? '<' : ' '),
1362                (action & JOY_RIGHT ? '>' : ' '),
1363                (action & JOY_UP ? '^' : ' '),
1364                (action & JOY_DOWN ? 'v' : ' '),
1365                (action & JOY_BUTTON_1 ? '1' : ' '),
1366                (action & JOY_BUTTON_2 ? '2' : ' '));
1367       }
1368     }
1369
1370     printf("(%03d)\n", tape->pos[i].delay);
1371   }
1372
1373   printf_line("-", 79);
1374 }
1375
1376
1377 /* ========================================================================= */
1378 /* score file functions                                                      */
1379 /* ========================================================================= */
1380
1381 void LoadScore(int level_nr)
1382 {
1383   int i;
1384   char *filename = getScoreFilename(level_nr);
1385   char cookie[MAX_LINE_LEN];
1386   char line[MAX_LINE_LEN];
1387   char *line_ptr;
1388   FILE *file;
1389
1390   /* always start with reliable default values */
1391   for(i=0; i<MAX_SCORE_ENTRIES; i++)
1392   {
1393     strcpy(highscore[i].Name, EMPTY_PLAYER_NAME);
1394     highscore[i].Score = 0;
1395   }
1396
1397   if (!(file = fopen(filename, MODE_READ)))
1398     return;
1399
1400   /* check file identifier */
1401   fgets(cookie, MAX_LINE_LEN, file);
1402   if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n')
1403     cookie[strlen(cookie) - 1] = '\0';
1404
1405   if (!checkCookieString(cookie, SCORE_COOKIE))
1406   {
1407     Error(ERR_WARN, "unknown format of score file '%s'", filename);
1408     fclose(file);
1409     return;
1410   }
1411
1412   for(i=0; i<MAX_SCORE_ENTRIES; i++)
1413   {
1414     fscanf(file, "%d", &highscore[i].Score);
1415     fgets(line, MAX_LINE_LEN, file);
1416
1417     if (line[strlen(line) - 1] == '\n')
1418       line[strlen(line) - 1] = '\0';
1419
1420     for (line_ptr = line; *line_ptr; line_ptr++)
1421     {
1422       if (*line_ptr != ' ' && *line_ptr != '\t' && *line_ptr != '\0')
1423       {
1424         strncpy(highscore[i].Name, line_ptr, MAX_PLAYER_NAME_LEN);
1425         highscore[i].Name[MAX_PLAYER_NAME_LEN] = '\0';
1426         break;
1427       }
1428     }
1429   }
1430
1431   fclose(file);
1432 }
1433
1434 void SaveScore(int level_nr)
1435 {
1436   int i;
1437   char *filename = getScoreFilename(level_nr);
1438   FILE *file;
1439
1440   InitScoreDirectory(leveldir_current->filename);
1441
1442   if (!(file = fopen(filename, MODE_WRITE)))
1443   {
1444     Error(ERR_WARN, "cannot save score for level %d", level_nr);
1445     return;
1446   }
1447
1448   fprintf(file, "%s\n\n", SCORE_COOKIE);
1449
1450   for(i=0; i<MAX_SCORE_ENTRIES; i++)
1451     fprintf(file, "%d %s\n", highscore[i].Score, highscore[i].Name);
1452
1453   fclose(file);
1454
1455   SetFilePermissions(filename, PERMS_PUBLIC);
1456 }
1457
1458
1459 /* ========================================================================= */
1460 /* setup file functions                                                      */
1461 /* ========================================================================= */
1462
1463 #define TOKEN_STR_PLAYER_PREFIX                 "player_"
1464
1465 /* global setup */
1466 #define SETUP_TOKEN_PLAYER_NAME                 0
1467 #define SETUP_TOKEN_SOUND                       1
1468 #define SETUP_TOKEN_SOUND_LOOPS                 2
1469 #define SETUP_TOKEN_SOUND_MUSIC                 3
1470 #define SETUP_TOKEN_SOUND_SIMPLE                4
1471 #define SETUP_TOKEN_TOONS                       5
1472 #define SETUP_TOKEN_SCROLL_DELAY                6
1473 #define SETUP_TOKEN_SOFT_SCROLLING              7
1474 #define SETUP_TOKEN_FADING                      8
1475 #define SETUP_TOKEN_AUTORECORD                  9
1476 #define SETUP_TOKEN_QUICK_DOORS                 10
1477 #define SETUP_TOKEN_TEAM_MODE                   11
1478 #define SETUP_TOKEN_HANDICAP                    12
1479 #define SETUP_TOKEN_TIME_LIMIT                  13
1480 #define SETUP_TOKEN_FULLSCREEN                  14
1481 #define SETUP_TOKEN_ASK_ON_ESCAPE               15
1482 #define SETUP_TOKEN_GRAPHICS_SET                16
1483 #define SETUP_TOKEN_SOUNDS_SET                  17
1484 #define SETUP_TOKEN_MUSIC_SET                   18
1485 #define SETUP_TOKEN_OVERRIDE_LEVEL_GRAPHICS     19
1486 #define SETUP_TOKEN_OVERRIDE_LEVEL_SOUNDS       20
1487 #define SETUP_TOKEN_OVERRIDE_LEVEL_MUSIC        21
1488
1489 #define NUM_GLOBAL_SETUP_TOKENS                 22
1490
1491 /* editor setup */
1492 #define SETUP_TOKEN_EDITOR_EL_BOULDERDASH       0
1493 #define SETUP_TOKEN_EDITOR_EL_EMERALD_MINE      1
1494 #define SETUP_TOKEN_EDITOR_EL_MORE              2
1495 #define SETUP_TOKEN_EDITOR_EL_SOKOBAN           3
1496 #define SETUP_TOKEN_EDITOR_EL_SUPAPLEX          4
1497 #define SETUP_TOKEN_EDITOR_EL_DIAMOND_CAVES     5
1498 #define SETUP_TOKEN_EDITOR_EL_DX_BOULDERDASH    6
1499 #define SETUP_TOKEN_EDITOR_EL_CHARS             7
1500 #define SETUP_TOKEN_EDITOR_EL_CUSTOM            8
1501
1502 #define NUM_EDITOR_SETUP_TOKENS                 9
1503
1504 /* shortcut setup */
1505 #define SETUP_TOKEN_SHORTCUT_SAVE_GAME          0
1506 #define SETUP_TOKEN_SHORTCUT_LOAD_GAME          1
1507 #define SETUP_TOKEN_SHORTCUT_TOGGLE_PAUSE       2
1508
1509 #define NUM_SHORTCUT_SETUP_TOKENS               3
1510
1511 /* player setup */
1512 #define SETUP_TOKEN_PLAYER_USE_JOYSTICK         0
1513 #define SETUP_TOKEN_PLAYER_JOY_DEVICE_NAME      1
1514 #define SETUP_TOKEN_PLAYER_JOY_XLEFT            2
1515 #define SETUP_TOKEN_PLAYER_JOY_XMIDDLE          3
1516 #define SETUP_TOKEN_PLAYER_JOY_XRIGHT           4
1517 #define SETUP_TOKEN_PLAYER_JOY_YUPPER           5
1518 #define SETUP_TOKEN_PLAYER_JOY_YMIDDLE          6
1519 #define SETUP_TOKEN_PLAYER_JOY_YLOWER           7
1520 #define SETUP_TOKEN_PLAYER_JOY_SNAP             8
1521 #define SETUP_TOKEN_PLAYER_JOY_BOMB             9
1522 #define SETUP_TOKEN_PLAYER_KEY_LEFT             10
1523 #define SETUP_TOKEN_PLAYER_KEY_RIGHT            11
1524 #define SETUP_TOKEN_PLAYER_KEY_UP               12
1525 #define SETUP_TOKEN_PLAYER_KEY_DOWN             13
1526 #define SETUP_TOKEN_PLAYER_KEY_SNAP             14
1527 #define SETUP_TOKEN_PLAYER_KEY_BOMB             15
1528
1529 #define NUM_PLAYER_SETUP_TOKENS                 16
1530
1531 /* system setup */
1532 #define SETUP_TOKEN_SYSTEM_SDL_AUDIODRIVER      0
1533 #define SETUP_TOKEN_SYSTEM_AUDIO_FRAGMENT_SIZE  1
1534
1535 #define NUM_SYSTEM_SETUP_TOKENS                 2
1536
1537 /* options setup */
1538 #define SETUP_TOKEN_OPTIONS_VERBOSE             0
1539
1540 #define NUM_OPTIONS_SETUP_TOKENS                1
1541
1542
1543 static struct SetupInfo si;
1544 static struct SetupEditorInfo sei;
1545 static struct SetupShortcutInfo ssi;
1546 static struct SetupInputInfo sii;
1547 static struct SetupSystemInfo syi;
1548 static struct OptionInfo soi;
1549
1550 static struct TokenInfo global_setup_tokens[] =
1551 {
1552   { TYPE_STRING, &si.player_name,       "player_name"                   },
1553   { TYPE_SWITCH, &si.sound,             "sound"                         },
1554   { TYPE_SWITCH, &si.sound_loops,       "repeating_sound_loops"         },
1555   { TYPE_SWITCH, &si.sound_music,       "background_music"              },
1556   { TYPE_SWITCH, &si.sound_simple,      "simple_sound_effects"          },
1557   { TYPE_SWITCH, &si.toons,             "toons"                         },
1558   { TYPE_SWITCH, &si.scroll_delay,      "scroll_delay"                  },
1559   { TYPE_SWITCH, &si.soft_scrolling,    "soft_scrolling"                },
1560   { TYPE_SWITCH, &si.fading,            "screen_fading"                 },
1561   { TYPE_SWITCH, &si.autorecord,        "automatic_tape_recording"      },
1562   { TYPE_SWITCH, &si.quick_doors,       "quick_doors"                   },
1563   { TYPE_SWITCH, &si.team_mode,         "team_mode"                     },
1564   { TYPE_SWITCH, &si.handicap,          "handicap"                      },
1565   { TYPE_SWITCH, &si.time_limit,        "time_limit"                    },
1566   { TYPE_SWITCH, &si.fullscreen,        "fullscreen"                    },
1567   { TYPE_SWITCH, &si.ask_on_escape,     "ask_on_escape"                 },
1568   { TYPE_STRING, &si.graphics_set,      "graphics_set"                  },
1569   { TYPE_STRING, &si.sounds_set,        "sounds_set"                    },
1570   { TYPE_STRING, &si.music_set,         "music_set"                     },
1571   { TYPE_SWITCH, &si.override_level_graphics, "override_level_graphics" },
1572   { TYPE_SWITCH, &si.override_level_sounds,   "override_level_sounds"   },
1573   { TYPE_SWITCH, &si.override_level_music,    "override_level_music"    },
1574 };
1575
1576 static struct TokenInfo editor_setup_tokens[] =
1577 {
1578   { TYPE_SWITCH, &sei.el_boulderdash,   "editor.el_boulderdash"         },
1579   { TYPE_SWITCH, &sei.el_emerald_mine,  "editor.el_emerald_mine"        },
1580   { TYPE_SWITCH, &sei.el_more,          "editor.el_more"                },
1581   { TYPE_SWITCH, &sei.el_sokoban,       "editor.el_sokoban"             },
1582   { TYPE_SWITCH, &sei.el_supaplex,      "editor.el_supaplex"            },
1583   { TYPE_SWITCH, &sei.el_diamond_caves, "editor.el_diamond_caves"       },
1584   { TYPE_SWITCH, &sei.el_dx_boulderdash,"editor.el_dx_boulderdash"      },
1585   { TYPE_SWITCH, &sei.el_chars,         "editor.el_chars"               },
1586   { TYPE_SWITCH, &sei.el_custom,        "editor.el_custom"              },
1587 };
1588
1589 static struct TokenInfo shortcut_setup_tokens[] =
1590 {
1591   { TYPE_KEY_X11, &ssi.save_game,       "shortcut.save_game"            },
1592   { TYPE_KEY_X11, &ssi.load_game,       "shortcut.load_game"            },
1593   { TYPE_KEY_X11, &ssi.toggle_pause,    "shortcut.toggle_pause"         }
1594 };
1595
1596 static struct TokenInfo player_setup_tokens[] =
1597 {
1598   { TYPE_BOOLEAN, &sii.use_joystick,    ".use_joystick"                 },
1599   { TYPE_STRING,  &sii.joy.device_name, ".joy.device_name"              },
1600   { TYPE_INTEGER, &sii.joy.xleft,       ".joy.xleft"                    },
1601   { TYPE_INTEGER, &sii.joy.xmiddle,     ".joy.xmiddle"                  },
1602   { TYPE_INTEGER, &sii.joy.xright,      ".joy.xright"                   },
1603   { TYPE_INTEGER, &sii.joy.yupper,      ".joy.yupper"                   },
1604   { TYPE_INTEGER, &sii.joy.ymiddle,     ".joy.ymiddle"                  },
1605   { TYPE_INTEGER, &sii.joy.ylower,      ".joy.ylower"                   },
1606   { TYPE_INTEGER, &sii.joy.snap,        ".joy.snap_field"               },
1607   { TYPE_INTEGER, &sii.joy.bomb,        ".joy.place_bomb"               },
1608   { TYPE_KEY_X11, &sii.key.left,        ".key.move_left"                },
1609   { TYPE_KEY_X11, &sii.key.right,       ".key.move_right"               },
1610   { TYPE_KEY_X11, &sii.key.up,          ".key.move_up"                  },
1611   { TYPE_KEY_X11, &sii.key.down,        ".key.move_down"                },
1612   { TYPE_KEY_X11, &sii.key.snap,        ".key.snap_field"               },
1613   { TYPE_KEY_X11, &sii.key.bomb,        ".key.place_bomb"               }
1614 };
1615
1616 static struct TokenInfo system_setup_tokens[] =
1617 {
1618   { TYPE_STRING,  &syi.sdl_audiodriver, "system.sdl_audiodriver"        },
1619   { TYPE_INTEGER, &syi.audio_fragment_size,"system.audio_fragment_size" }
1620 };
1621
1622 static struct TokenInfo options_setup_tokens[] =
1623 {
1624   { TYPE_BOOLEAN, &soi.verbose,         "options.verbose"               }
1625 };
1626
1627 static char *get_corrected_login_name(char *login_name)
1628 {
1629   /* needed because player name must be a fixed length string */
1630   char *login_name_new = checked_malloc(MAX_PLAYER_NAME_LEN + 1);
1631
1632   strncpy(login_name_new, login_name, MAX_PLAYER_NAME_LEN);
1633   login_name_new[MAX_PLAYER_NAME_LEN] = '\0';
1634
1635   if (strlen(login_name) > MAX_PLAYER_NAME_LEN)         /* name has been cut */
1636     if (strchr(login_name_new, ' '))
1637       *strchr(login_name_new, ' ') = '\0';
1638
1639   return login_name_new;
1640 }
1641
1642 static void setSetupInfoToDefaults(struct SetupInfo *si)
1643 {
1644   int i;
1645
1646   si->player_name = get_corrected_login_name(getLoginName());
1647
1648   si->sound = TRUE;
1649   si->sound_loops = TRUE;
1650   si->sound_music = TRUE;
1651   si->sound_simple = TRUE;
1652   si->toons = TRUE;
1653   si->double_buffering = TRUE;
1654   si->direct_draw = !si->double_buffering;
1655   si->scroll_delay = TRUE;
1656   si->soft_scrolling = TRUE;
1657   si->fading = FALSE;
1658   si->autorecord = TRUE;
1659   si->quick_doors = FALSE;
1660   si->team_mode = FALSE;
1661   si->handicap = TRUE;
1662   si->time_limit = TRUE;
1663   si->fullscreen = FALSE;
1664   si->ask_on_escape = TRUE;
1665
1666   si->graphics_set = getStringCopy(GRAPHICS_SUBDIR);
1667   si->sounds_set = getStringCopy(SOUNDS_SUBDIR);
1668   si->music_set = getStringCopy(MUSIC_SUBDIR);
1669   si->override_level_graphics = FALSE;
1670   si->override_level_sounds = FALSE;
1671   si->override_level_music = FALSE;
1672
1673   si->editor.el_boulderdash = TRUE;
1674   si->editor.el_emerald_mine = TRUE;
1675   si->editor.el_more = TRUE;
1676   si->editor.el_sokoban = TRUE;
1677   si->editor.el_supaplex = TRUE;
1678   si->editor.el_diamond_caves = TRUE;
1679   si->editor.el_dx_boulderdash = TRUE;
1680   si->editor.el_chars = TRUE;
1681   si->editor.el_custom = TRUE;
1682
1683   si->shortcut.save_game = DEFAULT_KEY_SAVE_GAME;
1684   si->shortcut.load_game = DEFAULT_KEY_LOAD_GAME;
1685   si->shortcut.toggle_pause = DEFAULT_KEY_TOGGLE_PAUSE;
1686
1687   for (i=0; i<MAX_PLAYERS; i++)
1688   {
1689     si->input[i].use_joystick = FALSE;
1690     si->input[i].joy.device_name=getStringCopy(getDeviceNameFromJoystickNr(i));
1691     si->input[i].joy.xleft   = JOYSTICK_XLEFT;
1692     si->input[i].joy.xmiddle = JOYSTICK_XMIDDLE;
1693     si->input[i].joy.xright  = JOYSTICK_XRIGHT;
1694     si->input[i].joy.yupper  = JOYSTICK_YUPPER;
1695     si->input[i].joy.ymiddle = JOYSTICK_YMIDDLE;
1696     si->input[i].joy.ylower  = JOYSTICK_YLOWER;
1697     si->input[i].joy.snap  = (i == 0 ? JOY_BUTTON_1 : 0);
1698     si->input[i].joy.bomb  = (i == 0 ? JOY_BUTTON_2 : 0);
1699     si->input[i].key.left  = (i == 0 ? DEFAULT_KEY_LEFT  : KSYM_UNDEFINED);
1700     si->input[i].key.right = (i == 0 ? DEFAULT_KEY_RIGHT : KSYM_UNDEFINED);
1701     si->input[i].key.up    = (i == 0 ? DEFAULT_KEY_UP    : KSYM_UNDEFINED);
1702     si->input[i].key.down  = (i == 0 ? DEFAULT_KEY_DOWN  : KSYM_UNDEFINED);
1703     si->input[i].key.snap  = (i == 0 ? DEFAULT_KEY_SNAP  : KSYM_UNDEFINED);
1704     si->input[i].key.bomb  = (i == 0 ? DEFAULT_KEY_BOMB  : KSYM_UNDEFINED);
1705   }
1706
1707   si->system.sdl_audiodriver = getStringCopy(ARG_DEFAULT);
1708   si->system.audio_fragment_size = DEFAULT_AUDIO_FRAGMENT_SIZE;
1709
1710   si->options.verbose = FALSE;
1711 }
1712
1713 static void decodeSetupFileHash(SetupFileHash *setup_file_hash)
1714 {
1715   int i, pnr;
1716
1717   if (!setup_file_hash)
1718     return;
1719
1720   /* global setup */
1721   si = setup;
1722   for (i=0; i<NUM_GLOBAL_SETUP_TOKENS; i++)
1723     setSetupInfo(global_setup_tokens, i,
1724                  getHashEntry(setup_file_hash, global_setup_tokens[i].text));
1725   setup = si;
1726
1727   /* editor setup */
1728   sei = setup.editor;
1729   for (i=0; i<NUM_EDITOR_SETUP_TOKENS; i++)
1730     setSetupInfo(editor_setup_tokens, i,
1731                  getHashEntry(setup_file_hash,editor_setup_tokens[i].text));
1732   setup.editor = sei;
1733
1734   /* shortcut setup */
1735   ssi = setup.shortcut;
1736   for (i=0; i<NUM_SHORTCUT_SETUP_TOKENS; i++)
1737     setSetupInfo(shortcut_setup_tokens, i,
1738                  getHashEntry(setup_file_hash,shortcut_setup_tokens[i].text));
1739   setup.shortcut = ssi;
1740
1741   /* player setup */
1742   for (pnr=0; pnr<MAX_PLAYERS; pnr++)
1743   {
1744     char prefix[30];
1745
1746     sprintf(prefix, "%s%d", TOKEN_STR_PLAYER_PREFIX, pnr + 1);
1747
1748     sii = setup.input[pnr];
1749     for (i=0; i<NUM_PLAYER_SETUP_TOKENS; i++)
1750     {
1751       char full_token[100];
1752
1753       sprintf(full_token, "%s%s", prefix, player_setup_tokens[i].text);
1754       setSetupInfo(player_setup_tokens, i,
1755                    getHashEntry(setup_file_hash, full_token));
1756     }
1757     setup.input[pnr] = sii;
1758   }
1759
1760   /* system setup */
1761   syi = setup.system;
1762   for (i=0; i<NUM_SYSTEM_SETUP_TOKENS; i++)
1763     setSetupInfo(system_setup_tokens, i,
1764                  getHashEntry(setup_file_hash, system_setup_tokens[i].text));
1765   setup.system = syi;
1766
1767   /* options setup */
1768   soi = setup.options;
1769   for (i=0; i<NUM_OPTIONS_SETUP_TOKENS; i++)
1770     setSetupInfo(options_setup_tokens, i,
1771                  getHashEntry(setup_file_hash, options_setup_tokens[i].text));
1772   setup.options = soi;
1773 }
1774
1775 void LoadSetup()
1776 {
1777   char *filename = getSetupFilename();
1778   SetupFileHash *setup_file_hash = NULL;
1779
1780   /* always start with reliable default values */
1781   setSetupInfoToDefaults(&setup);
1782
1783   setup_file_hash = loadSetupFileHash(filename);
1784
1785   if (setup_file_hash)
1786   {
1787     char *player_name_new;
1788
1789     checkSetupFileHashIdentifier(setup_file_hash, getCookie("SETUP"));
1790     decodeSetupFileHash(setup_file_hash);
1791
1792     setup.direct_draw = !setup.double_buffering;
1793
1794     freeSetupFileHash(setup_file_hash);
1795
1796     /* needed to work around problems with fixed length strings */
1797     player_name_new = get_corrected_login_name(setup.player_name);
1798     free(setup.player_name);
1799     setup.player_name = player_name_new;
1800   }
1801   else
1802     Error(ERR_WARN, "using default setup values");
1803 }
1804
1805 void SaveSetup()
1806 {
1807   char *filename = getSetupFilename();
1808   FILE *file;
1809   int i, pnr;
1810
1811   InitUserDataDirectory();
1812
1813   if (!(file = fopen(filename, MODE_WRITE)))
1814   {
1815     Error(ERR_WARN, "cannot write setup file '%s'", filename);
1816     return;
1817   }
1818
1819   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1820                                                getCookie("SETUP")));
1821   fprintf(file, "\n");
1822
1823   /* global setup */
1824   si = setup;
1825   for (i=0; i<NUM_GLOBAL_SETUP_TOKENS; i++)
1826   {
1827     /* just to make things nicer :) */
1828     if (i == SETUP_TOKEN_PLAYER_NAME + 1 ||
1829         i == SETUP_TOKEN_GRAPHICS_SET)
1830       fprintf(file, "\n");
1831
1832     fprintf(file, "%s\n", getSetupLine(global_setup_tokens, "", i));
1833   }
1834
1835   /* editor setup */
1836   sei = setup.editor;
1837   fprintf(file, "\n");
1838   for (i=0; i<NUM_EDITOR_SETUP_TOKENS; i++)
1839     fprintf(file, "%s\n", getSetupLine(editor_setup_tokens, "", i));
1840
1841   /* shortcut setup */
1842   ssi = setup.shortcut;
1843   fprintf(file, "\n");
1844   for (i=0; i<NUM_SHORTCUT_SETUP_TOKENS; i++)
1845     fprintf(file, "%s\n", getSetupLine(shortcut_setup_tokens, "", i));
1846
1847   /* player setup */
1848   for (pnr=0; pnr<MAX_PLAYERS; pnr++)
1849   {
1850     char prefix[30];
1851
1852     sprintf(prefix, "%s%d", TOKEN_STR_PLAYER_PREFIX, pnr + 1);
1853     fprintf(file, "\n");
1854
1855     sii = setup.input[pnr];
1856     for (i=0; i<NUM_PLAYER_SETUP_TOKENS; i++)
1857       fprintf(file, "%s\n", getSetupLine(player_setup_tokens, prefix, i));
1858   }
1859
1860   /* system setup */
1861   syi = setup.system;
1862   fprintf(file, "\n");
1863   for (i=0; i<NUM_SYSTEM_SETUP_TOKENS; i++)
1864     fprintf(file, "%s\n", getSetupLine(system_setup_tokens, "", i));
1865
1866   /* options setup */
1867   soi = setup.options;
1868   fprintf(file, "\n");
1869   for (i=0; i<NUM_OPTIONS_SETUP_TOKENS; i++)
1870     fprintf(file, "%s\n", getSetupLine(options_setup_tokens, "", i));
1871
1872   fclose(file);
1873
1874   SetFilePermissions(filename, PERMS_PRIVATE);
1875 }
1876
1877 void LoadCustomElementDescriptions()
1878 {
1879   char *filename = getCustomArtworkConfigFilename(ARTWORK_TYPE_GRAPHICS);
1880   SetupFileHash *setup_file_hash;
1881   int i;
1882
1883   for (i=0; i<NUM_FILE_ELEMENTS; i++)
1884   {
1885     if (element_info[i].custom_description != NULL)
1886     {
1887       free(element_info[i].custom_description);
1888       element_info[i].custom_description = NULL;
1889     }
1890   }
1891
1892   if ((setup_file_hash = loadSetupFileHash(filename)) == NULL)
1893     return;
1894
1895   for (i=0; i<NUM_FILE_ELEMENTS; i++)
1896   {
1897     char *token = getStringCat2(element_info[i].token_name, ".name");
1898     char *value = getHashEntry(setup_file_hash, token);
1899
1900     if (value != NULL)
1901       element_info[i].custom_description = getStringCopy(value);
1902
1903     free(token);
1904   }
1905
1906   freeSetupFileHash(setup_file_hash);
1907 }
1908
1909 void LoadSpecialMenuDesignSettings()
1910 {
1911   char *filename = getCustomArtworkConfigFilename(ARTWORK_TYPE_GRAPHICS);
1912   SetupFileHash *setup_file_hash;
1913   int i, j;
1914
1915   /* always start with reliable default values from default config */
1916   for (i=0; image_config_vars[i].token != NULL; i++)
1917     for (j=0; image_config[j].token != NULL; j++)
1918       if (strcmp(image_config_vars[i].token, image_config[j].token) == 0)
1919         *image_config_vars[i].value =
1920           get_integer_from_string(image_config[j].value);
1921
1922   if ((setup_file_hash = loadSetupFileHash(filename)) == NULL)
1923     return;
1924
1925   /* special case: initialize with default values that may be overwrittem */
1926   for (i=0; i < NUM_SPECIAL_GFX_ARGS; i++)
1927   {
1928     char *value_x = getHashEntry(setup_file_hash, "menu.draw_xoffset");
1929     char *value_y = getHashEntry(setup_file_hash, "menu.draw_yoffset");
1930
1931     if (value_x != NULL)
1932       menu.draw_xoffset[i] = get_integer_from_string(value_x);
1933     if (value_y != NULL)
1934       menu.draw_yoffset[i] = get_integer_from_string(value_y);
1935   }
1936
1937   /* read (and overwrite with) values that may be specified in config file */
1938   for (i=0; image_config_vars[i].token != NULL; i++)
1939   {
1940     char *value = getHashEntry(setup_file_hash, image_config_vars[i].token);
1941
1942     if (value != NULL)
1943       *image_config_vars[i].value = get_integer_from_string(value);
1944   }
1945
1946   freeSetupFileHash(setup_file_hash);
1947 }