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