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