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