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