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