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