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