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