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