42f7a9930dad5b2ca4fe93ad44a10f5078fb7548
[rocksndiamonds.git] / src / files.c
1 /***********************************************************
2 *  Rocks'n'Diamonds -- McDuffin Strikes Back!              *
3 *----------------------------------------------------------*
4 *  (c) 1995-98 Artsoft Entertainment                       *
5 *              Holger Schemel                              *
6 *              Oststrasse 11a                              *
7 *              33604 Bielefeld                             *
8 *              phone: ++49 +521 290471                     *
9 *              email: aeglos@valinor.owl.de                *
10 *----------------------------------------------------------*
11 *  files.h                                                 *
12 ***********************************************************/
13
14 #include <ctype.h>
15 #include <dirent.h>
16 #include <sys/stat.h>
17
18 #include "files.h"
19 #include "tools.h"
20 #include "misc.h"
21 #include "tape.h"
22 #include "joystick.h"
23
24 #define MAX_FILENAME_LEN        256     /* maximal filename length */
25 #define MAX_LINE_LEN            1000    /* maximal input line length */
26 #define CHUNK_ID_LEN            4       /* IFF style chunk id length */
27 #define LEVEL_HEADER_SIZE       80      /* size of level file header */
28 #define LEVEL_HEADER_UNUSED     15      /* unused level header bytes */
29 #define TAPE_HEADER_SIZE        20      /* size of tape file header */
30 #define TAPE_HEADER_UNUSED      7       /* unused tape header bytes */
31 #define FILE_VERSION_1_0        10      /* 1.0 file version (old) */
32 #define FILE_VERSION_1_2        12      /* 1.2 file version (still in use) */
33 #define FILE_VERSION_1_4        14      /* 1.4 file version (new) */
34
35 /* file identifier strings */
36 #define LEVEL_COOKIE            "ROCKSNDIAMONDS_LEVEL_FILE_VERSION_1.4"
37 #define SCORE_COOKIE            "ROCKSNDIAMONDS_SCORE_FILE_VERSION_1.2"
38 #define TAPE_COOKIE             "ROCKSNDIAMONDS_TAPE_FILE_VERSION_1.2"
39 #define SETUP_COOKIE            "ROCKSNDIAMONDS_SETUP_FILE_VERSION_1.2"
40 #define LEVELSETUP_COOKIE       "ROCKSNDIAMONDS_LEVELSETUP_FILE_VERSION_1.2"
41 #define LEVELINFO_COOKIE        "ROCKSNDIAMONDS_LEVELINFO_FILE_VERSION_1.2"
42 /* old file identifiers for backward compatibility */
43 #define LEVEL_COOKIE_10         "ROCKSNDIAMONDS_LEVEL_FILE_VERSION_1.0"
44 #define LEVEL_COOKIE_12         "ROCKSNDIAMONDS_LEVEL_FILE_VERSION_1.2"
45 #define TAPE_COOKIE_10          "ROCKSNDIAMONDS_LEVELREC_FILE_VERSION_1.0"
46
47 /* file names and filename extensions */
48 #if !defined(PLATFORM_MSDOS)
49 #define USERDATA_DIRECTORY      ".rocksndiamonds"
50 #define LEVELSETUP_DIRECTORY    "levelsetup"
51 #define SETUP_FILENAME          "setup.conf"
52 #define LEVELSETUP_FILENAME     "levelsetup.conf"
53 #define LEVELINFO_FILENAME      "levelinfo.conf"
54 #define LEVELFILE_EXTENSION     "level"
55 #define TAPEFILE_EXTENSION      "tape"
56 #define SCOREFILE_EXTENSION     "score"
57 #else
58 #define USERDATA_DIRECTORY      "userdata"
59 #define LEVELSETUP_DIRECTORY    "lvlsetup"
60 #define SETUP_FILENAME          "setup.cnf"
61 #define LEVELSETUP_FILENAME     "lvlsetup.cnf"
62 #define LEVELINFO_FILENAME      "lvlinfo.cnf"
63 #define LEVELFILE_EXTENSION     "lvl"
64 #define TAPEFILE_EXTENSION      "tap"
65 #define SCOREFILE_EXTENSION     "sco"
66 #endif
67
68 #if !defined(PLATFORM_UNIX)
69 #define ERROR_FILENAME          "error.out"
70 #endif
71
72 #if defined(PLATFORM_WIN32)
73 #ifndef S_IRGRP
74 #define S_IRGRP S_IRUSR
75 #endif
76 #ifndef S_IROTH
77 #define S_IROTH S_IRUSR
78 #endif
79 #ifndef S_IWGRP
80 #define S_IWGRP S_IWUSR
81 #endif
82 #ifndef S_IWOTH
83 #define S_IWOTH S_IWUSR
84 #endif
85 #ifndef S_IXGRP
86 #define S_IXGRP S_IXUSR
87 #endif
88 #ifndef S_IXOTH
89 #define S_IXOTH S_IXUSR
90 #endif
91 #endif  /* PLATFORM_WIN32 */
92
93 /* file permissions for newly written files */
94 #define MODE_R_ALL              (S_IRUSR | S_IRGRP | S_IROTH)
95 #define MODE_W_ALL              (S_IWUSR | S_IWGRP | S_IWOTH)
96 #define MODE_X_ALL              (S_IXUSR | S_IXGRP | S_IXOTH)
97 #define USERDATA_DIR_MODE       (MODE_R_ALL | MODE_X_ALL | S_IWUSR)
98 #define LEVEL_PERMS             (MODE_R_ALL | MODE_W_ALL)
99 #define SCORE_PERMS             LEVEL_PERMS
100 #define TAPE_PERMS              LEVEL_PERMS
101 #define SETUP_PERMS             LEVEL_PERMS
102
103 /* sort priorities of level series (also used as level series classes) */
104 #define LEVELCLASS_TUTORIAL_START       10
105 #define LEVELCLASS_TUTORIAL_END         99
106 #define LEVELCLASS_CLASSICS_START       100
107 #define LEVELCLASS_CLASSICS_END         199
108 #define LEVELCLASS_CONTRIBUTION_START   200
109 #define LEVELCLASS_CONTRIBUTION_END     299
110 #define LEVELCLASS_USER_START           300
111 #define LEVELCLASS_USER_END             399
112 #define LEVELCLASS_BD_START             400
113 #define LEVELCLASS_BD_END               499
114 #define LEVELCLASS_EM_START             500
115 #define LEVELCLASS_EM_END               599
116 #define LEVELCLASS_SP_START             600
117 #define LEVELCLASS_SP_END               699
118 #define LEVELCLASS_DX_START             700
119 #define LEVELCLASS_DX_END               799
120
121 #define LEVELCLASS_TUTORIAL             LEVELCLASS_TUTORIAL_START
122 #define LEVELCLASS_CLASSICS             LEVELCLASS_CLASSICS_START
123 #define LEVELCLASS_CONTRIBUTION         LEVELCLASS_CONTRIBUTION_START
124 #define LEVELCLASS_USER                 LEVELCLASS_USER_START
125 #define LEVELCLASS_BD                   LEVELCLASS_BD_START
126 #define LEVELCLASS_EM                   LEVELCLASS_EM_START
127 #define LEVELCLASS_SP                   LEVELCLASS_SP_START
128 #define LEVELCLASS_DX                   LEVELCLASS_DX_START
129
130 #define LEVELCLASS_UNDEFINED            999
131
132 #define NUM_LEVELCLASS_DESC     8
133 char *levelclass_desc[NUM_LEVELCLASS_DESC] =
134 {
135   "Tutorial Levels",
136   "Classic Originals",
137   "Contributions",
138   "Private Levels",
139   "Boulderdash",
140   "Emerald Mine",
141   "Supaplex",
142   "DX Boulderdash"
143 };
144
145 #define IS_LEVELCLASS_TUTORIAL(p) \
146         ((p)->sort_priority >= LEVELCLASS_TUTORIAL_START && \
147          (p)->sort_priority <= LEVELCLASS_TUTORIAL_END)
148 #define IS_LEVELCLASS_CLASSICS(p) \
149         ((p)->sort_priority >= LEVELCLASS_CLASSICS_START && \
150          (p)->sort_priority <= LEVELCLASS_CLASSICS_END)
151 #define IS_LEVELCLASS_CONTRIBUTION(p) \
152         ((p)->sort_priority >= LEVELCLASS_CONTRIBUTION_START && \
153          (p)->sort_priority <= LEVELCLASS_CONTRIBUTION_END)
154 #define IS_LEVELCLASS_USER(p) \
155         ((p)->sort_priority >= LEVELCLASS_USER_START && \
156          (p)->sort_priority <= LEVELCLASS_USER_END)
157 #define IS_LEVELCLASS_BD(p) \
158         ((p)->sort_priority >= LEVELCLASS_BD_START && \
159          (p)->sort_priority <= LEVELCLASS_BD_END)
160 #define IS_LEVELCLASS_EM(p) \
161         ((p)->sort_priority >= LEVELCLASS_EM_START && \
162          (p)->sort_priority <= LEVELCLASS_EM_END)
163 #define IS_LEVELCLASS_SP(p) \
164         ((p)->sort_priority >= LEVELCLASS_SP_START && \
165          (p)->sort_priority <= LEVELCLASS_SP_END)
166 #define IS_LEVELCLASS_DX(p) \
167         ((p)->sort_priority >= LEVELCLASS_DX_START && \
168          (p)->sort_priority <= LEVELCLASS_DX_END)
169
170 #define LEVELCLASS(n)   (IS_LEVELCLASS_TUTORIAL(n) ? LEVELCLASS_TUTORIAL : \
171                          IS_LEVELCLASS_CLASSICS(n) ? LEVELCLASS_CLASSICS : \
172                          IS_LEVELCLASS_CONTRIBUTION(n) ? LEVELCLASS_CONTRIBUTION : \
173                          IS_LEVELCLASS_USER(n) ? LEVELCLASS_USER : \
174                          IS_LEVELCLASS_BD(n) ? LEVELCLASS_BD : \
175                          IS_LEVELCLASS_EM(n) ? LEVELCLASS_EM : \
176                          IS_LEVELCLASS_SP(n) ? LEVELCLASS_SP : \
177                          IS_LEVELCLASS_DX(n) ? LEVELCLASS_DX : \
178                          LEVELCLASS_UNDEFINED)
179
180 #define LEVELCOLOR(n)   (IS_LEVELCLASS_TUTORIAL(n) ?            FC_BLUE : \
181                          IS_LEVELCLASS_CLASSICS(n) ?            FC_RED : \
182                          IS_LEVELCLASS_BD(n) ?                  FC_GREEN : \
183                          IS_LEVELCLASS_EM(n) ?                  FC_YELLOW : \
184                          IS_LEVELCLASS_SP(n) ?                  FC_GREEN : \
185                          IS_LEVELCLASS_DX(n) ?                  FC_YELLOW : \
186                          IS_LEVELCLASS_CONTRIBUTION(n) ?        FC_GREEN : \
187                          IS_LEVELCLASS_USER(n) ?                FC_RED : \
188                          FC_BLUE)
189
190 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ?            0 : \
191                          IS_LEVELCLASS_CLASSICS(n) ?            1 : \
192                          IS_LEVELCLASS_BD(n) ?                  2 : \
193                          IS_LEVELCLASS_EM(n) ?                  3 : \
194                          IS_LEVELCLASS_SP(n) ?                  4 : \
195                          IS_LEVELCLASS_DX(n) ?                  5 : \
196                          IS_LEVELCLASS_CONTRIBUTION(n) ?        6 : \
197                          IS_LEVELCLASS_USER(n) ?                7 : \
198                          9)
199
200 char *getLevelClassDescription(struct LevelDirInfo *ldi)
201 {
202   int position = ldi->sort_priority / 100;
203
204   if (position >= 0 && position < NUM_LEVELCLASS_DESC)
205     return levelclass_desc[position];
206   else
207     return "Unknown Level Class";
208 }
209
210 static void SaveUserLevelInfo();                /* for 'InitUserLevelDir()' */
211 static char *getSetupLine(char *, int);         /* for 'SaveUserLevelInfo()' */
212
213 char *getUserDataDir()
214 {
215   static char *userdata_dir = NULL;
216
217   if (!userdata_dir)
218   {
219     char *home_dir = getHomeDir();
220     char *data_dir = USERDATA_DIRECTORY;
221
222     userdata_dir = getPath2(home_dir, data_dir);
223   }
224
225   return userdata_dir;
226 }
227
228 static char *getSetupDir()
229 {
230   return getUserDataDir();
231 }
232
233 static char *getUserLevelDir(char *level_subdir)
234 {
235   static char *userlevel_dir = NULL;
236   char *data_dir = getUserDataDir();
237   char *userlevel_subdir = LEVELS_DIRECTORY;
238
239   if (userlevel_dir)
240     free(userlevel_dir);
241
242   if (strlen(level_subdir) > 0)
243     userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
244   else
245     userlevel_dir = getPath2(data_dir, userlevel_subdir);
246
247   return userlevel_dir;
248 }
249
250 static char *getTapeDir(char *level_subdir)
251 {
252   static char *tape_dir = NULL;
253   char *data_dir = getUserDataDir();
254   char *tape_subdir = TAPES_DIRECTORY;
255
256   if (tape_dir)
257     free(tape_dir);
258
259   if (strlen(level_subdir) > 0)
260     tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
261   else
262     tape_dir = getPath2(data_dir, tape_subdir);
263
264   return tape_dir;
265 }
266
267 static char *getScoreDir(char *level_subdir)
268 {
269   static char *score_dir = NULL;
270   char *data_dir = options.rw_base_directory;
271   char *score_subdir = SCORES_DIRECTORY;
272
273   if (score_dir)
274     free(score_dir);
275
276   if (strlen(level_subdir) > 0)
277     score_dir = getPath3(data_dir, score_subdir, level_subdir);
278   else
279     score_dir = getPath2(data_dir, score_subdir);
280
281   return score_dir;
282 }
283
284 static char *getLevelSetupDir(char *level_subdir)
285 {
286   static char *levelsetup_dir = NULL;
287   char *data_dir = getUserDataDir();
288   char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
289
290   if (levelsetup_dir)
291     free(levelsetup_dir);
292
293   if (strlen(level_subdir) > 0)
294     levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
295   else
296     levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
297
298   return levelsetup_dir;
299 }
300
301 static char *getLevelFilename(int nr)
302 {
303   static char *filename = NULL;
304   char basename[MAX_FILENAME_LEN];
305
306   if (filename != NULL)
307     free(filename);
308
309   sprintf(basename, "%03d.%s", nr, LEVELFILE_EXTENSION);
310   filename = getPath3((leveldir_current->user_defined ?
311                        getUserLevelDir("") :
312                        options.level_directory),
313                       leveldir_current->fullpath,
314                       basename);
315
316   return filename;
317 }
318
319 static char *getTapeFilename(int nr)
320 {
321   static char *filename = NULL;
322   char basename[MAX_FILENAME_LEN];
323
324   if (filename != NULL)
325     free(filename);
326
327   sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
328   filename = getPath2(getTapeDir(leveldir_current->filename), basename);
329
330   return filename;
331 }
332
333 static char *getScoreFilename(int nr)
334 {
335   static char *filename = NULL;
336   char basename[MAX_FILENAME_LEN];
337
338   if (filename != NULL)
339     free(filename);
340
341   sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
342   filename = getPath2(getScoreDir(leveldir_current->filename), basename);
343
344   return filename;
345 }
346
347 static void createDirectory(char *dir, char *text)
348 {
349   if (access(dir, F_OK) != 0)
350 #if defined(PLATFORM_WIN32)
351     if (mkdir(dir) != 0)
352 #else
353     if (mkdir(dir, USERDATA_DIR_MODE) != 0)
354 #endif
355       Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
356 }
357
358 static void InitUserDataDirectory()
359 {
360   createDirectory(getUserDataDir(), "user data");
361 }
362
363 static void InitTapeDirectory(char *level_subdir)
364 {
365   createDirectory(getUserDataDir(), "user data");
366   createDirectory(getTapeDir(""), "main tape");
367   createDirectory(getTapeDir(level_subdir), "level tape");
368 }
369
370 static void InitScoreDirectory(char *level_subdir)
371 {
372   createDirectory(getScoreDir(""), "main score");
373   createDirectory(getScoreDir(level_subdir), "level score");
374 }
375
376 static void InitUserLevelDirectory(char *level_subdir)
377 {
378   if (access(getUserLevelDir(level_subdir), F_OK) != 0)
379   {
380     createDirectory(getUserDataDir(), "user data");
381     createDirectory(getUserLevelDir(""), "main user level");
382     createDirectory(getUserLevelDir(level_subdir), "user level");
383
384     SaveUserLevelInfo();
385   }
386 }
387
388 static void InitLevelSetupDirectory(char *level_subdir)
389 {
390   createDirectory(getUserDataDir(), "user data");
391   createDirectory(getLevelSetupDir(""), "main level setup");
392   createDirectory(getLevelSetupDir(level_subdir), "level setup");
393 }
394
395 static void setLevelInfoToDefaults()
396 {
397   int i, x, y;
398
399   lev_fieldx = level.fieldx = STD_LEV_FIELDX;
400   lev_fieldy = level.fieldy = STD_LEV_FIELDY;
401
402   for(x=0; x<MAX_LEV_FIELDX; x++)
403     for(y=0; y<MAX_LEV_FIELDY; y++)
404       Feld[x][y] = Ur[x][y] = EL_ERDREICH;
405
406   level.time = 100;
407   level.gems_needed = 0;
408   level.amoeba_speed = 10;
409   level.time_magic_wall = 10;
410   level.time_wheel = 10;
411   level.time_light = 10;
412   level.time_timegate = 10;
413   level.amoeba_content = EL_DIAMANT;
414   level.double_speed = FALSE;
415   level.gravity = FALSE;
416
417   for(i=0; i<MAX_LEVEL_NAME_LEN; i++)
418     level.name[i] = '\0';
419   for(i=0; i<MAX_LEVEL_AUTHOR_LEN; i++)
420     level.author[i] = '\0';
421
422   strcpy(level.name, NAMELESS_LEVEL_NAME);
423   strcpy(level.author, ANONYMOUS_NAME);
424
425   for(i=0; i<LEVEL_SCORE_ELEMENTS; i++)
426     level.score[i] = 10;
427
428   level.num_yam_contents = STD_ELEMENT_CONTENTS;
429   for(i=0; i<MAX_ELEMENT_CONTENTS; i++)
430     for(x=0; x<3; x++)
431       for(y=0; y<3; y++)
432         level.yam_content[i][x][y] = EL_FELSBROCKEN;
433
434   Feld[0][0] = Ur[0][0] = EL_SPIELFIGUR;
435   Feld[STD_LEV_FIELDX-1][STD_LEV_FIELDY-1] =
436     Ur[STD_LEV_FIELDX-1][STD_LEV_FIELDY-1] = EL_AUSGANG_ZU;
437
438   BorderElement = EL_BETON;
439
440   /* try to determine better author name than 'anonymous' */
441   if (strcmp(leveldir_current->author, ANONYMOUS_NAME) != 0)
442   {
443     strncpy(level.author, leveldir_current->author, MAX_LEVEL_AUTHOR_LEN);
444     level.author[MAX_LEVEL_AUTHOR_LEN] = '\0';
445   }
446   else
447   {
448     switch (LEVELCLASS(leveldir_current))
449     {
450       case LEVELCLASS_TUTORIAL:
451         strcpy(level.author, PROGRAM_AUTHOR_STRING);
452         break;
453
454       case LEVELCLASS_CONTRIBUTION:
455         strncpy(level.author, leveldir_current->name,MAX_LEVEL_AUTHOR_LEN);
456         level.author[MAX_LEVEL_AUTHOR_LEN] = '\0';
457         break;
458
459       case LEVELCLASS_USER:
460         strncpy(level.author, getRealName(), MAX_LEVEL_AUTHOR_LEN);
461         level.author[MAX_LEVEL_AUTHOR_LEN] = '\0';
462         break;
463
464       default:
465         /* keep default value */
466         break;
467     }
468   }
469 }
470
471 static int checkLevelElement(int element)
472 {
473   if (element >= EL_FIRST_RUNTIME_EL)
474   {
475     Error(ERR_WARN, "invalid level element %d", element);
476     element = EL_CHAR_FRAGE;
477   }
478
479   return element;
480 }
481
482 void LoadLevel(int level_nr)
483 {
484   int i, x, y;
485   char *filename = getLevelFilename(level_nr);
486   char cookie[MAX_LINE_LEN];
487   char chunk[CHUNK_ID_LEN + 1];
488   boolean encoding_16bit = FALSE;       /* default: maximal 256 elements */
489   int file_version = FILE_VERSION_1_4;  /* last version of level files */
490   int chunk_length;
491   FILE *file;
492
493   /* always start with reliable default values */
494   setLevelInfoToDefaults();
495
496   if (!(file = fopen(filename, "r")))
497   {
498     Error(ERR_WARN, "cannot read level '%s' - creating new level", filename);
499     return;
500   }
501
502   /* check file identifier */
503   fgets(cookie, MAX_LINE_LEN, file);
504   if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n')
505     cookie[strlen(cookie) - 1] = '\0';
506
507   if (strcmp(cookie, LEVEL_COOKIE_10) == 0)     /* old 1.0 level format */
508     file_version = FILE_VERSION_1_0;
509   else if (strcmp(cookie, LEVEL_COOKIE_12) == 0)/* 1.2 (8 bit) level format */
510     file_version = FILE_VERSION_1_2;
511   else if (strcmp(cookie, LEVEL_COOKIE) != 0)   /* unknown level format */
512   {
513     Error(ERR_WARN, "wrong file identifier of level file '%s'", filename);
514     fclose(file);
515     return;
516   }
517
518   /* read chunk "HEAD" */
519   if (file_version >= FILE_VERSION_1_2)
520   {
521     getFileChunk(file, chunk, &chunk_length, BYTE_ORDER_BIG_ENDIAN);
522     if (strcmp(chunk, "HEAD") || chunk_length != LEVEL_HEADER_SIZE)
523     {
524       Error(ERR_WARN, "wrong 'HEAD' chunk of level file '%s'", filename);
525       fclose(file);
526       return;
527     }
528   }
529
530   lev_fieldx = level.fieldx = fgetc(file);
531   lev_fieldy = level.fieldy = fgetc(file);
532
533   level.time        = getFile16BitInteger(file, BYTE_ORDER_BIG_ENDIAN);
534   level.gems_needed = getFile16BitInteger(file, BYTE_ORDER_BIG_ENDIAN);
535
536   for(i=0; i<MAX_LEVEL_NAME_LEN; i++)
537     level.name[i] = fgetc(file);
538   level.name[MAX_LEVEL_NAME_LEN] = 0;
539
540   for(i=0; i<LEVEL_SCORE_ELEMENTS; i++)
541     level.score[i] = fgetc(file);
542
543   level.num_yam_contents = STD_ELEMENT_CONTENTS;
544   for(i=0; i<MAX_ELEMENT_CONTENTS; i++)
545   {
546     for(y=0; y<3; y++)
547     {
548       for(x=0; x<3; x++)
549       {
550         if (i < STD_ELEMENT_CONTENTS)
551           level.yam_content[i][x][y] = checkLevelElement(fgetc(file));
552         else
553           level.yam_content[i][x][y] = EL_LEERRAUM;
554       }
555     }
556   }
557
558   level.amoeba_speed    = fgetc(file);
559   level.time_magic_wall = fgetc(file);
560   level.time_wheel      = fgetc(file);
561   level.amoeba_content  = checkLevelElement(fgetc(file));
562   level.double_speed    = (fgetc(file) == 1 ? TRUE : FALSE);
563   level.gravity         = (fgetc(file) == 1 ? TRUE : FALSE);
564
565   encoding_16bit        = (fgetc(file) == 1 ? TRUE : FALSE);
566
567   for(i=0; i<LEVEL_HEADER_UNUSED; i++)  /* skip unused header bytes */
568     fgetc(file);
569
570   if (file_version >= FILE_VERSION_1_2)
571   {
572     getFileChunk(file, chunk, &chunk_length, BYTE_ORDER_BIG_ENDIAN);
573
574     /* look for optional author chunk */
575     if (strcmp(chunk, "AUTH") == 0 && chunk_length == MAX_LEVEL_AUTHOR_LEN)
576     {
577       for(i=0; i<MAX_LEVEL_AUTHOR_LEN; i++)
578         level.author[i] = fgetc(file);
579       level.author[MAX_LEVEL_NAME_LEN] = 0;
580
581       getFileChunk(file, chunk, &chunk_length, BYTE_ORDER_BIG_ENDIAN);
582     }
583
584     /* look for optional content chunk */
585     if (strcmp(chunk, "CONT") == 0 &&
586         chunk_length == 4 + MAX_ELEMENT_CONTENTS * 3 * 3)
587     {
588       fgetc(file);
589       level.num_yam_contents = fgetc(file);
590       fgetc(file);
591       fgetc(file);
592
593       if (level.num_yam_contents < 1 ||
594           level.num_yam_contents > MAX_ELEMENT_CONTENTS)
595       {
596 #if DEBUG
597         printf("WARNING: num_yam_contents == %d (corrected)\n",
598                level.num_yam_contents);
599 #endif
600         level.num_yam_contents = STD_ELEMENT_CONTENTS;
601       }
602
603       for(i=0; i<MAX_ELEMENT_CONTENTS; i++)
604         for(y=0; y<3; y++)
605           for(x=0; x<3; x++)
606             level.yam_content[i][x][y] =
607               checkLevelElement(encoding_16bit ?
608                                 getFile16BitInteger(file,
609                                                     BYTE_ORDER_BIG_ENDIAN) :
610                                 fgetc(file));
611
612       getFileChunk(file, chunk, &chunk_length, BYTE_ORDER_BIG_ENDIAN);
613     }
614
615     /* next check body chunk identifier and chunk length */
616     if (strcmp(chunk, "BODY") || chunk_length != lev_fieldx * lev_fieldy)
617     {
618       Error(ERR_WARN, "wrong 'BODY' chunk of level file '%s'", filename);
619       fclose(file);
620       return;
621     }
622   }
623
624   /* clear all other level fields (needed if resized in level editor later) */
625   for(x=0; x<MAX_LEV_FIELDX; x++)
626     for(y=0; y<MAX_LEV_FIELDY; y++)
627       Feld[x][y] = Ur[x][y] = EL_LEERRAUM;
628
629   /* now read in the valid level fields from level file */
630   for(y=0; y<lev_fieldy; y++)
631     for(x=0; x<lev_fieldx; x++)
632       Feld[x][y] = Ur[x][y] =
633         checkLevelElement(encoding_16bit ?
634                           getFile16BitInteger(file, BYTE_ORDER_BIG_ENDIAN) :
635                           fgetc(file));
636
637   fclose(file);
638
639   /* player was faster than monsters in pre-1.0 levels */
640   if (file_version == FILE_VERSION_1_0 &&
641       IS_LEVELCLASS_CONTRIBUTION(leveldir_current))
642   {
643     Error(ERR_WARN, "level file '%s' has version number 1.0", filename);
644     Error(ERR_WARN, "using high speed movement for player");
645     level.double_speed = TRUE;
646   }
647
648   /* determine border element for this level */
649   SetBorderElement();
650 }
651
652 void SaveLevel(int level_nr)
653 {
654   int i, x, y;
655   char *filename = getLevelFilename(level_nr);
656   boolean encoding_16bit = FALSE;       /* default: maximal 256 elements */
657   char *oldest_possible_cookie;
658   FILE *file;
659
660   if (!(file = fopen(filename, "w")))
661   {
662     Error(ERR_WARN, "cannot save level file '%s'", filename);
663     return;
664   }
665
666   /* check yam content for 16-bit elements */
667   for(i=0; i<MAX_ELEMENT_CONTENTS; i++)
668     for(y=0; y<3; y++)
669       for(x=0; x<3; x++)
670         if (level.yam_content[i][x][y] > 255)
671           encoding_16bit = TRUE;
672
673   /* check level field for 16-bit elements */
674   for(y=0; y<lev_fieldy; y++) 
675     for(x=0; x<lev_fieldx; x++) 
676       if (Ur[x][y] > 255)
677         encoding_16bit = TRUE;
678
679   oldest_possible_cookie = (encoding_16bit ? LEVEL_COOKIE : LEVEL_COOKIE_12);
680
681   fputs(oldest_possible_cookie, file);          /* file identifier */
682   fputc('\n', file);
683
684   putFileChunk(file, "HEAD", LEVEL_HEADER_SIZE, BYTE_ORDER_BIG_ENDIAN);
685
686   fputc(level.fieldx, file);
687   fputc(level.fieldy, file);
688
689   putFile16BitInteger(file, level.time, BYTE_ORDER_BIG_ENDIAN);
690   putFile16BitInteger(file, level.gems_needed, BYTE_ORDER_BIG_ENDIAN);
691
692   for(i=0; i<MAX_LEVEL_NAME_LEN; i++)
693     fputc(level.name[i], file);
694   for(i=0; i<LEVEL_SCORE_ELEMENTS; i++)
695     fputc(level.score[i], file);
696   for(i=0; i<STD_ELEMENT_CONTENTS; i++)
697     for(y=0; y<3; y++)
698       for(x=0; x<3; x++)
699         fputc(encoding_16bit ? EL_LEERRAUM : level.yam_content[i][x][y], file);
700   fputc(level.amoeba_speed, file);
701   fputc(level.time_magic_wall, file);
702   fputc(level.time_wheel, file);
703   fputc(level.amoeba_content, file);
704   fputc((level.double_speed ? 1 : 0), file);
705   fputc((level.gravity ? 1 : 0), file);
706
707   fputc((encoding_16bit ? 1 : 0), file);
708
709   for(i=0; i<LEVEL_HEADER_UNUSED; i++)  /* set unused header bytes to zero */
710     fputc(0, file);
711
712   putFileChunk(file, "AUTH", MAX_LEVEL_AUTHOR_LEN, BYTE_ORDER_BIG_ENDIAN);
713
714   for(i=0; i<MAX_LEVEL_AUTHOR_LEN; i++)
715     fputc(level.author[i], file);
716
717   putFileChunk(file, "CONT", 4 + MAX_ELEMENT_CONTENTS * 3 * 3,
718                BYTE_ORDER_BIG_ENDIAN);
719
720   fputc(EL_MAMPFER, file);
721   fputc(level.num_yam_contents, file);
722   fputc(0, file);
723   fputc(0, file);
724
725   for(i=0; i<MAX_ELEMENT_CONTENTS; i++)
726     for(y=0; y<3; y++)
727       for(x=0; x<3; x++)
728         if (encoding_16bit)
729           putFile16BitInteger(file, level.yam_content[i][x][y],
730                               BYTE_ORDER_BIG_ENDIAN);
731         else
732           fputc(level.yam_content[i][x][y], file);
733
734   putFileChunk(file, "BODY", lev_fieldx * lev_fieldy, BYTE_ORDER_BIG_ENDIAN);
735
736   for(y=0; y<lev_fieldy; y++) 
737     for(x=0; x<lev_fieldx; x++) 
738       if (encoding_16bit)
739         putFile16BitInteger(file, Ur[x][y], BYTE_ORDER_BIG_ENDIAN);
740       else
741         fputc(Ur[x][y], file);
742
743   fclose(file);
744
745   chmod(filename, LEVEL_PERMS);
746 }
747
748 void LoadTape(int level_nr)
749 {
750   int i, j;
751   char *filename = getTapeFilename(level_nr);
752   char cookie[MAX_LINE_LEN];
753   char chunk[CHUNK_ID_LEN + 1];
754   FILE *file;
755   int num_participating_players;
756   int file_version = FILE_VERSION_1_2;  /* last version of tape files */
757   int chunk_length;
758
759   /* always start with reliable default values (empty tape) */
760   TapeErase();
761
762   /* default values (also for pre-1.2 tapes) with only the first player */
763   tape.player_participates[0] = TRUE;
764   for(i=1; i<MAX_PLAYERS; i++)
765     tape.player_participates[i] = FALSE;
766
767   /* at least one (default: the first) player participates in every tape */
768   num_participating_players = 1;
769
770   if (!(file = fopen(filename, "r")))
771     return;
772
773   /* check file identifier */
774   fgets(cookie, MAX_LINE_LEN, file);
775   if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n')
776     cookie[strlen(cookie) - 1] = '\0';
777
778   if (strcmp(cookie, TAPE_COOKIE_10) == 0)      /* old 1.0 tape format */
779     file_version = FILE_VERSION_1_0;
780   else if (strcmp(cookie, TAPE_COOKIE) != 0)    /* unknown tape format */
781   {
782     Error(ERR_WARN, "wrong file identifier of tape file '%s'", filename);
783     fclose(file);
784     return;
785   }
786
787   /* read chunk "HEAD" */
788   if (file_version >= FILE_VERSION_1_2)
789   {
790     getFileChunk(file, chunk, &chunk_length, BYTE_ORDER_BIG_ENDIAN);
791     if (strcmp(chunk, "HEAD") || chunk_length != TAPE_HEADER_SIZE)
792     {
793       Error(ERR_WARN, "wrong 'HEAD' chunk of tape file '%s'", filename);
794       fclose(file);
795       return;
796     }
797   }
798
799   tape.random_seed = getFile32BitInteger(file, BYTE_ORDER_BIG_ENDIAN);
800   tape.date        = getFile32BitInteger(file, BYTE_ORDER_BIG_ENDIAN);
801   tape.length      = getFile32BitInteger(file, BYTE_ORDER_BIG_ENDIAN);
802
803   /* read header fields that are new since version 1.2 */
804   if (file_version >= FILE_VERSION_1_2)
805   {
806     byte store_participating_players = fgetc(file);
807
808     for(i=0; i<TAPE_HEADER_UNUSED; i++)         /* skip unused header bytes */
809       fgetc(file);
810
811     /* since version 1.2, tapes store which players participate in the tape */
812     num_participating_players = 0;
813     for(i=0; i<MAX_PLAYERS; i++)
814     {
815       tape.player_participates[i] = FALSE;
816
817       if (store_participating_players & (1 << i))
818       {
819         tape.player_participates[i] = TRUE;
820         num_participating_players++;
821       }
822     }
823   }
824
825   tape.level_nr = level_nr;
826   tape.counter = 0;
827   tape.changed = FALSE;
828
829   tape.recording = FALSE;
830   tape.playing = FALSE;
831   tape.pausing = FALSE;
832
833   /* read chunk "BODY" */
834   if (file_version >= FILE_VERSION_1_2)
835   {
836     getFileChunk(file, chunk, &chunk_length, BYTE_ORDER_BIG_ENDIAN);
837     if (strcmp(chunk, "BODY") ||
838         chunk_length != (num_participating_players + 1) * tape.length)
839     {
840       Error(ERR_WARN, "wrong 'BODY' chunk of tape file '%s'", filename);
841       fclose(file);
842       return;
843     }
844   }
845
846   for(i=0; i<tape.length; i++)
847   {
848     if (i >= MAX_TAPELEN)
849       break;
850
851     for(j=0; j<MAX_PLAYERS; j++)
852     {
853       tape.pos[i].action[j] = MV_NO_MOVING;
854
855       if (tape.player_participates[j])
856         tape.pos[i].action[j] = fgetc(file);
857     }
858
859     tape.pos[i].delay = fgetc(file);
860
861     if (file_version == FILE_VERSION_1_0)
862     {
863       /* eliminate possible diagonal moves in old tapes */
864       /* this is only for backward compatibility */
865
866       byte joy_dir[4] = { JOY_LEFT, JOY_RIGHT, JOY_UP, JOY_DOWN };
867       byte action = tape.pos[i].action[0];
868       int k, num_moves = 0;
869
870       for (k=0; k<4; k++)
871       {
872         if (action & joy_dir[k])
873         {
874           tape.pos[i + num_moves].action[0] = joy_dir[k];
875           if (num_moves > 0)
876             tape.pos[i + num_moves].delay = 0;
877           num_moves++;
878         }
879       }
880
881       if (num_moves > 1)
882       {
883         num_moves--;
884         i += num_moves;
885         tape.length += num_moves;
886       }
887     }
888
889     if (feof(file))
890       break;
891   }
892
893   fclose(file);
894
895   if (i != tape.length)
896     Error(ERR_WARN, "level recording file '%s' corrupted", filename);
897
898   tape.length_seconds = GetTapeLength();
899 }
900
901 void SaveTape(int level_nr)
902 {
903   int i;
904   char *filename = getTapeFilename(level_nr);
905   FILE *file;
906   boolean new_tape = TRUE;
907   byte store_participating_players;
908   int num_participating_players;
909
910   InitTapeDirectory(leveldir_current->filename);
911
912   /* if a tape still exists, ask to overwrite it */
913   if (access(filename, F_OK) == 0)
914   {
915     new_tape = FALSE;
916     if (!Request("Replace old tape ?", REQ_ASK))
917       return;
918   }
919
920   /* count number of players and set corresponding bits for compact storage */
921   store_participating_players = 0;
922   num_participating_players = 0;
923   for(i=0; i<MAX_PLAYERS; i++)
924   {
925     if (tape.player_participates[i])
926     {
927       num_participating_players++;
928       store_participating_players |= (1 << i);
929     }
930   }
931
932   if (!(file = fopen(filename, "w")))
933   {
934     Error(ERR_WARN, "cannot save level recording file '%s'", filename);
935     return;
936   }
937
938   fputs(TAPE_COOKIE, file);             /* file identifier */
939   fputc('\n', file);
940
941   putFileChunk(file, "HEAD", TAPE_HEADER_SIZE, BYTE_ORDER_BIG_ENDIAN);
942
943   putFile32BitInteger(file, tape.random_seed, BYTE_ORDER_BIG_ENDIAN);
944   putFile32BitInteger(file, tape.date, BYTE_ORDER_BIG_ENDIAN);
945   putFile32BitInteger(file, tape.length, BYTE_ORDER_BIG_ENDIAN);
946
947   fputc(store_participating_players, file);
948
949   for(i=0; i<TAPE_HEADER_UNUSED; i++)   /* set unused header bytes to zero */
950     fputc(0, file);
951
952   putFileChunk(file, "BODY", (num_participating_players + 1) * tape.length,
953                BYTE_ORDER_BIG_ENDIAN);
954
955   for(i=0; i<tape.length; i++)
956   {
957     int j;
958
959     for(j=0; j<MAX_PLAYERS; j++)
960       if (tape.player_participates[j])
961         fputc(tape.pos[i].action[j], file);
962
963     fputc(tape.pos[i].delay, file);
964   }
965
966   fclose(file);
967
968   chmod(filename, TAPE_PERMS);
969
970   tape.changed = FALSE;
971
972   if (new_tape)
973     Request("tape saved !", REQ_CONFIRM);
974 }
975
976 void LoadScore(int level_nr)
977 {
978   int i;
979   char *filename = getScoreFilename(level_nr);
980   char cookie[MAX_LINE_LEN];
981   char line[MAX_LINE_LEN];
982   char *line_ptr;
983   FILE *file;
984
985   /* always start with reliable default values */
986   for(i=0; i<MAX_SCORE_ENTRIES; i++)
987   {
988     strcpy(highscore[i].Name, EMPTY_PLAYER_NAME);
989     highscore[i].Score = 0;
990   }
991
992   if (!(file = fopen(filename, "r")))
993     return;
994
995   /* check file identifier */
996   fgets(cookie, MAX_LINE_LEN, file);
997   if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n')
998     cookie[strlen(cookie) - 1] = '\0';
999
1000   if (strcmp(cookie, SCORE_COOKIE) != 0)
1001   {
1002     Error(ERR_WARN, "wrong file identifier of score file '%s'", filename);
1003     fclose(file);
1004     return;
1005   }
1006
1007   for(i=0; i<MAX_SCORE_ENTRIES; i++)
1008   {
1009     fscanf(file, "%d", &highscore[i].Score);
1010     fgets(line, MAX_LINE_LEN, file);
1011
1012     if (line[strlen(line) - 1] == '\n')
1013       line[strlen(line) - 1] = '\0';
1014
1015     for (line_ptr = line; *line_ptr; line_ptr++)
1016     {
1017       if (*line_ptr != ' ' && *line_ptr != '\t' && *line_ptr != '\0')
1018       {
1019         strncpy(highscore[i].Name, line_ptr, MAX_PLAYER_NAME_LEN);
1020         highscore[i].Name[MAX_PLAYER_NAME_LEN] = '\0';
1021         break;
1022       }
1023     }
1024   }
1025
1026   fclose(file);
1027 }
1028
1029 void SaveScore(int level_nr)
1030 {
1031   int i;
1032   char *filename = getScoreFilename(level_nr);
1033   FILE *file;
1034
1035   InitScoreDirectory(leveldir_current->filename);
1036
1037   if (!(file = fopen(filename, "w")))
1038   {
1039     Error(ERR_WARN, "cannot save score for level %d", level_nr);
1040     return;
1041   }
1042
1043   fprintf(file, "%s\n\n", SCORE_COOKIE);
1044
1045   for(i=0; i<MAX_SCORE_ENTRIES; i++)
1046     fprintf(file, "%d %s\n", highscore[i].Score, highscore[i].Name);
1047
1048   fclose(file);
1049
1050   chmod(filename, SCORE_PERMS);
1051 }
1052
1053 #define TOKEN_STR_FILE_IDENTIFIER       "file_identifier"
1054 #define TOKEN_STR_LAST_LEVEL_SERIES     "last_level_series"
1055 #define TOKEN_STR_LAST_PLAYED_LEVEL     "last_played_level"
1056 #define TOKEN_STR_HANDICAP_LEVEL        "handicap_level"
1057 #define TOKEN_STR_PLAYER_PREFIX         "player_"
1058
1059 #define TOKEN_VALUE_POSITION            30
1060
1061 /* global setup */
1062 #define SETUP_TOKEN_PLAYER_NAME         0
1063 #define SETUP_TOKEN_SOUND               1
1064 #define SETUP_TOKEN_SOUND_LOOPS         2
1065 #define SETUP_TOKEN_SOUND_MUSIC         3
1066 #define SETUP_TOKEN_SOUND_SIMPLE        4
1067
1068 #if 0
1069 #define SETUP_TOKEN_TOONS               5
1070 #define SETUP_TOKEN_DOUBLE_BUFFERING    6
1071 #endif
1072
1073 #define SETUP_TOKEN_SCROLL_DELAY        5
1074 #define SETUP_TOKEN_SOFT_SCROLLING      6
1075 #define SETUP_TOKEN_FADING              7
1076 #define SETUP_TOKEN_AUTORECORD          8
1077 #define SETUP_TOKEN_QUICK_DOORS         9
1078 #define SETUP_TOKEN_TEAM_MODE           10
1079 #define SETUP_TOKEN_HANDICAP            11
1080 #define SETUP_TOKEN_TIME_LIMIT          12
1081 #define SETUP_TOKEN_FULLSCREEN          13
1082
1083 /* player setup */
1084 #define SETUP_TOKEN_USE_JOYSTICK        14
1085 #define SETUP_TOKEN_JOY_DEVICE_NAME     15
1086 #define SETUP_TOKEN_JOY_XLEFT           16
1087 #define SETUP_TOKEN_JOY_XMIDDLE         17
1088 #define SETUP_TOKEN_JOY_XRIGHT          18
1089 #define SETUP_TOKEN_JOY_YUPPER          19
1090 #define SETUP_TOKEN_JOY_YMIDDLE         20
1091 #define SETUP_TOKEN_JOY_YLOWER          21
1092 #define SETUP_TOKEN_JOY_SNAP            22
1093 #define SETUP_TOKEN_JOY_BOMB            23
1094 #define SETUP_TOKEN_KEY_LEFT            24
1095 #define SETUP_TOKEN_KEY_RIGHT           25
1096 #define SETUP_TOKEN_KEY_UP              26
1097 #define SETUP_TOKEN_KEY_DOWN            27
1098 #define SETUP_TOKEN_KEY_SNAP            28
1099 #define SETUP_TOKEN_KEY_BOMB            29
1100
1101 /* level directory info */
1102 #define LEVELINFO_TOKEN_NAME            30
1103 #define LEVELINFO_TOKEN_NAME_SHORT      31
1104 #define LEVELINFO_TOKEN_NAME_SORTING    32
1105 #define LEVELINFO_TOKEN_AUTHOR          33
1106 #define LEVELINFO_TOKEN_IMPORTED_FROM   34
1107 #define LEVELINFO_TOKEN_LEVELS          35
1108 #define LEVELINFO_TOKEN_FIRST_LEVEL     36
1109 #define LEVELINFO_TOKEN_SORT_PRIORITY   37
1110 #define LEVELINFO_TOKEN_LEVEL_GROUP     38
1111 #define LEVELINFO_TOKEN_READONLY        39
1112
1113 #define FIRST_GLOBAL_SETUP_TOKEN        SETUP_TOKEN_PLAYER_NAME
1114 #define LAST_GLOBAL_SETUP_TOKEN         SETUP_TOKEN_FULLSCREEN
1115
1116 #define FIRST_PLAYER_SETUP_TOKEN        SETUP_TOKEN_USE_JOYSTICK
1117 #define LAST_PLAYER_SETUP_TOKEN         SETUP_TOKEN_KEY_BOMB
1118
1119 #define FIRST_LEVELINFO_TOKEN           LEVELINFO_TOKEN_NAME
1120 #define LAST_LEVELINFO_TOKEN            LEVELINFO_TOKEN_READONLY
1121
1122 #define TYPE_BOOLEAN                    1
1123 #define TYPE_SWITCH                     2
1124 #define TYPE_KEY                        3
1125 #define TYPE_INTEGER                    4
1126 #define TYPE_STRING                     5
1127
1128 static struct SetupInfo si;
1129 static struct SetupInputInfo sii;
1130 static struct LevelDirInfo ldi;
1131 static struct
1132 {
1133   int type;
1134   void *value;
1135   char *text;
1136 } token_info[] =
1137 {
1138   /* global setup */
1139   { TYPE_STRING,  &si.player_name,      "player_name"                   },
1140   { TYPE_SWITCH,  &si.sound,            "sound"                         },
1141   { TYPE_SWITCH,  &si.sound_loops,      "repeating_sound_loops"         },
1142   { TYPE_SWITCH,  &si.sound_music,      "background_music"              },
1143   { TYPE_SWITCH,  &si.sound_simple,     "simple_sound_effects"          },
1144
1145 #if 0
1146   { TYPE_SWITCH,  &si.toons,            "toons"                         },
1147   { TYPE_SWITCH,  &si.double_buffering, "double_buffering"              },
1148 #endif
1149
1150   { TYPE_SWITCH,  &si.scroll_delay,     "scroll_delay"                  },
1151   { TYPE_SWITCH,  &si.soft_scrolling,   "soft_scrolling"                },
1152   { TYPE_SWITCH,  &si.fading,           "screen_fading"                 },
1153   { TYPE_SWITCH,  &si.autorecord,       "automatic_tape_recording"      },
1154   { TYPE_SWITCH,  &si.quick_doors,      "quick_doors"                   },
1155   { TYPE_SWITCH,  &si.team_mode,        "team_mode"                     },
1156   { TYPE_SWITCH,  &si.handicap,         "handicap"                      },
1157   { TYPE_SWITCH,  &si.time_limit,       "time_limit"                    },
1158   { TYPE_SWITCH,  &si.fullscreen,       "fullscreen"                    },
1159
1160   /* player setup */
1161   { TYPE_BOOLEAN, &sii.use_joystick,    ".use_joystick"                 },
1162   { TYPE_STRING,  &sii.joy.device_name, ".joy.device_name"              },
1163   { TYPE_INTEGER, &sii.joy.xleft,       ".joy.xleft"                    },
1164   { TYPE_INTEGER, &sii.joy.xmiddle,     ".joy.xmiddle"                  },
1165   { TYPE_INTEGER, &sii.joy.xright,      ".joy.xright"                   },
1166   { TYPE_INTEGER, &sii.joy.yupper,      ".joy.yupper"                   },
1167   { TYPE_INTEGER, &sii.joy.ymiddle,     ".joy.ymiddle"                  },
1168   { TYPE_INTEGER, &sii.joy.ylower,      ".joy.ylower"                   },
1169   { TYPE_INTEGER, &sii.joy.snap,        ".joy.snap_field"               },
1170   { TYPE_INTEGER, &sii.joy.bomb,        ".joy.place_bomb"               },
1171   { TYPE_KEY,     &sii.key.left,        ".key.move_left"                },
1172   { TYPE_KEY,     &sii.key.right,       ".key.move_right"               },
1173   { TYPE_KEY,     &sii.key.up,          ".key.move_up"                  },
1174   { TYPE_KEY,     &sii.key.down,        ".key.move_down"                },
1175   { TYPE_KEY,     &sii.key.snap,        ".key.snap_field"               },
1176   { TYPE_KEY,     &sii.key.bomb,        ".key.place_bomb"               },
1177
1178   /* level directory info */
1179   { TYPE_STRING,  &ldi.name,            "name"                          },
1180   { TYPE_STRING,  &ldi.name_short,      "name_short"                    },
1181   { TYPE_STRING,  &ldi.name_sorting,    "name_sorting"                  },
1182   { TYPE_STRING,  &ldi.author,          "author"                        },
1183   { TYPE_STRING,  &ldi.imported_from,   "imported_from"                 },
1184   { TYPE_INTEGER, &ldi.levels,          "levels"                        },
1185   { TYPE_INTEGER, &ldi.first_level,     "first_level"                   },
1186   { TYPE_INTEGER, &ldi.sort_priority,   "sort_priority"                 },
1187   { TYPE_BOOLEAN, &ldi.level_group,     "level_group"                   },
1188   { TYPE_BOOLEAN, &ldi.readonly,        "readonly"                      }
1189 };
1190
1191 static char *string_tolower(char *s)
1192 {
1193   static char s_lower[100];
1194   int i;
1195
1196   if (strlen(s) >= 100)
1197     return s;
1198
1199   strcpy(s_lower, s);
1200
1201   for (i=0; i<strlen(s_lower); i++)
1202     s_lower[i] = tolower(s_lower[i]);
1203
1204   return s_lower;
1205 }
1206
1207 static int get_string_integer_value(char *s)
1208 {
1209   static char *number_text[][3] =
1210   {
1211     { "0", "zero", "null", },
1212     { "1", "one", "first" },
1213     { "2", "two", "second" },
1214     { "3", "three", "third" },
1215     { "4", "four", "fourth" },
1216     { "5", "five", "fifth" },
1217     { "6", "six", "sixth" },
1218     { "7", "seven", "seventh" },
1219     { "8", "eight", "eighth" },
1220     { "9", "nine", "ninth" },
1221     { "10", "ten", "tenth" },
1222     { "11", "eleven", "eleventh" },
1223     { "12", "twelve", "twelfth" },
1224   };
1225
1226   int i, j;
1227
1228   for (i=0; i<13; i++)
1229     for (j=0; j<3; j++)
1230       if (strcmp(string_tolower(s), number_text[i][j]) == 0)
1231         return i;
1232
1233   return atoi(s);
1234 }
1235
1236 static boolean get_string_boolean_value(char *s)
1237 {
1238   if (strcmp(string_tolower(s), "true") == 0 ||
1239       strcmp(string_tolower(s), "yes") == 0 ||
1240       strcmp(string_tolower(s), "on") == 0 ||
1241       get_string_integer_value(s) == 1)
1242     return TRUE;
1243   else
1244     return FALSE;
1245 }
1246
1247 static char *getFormattedSetupEntry(char *token, char *value)
1248 {
1249   int i;
1250   static char entry[MAX_LINE_LEN];
1251
1252   sprintf(entry, "%s:", token);
1253   for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
1254     entry[i] = ' ';
1255   entry[i] = '\0';
1256
1257   strcat(entry, value);
1258
1259   return entry;
1260 }
1261
1262 static void freeSetupFileList(struct SetupFileList *setup_file_list)
1263 {
1264   if (!setup_file_list)
1265     return;
1266
1267   if (setup_file_list->token)
1268     free(setup_file_list->token);
1269   if (setup_file_list->value)
1270     free(setup_file_list->value);
1271   if (setup_file_list->next)
1272     freeSetupFileList(setup_file_list->next);
1273   free(setup_file_list);
1274 }
1275
1276 static struct SetupFileList *newSetupFileList(char *token, char *value)
1277 {
1278   struct SetupFileList *new = checked_malloc(sizeof(struct SetupFileList));
1279
1280   new->token = checked_malloc(strlen(token) + 1);
1281   strcpy(new->token, token);
1282
1283   new->value = checked_malloc(strlen(value) + 1);
1284   strcpy(new->value, value);
1285
1286   new->next = NULL;
1287
1288   return new;
1289 }
1290
1291 static char *getTokenValue(struct SetupFileList *setup_file_list,
1292                            char *token)
1293 {
1294   if (!setup_file_list)
1295     return NULL;
1296
1297   if (strcmp(setup_file_list->token, token) == 0)
1298     return setup_file_list->value;
1299   else
1300     return getTokenValue(setup_file_list->next, token);
1301 }
1302
1303 static void setTokenValue(struct SetupFileList *setup_file_list,
1304                           char *token, char *value)
1305 {
1306   if (!setup_file_list)
1307     return;
1308
1309   if (strcmp(setup_file_list->token, token) == 0)
1310   {
1311     free(setup_file_list->value);
1312     setup_file_list->value = checked_malloc(strlen(value) + 1);
1313     strcpy(setup_file_list->value, value);
1314   }
1315   else if (setup_file_list->next == NULL)
1316     setup_file_list->next = newSetupFileList(token, value);
1317   else
1318     setTokenValue(setup_file_list->next, token, value);
1319 }
1320
1321 #ifdef DEBUG
1322 static void printSetupFileList(struct SetupFileList *setup_file_list)
1323 {
1324   if (!setup_file_list)
1325     return;
1326
1327   printf("token: '%s'\n", setup_file_list->token);
1328   printf("value: '%s'\n", setup_file_list->value);
1329
1330   printSetupFileList(setup_file_list->next);
1331 }
1332 #endif
1333
1334 static struct SetupFileList *loadSetupFileList(char *filename)
1335 {
1336   int line_len;
1337   char line[MAX_LINE_LEN];
1338   char *token, *value, *line_ptr;
1339   struct SetupFileList *setup_file_list = newSetupFileList("", "");
1340   struct SetupFileList *first_valid_list_entry;
1341
1342   FILE *file;
1343
1344   if (!(file = fopen(filename, "r")))
1345   {
1346     Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1347     return NULL;
1348   }
1349
1350   while(!feof(file))
1351   {
1352     /* read next line of input file */
1353     if (!fgets(line, MAX_LINE_LEN, file))
1354       break;
1355
1356     /* cut trailing comment or whitespace from input line */
1357     for (line_ptr = line; *line_ptr; line_ptr++)
1358     {
1359       if (*line_ptr == '#' || *line_ptr == '\n' || *line_ptr == '\r')
1360       {
1361         *line_ptr = '\0';
1362         break;
1363       }
1364     }
1365
1366     /* cut trailing whitespaces from input line */
1367     for (line_ptr = &line[strlen(line)]; line_ptr > line; line_ptr--)
1368       if ((*line_ptr == ' ' || *line_ptr == '\t') && line_ptr[1] == '\0')
1369         *line_ptr = '\0';
1370
1371     /* ignore empty lines */
1372     if (*line == '\0')
1373       continue;
1374
1375     line_len = strlen(line);
1376
1377     /* cut leading whitespaces from token */
1378     for (token = line; *token; token++)
1379       if (*token != ' ' && *token != '\t')
1380         break;
1381
1382     /* find end of token */
1383     for (line_ptr = token; *line_ptr; line_ptr++)
1384     {
1385       if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1386       {
1387         *line_ptr = '\0';
1388         break;
1389       }
1390     }
1391
1392     if (line_ptr < line + line_len)
1393       value = line_ptr + 1;
1394     else
1395       value = "\0";
1396
1397     /* cut leading whitespaces from value */
1398     for (; *value; value++)
1399       if (*value != ' ' && *value != '\t')
1400         break;
1401
1402     if (*token && *value)
1403       setTokenValue(setup_file_list, token, value);
1404   }
1405
1406   fclose(file);
1407
1408   first_valid_list_entry = setup_file_list->next;
1409
1410   /* free empty list header */
1411   setup_file_list->next = NULL;
1412   freeSetupFileList(setup_file_list);
1413
1414   if (first_valid_list_entry == NULL)
1415     Error(ERR_WARN, "configuration file '%s' is empty", filename);
1416
1417   return first_valid_list_entry;
1418 }
1419
1420 static void checkSetupFileListIdentifier(struct SetupFileList *setup_file_list,
1421                                          char *identifier)
1422 {
1423   if (!setup_file_list)
1424     return;
1425
1426   if (strcmp(setup_file_list->token, TOKEN_STR_FILE_IDENTIFIER) == 0)
1427   {
1428     if (strcmp(setup_file_list->value, identifier) != 0)
1429     {
1430       Error(ERR_WARN, "configuration file has wrong version");
1431       return;
1432     }
1433     else
1434       return;
1435   }
1436
1437   if (setup_file_list->next)
1438     checkSetupFileListIdentifier(setup_file_list->next, identifier);
1439   else
1440   {
1441     Error(ERR_WARN, "configuration file has no version information");
1442     return;
1443   }
1444 }
1445
1446 static void setLevelDirInfoToDefaults(struct LevelDirInfo *ldi)
1447 {
1448   ldi->filename = NULL;
1449   ldi->fullpath = NULL;
1450   ldi->basepath = NULL;
1451   ldi->name = getStringCopy(ANONYMOUS_NAME);
1452   ldi->name_short = NULL;
1453   ldi->name_sorting = NULL;
1454   ldi->author = getStringCopy(ANONYMOUS_NAME);
1455   ldi->imported_from = NULL;
1456   ldi->levels = 0;
1457   ldi->first_level = 0;
1458   ldi->last_level = 0;
1459   ldi->sort_priority = LEVELCLASS_UNDEFINED;    /* default: least priority */
1460   ldi->level_group = FALSE;
1461   ldi->parent_link = FALSE;
1462   ldi->user_defined = FALSE;
1463   ldi->readonly = TRUE;
1464   ldi->color = 0;
1465   ldi->class_desc = NULL;
1466   ldi->handicap_level = 0;
1467   ldi->cl_first = -1;
1468   ldi->cl_cursor = -1;
1469
1470   ldi->node_parent = NULL;
1471   ldi->node_group = NULL;
1472   ldi->next = NULL;
1473 }
1474
1475 static void setLevelDirInfoToDefaultsFromParent(struct LevelDirInfo *ldi,
1476                                                 struct LevelDirInfo *parent)
1477 {
1478   if (parent == NULL)
1479   {
1480     setLevelDirInfoToDefaults(ldi);
1481     return;
1482   }
1483
1484   /* first copy all values from the parent structure ... */
1485   *ldi = *parent;
1486
1487   /* ... then set all fields to default that cannot be inherited from parent.
1488      This is especially important for all those fields that can be set from
1489      the 'levelinfo.conf' config file, because the function 'setSetupInfo()'
1490      calls 'free()' for all already set token values which requires that no
1491      other structure's pointer may point to them!
1492   */
1493
1494   ldi->filename = NULL;
1495   ldi->fullpath = NULL;
1496   ldi->basepath = NULL;
1497   ldi->name = getStringCopy(ANONYMOUS_NAME);
1498   ldi->name_short = NULL;
1499   ldi->name_sorting = NULL;
1500   ldi->author = getStringCopy(parent->author);
1501   ldi->imported_from = getStringCopy(parent->imported_from);
1502
1503   ldi->level_group = FALSE;
1504   ldi->parent_link = FALSE;
1505
1506   ldi->node_parent = parent;
1507   ldi->node_group = NULL;
1508   ldi->next = NULL;
1509 }
1510
1511 static void setSetupInfoToDefaults(struct SetupInfo *si)
1512 {
1513   int i;
1514
1515   si->player_name = getStringCopy(getLoginName());
1516
1517   si->sound = TRUE;
1518   si->sound_loops = TRUE;
1519   si->sound_music = TRUE;
1520   si->sound_simple = TRUE;
1521   si->toons = TRUE;
1522   si->double_buffering = TRUE;
1523   si->direct_draw = !si->double_buffering;
1524   si->scroll_delay = TRUE;
1525   si->soft_scrolling = TRUE;
1526   si->fading = FALSE;
1527   si->autorecord = TRUE;
1528   si->quick_doors = FALSE;
1529   si->team_mode = FALSE;
1530   si->handicap = TRUE;
1531   si->time_limit = TRUE;
1532   si->fullscreen = FALSE;
1533
1534   for (i=0; i<MAX_PLAYERS; i++)
1535   {
1536     si->input[i].use_joystick = FALSE;
1537     si->input[i].joy.device_name = getStringCopy(joystick_device_name[i]);
1538     si->input[i].joy.xleft   = JOYSTICK_XLEFT;
1539     si->input[i].joy.xmiddle = JOYSTICK_XMIDDLE;
1540     si->input[i].joy.xright  = JOYSTICK_XRIGHT;
1541     si->input[i].joy.yupper  = JOYSTICK_YUPPER;
1542     si->input[i].joy.ymiddle = JOYSTICK_YMIDDLE;
1543     si->input[i].joy.ylower  = JOYSTICK_YLOWER;
1544     si->input[i].joy.snap  = (i == 0 ? JOY_BUTTON_1 : 0);
1545     si->input[i].joy.bomb  = (i == 0 ? JOY_BUTTON_2 : 0);
1546     si->input[i].key.left  = (i == 0 ? DEFAULT_KEY_LEFT  : KSYM_UNDEFINED);
1547     si->input[i].key.right = (i == 0 ? DEFAULT_KEY_RIGHT : KSYM_UNDEFINED);
1548     si->input[i].key.up    = (i == 0 ? DEFAULT_KEY_UP    : KSYM_UNDEFINED);
1549     si->input[i].key.down  = (i == 0 ? DEFAULT_KEY_DOWN  : KSYM_UNDEFINED);
1550     si->input[i].key.snap  = (i == 0 ? DEFAULT_KEY_SNAP  : KSYM_UNDEFINED);
1551     si->input[i].key.bomb  = (i == 0 ? DEFAULT_KEY_BOMB  : KSYM_UNDEFINED);
1552   }
1553 }
1554
1555 static void setSetupInfo(int token_nr, char *token_value)
1556 {
1557   int token_type = token_info[token_nr].type;
1558   void *setup_value = token_info[token_nr].value;
1559
1560   if (token_value == NULL)
1561     return;
1562
1563   /* set setup field to corresponding token value */
1564   switch (token_type)
1565   {
1566     case TYPE_BOOLEAN:
1567     case TYPE_SWITCH:
1568       *(boolean *)setup_value = get_string_boolean_value(token_value);
1569       break;
1570
1571     case TYPE_KEY:
1572       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
1573       break;
1574
1575     case TYPE_INTEGER:
1576       *(int *)setup_value = get_string_integer_value(token_value);
1577       break;
1578
1579     case TYPE_STRING:
1580       if (*(char **)setup_value != NULL)
1581         free(*(char **)setup_value);
1582       *(char **)setup_value = getStringCopy(token_value);
1583       break;
1584
1585     default:
1586       break;
1587   }
1588 }
1589
1590 static void decodeSetupFileList(struct SetupFileList *setup_file_list)
1591 {
1592   int i, pnr;
1593
1594   if (!setup_file_list)
1595     return;
1596
1597   /* handle global setup values */
1598   si = setup;
1599   for (i=FIRST_GLOBAL_SETUP_TOKEN; i<=LAST_GLOBAL_SETUP_TOKEN; i++)
1600     setSetupInfo(i, getTokenValue(setup_file_list, token_info[i].text));
1601   setup = si;
1602
1603   /* handle player specific setup values */
1604   for (pnr=0; pnr<MAX_PLAYERS; pnr++)
1605   {
1606     char prefix[30];
1607
1608     sprintf(prefix, "%s%d", TOKEN_STR_PLAYER_PREFIX, pnr + 1);
1609
1610     sii = setup.input[pnr];
1611     for (i=FIRST_PLAYER_SETUP_TOKEN; i<=LAST_PLAYER_SETUP_TOKEN; i++)
1612     {
1613       char full_token[100];
1614
1615       sprintf(full_token, "%s%s", prefix, token_info[i].text);
1616       setSetupInfo(i, getTokenValue(setup_file_list, full_token));
1617     }
1618     setup.input[pnr] = sii;
1619   }
1620 }
1621
1622 static int compareLevelDirInfoEntries(const void *object1, const void *object2)
1623 {
1624   const struct LevelDirInfo *entry1 = *((struct LevelDirInfo **)object1);
1625   const struct LevelDirInfo *entry2 = *((struct LevelDirInfo **)object2);
1626   int compare_result;
1627
1628   if (entry1->parent_link || entry2->parent_link)
1629     compare_result = (entry1->parent_link ? -1 : +1);
1630   else if (entry1->sort_priority == entry2->sort_priority)
1631   {
1632     char *name1 = getStringToLower(entry1->name_sorting);
1633     char *name2 = getStringToLower(entry2->name_sorting);
1634
1635     compare_result = strcmp(name1, name2);
1636
1637     free(name1);
1638     free(name2);
1639   }
1640   else if (LEVELSORTING(entry1) == LEVELSORTING(entry2))
1641     compare_result = entry1->sort_priority - entry2->sort_priority;
1642   else
1643     compare_result = LEVELSORTING(entry1) - LEVELSORTING(entry2);
1644
1645   return compare_result;
1646 }
1647
1648 static void createParentLevelDirNode(struct LevelDirInfo *node_parent)
1649 {
1650   struct LevelDirInfo *leveldir_new = newLevelDirInfo();
1651
1652   setLevelDirInfoToDefaults(leveldir_new);
1653
1654   leveldir_new->node_parent = node_parent;
1655   leveldir_new->parent_link = TRUE;
1656
1657   leveldir_new->name = ".. (parent directory)";
1658   leveldir_new->name_short = getStringCopy(leveldir_new->name);
1659   leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1660
1661   leveldir_new->filename = "..";
1662   leveldir_new->fullpath = getStringCopy(node_parent->fullpath);
1663
1664   leveldir_new->sort_priority = node_parent->sort_priority;
1665   leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1666
1667   pushLevelDirInfo(&node_parent->node_group, leveldir_new);
1668 }
1669
1670 static void LoadLevelInfoFromLevelDir(struct LevelDirInfo **node_first,
1671                                       struct LevelDirInfo *node_parent,
1672                                       char *level_directory)
1673 {
1674   DIR *dir;
1675   struct dirent *dir_entry;
1676   boolean valid_entry_found = FALSE;
1677
1678   if ((dir = opendir(level_directory)) == NULL)
1679   {
1680     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1681     return;
1682   }
1683
1684   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
1685   {
1686     struct SetupFileList *setup_file_list = NULL;
1687     struct stat file_status;
1688     char *directory_name = dir_entry->d_name;
1689     char *directory_path = getPath2(level_directory, directory_name);
1690     char *filename = NULL;
1691
1692     /* skip entries for current and parent directory */
1693     if (strcmp(directory_name, ".")  == 0 ||
1694         strcmp(directory_name, "..") == 0)
1695     {
1696       free(directory_path);
1697       continue;
1698     }
1699
1700     /* find out if directory entry is itself a directory */
1701     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
1702         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
1703     {
1704       free(directory_path);
1705       continue;
1706     }
1707
1708     filename = getPath2(directory_path, LEVELINFO_FILENAME);
1709     setup_file_list = loadSetupFileList(filename);
1710
1711     if (setup_file_list)
1712     {
1713       struct LevelDirInfo *leveldir_new = newLevelDirInfo();
1714       int i;
1715
1716       checkSetupFileListIdentifier(setup_file_list, LEVELINFO_COOKIE);
1717       setLevelDirInfoToDefaultsFromParent(leveldir_new, node_parent);
1718
1719       /* set all structure fields according to the token/value pairs */
1720       ldi = *leveldir_new;
1721       for (i=FIRST_LEVELINFO_TOKEN; i<=LAST_LEVELINFO_TOKEN; i++)
1722         setSetupInfo(i, getTokenValue(setup_file_list, token_info[i].text));
1723       *leveldir_new = ldi;
1724
1725       DrawInitText(leveldir_new->name, 150, FC_YELLOW);
1726
1727       if (leveldir_new->name_short == NULL)
1728         leveldir_new->name_short = getStringCopy(leveldir_new->name);
1729
1730       if (leveldir_new->name_sorting == NULL)
1731         leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1732
1733       leveldir_new->filename = getStringCopy(directory_name);
1734
1735       if (node_parent == NULL)          /* top level group */
1736       {
1737         leveldir_new->basepath = level_directory;
1738         leveldir_new->fullpath = leveldir_new->filename;
1739       }
1740       else                              /* sub level group */
1741       {
1742         leveldir_new->basepath = node_parent->basepath;
1743         leveldir_new->fullpath = getPath2(node_parent->fullpath,
1744                                           directory_name);
1745       }
1746
1747       if (leveldir_new->levels < 1)
1748         leveldir_new->levels = 1;
1749
1750       leveldir_new->last_level =
1751         leveldir_new->first_level + leveldir_new->levels - 1;
1752
1753       leveldir_new->user_defined =
1754         (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
1755
1756       leveldir_new->color = LEVELCOLOR(leveldir_new);
1757       leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1758
1759       leveldir_new->handicap_level =    /* set handicap to default value */
1760         (leveldir_new->user_defined ?
1761          leveldir_new->last_level :
1762          leveldir_new->first_level);
1763
1764       pushLevelDirInfo(node_first, leveldir_new);
1765
1766       freeSetupFileList(setup_file_list);
1767       valid_entry_found = TRUE;
1768
1769       if (leveldir_new->level_group)
1770       {
1771         /* create node to link back to current level directory */
1772         createParentLevelDirNode(leveldir_new);
1773
1774         /* step into sub-directory and look for more level series */
1775         LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
1776                                   leveldir_new, directory_path);
1777       }
1778     }
1779     else
1780       Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
1781
1782     free(directory_path);
1783     free(filename);
1784   }
1785
1786   closedir(dir);
1787
1788   if (!valid_entry_found)
1789     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
1790           level_directory);
1791 }
1792
1793 void LoadLevelInfo()
1794 {
1795   InitUserLevelDirectory(getLoginName());
1796
1797   DrawInitText("Loading level series:", 120, FC_GREEN);
1798
1799   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
1800   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(""));
1801
1802   leveldir_current = getFirstValidLevelSeries(leveldir_first);
1803
1804   if (leveldir_first == NULL)
1805     Error(ERR_EXIT, "cannot find any valid level series in any directory");
1806
1807   sortLevelDirInfo(&leveldir_first, compareLevelDirInfoEntries);
1808
1809 #if 0
1810   dumpLevelDirInfo(leveldir_first, 0);
1811 #endif
1812 }
1813
1814 static void SaveUserLevelInfo()
1815 {
1816   char *filename;
1817   FILE *file;
1818   int i;
1819
1820   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
1821
1822   if (!(file = fopen(filename, "w")))
1823   {
1824     Error(ERR_WARN, "cannot write level info file '%s'", filename);
1825     free(filename);
1826     return;
1827   }
1828
1829   /* always start with reliable default values */
1830   setLevelDirInfoToDefaults(&ldi);
1831
1832   ldi.name = getLoginName();
1833   ldi.author = getRealName();
1834   ldi.levels = 100;
1835   ldi.first_level = 1;
1836   ldi.sort_priority = LEVELCLASS_USER_START;
1837   ldi.readonly = FALSE;
1838
1839   fprintf(file, "%s\n\n",
1840           getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER, LEVELINFO_COOKIE));
1841
1842   for (i=FIRST_LEVELINFO_TOKEN; i<=LAST_LEVELINFO_TOKEN; i++)
1843     if (i != LEVELINFO_TOKEN_NAME_SHORT &&
1844         i != LEVELINFO_TOKEN_NAME_SORTING &&
1845         i != LEVELINFO_TOKEN_IMPORTED_FROM)
1846       fprintf(file, "%s\n", getSetupLine("", i));
1847
1848   fclose(file);
1849   free(filename);
1850
1851   chmod(filename, SETUP_PERMS);
1852 }
1853
1854 void LoadSetup()
1855 {
1856   char *filename;
1857   struct SetupFileList *setup_file_list = NULL;
1858
1859   /* always start with reliable default values */
1860   setSetupInfoToDefaults(&setup);
1861
1862   filename = getPath2(getSetupDir(), SETUP_FILENAME);
1863
1864   setup_file_list = loadSetupFileList(filename);
1865
1866   if (setup_file_list)
1867   {
1868     checkSetupFileListIdentifier(setup_file_list, SETUP_COOKIE);
1869     decodeSetupFileList(setup_file_list);
1870
1871     setup.direct_draw = !setup.double_buffering;
1872
1873     freeSetupFileList(setup_file_list);
1874
1875     /* needed to work around problems with fixed length strings */
1876     if (strlen(setup.player_name) > MAX_PLAYER_NAME_LEN)
1877       setup.player_name[MAX_PLAYER_NAME_LEN] = '\0';
1878     else if (strlen(setup.player_name) < MAX_PLAYER_NAME_LEN)
1879     {
1880       char *new_name = checked_malloc(MAX_PLAYER_NAME_LEN + 1);
1881
1882       strcpy(new_name, setup.player_name);
1883       free(setup.player_name);
1884       setup.player_name = new_name;
1885     }
1886   }
1887   else
1888     Error(ERR_WARN, "using default setup values");
1889
1890   free(filename);
1891 }
1892
1893 static char *getSetupLine(char *prefix, int token_nr)
1894 {
1895   int i;
1896   static char entry[MAX_LINE_LEN];
1897   int token_type = token_info[token_nr].type;
1898   void *setup_value = token_info[token_nr].value;
1899   char *token_text = token_info[token_nr].text;
1900
1901   /* start with the prefix, token and some spaces to format output line */
1902   sprintf(entry, "%s%s:", prefix, token_text);
1903   for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
1904     strcat(entry, " ");
1905
1906   /* continue with the token's value (which can have different types) */
1907   switch (token_type)
1908   {
1909     case TYPE_BOOLEAN:
1910       strcat(entry, (*(boolean *)setup_value ? "true" : "false"));
1911       break;
1912
1913     case TYPE_SWITCH:
1914       strcat(entry, (*(boolean *)setup_value ? "on" : "off"));
1915       break;
1916
1917     case TYPE_KEY:
1918       {
1919         Key key = *(Key *)setup_value;
1920         char *keyname = getKeyNameFromKey(key);
1921
1922         strcat(entry, getX11KeyNameFromKey(key));
1923         for (i=strlen(entry); i<50; i++)
1924           strcat(entry, " ");
1925
1926         /* add comment, if useful */
1927         if (strcmp(keyname, "(undefined)") != 0 &&
1928             strcmp(keyname, "(unknown)") != 0)
1929         {
1930           strcat(entry, "# ");
1931           strcat(entry, keyname);
1932         }
1933       }
1934       break;
1935
1936     case TYPE_INTEGER:
1937       {
1938         char buffer[MAX_LINE_LEN];
1939
1940         sprintf(buffer, "%d", *(int *)setup_value);
1941         strcat(entry, buffer);
1942       }
1943       break;
1944
1945     case TYPE_STRING:
1946       strcat(entry, *(char **)setup_value);
1947       break;
1948
1949     default:
1950       break;
1951   }
1952
1953   return entry;
1954 }
1955
1956 void SaveSetup()
1957 {
1958   int i, pnr;
1959   char *filename;
1960   FILE *file;
1961
1962   InitUserDataDirectory();
1963
1964   filename = getPath2(getSetupDir(), SETUP_FILENAME);
1965
1966   if (!(file = fopen(filename, "w")))
1967   {
1968     Error(ERR_WARN, "cannot write setup file '%s'", filename);
1969     free(filename);
1970     return;
1971   }
1972
1973   fprintf(file, "%s\n",
1974           getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER, SETUP_COOKIE));
1975   fprintf(file, "\n");
1976
1977   /* handle global setup values */
1978   si = setup;
1979   for (i=FIRST_GLOBAL_SETUP_TOKEN; i<=LAST_GLOBAL_SETUP_TOKEN; i++)
1980   {
1981     fprintf(file, "%s\n", getSetupLine("", i));
1982
1983     /* just to make things nicer :) */
1984     if (i == SETUP_TOKEN_PLAYER_NAME)
1985       fprintf(file, "\n");
1986   }
1987
1988   /* handle player specific setup values */
1989   for (pnr=0; pnr<MAX_PLAYERS; pnr++)
1990   {
1991     char prefix[30];
1992
1993     sprintf(prefix, "%s%d", TOKEN_STR_PLAYER_PREFIX, pnr + 1);
1994     fprintf(file, "\n");
1995
1996     sii = setup.input[pnr];
1997     for (i=FIRST_PLAYER_SETUP_TOKEN; i<=LAST_PLAYER_SETUP_TOKEN; i++)
1998       fprintf(file, "%s\n", getSetupLine(prefix, i));
1999   }
2000
2001   fclose(file);
2002   free(filename);
2003
2004   chmod(filename, SETUP_PERMS);
2005 }
2006
2007 void LoadLevelSetup_LastSeries()
2008 {
2009   char *filename;
2010   struct SetupFileList *level_setup_list = NULL;
2011
2012   /* always start with reliable default values */
2013   leveldir_current = leveldir_first;
2014
2015   /* ----------------------------------------------------------------------- */
2016   /* ~/.rocksndiamonds/levelsetup.conf                                       */
2017   /* ----------------------------------------------------------------------- */
2018
2019   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2020
2021   if ((level_setup_list = loadSetupFileList(filename)))
2022   {
2023     char *last_level_series =
2024       getTokenValue(level_setup_list, TOKEN_STR_LAST_LEVEL_SERIES);
2025
2026     leveldir_current = getLevelDirInfoFromFilename(last_level_series);
2027     if (leveldir_current == NULL)
2028       leveldir_current = leveldir_first;
2029
2030     checkSetupFileListIdentifier(level_setup_list, LEVELSETUP_COOKIE);
2031
2032     freeSetupFileList(level_setup_list);
2033   }
2034   else
2035     Error(ERR_WARN, "using default setup values");
2036
2037   free(filename);
2038 }
2039
2040 void SaveLevelSetup_LastSeries()
2041 {
2042   char *filename;
2043   char *level_subdir = leveldir_current->filename;
2044   FILE *file;
2045
2046   /* ----------------------------------------------------------------------- */
2047   /* ~/.rocksndiamonds/levelsetup.conf                                       */
2048   /* ----------------------------------------------------------------------- */
2049
2050   InitUserDataDirectory();
2051
2052   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2053
2054   if (!(file = fopen(filename, "w")))
2055   {
2056     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2057     free(filename);
2058     return;
2059   }
2060
2061   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2062                                                  LEVELSETUP_COOKIE));
2063   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
2064                                                level_subdir));
2065
2066   fclose(file);
2067   free(filename);
2068
2069   chmod(filename, SETUP_PERMS);
2070 }
2071
2072 static void checkSeriesInfo()
2073 {
2074   static char *level_directory = NULL;
2075   DIR *dir;
2076   struct dirent *dir_entry;
2077
2078   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
2079
2080   level_directory = getPath2((leveldir_current->user_defined ?
2081                               getUserLevelDir("") :
2082                               options.level_directory),
2083                              leveldir_current->fullpath);
2084
2085   if ((dir = opendir(level_directory)) == NULL)
2086   {
2087     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2088     return;
2089   }
2090
2091   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
2092   {
2093     if (strlen(dir_entry->d_name) > 4 &&
2094         dir_entry->d_name[3] == '.' &&
2095         strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
2096     {
2097       char levelnum_str[4];
2098       int levelnum_value;
2099
2100       strncpy(levelnum_str, dir_entry->d_name, 3);
2101       levelnum_str[3] = '\0';
2102
2103       levelnum_value = atoi(levelnum_str);
2104
2105       if (levelnum_value < leveldir_current->first_level)
2106       {
2107         Error(ERR_WARN, "additional level %d found", levelnum_value);
2108         leveldir_current->first_level = levelnum_value;
2109       }
2110       else if (levelnum_value > leveldir_current->last_level)
2111       {
2112         Error(ERR_WARN, "additional level %d found", levelnum_value);
2113         leveldir_current->last_level = levelnum_value;
2114       }
2115     }
2116   }
2117
2118   closedir(dir);
2119 }
2120
2121 void LoadLevelSetup_SeriesInfo()
2122 {
2123   char *filename;
2124   struct SetupFileList *level_setup_list = NULL;
2125   char *level_subdir = leveldir_current->filename;
2126
2127   /* always start with reliable default values */
2128   level_nr = leveldir_current->first_level;
2129
2130   checkSeriesInfo(leveldir_current);
2131
2132   /* ----------------------------------------------------------------------- */
2133   /* ~/.rocksndiamonds/levelsetup/<level series>/levelsetup.conf             */
2134   /* ----------------------------------------------------------------------- */
2135
2136   level_subdir = leveldir_current->filename;
2137
2138   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2139
2140   if ((level_setup_list = loadSetupFileList(filename)))
2141   {
2142     char *token_value;
2143
2144     token_value = getTokenValue(level_setup_list, TOKEN_STR_LAST_PLAYED_LEVEL);
2145
2146     if (token_value)
2147     {
2148       level_nr = atoi(token_value);
2149
2150       if (level_nr < leveldir_current->first_level)
2151         level_nr = leveldir_current->first_level;
2152       if (level_nr > leveldir_current->last_level)
2153         level_nr = leveldir_current->last_level;
2154     }
2155
2156     token_value = getTokenValue(level_setup_list, TOKEN_STR_HANDICAP_LEVEL);
2157
2158     if (token_value)
2159     {
2160       int level_nr = atoi(token_value);
2161
2162       if (level_nr < leveldir_current->first_level)
2163         level_nr = leveldir_current->first_level;
2164       if (level_nr > leveldir_current->last_level + 1)
2165         level_nr = leveldir_current->last_level;
2166
2167       if (leveldir_current->user_defined)
2168         level_nr = leveldir_current->last_level;
2169
2170       leveldir_current->handicap_level = level_nr;
2171     }
2172
2173     checkSetupFileListIdentifier(level_setup_list, LEVELSETUP_COOKIE);
2174
2175     freeSetupFileList(level_setup_list);
2176   }
2177   else
2178     Error(ERR_WARN, "using default setup values");
2179
2180   free(filename);
2181 }
2182
2183 void SaveLevelSetup_SeriesInfo()
2184 {
2185   char *filename;
2186   char *level_subdir = leveldir_current->filename;
2187   char *level_nr_str = int2str(level_nr, 0);
2188   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
2189   FILE *file;
2190
2191   /* ----------------------------------------------------------------------- */
2192   /* ~/.rocksndiamonds/levelsetup/<level series>/levelsetup.conf             */
2193   /* ----------------------------------------------------------------------- */
2194
2195   InitLevelSetupDirectory(level_subdir);
2196
2197   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2198
2199   if (!(file = fopen(filename, "w")))
2200   {
2201     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2202     free(filename);
2203     return;
2204   }
2205
2206   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2207                                                  LEVELSETUP_COOKIE));
2208   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
2209                                                level_nr_str));
2210   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
2211                                                handicap_level_str));
2212
2213   fclose(file);
2214   free(filename);
2215
2216   chmod(filename, SETUP_PERMS);
2217 }
2218
2219 #if !defined(PLATFORM_UNIX)
2220 void initErrorFile()
2221 {
2222   char *filename;
2223
2224   InitUserDataDirectory();
2225
2226   filename = getPath2(getUserDataDir(), ERROR_FILENAME);
2227   unlink(filename);
2228   free(filename);
2229 }
2230
2231 FILE *openErrorFile()
2232 {
2233   char *filename;
2234   FILE *error_file;
2235
2236   filename = getPath2(getUserDataDir(), ERROR_FILENAME);
2237   error_file = fopen(filename, "a");
2238   free(filename);
2239
2240   return error_file;
2241 }
2242
2243 void dumpErrorFile()
2244 {
2245   char *filename;
2246   FILE *error_file;
2247
2248   filename = getPath2(getUserDataDir(), ERROR_FILENAME);
2249   error_file = fopen(filename, "r");
2250   free(filename);
2251
2252   if (error_file != NULL)
2253   {
2254     while (!feof(error_file))
2255       fputc(fgetc(error_file), stderr);
2256
2257     fclose(error_file);
2258   }
2259 }
2260 #endif