e0d444b1cd2ceba546f9227c70e6faab3ae68dff
[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 setSetupInfoToDefaults(struct SetupInfo *si)
1435 {
1436   int i;
1437
1438   si->player_name = getStringCopy(getLoginName());
1439
1440   si->sound = TRUE;
1441   si->sound_loops = TRUE;
1442   si->sound_music = TRUE;
1443   si->sound_simple = TRUE;
1444   si->toons = TRUE;
1445   si->double_buffering = TRUE;
1446   si->direct_draw = !si->double_buffering;
1447   si->scroll_delay = TRUE;
1448   si->soft_scrolling = TRUE;
1449   si->fading = FALSE;
1450   si->autorecord = TRUE;
1451   si->quick_doors = FALSE;
1452   si->team_mode = FALSE;
1453   si->handicap = TRUE;
1454   si->time_limit = TRUE;
1455
1456   for (i=0; i<MAX_PLAYERS; i++)
1457   {
1458     si->input[i].use_joystick = FALSE;
1459     si->input[i].joy.device_name = getStringCopy(joystick_device_name[i]);
1460     si->input[i].joy.xleft   = JOYSTICK_XLEFT;
1461     si->input[i].joy.xmiddle = JOYSTICK_XMIDDLE;
1462     si->input[i].joy.xright  = JOYSTICK_XRIGHT;
1463     si->input[i].joy.yupper  = JOYSTICK_YUPPER;
1464     si->input[i].joy.ymiddle = JOYSTICK_YMIDDLE;
1465     si->input[i].joy.ylower  = JOYSTICK_YLOWER;
1466     si->input[i].joy.snap  = (i == 0 ? JOY_BUTTON_1 : 0);
1467     si->input[i].joy.bomb  = (i == 0 ? JOY_BUTTON_2 : 0);
1468     si->input[i].key.left  = (i == 0 ? DEFAULT_KEY_LEFT  : KEY_UNDEFINDED);
1469     si->input[i].key.right = (i == 0 ? DEFAULT_KEY_RIGHT : KEY_UNDEFINDED);
1470     si->input[i].key.up    = (i == 0 ? DEFAULT_KEY_UP    : KEY_UNDEFINDED);
1471     si->input[i].key.down  = (i == 0 ? DEFAULT_KEY_DOWN  : KEY_UNDEFINDED);
1472     si->input[i].key.snap  = (i == 0 ? DEFAULT_KEY_SNAP  : KEY_UNDEFINDED);
1473     si->input[i].key.bomb  = (i == 0 ? DEFAULT_KEY_BOMB  : KEY_UNDEFINDED);
1474   }
1475 }
1476
1477 static void setSetupInfo(int token_nr, char *token_value)
1478 {
1479   int token_type = token_info[token_nr].type;
1480   void *setup_value = token_info[token_nr].value;
1481
1482   if (token_value == NULL)
1483     return;
1484
1485   /* set setup field to corresponding token value */
1486   switch (token_type)
1487   {
1488     case TYPE_BOOLEAN:
1489     case TYPE_SWITCH:
1490       *(boolean *)setup_value = get_string_boolean_value(token_value);
1491       break;
1492
1493     case TYPE_KEYSYM:
1494       *(KeySym *)setup_value = getKeySymFromX11KeyName(token_value);
1495       break;
1496
1497     case TYPE_INTEGER:
1498       *(int *)setup_value = get_string_integer_value(token_value);
1499       break;
1500
1501     case TYPE_STRING:
1502       if (*(char **)setup_value != NULL)
1503         free(*(char **)setup_value);
1504       *(char **)setup_value = getStringCopy(token_value);
1505       break;
1506
1507     default:
1508       break;
1509   }
1510 }
1511
1512 static void decodeSetupFileList(struct SetupFileList *setup_file_list)
1513 {
1514   int i, pnr;
1515
1516   if (!setup_file_list)
1517     return;
1518
1519   /* handle global setup values */
1520   si = setup;
1521   for (i=FIRST_GLOBAL_SETUP_TOKEN; i<=LAST_GLOBAL_SETUP_TOKEN; i++)
1522     setSetupInfo(i, getTokenValue(setup_file_list, token_info[i].text));
1523   setup = si;
1524
1525   /* handle player specific setup values */
1526   for (pnr=0; pnr<MAX_PLAYERS; pnr++)
1527   {
1528     char prefix[30];
1529
1530     sprintf(prefix, "%s%d", TOKEN_STR_PLAYER_PREFIX, pnr + 1);
1531
1532     sii = setup.input[pnr];
1533     for (i=FIRST_PLAYER_SETUP_TOKEN; i<=LAST_PLAYER_SETUP_TOKEN; i++)
1534     {
1535       char full_token[100];
1536
1537       sprintf(full_token, "%s%s", prefix, token_info[i].text);
1538       setSetupInfo(i, getTokenValue(setup_file_list, full_token));
1539     }
1540     setup.input[pnr] = sii;
1541   }
1542 }
1543
1544 static int compareLevelDirInfoEntries(const void *object1, const void *object2)
1545 {
1546   const struct LevelDirInfo *entry1 = *((struct LevelDirInfo **)object1);
1547   const struct LevelDirInfo *entry2 = *((struct LevelDirInfo **)object2);
1548   int compare_result;
1549
1550   if (entry1->parent_link || entry2->parent_link)
1551     compare_result = (entry1->parent_link ? -1 : +1);
1552   else if (entry1->sort_priority == entry2->sort_priority)
1553   {
1554     char *name1 = getStringToLower(entry1->name_sorting);
1555     char *name2 = getStringToLower(entry2->name_sorting);
1556
1557     compare_result = strcmp(name1, name2);
1558
1559     free(name1);
1560     free(name2);
1561   }
1562   else if (LEVELSORTING(entry1) == LEVELSORTING(entry2))
1563     compare_result = entry1->sort_priority - entry2->sort_priority;
1564   else
1565     compare_result = LEVELSORTING(entry1) - LEVELSORTING(entry2);
1566
1567   return compare_result;
1568 }
1569
1570 static void createParentLevelDirNode(struct LevelDirInfo *node_parent)
1571 {
1572   struct LevelDirInfo *leveldir_new = newLevelDirInfo();
1573
1574   setLevelDirInfoToDefaults(leveldir_new);
1575
1576   leveldir_new->node_parent = node_parent;
1577   leveldir_new->parent_link = TRUE;
1578
1579   leveldir_new->name = ".. (parent directory)";
1580   leveldir_new->name_short = getStringCopy(leveldir_new->name);
1581   leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1582
1583   leveldir_new->filename = "..";
1584   leveldir_new->fullpath = getStringCopy(node_parent->fullpath);
1585
1586   leveldir_new->sort_priority = node_parent->sort_priority;
1587   leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1588
1589   pushLevelDirInfo(&node_parent->node_group, leveldir_new);
1590 }
1591
1592 static void LoadLevelInfoFromLevelDir(struct LevelDirInfo **node_first,
1593                                       struct LevelDirInfo *node_parent,
1594                                       char *level_directory)
1595 {
1596   DIR *dir;
1597   struct dirent *dir_entry;
1598   boolean valid_entry_found = FALSE;
1599
1600   if ((dir = opendir(level_directory)) == NULL)
1601   {
1602     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1603     return;
1604   }
1605
1606   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
1607   {
1608     struct SetupFileList *setup_file_list = NULL;
1609     struct stat file_status;
1610     char *directory_name = dir_entry->d_name;
1611     char *directory_path = getPath2(level_directory, directory_name);
1612     char *filename = NULL;
1613
1614     /* skip entries for current and parent directory */
1615     if (strcmp(directory_name, ".")  == 0 ||
1616         strcmp(directory_name, "..") == 0)
1617     {
1618       free(directory_path);
1619       continue;
1620     }
1621
1622     /* find out if directory entry is itself a directory */
1623     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
1624         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
1625     {
1626       free(directory_path);
1627       continue;
1628     }
1629
1630     filename = getPath2(directory_path, LEVELINFO_FILENAME);
1631     setup_file_list = loadSetupFileList(filename);
1632
1633     if (setup_file_list)
1634     {
1635       struct LevelDirInfo *leveldir_new = newLevelDirInfo();
1636       int i;
1637
1638       checkSetupFileListIdentifier(setup_file_list, LEVELINFO_COOKIE);
1639       setLevelDirInfoToDefaults(leveldir_new);
1640
1641       leveldir_new->node_parent = node_parent;
1642
1643       /* set all structure fields according to the token/value pairs */
1644       ldi = *leveldir_new;
1645       for (i=FIRST_LEVELINFO_TOKEN; i<=LAST_LEVELINFO_TOKEN; i++)
1646         setSetupInfo(i, getTokenValue(setup_file_list, token_info[i].text));
1647       *leveldir_new = ldi;
1648
1649       DrawInitText(leveldir_new->name, 150, FC_YELLOW);
1650
1651       if (leveldir_new->name_short == NULL)
1652         leveldir_new->name_short = getStringCopy(leveldir_new->name);
1653
1654       if (leveldir_new->name_sorting == NULL)
1655         leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1656
1657       leveldir_new->filename = getStringCopy(directory_name);
1658
1659       if (node_parent == NULL)          /* top level group */
1660       {
1661         leveldir_new->basepath = level_directory;
1662         leveldir_new->fullpath = leveldir_new->filename;
1663       }
1664       else                              /* sub level group */
1665       {
1666         leveldir_new->basepath = node_parent->basepath;
1667         leveldir_new->fullpath = getPath2(node_parent->fullpath,
1668                                           directory_name);
1669       }
1670
1671       if (leveldir_new->levels < 1)
1672         leveldir_new->levels = 1;
1673
1674       leveldir_new->last_level =
1675         leveldir_new->first_level + leveldir_new->levels - 1;
1676
1677       leveldir_new->user_defined =
1678         (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
1679
1680       leveldir_new->color = LEVELCOLOR(leveldir_new);
1681       leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1682
1683       leveldir_new->handicap_level =    /* set handicap to default value */
1684         (leveldir_new->user_defined ?
1685          leveldir_new->last_level :
1686          leveldir_new->first_level);
1687
1688       pushLevelDirInfo(node_first, leveldir_new);
1689
1690       freeSetupFileList(setup_file_list);
1691       valid_entry_found = TRUE;
1692
1693       if (leveldir_new->level_group)
1694       {
1695         /* create node to link back to current level directory */
1696         createParentLevelDirNode(leveldir_new);
1697
1698         /* step into sub-directory and look for more level series */
1699         LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
1700                                   leveldir_new, directory_path);
1701       }
1702     }
1703     else
1704       Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
1705
1706     free(directory_path);
1707     free(filename);
1708   }
1709
1710   closedir(dir);
1711
1712   if (!valid_entry_found)
1713     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
1714           level_directory);
1715 }
1716
1717 void LoadLevelInfo()
1718 {
1719   InitUserLevelDirectory(getLoginName());
1720
1721   DrawInitText("Loading level series:", 120, FC_GREEN);
1722
1723   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
1724   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(""));
1725
1726   leveldir_current = getFirstValidLevelSeries(leveldir_first);
1727
1728   if (leveldir_first == NULL)
1729     Error(ERR_EXIT, "cannot find any valid level series in any directory");
1730
1731   sortLevelDirInfo(&leveldir_first, compareLevelDirInfoEntries);
1732
1733 #if 0
1734   dumpLevelDirInfo(leveldir_first, 0);
1735 #endif
1736 }
1737
1738 static void SaveUserLevelInfo()
1739 {
1740   char *filename;
1741   FILE *file;
1742   int i;
1743
1744   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
1745
1746   if (!(file = fopen(filename, "w")))
1747   {
1748     Error(ERR_WARN, "cannot write level info file '%s'", filename);
1749     free(filename);
1750     return;
1751   }
1752
1753   /* always start with reliable default values */
1754   setLevelDirInfoToDefaults(&ldi);
1755
1756   ldi.name = getLoginName();
1757   ldi.author = getRealName();
1758   ldi.levels = 100;
1759   ldi.first_level = 1;
1760   ldi.sort_priority = LEVELCLASS_USER_START;
1761   ldi.readonly = FALSE;
1762
1763   fprintf(file, "%s\n\n",
1764           getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER, LEVELINFO_COOKIE));
1765
1766   for (i=FIRST_LEVELINFO_TOKEN; i<=LAST_LEVELINFO_TOKEN; i++)
1767     if (i != LEVELINFO_TOKEN_NAME_SHORT &&
1768         i != LEVELINFO_TOKEN_NAME_SORTING &&
1769         i != LEVELINFO_TOKEN_IMPORTED_FROM)
1770       fprintf(file, "%s\n", getSetupLine("", i));
1771
1772   fclose(file);
1773   free(filename);
1774
1775   chmod(filename, SETUP_PERMS);
1776 }
1777
1778 void LoadSetup()
1779 {
1780   char *filename;
1781   struct SetupFileList *setup_file_list = NULL;
1782
1783   /* always start with reliable default values */
1784   setSetupInfoToDefaults(&setup);
1785
1786   filename = getPath2(getSetupDir(), SETUP_FILENAME);
1787
1788   setup_file_list = loadSetupFileList(filename);
1789
1790   if (setup_file_list)
1791   {
1792     checkSetupFileListIdentifier(setup_file_list, SETUP_COOKIE);
1793     decodeSetupFileList(setup_file_list);
1794
1795     setup.direct_draw = !setup.double_buffering;
1796
1797     freeSetupFileList(setup_file_list);
1798
1799     /* needed to work around problems with fixed length strings */
1800     if (strlen(setup.player_name) > MAX_PLAYER_NAME_LEN)
1801       setup.player_name[MAX_PLAYER_NAME_LEN] = '\0';
1802     else if (strlen(setup.player_name) < MAX_PLAYER_NAME_LEN)
1803     {
1804       char *new_name = checked_malloc(MAX_PLAYER_NAME_LEN + 1);
1805
1806       strcpy(new_name, setup.player_name);
1807       free(setup.player_name);
1808       setup.player_name = new_name;
1809     }
1810   }
1811   else
1812     Error(ERR_WARN, "using default setup values");
1813
1814   free(filename);
1815 }
1816
1817 static char *getSetupLine(char *prefix, int token_nr)
1818 {
1819   int i;
1820   static char entry[MAX_LINE_LEN];
1821   int token_type = token_info[token_nr].type;
1822   void *setup_value = token_info[token_nr].value;
1823   char *token_text = token_info[token_nr].text;
1824
1825   /* start with the prefix, token and some spaces to format output line */
1826   sprintf(entry, "%s%s:", prefix, token_text);
1827   for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
1828     strcat(entry, " ");
1829
1830   /* continue with the token's value (which can have different types) */
1831   switch (token_type)
1832   {
1833     case TYPE_BOOLEAN:
1834       strcat(entry, (*(boolean *)setup_value ? "true" : "false"));
1835       break;
1836
1837     case TYPE_SWITCH:
1838       strcat(entry, (*(boolean *)setup_value ? "on" : "off"));
1839       break;
1840
1841     case TYPE_KEYSYM:
1842       {
1843         KeySym keysym = *(KeySym *)setup_value;
1844         char *keyname = getKeyNameFromKeySym(keysym);
1845
1846         strcat(entry, getX11KeyNameFromKeySym(keysym));
1847         for (i=strlen(entry); i<50; i++)
1848           strcat(entry, " ");
1849
1850         /* add comment, if useful */
1851         if (strcmp(keyname, "(undefined)") != 0 &&
1852             strcmp(keyname, "(unknown)") != 0)
1853         {
1854           strcat(entry, "# ");
1855           strcat(entry, keyname);
1856         }
1857       }
1858       break;
1859
1860     case TYPE_INTEGER:
1861       {
1862         char buffer[MAX_LINE_LEN];
1863
1864         sprintf(buffer, "%d", *(int *)setup_value);
1865         strcat(entry, buffer);
1866       }
1867       break;
1868
1869     case TYPE_STRING:
1870       strcat(entry, *(char **)setup_value);
1871       break;
1872
1873     default:
1874       break;
1875   }
1876
1877   return entry;
1878 }
1879
1880 void SaveSetup()
1881 {
1882   int i, pnr;
1883   char *filename;
1884   FILE *file;
1885
1886   InitUserDataDirectory();
1887
1888   filename = getPath2(getSetupDir(), SETUP_FILENAME);
1889
1890   if (!(file = fopen(filename, "w")))
1891   {
1892     Error(ERR_WARN, "cannot write setup file '%s'", filename);
1893     free(filename);
1894     return;
1895   }
1896
1897   fprintf(file, "%s\n",
1898           getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER, SETUP_COOKIE));
1899   fprintf(file, "\n");
1900
1901   /* handle global setup values */
1902   si = setup;
1903   for (i=FIRST_GLOBAL_SETUP_TOKEN; i<=LAST_GLOBAL_SETUP_TOKEN; i++)
1904   {
1905     fprintf(file, "%s\n", getSetupLine("", i));
1906
1907     /* just to make things nicer :) */
1908     if (i == SETUP_TOKEN_PLAYER_NAME)
1909       fprintf(file, "\n");
1910   }
1911
1912   /* handle player specific setup values */
1913   for (pnr=0; pnr<MAX_PLAYERS; pnr++)
1914   {
1915     char prefix[30];
1916
1917     sprintf(prefix, "%s%d", TOKEN_STR_PLAYER_PREFIX, pnr + 1);
1918     fprintf(file, "\n");
1919
1920     sii = setup.input[pnr];
1921     for (i=FIRST_PLAYER_SETUP_TOKEN; i<=LAST_PLAYER_SETUP_TOKEN; i++)
1922       fprintf(file, "%s\n", getSetupLine(prefix, i));
1923   }
1924
1925   fclose(file);
1926   free(filename);
1927
1928   chmod(filename, SETUP_PERMS);
1929 }
1930
1931 void LoadLevelSetup_LastSeries()
1932 {
1933   char *filename;
1934   struct SetupFileList *level_setup_list = NULL;
1935
1936   /* always start with reliable default values */
1937   leveldir_current = leveldir_first;
1938
1939   /* ----------------------------------------------------------------------- */
1940   /* ~/.rocksndiamonds/levelsetup.conf                                       */
1941   /* ----------------------------------------------------------------------- */
1942
1943   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
1944
1945   if ((level_setup_list = loadSetupFileList(filename)))
1946   {
1947     char *last_level_series =
1948       getTokenValue(level_setup_list, TOKEN_STR_LAST_LEVEL_SERIES);
1949
1950     leveldir_current = getLevelDirInfoFromFilename(last_level_series);
1951     if (leveldir_current == NULL)
1952       leveldir_current = leveldir_first;
1953
1954     checkSetupFileListIdentifier(level_setup_list, LEVELSETUP_COOKIE);
1955
1956     freeSetupFileList(level_setup_list);
1957   }
1958   else
1959     Error(ERR_WARN, "using default setup values");
1960
1961   free(filename);
1962 }
1963
1964 void SaveLevelSetup_LastSeries()
1965 {
1966   char *filename;
1967   char *level_subdir = leveldir_current->filename;
1968   FILE *file;
1969
1970   /* ----------------------------------------------------------------------- */
1971   /* ~/.rocksndiamonds/levelsetup.conf                                       */
1972   /* ----------------------------------------------------------------------- */
1973
1974   InitUserDataDirectory();
1975
1976   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
1977
1978   if (!(file = fopen(filename, "w")))
1979   {
1980     Error(ERR_WARN, "cannot write setup file '%s'", filename);
1981     free(filename);
1982     return;
1983   }
1984
1985   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1986                                                  LEVELSETUP_COOKIE));
1987   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
1988                                                level_subdir));
1989
1990   fclose(file);
1991   free(filename);
1992
1993   chmod(filename, SETUP_PERMS);
1994 }
1995
1996 static void checkSeriesInfo()
1997 {
1998   static char *level_directory = NULL;
1999   DIR *dir;
2000   struct dirent *dir_entry;
2001
2002   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
2003
2004   level_directory = getPath2((leveldir_current->user_defined ?
2005                               getUserLevelDir("") :
2006                               options.level_directory),
2007                              leveldir_current->filename);
2008
2009   if ((dir = opendir(level_directory)) == NULL)
2010   {
2011     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2012     return;
2013   }
2014
2015   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
2016   {
2017     if (strlen(dir_entry->d_name) > 4 &&
2018         dir_entry->d_name[3] == '.' &&
2019         strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
2020     {
2021       char levelnum_str[4];
2022       int levelnum_value;
2023
2024       strncpy(levelnum_str, dir_entry->d_name, 3);
2025       levelnum_str[3] = '\0';
2026
2027       levelnum_value = atoi(levelnum_str);
2028
2029       if (levelnum_value < leveldir_current->first_level)
2030       {
2031         Error(ERR_WARN, "additional level %d found", levelnum_value);
2032         leveldir_current->first_level = levelnum_value;
2033       }
2034       else if (levelnum_value > leveldir_current->last_level)
2035       {
2036         Error(ERR_WARN, "additional level %d found", levelnum_value);
2037         leveldir_current->last_level = levelnum_value;
2038       }
2039     }
2040   }
2041
2042   closedir(dir);
2043 }
2044
2045 void LoadLevelSetup_SeriesInfo()
2046 {
2047   char *filename;
2048   struct SetupFileList *level_setup_list = NULL;
2049   char *level_subdir = leveldir_current->filename;
2050
2051   /* always start with reliable default values */
2052   level_nr = leveldir_current->first_level;
2053
2054   checkSeriesInfo(leveldir_current);
2055
2056   /* ----------------------------------------------------------------------- */
2057   /* ~/.rocksndiamonds/levelsetup/<level series>/levelsetup.conf             */
2058   /* ----------------------------------------------------------------------- */
2059
2060   level_subdir = leveldir_current->filename;
2061
2062   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2063
2064   if ((level_setup_list = loadSetupFileList(filename)))
2065   {
2066     char *token_value;
2067
2068     token_value = getTokenValue(level_setup_list, TOKEN_STR_LAST_PLAYED_LEVEL);
2069
2070     if (token_value)
2071     {
2072       level_nr = atoi(token_value);
2073
2074       if (level_nr < leveldir_current->first_level)
2075         level_nr = leveldir_current->first_level;
2076       if (level_nr > leveldir_current->last_level)
2077         level_nr = leveldir_current->last_level;
2078     }
2079
2080     token_value = getTokenValue(level_setup_list, TOKEN_STR_HANDICAP_LEVEL);
2081
2082     if (token_value)
2083     {
2084       int level_nr = atoi(token_value);
2085
2086       if (level_nr < leveldir_current->first_level)
2087         level_nr = leveldir_current->first_level;
2088       if (level_nr > leveldir_current->last_level + 1)
2089         level_nr = leveldir_current->last_level;
2090
2091       if (leveldir_current->user_defined)
2092         level_nr = leveldir_current->last_level;
2093
2094       leveldir_current->handicap_level = level_nr;
2095     }
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_SeriesInfo()
2108 {
2109   char *filename;
2110   char *level_subdir = leveldir_current->filename;
2111   char *level_nr_str = int2str(level_nr, 0);
2112   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
2113   FILE *file;
2114
2115   /* ----------------------------------------------------------------------- */
2116   /* ~/.rocksndiamonds/levelsetup/<level series>/levelsetup.conf             */
2117   /* ----------------------------------------------------------------------- */
2118
2119   InitLevelSetupDirectory(level_subdir);
2120
2121   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2122
2123   if (!(file = fopen(filename, "w")))
2124   {
2125     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2126     free(filename);
2127     return;
2128   }
2129
2130   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2131                                                  LEVELSETUP_COOKIE));
2132   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
2133                                                level_nr_str));
2134   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
2135                                                handicap_level_str));
2136
2137   fclose(file);
2138   free(filename);
2139
2140   chmod(filename, SETUP_PERMS);
2141 }
2142
2143 #ifdef MSDOS
2144 void initErrorFile()
2145 {
2146   char *filename;
2147
2148   InitUserDataDirectory();
2149
2150   filename = getPath2(getUserDataDir(), ERROR_FILENAME);
2151   unlink(filename);
2152   free(filename);
2153 }
2154
2155 FILE *openErrorFile()
2156 {
2157   char *filename;
2158   FILE *error_file;
2159
2160   filename = getPath2(getUserDataDir(), ERROR_FILENAME);
2161   error_file = fopen(filename, "a");
2162   free(filename);
2163
2164   return error_file;
2165 }
2166
2167 void dumpErrorFile()
2168 {
2169   char *filename;
2170   FILE *error_file;
2171
2172   filename = getPath2(getUserDataDir(), ERROR_FILENAME);
2173   error_file = fopen(filename, "r");
2174   free(filename);
2175
2176   if (error_file != NULL)
2177   {
2178     while (!feof(error_file))
2179       fputc(fgetc(error_file), stderr);
2180
2181     fclose(error_file);
2182   }
2183 }
2184 #endif