rnd-20000815-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 "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 #ifndef 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(MSDOS) || defined(WIN32)
69 #define ERROR_FILENAME          "error.out"
70 #endif
71
72 #ifdef 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
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 #ifdef 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
1082 /* player setup */
1083 #define SETUP_TOKEN_USE_JOYSTICK        13
1084 #define SETUP_TOKEN_JOY_DEVICE_NAME     14
1085 #define SETUP_TOKEN_JOY_XLEFT           15
1086 #define SETUP_TOKEN_JOY_XMIDDLE         16
1087 #define SETUP_TOKEN_JOY_XRIGHT          17
1088 #define SETUP_TOKEN_JOY_YUPPER          18
1089 #define SETUP_TOKEN_JOY_YMIDDLE         19
1090 #define SETUP_TOKEN_JOY_YLOWER          20
1091 #define SETUP_TOKEN_JOY_SNAP            21
1092 #define SETUP_TOKEN_JOY_BOMB            22
1093 #define SETUP_TOKEN_KEY_LEFT            23
1094 #define SETUP_TOKEN_KEY_RIGHT           24
1095 #define SETUP_TOKEN_KEY_UP              25
1096 #define SETUP_TOKEN_KEY_DOWN            26
1097 #define SETUP_TOKEN_KEY_SNAP            27
1098 #define SETUP_TOKEN_KEY_BOMB            28
1099
1100 /* level directory info */
1101 #define LEVELINFO_TOKEN_NAME            29
1102 #define LEVELINFO_TOKEN_NAME_SHORT      30
1103 #define LEVELINFO_TOKEN_NAME_SORTING    31
1104 #define LEVELINFO_TOKEN_AUTHOR          32
1105 #define LEVELINFO_TOKEN_IMPORTED_FROM   33
1106 #define LEVELINFO_TOKEN_LEVELS          34
1107 #define LEVELINFO_TOKEN_FIRST_LEVEL     35
1108 #define LEVELINFO_TOKEN_SORT_PRIORITY   36
1109 #define LEVELINFO_TOKEN_LEVEL_GROUP     37
1110 #define LEVELINFO_TOKEN_READONLY        38
1111
1112 #define FIRST_GLOBAL_SETUP_TOKEN        SETUP_TOKEN_PLAYER_NAME
1113 #define LAST_GLOBAL_SETUP_TOKEN         SETUP_TOKEN_TIME_LIMIT
1114
1115 #define FIRST_PLAYER_SETUP_TOKEN        SETUP_TOKEN_USE_JOYSTICK
1116 #define LAST_PLAYER_SETUP_TOKEN         SETUP_TOKEN_KEY_BOMB
1117
1118 #define FIRST_LEVELINFO_TOKEN           LEVELINFO_TOKEN_NAME
1119 #define LAST_LEVELINFO_TOKEN            LEVELINFO_TOKEN_READONLY
1120
1121 #define TYPE_BOOLEAN                    1
1122 #define TYPE_SWITCH                     2
1123 #define TYPE_KEY                        3
1124 #define TYPE_INTEGER                    4
1125 #define TYPE_STRING                     5
1126
1127 static struct SetupInfo si;
1128 static struct SetupInputInfo sii;
1129 static struct LevelDirInfo ldi;
1130 static struct
1131 {
1132   int type;
1133   void *value;
1134   char *text;
1135 } token_info[] =
1136 {
1137   /* global setup */
1138   { TYPE_STRING,  &si.player_name,      "player_name"                   },
1139   { TYPE_SWITCH,  &si.sound,            "sound"                         },
1140   { TYPE_SWITCH,  &si.sound_loops,      "repeating_sound_loops"         },
1141   { TYPE_SWITCH,  &si.sound_music,      "background_music"              },
1142   { TYPE_SWITCH,  &si.sound_simple,     "simple_sound_effects"          },
1143
1144 #if 0
1145   { TYPE_SWITCH,  &si.toons,            "toons"                         },
1146   { TYPE_SWITCH,  &si.double_buffering, "double_buffering"              },
1147 #endif
1148
1149   { TYPE_SWITCH,  &si.scroll_delay,     "scroll_delay"                  },
1150   { TYPE_SWITCH,  &si.soft_scrolling,   "soft_scrolling"                },
1151   { TYPE_SWITCH,  &si.fading,           "screen_fading"                 },
1152   { TYPE_SWITCH,  &si.autorecord,       "automatic_tape_recording"      },
1153   { TYPE_SWITCH,  &si.quick_doors,      "quick_doors"                   },
1154   { TYPE_SWITCH,  &si.team_mode,        "team_mode"                     },
1155   { TYPE_SWITCH,  &si.handicap,         "handicap"                      },
1156   { TYPE_SWITCH,  &si.time_limit,       "time_limit"                    },
1157
1158   /* player setup */
1159   { TYPE_BOOLEAN, &sii.use_joystick,    ".use_joystick"                 },
1160   { TYPE_STRING,  &sii.joy.device_name, ".joy.device_name"              },
1161   { TYPE_INTEGER, &sii.joy.xleft,       ".joy.xleft"                    },
1162   { TYPE_INTEGER, &sii.joy.xmiddle,     ".joy.xmiddle"                  },
1163   { TYPE_INTEGER, &sii.joy.xright,      ".joy.xright"                   },
1164   { TYPE_INTEGER, &sii.joy.yupper,      ".joy.yupper"                   },
1165   { TYPE_INTEGER, &sii.joy.ymiddle,     ".joy.ymiddle"                  },
1166   { TYPE_INTEGER, &sii.joy.ylower,      ".joy.ylower"                   },
1167   { TYPE_INTEGER, &sii.joy.snap,        ".joy.snap_field"               },
1168   { TYPE_INTEGER, &sii.joy.bomb,        ".joy.place_bomb"               },
1169   { TYPE_KEY,     &sii.key.left,        ".key.move_left"                },
1170   { TYPE_KEY,     &sii.key.right,       ".key.move_right"               },
1171   { TYPE_KEY,     &sii.key.up,          ".key.move_up"                  },
1172   { TYPE_KEY,     &sii.key.down,        ".key.move_down"                },
1173   { TYPE_KEY,     &sii.key.snap,        ".key.snap_field"               },
1174   { TYPE_KEY,     &sii.key.bomb,        ".key.place_bomb"               },
1175
1176   /* level directory info */
1177   { TYPE_STRING,  &ldi.name,            "name"                          },
1178   { TYPE_STRING,  &ldi.name_short,      "name_short"                    },
1179   { TYPE_STRING,  &ldi.name_sorting,    "name_sorting"                  },
1180   { TYPE_STRING,  &ldi.author,          "author"                        },
1181   { TYPE_STRING,  &ldi.imported_from,   "imported_from"                 },
1182   { TYPE_INTEGER, &ldi.levels,          "levels"                        },
1183   { TYPE_INTEGER, &ldi.first_level,     "first_level"                   },
1184   { TYPE_INTEGER, &ldi.sort_priority,   "sort_priority"                 },
1185   { TYPE_BOOLEAN, &ldi.level_group,     "level_group"                   },
1186   { TYPE_BOOLEAN, &ldi.readonly,        "readonly"                      }
1187 };
1188
1189 static char *string_tolower(char *s)
1190 {
1191   static char s_lower[100];
1192   int i;
1193
1194   if (strlen(s) >= 100)
1195     return s;
1196
1197   strcpy(s_lower, s);
1198
1199   for (i=0; i<strlen(s_lower); i++)
1200     s_lower[i] = tolower(s_lower[i]);
1201
1202   return s_lower;
1203 }
1204
1205 static int get_string_integer_value(char *s)
1206 {
1207   static char *number_text[][3] =
1208   {
1209     { "0", "zero", "null", },
1210     { "1", "one", "first" },
1211     { "2", "two", "second" },
1212     { "3", "three", "third" },
1213     { "4", "four", "fourth" },
1214     { "5", "five", "fifth" },
1215     { "6", "six", "sixth" },
1216     { "7", "seven", "seventh" },
1217     { "8", "eight", "eighth" },
1218     { "9", "nine", "ninth" },
1219     { "10", "ten", "tenth" },
1220     { "11", "eleven", "eleventh" },
1221     { "12", "twelve", "twelfth" },
1222   };
1223
1224   int i, j;
1225
1226   for (i=0; i<13; i++)
1227     for (j=0; j<3; j++)
1228       if (strcmp(string_tolower(s), number_text[i][j]) == 0)
1229         return i;
1230
1231   return atoi(s);
1232 }
1233
1234 static boolean get_string_boolean_value(char *s)
1235 {
1236   if (strcmp(string_tolower(s), "true") == 0 ||
1237       strcmp(string_tolower(s), "yes") == 0 ||
1238       strcmp(string_tolower(s), "on") == 0 ||
1239       get_string_integer_value(s) == 1)
1240     return TRUE;
1241   else
1242     return FALSE;
1243 }
1244
1245 static char *getFormattedSetupEntry(char *token, char *value)
1246 {
1247   int i;
1248   static char entry[MAX_LINE_LEN];
1249
1250   sprintf(entry, "%s:", token);
1251   for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
1252     entry[i] = ' ';
1253   entry[i] = '\0';
1254
1255   strcat(entry, value);
1256
1257   return entry;
1258 }
1259
1260 static void freeSetupFileList(struct SetupFileList *setup_file_list)
1261 {
1262   if (!setup_file_list)
1263     return;
1264
1265   if (setup_file_list->token)
1266     free(setup_file_list->token);
1267   if (setup_file_list->value)
1268     free(setup_file_list->value);
1269   if (setup_file_list->next)
1270     freeSetupFileList(setup_file_list->next);
1271   free(setup_file_list);
1272 }
1273
1274 static struct SetupFileList *newSetupFileList(char *token, char *value)
1275 {
1276   struct SetupFileList *new = checked_malloc(sizeof(struct SetupFileList));
1277
1278   new->token = checked_malloc(strlen(token) + 1);
1279   strcpy(new->token, token);
1280
1281   new->value = checked_malloc(strlen(value) + 1);
1282   strcpy(new->value, value);
1283
1284   new->next = NULL;
1285
1286   return new;
1287 }
1288
1289 static char *getTokenValue(struct SetupFileList *setup_file_list,
1290                            char *token)
1291 {
1292   if (!setup_file_list)
1293     return NULL;
1294
1295   if (strcmp(setup_file_list->token, token) == 0)
1296     return setup_file_list->value;
1297   else
1298     return getTokenValue(setup_file_list->next, token);
1299 }
1300
1301 static void setTokenValue(struct SetupFileList *setup_file_list,
1302                           char *token, char *value)
1303 {
1304   if (!setup_file_list)
1305     return;
1306
1307   if (strcmp(setup_file_list->token, token) == 0)
1308   {
1309     free(setup_file_list->value);
1310     setup_file_list->value = checked_malloc(strlen(value) + 1);
1311     strcpy(setup_file_list->value, value);
1312   }
1313   else if (setup_file_list->next == NULL)
1314     setup_file_list->next = newSetupFileList(token, value);
1315   else
1316     setTokenValue(setup_file_list->next, token, value);
1317 }
1318
1319 #ifdef DEBUG
1320 static void printSetupFileList(struct SetupFileList *setup_file_list)
1321 {
1322   if (!setup_file_list)
1323     return;
1324
1325   printf("token: '%s'\n", setup_file_list->token);
1326   printf("value: '%s'\n", setup_file_list->value);
1327
1328   printSetupFileList(setup_file_list->next);
1329 }
1330 #endif
1331
1332 static struct SetupFileList *loadSetupFileList(char *filename)
1333 {
1334   int line_len;
1335   char line[MAX_LINE_LEN];
1336   char *token, *value, *line_ptr;
1337   struct SetupFileList *setup_file_list = newSetupFileList("", "");
1338   struct SetupFileList *first_valid_list_entry;
1339
1340   FILE *file;
1341
1342   if (!(file = fopen(filename, "r")))
1343   {
1344     Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1345     return NULL;
1346   }
1347
1348   while(!feof(file))
1349   {
1350     /* read next line of input file */
1351     if (!fgets(line, MAX_LINE_LEN, file))
1352       break;
1353
1354     /* cut trailing comment or whitespace from input line */
1355     for (line_ptr = line; *line_ptr; line_ptr++)
1356     {
1357       if (*line_ptr == '#' || *line_ptr == '\n' || *line_ptr == '\r')
1358       {
1359         *line_ptr = '\0';
1360         break;
1361       }
1362     }
1363
1364     /* cut trailing whitespaces from input line */
1365     for (line_ptr = &line[strlen(line)]; line_ptr > line; line_ptr--)
1366       if ((*line_ptr == ' ' || *line_ptr == '\t') && line_ptr[1] == '\0')
1367         *line_ptr = '\0';
1368
1369     /* ignore empty lines */
1370     if (*line == '\0')
1371       continue;
1372
1373     line_len = strlen(line);
1374
1375     /* cut leading whitespaces from token */
1376     for (token = line; *token; token++)
1377       if (*token != ' ' && *token != '\t')
1378         break;
1379
1380     /* find end of token */
1381     for (line_ptr = token; *line_ptr; line_ptr++)
1382     {
1383       if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1384       {
1385         *line_ptr = '\0';
1386         break;
1387       }
1388     }
1389
1390     if (line_ptr < line + line_len)
1391       value = line_ptr + 1;
1392     else
1393       value = "\0";
1394
1395     /* cut leading whitespaces from value */
1396     for (; *value; value++)
1397       if (*value != ' ' && *value != '\t')
1398         break;
1399
1400     if (*token && *value)
1401       setTokenValue(setup_file_list, token, value);
1402   }
1403
1404   fclose(file);
1405
1406   first_valid_list_entry = setup_file_list->next;
1407
1408   /* free empty list header */
1409   setup_file_list->next = NULL;
1410   freeSetupFileList(setup_file_list);
1411
1412   if (first_valid_list_entry == NULL)
1413     Error(ERR_WARN, "configuration file '%s' is empty", filename);
1414
1415   return first_valid_list_entry;
1416 }
1417
1418 static void checkSetupFileListIdentifier(struct SetupFileList *setup_file_list,
1419                                          char *identifier)
1420 {
1421   if (!setup_file_list)
1422     return;
1423
1424   if (strcmp(setup_file_list->token, TOKEN_STR_FILE_IDENTIFIER) == 0)
1425   {
1426     if (strcmp(setup_file_list->value, identifier) != 0)
1427     {
1428       Error(ERR_WARN, "configuration file has wrong version");
1429       return;
1430     }
1431     else
1432       return;
1433   }
1434
1435   if (setup_file_list->next)
1436     checkSetupFileListIdentifier(setup_file_list->next, identifier);
1437   else
1438   {
1439     Error(ERR_WARN, "configuration file has no version information");
1440     return;
1441   }
1442 }
1443
1444 static void setLevelDirInfoToDefaults(struct LevelDirInfo *ldi)
1445 {
1446   ldi->filename = NULL;
1447   ldi->fullpath = NULL;
1448   ldi->basepath = NULL;
1449   ldi->name = getStringCopy(ANONYMOUS_NAME);
1450   ldi->name_short = NULL;
1451   ldi->name_sorting = NULL;
1452   ldi->author = getStringCopy(ANONYMOUS_NAME);
1453   ldi->imported_from = NULL;
1454   ldi->levels = 0;
1455   ldi->first_level = 0;
1456   ldi->last_level = 0;
1457   ldi->sort_priority = LEVELCLASS_UNDEFINED;    /* default: least priority */
1458   ldi->level_group = FALSE;
1459   ldi->parent_link = FALSE;
1460   ldi->user_defined = FALSE;
1461   ldi->readonly = TRUE;
1462   ldi->color = 0;
1463   ldi->class_desc = NULL;
1464   ldi->handicap_level = 0;
1465   ldi->cl_first = -1;
1466   ldi->cl_cursor = -1;
1467
1468   ldi->node_parent = NULL;
1469   ldi->node_group = NULL;
1470   ldi->next = NULL;
1471 }
1472
1473 static void setLevelDirInfoToDefaultsFromParent(struct LevelDirInfo *ldi,
1474                                                 struct LevelDirInfo *parent)
1475 {
1476   if (parent == NULL)
1477   {
1478     setLevelDirInfoToDefaults(ldi);
1479     return;
1480   }
1481
1482   /* first copy all values from the parent structure ... */
1483   *ldi = *parent;
1484
1485   /* ... then set all fields to default that cannot be inherited from parent.
1486      This is especially important for all those fields that can be set from
1487      the 'levelinfo.conf' config file, because the function 'setSetupInfo()'
1488      calls 'free()' for all already set token values which requires that no
1489      other structure's pointer may point to them!
1490   */
1491
1492   ldi->filename = NULL;
1493   ldi->fullpath = NULL;
1494   ldi->basepath = NULL;
1495   ldi->name = getStringCopy(ANONYMOUS_NAME);
1496   ldi->name_short = NULL;
1497   ldi->name_sorting = NULL;
1498   ldi->author = getStringCopy(parent->author);
1499   ldi->imported_from = getStringCopy(parent->imported_from);
1500
1501   ldi->level_group = FALSE;
1502   ldi->parent_link = FALSE;
1503
1504   ldi->node_parent = parent;
1505   ldi->node_group = NULL;
1506   ldi->next = NULL;
1507 }
1508
1509 static void setSetupInfoToDefaults(struct SetupInfo *si)
1510 {
1511   int i;
1512
1513   si->player_name = getStringCopy(getLoginName());
1514
1515   si->sound = TRUE;
1516   si->sound_loops = TRUE;
1517   si->sound_music = TRUE;
1518   si->sound_simple = TRUE;
1519   si->toons = TRUE;
1520   si->double_buffering = TRUE;
1521   si->direct_draw = !si->double_buffering;
1522   si->scroll_delay = TRUE;
1523   si->soft_scrolling = TRUE;
1524   si->fading = FALSE;
1525   si->autorecord = TRUE;
1526   si->quick_doors = FALSE;
1527   si->team_mode = FALSE;
1528   si->handicap = TRUE;
1529   si->time_limit = TRUE;
1530
1531   for (i=0; i<MAX_PLAYERS; i++)
1532   {
1533     si->input[i].use_joystick = FALSE;
1534     si->input[i].joy.device_name = getStringCopy(joystick_device_name[i]);
1535     si->input[i].joy.xleft   = JOYSTICK_XLEFT;
1536     si->input[i].joy.xmiddle = JOYSTICK_XMIDDLE;
1537     si->input[i].joy.xright  = JOYSTICK_XRIGHT;
1538     si->input[i].joy.yupper  = JOYSTICK_YUPPER;
1539     si->input[i].joy.ymiddle = JOYSTICK_YMIDDLE;
1540     si->input[i].joy.ylower  = JOYSTICK_YLOWER;
1541     si->input[i].joy.snap  = (i == 0 ? JOY_BUTTON_1 : 0);
1542     si->input[i].joy.bomb  = (i == 0 ? JOY_BUTTON_2 : 0);
1543     si->input[i].key.left  = (i == 0 ? DEFAULT_KEY_LEFT  : KEY_UNDEFINED);
1544     si->input[i].key.right = (i == 0 ? DEFAULT_KEY_RIGHT : KEY_UNDEFINED);
1545     si->input[i].key.up    = (i == 0 ? DEFAULT_KEY_UP    : KEY_UNDEFINED);
1546     si->input[i].key.down  = (i == 0 ? DEFAULT_KEY_DOWN  : KEY_UNDEFINED);
1547     si->input[i].key.snap  = (i == 0 ? DEFAULT_KEY_SNAP  : KEY_UNDEFINED);
1548     si->input[i].key.bomb  = (i == 0 ? DEFAULT_KEY_BOMB  : KEY_UNDEFINED);
1549   }
1550 }
1551
1552 static void setSetupInfo(int token_nr, char *token_value)
1553 {
1554   int token_type = token_info[token_nr].type;
1555   void *setup_value = token_info[token_nr].value;
1556
1557   if (token_value == NULL)
1558     return;
1559
1560   /* set setup field to corresponding token value */
1561   switch (token_type)
1562   {
1563     case TYPE_BOOLEAN:
1564     case TYPE_SWITCH:
1565       *(boolean *)setup_value = get_string_boolean_value(token_value);
1566       break;
1567
1568     case TYPE_KEY:
1569       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
1570       break;
1571
1572     case TYPE_INTEGER:
1573       *(int *)setup_value = get_string_integer_value(token_value);
1574       break;
1575
1576     case TYPE_STRING:
1577       if (*(char **)setup_value != NULL)
1578         free(*(char **)setup_value);
1579       *(char **)setup_value = getStringCopy(token_value);
1580       break;
1581
1582     default:
1583       break;
1584   }
1585 }
1586
1587 static void decodeSetupFileList(struct SetupFileList *setup_file_list)
1588 {
1589   int i, pnr;
1590
1591   if (!setup_file_list)
1592     return;
1593
1594   /* handle global setup values */
1595   si = setup;
1596   for (i=FIRST_GLOBAL_SETUP_TOKEN; i<=LAST_GLOBAL_SETUP_TOKEN; i++)
1597     setSetupInfo(i, getTokenValue(setup_file_list, token_info[i].text));
1598   setup = si;
1599
1600   /* handle player specific setup values */
1601   for (pnr=0; pnr<MAX_PLAYERS; pnr++)
1602   {
1603     char prefix[30];
1604
1605     sprintf(prefix, "%s%d", TOKEN_STR_PLAYER_PREFIX, pnr + 1);
1606
1607     sii = setup.input[pnr];
1608     for (i=FIRST_PLAYER_SETUP_TOKEN; i<=LAST_PLAYER_SETUP_TOKEN; i++)
1609     {
1610       char full_token[100];
1611
1612       sprintf(full_token, "%s%s", prefix, token_info[i].text);
1613       setSetupInfo(i, getTokenValue(setup_file_list, full_token));
1614     }
1615     setup.input[pnr] = sii;
1616   }
1617 }
1618
1619 static int compareLevelDirInfoEntries(const void *object1, const void *object2)
1620 {
1621   const struct LevelDirInfo *entry1 = *((struct LevelDirInfo **)object1);
1622   const struct LevelDirInfo *entry2 = *((struct LevelDirInfo **)object2);
1623   int compare_result;
1624
1625   if (entry1->parent_link || entry2->parent_link)
1626     compare_result = (entry1->parent_link ? -1 : +1);
1627   else if (entry1->sort_priority == entry2->sort_priority)
1628   {
1629     char *name1 = getStringToLower(entry1->name_sorting);
1630     char *name2 = getStringToLower(entry2->name_sorting);
1631
1632     compare_result = strcmp(name1, name2);
1633
1634     free(name1);
1635     free(name2);
1636   }
1637   else if (LEVELSORTING(entry1) == LEVELSORTING(entry2))
1638     compare_result = entry1->sort_priority - entry2->sort_priority;
1639   else
1640     compare_result = LEVELSORTING(entry1) - LEVELSORTING(entry2);
1641
1642   return compare_result;
1643 }
1644
1645 static void createParentLevelDirNode(struct LevelDirInfo *node_parent)
1646 {
1647   struct LevelDirInfo *leveldir_new = newLevelDirInfo();
1648
1649   setLevelDirInfoToDefaults(leveldir_new);
1650
1651   leveldir_new->node_parent = node_parent;
1652   leveldir_new->parent_link = TRUE;
1653
1654   leveldir_new->name = ".. (parent directory)";
1655   leveldir_new->name_short = getStringCopy(leveldir_new->name);
1656   leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1657
1658   leveldir_new->filename = "..";
1659   leveldir_new->fullpath = getStringCopy(node_parent->fullpath);
1660
1661   leveldir_new->sort_priority = node_parent->sort_priority;
1662   leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1663
1664   pushLevelDirInfo(&node_parent->node_group, leveldir_new);
1665 }
1666
1667 static void LoadLevelInfoFromLevelDir(struct LevelDirInfo **node_first,
1668                                       struct LevelDirInfo *node_parent,
1669                                       char *level_directory)
1670 {
1671   DIR *dir;
1672   struct dirent *dir_entry;
1673   boolean valid_entry_found = FALSE;
1674
1675   if ((dir = opendir(level_directory)) == NULL)
1676   {
1677     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1678     return;
1679   }
1680
1681   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
1682   {
1683     struct SetupFileList *setup_file_list = NULL;
1684     struct stat file_status;
1685     char *directory_name = dir_entry->d_name;
1686     char *directory_path = getPath2(level_directory, directory_name);
1687     char *filename = NULL;
1688
1689     /* skip entries for current and parent directory */
1690     if (strcmp(directory_name, ".")  == 0 ||
1691         strcmp(directory_name, "..") == 0)
1692     {
1693       free(directory_path);
1694       continue;
1695     }
1696
1697     /* find out if directory entry is itself a directory */
1698     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
1699         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
1700     {
1701       free(directory_path);
1702       continue;
1703     }
1704
1705     filename = getPath2(directory_path, LEVELINFO_FILENAME);
1706     setup_file_list = loadSetupFileList(filename);
1707
1708     if (setup_file_list)
1709     {
1710       struct LevelDirInfo *leveldir_new = newLevelDirInfo();
1711       int i;
1712
1713       checkSetupFileListIdentifier(setup_file_list, LEVELINFO_COOKIE);
1714       setLevelDirInfoToDefaultsFromParent(leveldir_new, node_parent);
1715
1716       /* set all structure fields according to the token/value pairs */
1717       ldi = *leveldir_new;
1718       for (i=FIRST_LEVELINFO_TOKEN; i<=LAST_LEVELINFO_TOKEN; i++)
1719         setSetupInfo(i, getTokenValue(setup_file_list, token_info[i].text));
1720       *leveldir_new = ldi;
1721
1722       DrawInitText(leveldir_new->name, 150, FC_YELLOW);
1723
1724       if (leveldir_new->name_short == NULL)
1725         leveldir_new->name_short = getStringCopy(leveldir_new->name);
1726
1727       if (leveldir_new->name_sorting == NULL)
1728         leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1729
1730       leveldir_new->filename = getStringCopy(directory_name);
1731
1732       if (node_parent == NULL)          /* top level group */
1733       {
1734         leveldir_new->basepath = level_directory;
1735         leveldir_new->fullpath = leveldir_new->filename;
1736       }
1737       else                              /* sub level group */
1738       {
1739         leveldir_new->basepath = node_parent->basepath;
1740         leveldir_new->fullpath = getPath2(node_parent->fullpath,
1741                                           directory_name);
1742       }
1743
1744       if (leveldir_new->levels < 1)
1745         leveldir_new->levels = 1;
1746
1747       leveldir_new->last_level =
1748         leveldir_new->first_level + leveldir_new->levels - 1;
1749
1750       leveldir_new->user_defined =
1751         (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
1752
1753       leveldir_new->color = LEVELCOLOR(leveldir_new);
1754       leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1755
1756       leveldir_new->handicap_level =    /* set handicap to default value */
1757         (leveldir_new->user_defined ?
1758          leveldir_new->last_level :
1759          leveldir_new->first_level);
1760
1761       pushLevelDirInfo(node_first, leveldir_new);
1762
1763       freeSetupFileList(setup_file_list);
1764       valid_entry_found = TRUE;
1765
1766       if (leveldir_new->level_group)
1767       {
1768         /* create node to link back to current level directory */
1769         createParentLevelDirNode(leveldir_new);
1770
1771         /* step into sub-directory and look for more level series */
1772         LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
1773                                   leveldir_new, directory_path);
1774       }
1775     }
1776     else
1777       Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
1778
1779     free(directory_path);
1780     free(filename);
1781   }
1782
1783   closedir(dir);
1784
1785   if (!valid_entry_found)
1786     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
1787           level_directory);
1788 }
1789
1790 void LoadLevelInfo()
1791 {
1792   InitUserLevelDirectory(getLoginName());
1793
1794   DrawInitText("Loading level series:", 120, FC_GREEN);
1795
1796   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
1797   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(""));
1798
1799   leveldir_current = getFirstValidLevelSeries(leveldir_first);
1800
1801   if (leveldir_first == NULL)
1802     Error(ERR_EXIT, "cannot find any valid level series in any directory");
1803
1804   sortLevelDirInfo(&leveldir_first, compareLevelDirInfoEntries);
1805
1806 #if 0
1807   dumpLevelDirInfo(leveldir_first, 0);
1808 #endif
1809 }
1810
1811 static void SaveUserLevelInfo()
1812 {
1813   char *filename;
1814   FILE *file;
1815   int i;
1816
1817   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
1818
1819   if (!(file = fopen(filename, "w")))
1820   {
1821     Error(ERR_WARN, "cannot write level info file '%s'", filename);
1822     free(filename);
1823     return;
1824   }
1825
1826   /* always start with reliable default values */
1827   setLevelDirInfoToDefaults(&ldi);
1828
1829   ldi.name = getLoginName();
1830   ldi.author = getRealName();
1831   ldi.levels = 100;
1832   ldi.first_level = 1;
1833   ldi.sort_priority = LEVELCLASS_USER_START;
1834   ldi.readonly = FALSE;
1835
1836   fprintf(file, "%s\n\n",
1837           getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER, LEVELINFO_COOKIE));
1838
1839   for (i=FIRST_LEVELINFO_TOKEN; i<=LAST_LEVELINFO_TOKEN; i++)
1840     if (i != LEVELINFO_TOKEN_NAME_SHORT &&
1841         i != LEVELINFO_TOKEN_NAME_SORTING &&
1842         i != LEVELINFO_TOKEN_IMPORTED_FROM)
1843       fprintf(file, "%s\n", getSetupLine("", i));
1844
1845   fclose(file);
1846   free(filename);
1847
1848   chmod(filename, SETUP_PERMS);
1849 }
1850
1851 void LoadSetup()
1852 {
1853   char *filename;
1854   struct SetupFileList *setup_file_list = NULL;
1855
1856   /* always start with reliable default values */
1857   setSetupInfoToDefaults(&setup);
1858
1859   filename = getPath2(getSetupDir(), SETUP_FILENAME);
1860
1861   setup_file_list = loadSetupFileList(filename);
1862
1863   if (setup_file_list)
1864   {
1865     checkSetupFileListIdentifier(setup_file_list, SETUP_COOKIE);
1866     decodeSetupFileList(setup_file_list);
1867
1868     setup.direct_draw = !setup.double_buffering;
1869
1870     freeSetupFileList(setup_file_list);
1871
1872     /* needed to work around problems with fixed length strings */
1873     if (strlen(setup.player_name) > MAX_PLAYER_NAME_LEN)
1874       setup.player_name[MAX_PLAYER_NAME_LEN] = '\0';
1875     else if (strlen(setup.player_name) < MAX_PLAYER_NAME_LEN)
1876     {
1877       char *new_name = checked_malloc(MAX_PLAYER_NAME_LEN + 1);
1878
1879       strcpy(new_name, setup.player_name);
1880       free(setup.player_name);
1881       setup.player_name = new_name;
1882     }
1883   }
1884   else
1885     Error(ERR_WARN, "using default setup values");
1886
1887   free(filename);
1888 }
1889
1890 static char *getSetupLine(char *prefix, int token_nr)
1891 {
1892   int i;
1893   static char entry[MAX_LINE_LEN];
1894   int token_type = token_info[token_nr].type;
1895   void *setup_value = token_info[token_nr].value;
1896   char *token_text = token_info[token_nr].text;
1897
1898   /* start with the prefix, token and some spaces to format output line */
1899   sprintf(entry, "%s%s:", prefix, token_text);
1900   for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
1901     strcat(entry, " ");
1902
1903   /* continue with the token's value (which can have different types) */
1904   switch (token_type)
1905   {
1906     case TYPE_BOOLEAN:
1907       strcat(entry, (*(boolean *)setup_value ? "true" : "false"));
1908       break;
1909
1910     case TYPE_SWITCH:
1911       strcat(entry, (*(boolean *)setup_value ? "on" : "off"));
1912       break;
1913
1914     case TYPE_KEY:
1915       {
1916         Key key = *(Key *)setup_value;
1917         char *keyname = getKeyNameFromKey(key);
1918
1919         strcat(entry, getX11KeyNameFromKey(key));
1920         for (i=strlen(entry); i<50; i++)
1921           strcat(entry, " ");
1922
1923         /* add comment, if useful */
1924         if (strcmp(keyname, "(undefined)") != 0 &&
1925             strcmp(keyname, "(unknown)") != 0)
1926         {
1927           strcat(entry, "# ");
1928           strcat(entry, keyname);
1929         }
1930       }
1931       break;
1932
1933     case TYPE_INTEGER:
1934       {
1935         char buffer[MAX_LINE_LEN];
1936
1937         sprintf(buffer, "%d", *(int *)setup_value);
1938         strcat(entry, buffer);
1939       }
1940       break;
1941
1942     case TYPE_STRING:
1943       strcat(entry, *(char **)setup_value);
1944       break;
1945
1946     default:
1947       break;
1948   }
1949
1950   return entry;
1951 }
1952
1953 void SaveSetup()
1954 {
1955   int i, pnr;
1956   char *filename;
1957   FILE *file;
1958
1959   InitUserDataDirectory();
1960
1961   filename = getPath2(getSetupDir(), SETUP_FILENAME);
1962
1963   if (!(file = fopen(filename, "w")))
1964   {
1965     Error(ERR_WARN, "cannot write setup file '%s'", filename);
1966     free(filename);
1967     return;
1968   }
1969
1970   fprintf(file, "%s\n",
1971           getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER, SETUP_COOKIE));
1972   fprintf(file, "\n");
1973
1974   /* handle global setup values */
1975   si = setup;
1976   for (i=FIRST_GLOBAL_SETUP_TOKEN; i<=LAST_GLOBAL_SETUP_TOKEN; i++)
1977   {
1978     fprintf(file, "%s\n", getSetupLine("", i));
1979
1980     /* just to make things nicer :) */
1981     if (i == SETUP_TOKEN_PLAYER_NAME)
1982       fprintf(file, "\n");
1983   }
1984
1985   /* handle player specific setup values */
1986   for (pnr=0; pnr<MAX_PLAYERS; pnr++)
1987   {
1988     char prefix[30];
1989
1990     sprintf(prefix, "%s%d", TOKEN_STR_PLAYER_PREFIX, pnr + 1);
1991     fprintf(file, "\n");
1992
1993     sii = setup.input[pnr];
1994     for (i=FIRST_PLAYER_SETUP_TOKEN; i<=LAST_PLAYER_SETUP_TOKEN; i++)
1995       fprintf(file, "%s\n", getSetupLine(prefix, i));
1996   }
1997
1998   fclose(file);
1999   free(filename);
2000
2001   chmod(filename, SETUP_PERMS);
2002 }
2003
2004 void LoadLevelSetup_LastSeries()
2005 {
2006   char *filename;
2007   struct SetupFileList *level_setup_list = NULL;
2008
2009   /* always start with reliable default values */
2010   leveldir_current = leveldir_first;
2011
2012   /* ----------------------------------------------------------------------- */
2013   /* ~/.rocksndiamonds/levelsetup.conf                                       */
2014   /* ----------------------------------------------------------------------- */
2015
2016   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2017
2018   if ((level_setup_list = loadSetupFileList(filename)))
2019   {
2020     char *last_level_series =
2021       getTokenValue(level_setup_list, TOKEN_STR_LAST_LEVEL_SERIES);
2022
2023     leveldir_current = getLevelDirInfoFromFilename(last_level_series);
2024     if (leveldir_current == NULL)
2025       leveldir_current = leveldir_first;
2026
2027     checkSetupFileListIdentifier(level_setup_list, LEVELSETUP_COOKIE);
2028
2029     freeSetupFileList(level_setup_list);
2030   }
2031   else
2032     Error(ERR_WARN, "using default setup values");
2033
2034   free(filename);
2035 }
2036
2037 void SaveLevelSetup_LastSeries()
2038 {
2039   char *filename;
2040   char *level_subdir = leveldir_current->filename;
2041   FILE *file;
2042
2043   /* ----------------------------------------------------------------------- */
2044   /* ~/.rocksndiamonds/levelsetup.conf                                       */
2045   /* ----------------------------------------------------------------------- */
2046
2047   InitUserDataDirectory();
2048
2049   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2050
2051   if (!(file = fopen(filename, "w")))
2052   {
2053     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2054     free(filename);
2055     return;
2056   }
2057
2058   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2059                                                  LEVELSETUP_COOKIE));
2060   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
2061                                                level_subdir));
2062
2063   fclose(file);
2064   free(filename);
2065
2066   chmod(filename, SETUP_PERMS);
2067 }
2068
2069 static void checkSeriesInfo()
2070 {
2071   static char *level_directory = NULL;
2072   DIR *dir;
2073   struct dirent *dir_entry;
2074
2075   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
2076
2077   level_directory = getPath2((leveldir_current->user_defined ?
2078                               getUserLevelDir("") :
2079                               options.level_directory),
2080                              leveldir_current->filename);
2081
2082   if ((dir = opendir(level_directory)) == NULL)
2083   {
2084     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2085     return;
2086   }
2087
2088   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
2089   {
2090     if (strlen(dir_entry->d_name) > 4 &&
2091         dir_entry->d_name[3] == '.' &&
2092         strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
2093     {
2094       char levelnum_str[4];
2095       int levelnum_value;
2096
2097       strncpy(levelnum_str, dir_entry->d_name, 3);
2098       levelnum_str[3] = '\0';
2099
2100       levelnum_value = atoi(levelnum_str);
2101
2102       if (levelnum_value < leveldir_current->first_level)
2103       {
2104         Error(ERR_WARN, "additional level %d found", levelnum_value);
2105         leveldir_current->first_level = levelnum_value;
2106       }
2107       else if (levelnum_value > leveldir_current->last_level)
2108       {
2109         Error(ERR_WARN, "additional level %d found", levelnum_value);
2110         leveldir_current->last_level = levelnum_value;
2111       }
2112     }
2113   }
2114
2115   closedir(dir);
2116 }
2117
2118 void LoadLevelSetup_SeriesInfo()
2119 {
2120   char *filename;
2121   struct SetupFileList *level_setup_list = NULL;
2122   char *level_subdir = leveldir_current->filename;
2123
2124   /* always start with reliable default values */
2125   level_nr = leveldir_current->first_level;
2126
2127   checkSeriesInfo(leveldir_current);
2128
2129   /* ----------------------------------------------------------------------- */
2130   /* ~/.rocksndiamonds/levelsetup/<level series>/levelsetup.conf             */
2131   /* ----------------------------------------------------------------------- */
2132
2133   level_subdir = leveldir_current->filename;
2134
2135   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2136
2137   if ((level_setup_list = loadSetupFileList(filename)))
2138   {
2139     char *token_value;
2140
2141     token_value = getTokenValue(level_setup_list, TOKEN_STR_LAST_PLAYED_LEVEL);
2142
2143     if (token_value)
2144     {
2145       level_nr = atoi(token_value);
2146
2147       if (level_nr < leveldir_current->first_level)
2148         level_nr = leveldir_current->first_level;
2149       if (level_nr > leveldir_current->last_level)
2150         level_nr = leveldir_current->last_level;
2151     }
2152
2153     token_value = getTokenValue(level_setup_list, TOKEN_STR_HANDICAP_LEVEL);
2154
2155     if (token_value)
2156     {
2157       int level_nr = atoi(token_value);
2158
2159       if (level_nr < leveldir_current->first_level)
2160         level_nr = leveldir_current->first_level;
2161       if (level_nr > leveldir_current->last_level + 1)
2162         level_nr = leveldir_current->last_level;
2163
2164       if (leveldir_current->user_defined)
2165         level_nr = leveldir_current->last_level;
2166
2167       leveldir_current->handicap_level = level_nr;
2168     }
2169
2170     checkSetupFileListIdentifier(level_setup_list, LEVELSETUP_COOKIE);
2171
2172     freeSetupFileList(level_setup_list);
2173   }
2174   else
2175     Error(ERR_WARN, "using default setup values");
2176
2177   free(filename);
2178 }
2179
2180 void SaveLevelSetup_SeriesInfo()
2181 {
2182   char *filename;
2183   char *level_subdir = leveldir_current->filename;
2184   char *level_nr_str = int2str(level_nr, 0);
2185   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
2186   FILE *file;
2187
2188   /* ----------------------------------------------------------------------- */
2189   /* ~/.rocksndiamonds/levelsetup/<level series>/levelsetup.conf             */
2190   /* ----------------------------------------------------------------------- */
2191
2192   InitLevelSetupDirectory(level_subdir);
2193
2194   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2195
2196   if (!(file = fopen(filename, "w")))
2197   {
2198     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2199     free(filename);
2200     return;
2201   }
2202
2203   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2204                                                  LEVELSETUP_COOKIE));
2205   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
2206                                                level_nr_str));
2207   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
2208                                                handicap_level_str));
2209
2210   fclose(file);
2211   free(filename);
2212
2213   chmod(filename, SETUP_PERMS);
2214 }
2215
2216 #if defined(MSDOS) || defined(WIN32)
2217 void initErrorFile()
2218 {
2219   char *filename;
2220
2221   InitUserDataDirectory();
2222
2223   filename = getPath2(getUserDataDir(), ERROR_FILENAME);
2224   unlink(filename);
2225   free(filename);
2226 }
2227
2228 FILE *openErrorFile()
2229 {
2230   char *filename;
2231   FILE *error_file;
2232
2233   filename = getPath2(getUserDataDir(), ERROR_FILENAME);
2234   error_file = fopen(filename, "a");
2235   free(filename);
2236
2237   return error_file;
2238 }
2239
2240 void dumpErrorFile()
2241 {
2242   char *filename;
2243   FILE *error_file;
2244
2245   filename = getPath2(getUserDataDir(), ERROR_FILENAME);
2246   error_file = fopen(filename, "r");
2247   free(filename);
2248
2249   if (error_file != NULL)
2250   {
2251     while (!feof(error_file))
2252       fputc(fgetc(error_file), stderr);
2253
2254     fclose(error_file);
2255   }
2256 }
2257 #endif