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