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