rnd-19981118-2
[rocksndiamonds.git] / src / files.c
1 /***********************************************************
2 *  Rocks'n'Diamonds -- McDuffin Strikes Back!              *
3 *----------------------------------------------------------*
4 *  (c) 1995-98 Artsoft Entertainment                       *
5 *              Holger Schemel                              *
6 *              Oststrasse 11a                              *
7 *              33604 Bielefeld                             *
8 *              phone: ++49 +521 290471                     *
9 *              email: aeglos@valinor.owl.de                *
10 *----------------------------------------------------------*
11 *  files.h                                                 *
12 ***********************************************************/
13
14 #include <ctype.h>
15 #include <dirent.h>
16 #include <sys/stat.h>
17 #include <unistd.h>
18
19 #include "files.h"
20 #include "tools.h"
21 #include "misc.h"
22 #include "tape.h"
23 #include "joystick.h"
24
25 #define MAX_LINE_LEN                    1000
26
27 static void SaveUserLevelInfo();                /* for 'InitUserLevelDir()' */
28 static char *getSetupLine(char *, int);         /* for 'SaveUserLevelInfo()' */
29
30 static char *getGlobalDataDir()
31 {
32   return GAME_DIR;
33 }
34
35 static char *getUserDataDir()
36 {
37   static char *userdata_dir = NULL;
38
39   if (!userdata_dir)
40   {
41     char *home_dir = getHomeDir();
42     char *data_dir = USERDATA_DIRECTORY;
43
44     userdata_dir = checked_malloc(strlen(home_dir) + strlen(data_dir) + 2);
45     sprintf(userdata_dir, "%s/%s", home_dir, data_dir);
46   }
47
48   return userdata_dir;
49 }
50
51 static char *getSetupDir()
52 {
53   return getUserDataDir();
54 }
55
56 static char *getUserLevelDir(char *level_subdir)
57 {
58   static char *userlevel_dir = NULL;
59   char *data_dir = getUserDataDir();
60   char *userlevel_subdir = LEVELS_DIRECTORY;
61
62   if (userlevel_dir)
63     free(userlevel_dir);
64
65   userlevel_dir = checked_malloc(strlen(data_dir) + strlen(userlevel_subdir) +
66                                  strlen(level_subdir) + 3);
67   sprintf(userlevel_dir, "%s/%s%s%s", data_dir, userlevel_subdir,
68           (strlen(level_subdir) > 0 ? "/" : ""), level_subdir);
69
70   return userlevel_dir;
71 }
72
73 static char *getTapeDir(char *level_subdir)
74 {
75   static char *tape_dir = NULL;
76   char *data_dir = getUserDataDir();
77   char *tape_subdir = TAPES_DIRECTORY;
78
79   if (tape_dir)
80     free(tape_dir);
81
82   tape_dir = checked_malloc(strlen(data_dir) + strlen(tape_subdir) +
83                             strlen(level_subdir) + 3);
84   sprintf(tape_dir, "%s/%s%s%s", data_dir, tape_subdir,
85           (strlen(level_subdir) > 0 ? "/" : ""), level_subdir);
86
87   return tape_dir;
88 }
89
90 static char *getScoreDir(char *level_subdir)
91 {
92   static char *score_dir = NULL;
93   char *data_dir = getGlobalDataDir();
94   char *score_subdir = SCORES_DIRECTORY;
95
96   if (score_dir)
97     free(score_dir);
98
99   score_dir = checked_malloc(strlen(data_dir) + strlen(score_subdir) +
100                              strlen(level_subdir) + 3);
101   sprintf(score_dir, "%s/%s%s%s", data_dir, score_subdir,
102           (strlen(level_subdir) > 0 ? "/" : ""), level_subdir);
103
104   return score_dir;
105 }
106
107 static void createDirectory(char *dir, char *text)
108 {
109   if (access(dir, F_OK) != 0)
110     if (mkdir(dir, USERDATA_DIR_MODE) != 0)
111       Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
112 }
113
114 static void InitUserDataDirectory()
115 {
116   createDirectory(getUserDataDir(), "user data");
117 }
118
119 static void InitTapeDirectory(char *level_subdir)
120 {
121   createDirectory(getTapeDir(""), "main tape");
122   createDirectory(getTapeDir(level_subdir), "level tape");
123 }
124
125 static void InitScoreDirectory(char *level_subdir)
126 {
127   createDirectory(getScoreDir(""), "main score");
128   createDirectory(getScoreDir(level_subdir), "level score");
129 }
130
131 static void InitUserLevelDirectory(char *level_subdir)
132 {
133   if (access(getUserLevelDir(level_subdir), F_OK) != 0)
134   {
135     createDirectory(getUserLevelDir(""), "main user level");
136     createDirectory(getUserLevelDir(level_subdir), "user level");
137
138     SaveUserLevelInfo();
139   }
140 }
141
142 void LoadLevel(int level_nr)
143 {
144   int i, x, y;
145   char filename[MAX_FILENAME_LEN];
146   char cookie[MAX_FILENAME_LEN];
147   FILE *file;
148
149   if (leveldir[leveldir_nr].user_defined)
150     sprintf(filename, "%s/%s/%d",
151             getUserLevelDir(""), leveldir[leveldir_nr].filename, level_nr);
152   else
153     sprintf(filename, "%s/%s/%d",
154             options.level_directory, leveldir[leveldir_nr].filename, level_nr);
155
156   if (!(file = fopen(filename, "r")))
157     Error(ERR_WARN, "cannot read level '%s' - creating new level", filename);
158   else
159   {
160     fgets(cookie, LEVEL_COOKIE_LEN, file);
161     fgetc(file);
162
163     if (strcmp(cookie,LEVEL_COOKIE))
164     {
165       Error(ERR_WARN, "wrong format of level file '%s'", filename);
166       fclose(file);
167       file = NULL;
168     }
169   }
170
171   if (file)
172   {
173     lev_fieldx = level.fieldx = fgetc(file);
174     lev_fieldy = level.fieldy = fgetc(file);
175
176     level.time          = (fgetc(file)<<8) | fgetc(file);
177     level.edelsteine    = (fgetc(file)<<8) | fgetc(file);
178     for(i=0; i<MAX_LEVNAMLEN; i++)
179       level.name[i]     = fgetc(file);
180     level.name[MAX_LEVNAMLEN-1] = 0;
181     for(i=0; i<MAX_LEVSCORE_ENTRIES; i++)
182       level.score[i]    = fgetc(file);
183     for(i=0; i<4; i++)
184       for(y=0; y<3; y++)
185         for(x=0; x<3; x++)
186           level.mampfer_inhalt[i][x][y] = fgetc(file);
187     level.tempo_amoebe  = fgetc(file);
188     level.dauer_sieb    = fgetc(file);
189     level.dauer_ablenk  = fgetc(file);
190     level.amoebe_inhalt = fgetc(file);
191
192     for(i=0; i<NUM_FREE_LVHD_BYTES; i++) /* Rest frei / Headergröße 80 Bytes */
193       fgetc(file);
194
195     for(y=0; y<MAX_LEV_FIELDY; y++) 
196       for(x=0; x<MAX_LEV_FIELDX; x++) 
197         Feld[x][y] = Ur[x][y] = EL_ERDREICH;
198
199     for(y=0; y<lev_fieldy; y++) 
200       for(x=0; x<lev_fieldx; x++) 
201         Feld[x][y] = Ur[x][y] = fgetc(file);
202
203     fclose(file);
204
205     if (level.time <= 10)       /* Mindestspieldauer */
206       level.time = 10;
207   }
208   else
209   {
210     lev_fieldx = level.fieldx = STD_LEV_FIELDX;
211     lev_fieldy = level.fieldy = STD_LEV_FIELDY;
212
213     level.time          = 100;
214     level.edelsteine    = 0;
215     strcpy(level.name, "Nameless Level");
216     for(i=0; i<MAX_LEVSCORE_ENTRIES; i++)
217       level.score[i]    = 10;
218     for(i=0; i<4; i++)
219       for(y=0; y<3; y++)
220         for(x=0; x<3; x++)
221           level.mampfer_inhalt[i][x][y] = EL_FELSBROCKEN;
222     level.tempo_amoebe  = 10;
223     level.dauer_sieb    = 10;
224     level.dauer_ablenk  = 10;
225     level.amoebe_inhalt = EL_DIAMANT;
226
227     for(y=0; y<STD_LEV_FIELDY; y++) 
228       for(x=0; x<STD_LEV_FIELDX; x++) 
229         Feld[x][y] = Ur[x][y] = EL_ERDREICH;
230     Feld[0][0] = Ur[0][0] = EL_SPIELFIGUR;
231     Feld[STD_LEV_FIELDX-1][STD_LEV_FIELDY-1] =
232       Ur[STD_LEV_FIELDX-1][STD_LEV_FIELDY-1] = EL_AUSGANG_ZU;
233   }
234 }
235
236 void SaveLevel(int level_nr)
237 {
238   int i, x, y;
239   char filename[MAX_FILENAME_LEN];
240   FILE *file;
241
242   if (leveldir[leveldir_nr].user_defined)
243     sprintf(filename, "%s/%s/%d",
244             getUserLevelDir(""), leveldir[leveldir_nr].filename, level_nr);
245   else
246     sprintf(filename, "%s/%s/%d",
247             options.level_directory, leveldir[leveldir_nr].filename, level_nr);
248
249   if (!(file = fopen(filename, "w")))
250   {
251     Error(ERR_WARN, "cannot save level file '%s'", filename);
252     return;
253   }
254
255   fputs(LEVEL_COOKIE,file);             /* Formatkennung */
256   fputc(0x0a, file);
257
258   fputc(level.fieldx, file);
259   fputc(level.fieldy, file);
260   fputc(level.time / 256, file);
261   fputc(level.time % 256, file);
262   fputc(level.edelsteine / 256, file);
263   fputc(level.edelsteine % 256, file);
264
265   for(i=0; i<MAX_LEVNAMLEN; i++)
266     fputc(level.name[i], file);
267   for(i=0; i<MAX_LEVSCORE_ENTRIES; i++)
268     fputc(level.score[i], file);
269   for(i=0; i<4; i++)
270     for(y=0; y<3; y++)
271       for(x=0; x<3; x++)
272         fputc(level.mampfer_inhalt[i][x][y], file);
273   fputc(level.tempo_amoebe, file);
274   fputc(level.dauer_sieb, file);
275   fputc(level.dauer_ablenk, file);
276   fputc(level.amoebe_inhalt, file);
277
278   for(i=0; i<NUM_FREE_LVHD_BYTES; i++)  /* Rest frei / Headergröße 80 Bytes */
279     fputc(0, file);
280
281   for(y=0; y<lev_fieldy; y++) 
282     for(x=0; x<lev_fieldx; x++) 
283       fputc(Ur[x][y], file);
284
285   fclose(file);
286
287   chmod(filename, LEVEL_PERMS);
288 }
289
290 void LoadTape(int level_nr)
291 {
292   int i;
293   char filename[MAX_FILENAME_LEN];
294   char cookie[MAX_FILENAME_LEN];
295   FILE *file;
296   boolean levelrec_10 = FALSE;
297
298   sprintf(filename, "%s/%d.%s",
299           getTapeDir(leveldir[leveldir_nr].filename),
300           level_nr, TAPEFILE_EXTENSION);
301
302   if ((file = fopen(filename, "r")))
303   {
304     fgets(cookie, LEVELREC_COOKIE_LEN, file);
305     fgetc(file);
306     if (!strcmp(cookie, LEVELREC_COOKIE_10))    /* old 1.0 tape format */
307       levelrec_10 = TRUE;
308     else if (strcmp(cookie, LEVELREC_COOKIE))   /* unknown tape format */
309     {
310       Error(ERR_WARN, "wrong format of level recording file '%s'", filename);
311       fclose(file);
312       file = NULL;
313     }
314   }
315
316   if (!file)
317     return;
318
319   tape.random_seed =
320     (fgetc(file)<<24) | (fgetc(file)<<16) | (fgetc(file)<<8) | fgetc(file);
321   tape.date =
322     (fgetc(file)<<24) | (fgetc(file)<<16) | (fgetc(file)<<8) | fgetc(file);
323   tape.length =
324     (fgetc(file)<<24) | (fgetc(file)<<16) | (fgetc(file)<<8) | fgetc(file);
325
326   tape.level_nr = level_nr;
327   tape.counter = 0;
328   tape.changed = FALSE;
329
330   tape.recording = FALSE;
331   tape.playing = FALSE;
332   tape.pausing = FALSE;
333
334   for(i=0; i<tape.length; i++)
335   {
336     int j;
337
338     if (i >= MAX_TAPELEN)
339       break;
340
341     for(j=0; j<MAX_PLAYERS; j++)
342     {
343       if (levelrec_10 && j > 0)
344       {
345         tape.pos[i].action[j] = MV_NO_MOVING;
346         continue;
347       }
348       tape.pos[i].action[j] = fgetc(file);
349     }
350
351     tape.pos[i].delay = fgetc(file);
352
353     if (levelrec_10)
354     {
355       /* eliminate possible diagonal moves in old tapes */
356       /* this is only for backward compatibility */
357
358       byte joy_dir[4] = { JOY_LEFT, JOY_RIGHT, JOY_UP, JOY_DOWN };
359       byte action = tape.pos[i].action[0];
360       int k, num_moves = 0;
361
362       for (k=0; k<4; k++)
363       {
364         if (action & joy_dir[k])
365         {
366           tape.pos[i + num_moves].action[0] = joy_dir[k];
367           if (num_moves > 0)
368             tape.pos[i + num_moves].delay = 0;
369           num_moves++;
370         }
371       }
372
373       if (num_moves > 1)
374       {
375         num_moves--;
376         i += num_moves;
377         tape.length += num_moves;
378       }
379     }
380
381     if (feof(file))
382       break;
383   }
384
385   fclose(file);
386
387   if (i != tape.length)
388     Error(ERR_WARN, "level recording file '%s' corrupted", filename);
389
390   tape.length_seconds = GetTapeLength();
391 }
392
393 void SaveTape(int level_nr)
394 {
395   int i;
396   char filename[MAX_FILENAME_LEN];
397   FILE *file;
398   boolean new_tape = TRUE;
399
400   InitTapeDirectory(leveldir[leveldir_nr].filename);
401
402   sprintf(filename, "%s/%d.%s",
403           getTapeDir(leveldir[leveldir_nr].filename),
404           level_nr, TAPEFILE_EXTENSION);
405
406   /* if a tape still exists, ask to overwrite it */
407   if ((file = fopen(filename, "r")))
408   {
409     new_tape = FALSE;
410     fclose(file);
411
412     if (!Request("Replace old tape ?", REQ_ASK))
413       return;
414   }
415
416   if (!(file = fopen(filename, "w")))
417   {
418     Error(ERR_WARN, "cannot save level recording file '%s'", filename);
419     return;
420   }
421
422   fputs(LEVELREC_COOKIE, file);         /* Formatkennung */
423   fputc(0x0a, file);
424
425   fputc((tape.random_seed >> 24) & 0xff,file);
426   fputc((tape.random_seed >> 16) & 0xff,file);
427   fputc((tape.random_seed >>  8) & 0xff,file);
428   fputc((tape.random_seed >>  0) & 0xff,file);
429
430   fputc((tape.date >>  24) & 0xff,file);
431   fputc((tape.date >>  16) & 0xff,file);
432   fputc((tape.date >>   8) & 0xff,file);
433   fputc((tape.date >>   0) & 0xff,file);
434
435   fputc((tape.length >>  24) & 0xff,file);
436   fputc((tape.length >>  16) & 0xff,file);
437   fputc((tape.length >>   8) & 0xff,file);
438   fputc((tape.length >>   0) & 0xff,file);
439
440   for(i=0; i<tape.length; i++)
441   {
442     int j;
443
444     for(j=0; j<MAX_PLAYERS; j++)
445       fputc(tape.pos[i].action[j], file);
446
447     fputc(tape.pos[i].delay, file);
448   }
449
450   fclose(file);
451
452   chmod(filename, LEVREC_PERMS);
453
454   tape.changed = FALSE;
455
456   if (new_tape)
457     Request("tape saved !",REQ_CONFIRM);
458 }
459
460 void LoadScore(int level_nr)
461 {
462   int i;
463   char filename[MAX_FILENAME_LEN];
464   char cookie[MAX_FILENAME_LEN];
465   char line[MAX_LINE_LEN];
466   char *line_ptr;
467   FILE *file;
468
469   /* start with empty score table */
470   for(i=0; i<MAX_SCORE_ENTRIES; i++)
471   {
472     strcpy(highscore[i].Name, EMPTY_ALIAS);
473     highscore[i].Score = 0;
474   }
475
476   sprintf(filename, "%s/%d.%s",
477           getScoreDir(leveldir[leveldir_nr].filename),
478           level_nr, SCOREFILE_EXTENSION);
479
480   if (!(file = fopen(filename, "r")))
481     return;
482
483   fgets(cookie, SCORE_COOKIE_LEN, file);
484
485   if (strcmp(cookie, SCORE_COOKIE) != 0)
486   {
487     Error(ERR_WARN, "wrong format of score file '%s'", filename);
488     fclose(file);
489     return;
490   }
491
492   for(i=0; i<MAX_SCORE_ENTRIES; i++)
493   {
494     fscanf(file, "%d", &highscore[i].Score);
495     fgets(line, MAX_LINE_LEN, file);
496
497     if (line[strlen(line)-1] == '\n')
498       line[strlen(line)-1] = '\0';
499
500     for (line_ptr = line; *line_ptr; line_ptr++)
501     {
502       if (*line_ptr != ' ' && *line_ptr != '\t' && *line_ptr != '\0')
503       {
504         strncpy(highscore[i].Name, line_ptr, MAX_NAMELEN - 1);
505         highscore[i].Name[MAX_NAMELEN - 1] = '\0';
506         break;
507       }
508     }
509   }
510
511   fclose(file);
512 }
513
514 void SaveScore(int level_nr)
515 {
516   int i;
517   char filename[MAX_FILENAME_LEN];
518   FILE *file;
519
520   InitScoreDirectory(leveldir[leveldir_nr].filename);
521
522   sprintf(filename, "%s/%d.%s",
523           getScoreDir(leveldir[leveldir_nr].filename),
524           level_nr, SCOREFILE_EXTENSION);
525
526   if (!(file = fopen(filename, "w")))
527   {
528     Error(ERR_WARN, "cannot save score for level %d", level_nr);
529     return;
530   }
531
532   fprintf(file, "%s\n\n", SCORE_COOKIE);
533
534   for(i=0; i<MAX_SCORE_ENTRIES; i++)
535     fprintf(file, "%d %s\n", highscore[i].Score, highscore[i].Name);
536
537   fclose(file);
538
539   chmod(filename, SCORE_PERMS);
540 }
541
542 #define TOKEN_STR_FILE_IDENTIFIER       "file_identifier"
543 #define TOKEN_STR_LAST_LEVEL_SERIES     "last_level_series"
544 #define TOKEN_STR_PLAYER_PREFIX         "player_"
545
546 #define TOKEN_VALUE_POSITION            30
547
548 /* global setup */
549 #define SETUP_TOKEN_PLAYER_NAME         0
550 #define SETUP_TOKEN_SOUND               1
551 #define SETUP_TOKEN_SOUND_LOOPS         2
552 #define SETUP_TOKEN_SOUND_MUSIC         3
553 #define SETUP_TOKEN_SOUND_SIMPLE        4
554 #define SETUP_TOKEN_TOONS               5
555 #define SETUP_TOKEN_DOUBLE_BUFFERING    6
556 #define SETUP_TOKEN_SCROLL_DELAY        7
557 #define SETUP_TOKEN_SOFT_SCROLLING      8
558 #define SETUP_TOKEN_FADING              9
559 #define SETUP_TOKEN_AUTORECORD          10
560 #define SETUP_TOKEN_QUICK_DOORS         11
561 #define SETUP_TOKEN_TEAM_MODE           12
562
563 /* player setup */
564 #define SETUP_TOKEN_USE_JOYSTICK        13
565 #define SETUP_TOKEN_JOY_DEVICE_NAME     14
566 #define SETUP_TOKEN_JOY_XLEFT           15
567 #define SETUP_TOKEN_JOY_XMIDDLE         16
568 #define SETUP_TOKEN_JOY_XRIGHT          17
569 #define SETUP_TOKEN_JOY_YUPPER          18
570 #define SETUP_TOKEN_JOY_YMIDDLE         19
571 #define SETUP_TOKEN_JOY_YLOWER          20
572 #define SETUP_TOKEN_JOY_SNAP            21
573 #define SETUP_TOKEN_JOY_BOMB            22
574 #define SETUP_TOKEN_KEY_LEFT            23
575 #define SETUP_TOKEN_KEY_RIGHT           24
576 #define SETUP_TOKEN_KEY_UP              25
577 #define SETUP_TOKEN_KEY_DOWN            26
578 #define SETUP_TOKEN_KEY_SNAP            27
579 #define SETUP_TOKEN_KEY_BOMB            28
580
581 /* level directory info */
582 #define LEVELINFO_TOKEN_NAME            29
583 #define LEVELINFO_TOKEN_LEVELS          30
584 #define LEVELINFO_TOKEN_SORT_PRIORITY   31
585 #define LEVELINFO_TOKEN_READONLY        32
586
587 #define FIRST_GLOBAL_SETUP_TOKEN        SETUP_TOKEN_PLAYER_NAME
588 #define LAST_GLOBAL_SETUP_TOKEN         SETUP_TOKEN_TEAM_MODE
589
590 #define FIRST_PLAYER_SETUP_TOKEN        SETUP_TOKEN_USE_JOYSTICK
591 #define LAST_PLAYER_SETUP_TOKEN         SETUP_TOKEN_KEY_BOMB
592
593 #define FIRST_LEVELINFO_TOKEN           LEVELINFO_TOKEN_NAME
594 #define LAST_LEVELINFO_TOKEN            LEVELINFO_TOKEN_READONLY
595
596 #define TYPE_BOOLEAN                    1
597 #define TYPE_SWITCH                     2
598 #define TYPE_KEYSYM                     3
599 #define TYPE_INTEGER                    4
600 #define TYPE_STRING                     5
601
602 static struct SetupInfo si;
603 static struct SetupInputInfo sii;
604 static struct LevelDirInfo ldi;
605 static struct
606 {
607   int type;
608   void *value;
609   char *text;
610 } token_info[] =
611 {
612   /* global setup */
613   { TYPE_STRING,  &si.player_name,      "player_name"                   },
614   { TYPE_SWITCH,  &si.sound,            "sound"                         },
615   { TYPE_SWITCH,  &si.sound_loops,      "repeating_sound_loops"         },
616   { TYPE_SWITCH,  &si.sound_music,      "background_music"              },
617   { TYPE_SWITCH,  &si.sound_simple,     "simple_sound_effects"          },
618   { TYPE_SWITCH,  &si.toons,            "toons"                         },
619   { TYPE_SWITCH,  &si.double_buffering, "double_buffering"              },
620   { TYPE_SWITCH,  &si.scroll_delay,     "scroll_delay"                  },
621   { TYPE_SWITCH,  &si.soft_scrolling,   "soft_scrolling"                },
622   { TYPE_SWITCH,  &si.fading,           "screen_fading"                 },
623   { TYPE_SWITCH,  &si.autorecord,       "automatic_tape_recording"      },
624   { TYPE_SWITCH,  &si.quick_doors,      "quick_doors"                   },
625   { TYPE_SWITCH,  &si.team_mode,        "team_mode"                     },
626
627   /* player setup */
628   { TYPE_BOOLEAN, &sii.use_joystick,    ".use_joystick"                 },
629   { TYPE_STRING,  &sii.joy.device_name, ".joy.device_name"              },
630   { TYPE_INTEGER, &sii.joy.xleft,       ".joy.xleft"                    },
631   { TYPE_INTEGER, &sii.joy.xmiddle,     ".joy.xmiddle"                  },
632   { TYPE_INTEGER, &sii.joy.xright,      ".joy.xright"                   },
633   { TYPE_INTEGER, &sii.joy.yupper,      ".joy.yupper"                   },
634   { TYPE_INTEGER, &sii.joy.ymiddle,     ".joy.ymiddle"                  },
635   { TYPE_INTEGER, &sii.joy.ylower,      ".joy.ylower"                   },
636   { TYPE_INTEGER, &sii.joy.snap,        ".joy.snap_field"               },
637   { TYPE_INTEGER, &sii.joy.bomb,        ".joy.place_bomb"               },
638   { TYPE_KEYSYM,  &sii.key.left,        ".key.move_left"                },
639   { TYPE_KEYSYM,  &sii.key.right,       ".key.move_right"               },
640   { TYPE_KEYSYM,  &sii.key.up,          ".key.move_up"                  },
641   { TYPE_KEYSYM,  &sii.key.down,        ".key.move_down"                },
642   { TYPE_KEYSYM,  &sii.key.snap,        ".key.snap_field"               },
643   { TYPE_KEYSYM,  &sii.key.bomb,        ".key.place_bomb"               },
644
645   /* level directory info */
646   { TYPE_STRING,  &ldi.name,            "name"                          },
647   { TYPE_INTEGER, &ldi.levels,          "levels"                        },
648   { TYPE_INTEGER, &ldi.sort_priority,   "sort_priority"                 },
649   { TYPE_BOOLEAN, &ldi.readonly,        "readonly"                      }
650 };
651
652 static char *string_tolower(char *s)
653 {
654   static char s_lower[100];
655   int i;
656
657   if (strlen(s) >= 100)
658     return s;
659
660   strcpy(s_lower, s);
661
662   for (i=0; i<strlen(s_lower); i++)
663     s_lower[i] = tolower(s_lower[i]);
664
665   return s_lower;
666 }
667
668 static int get_string_integer_value(char *s)
669 {
670   static char *number_text[][3] =
671   {
672     { "0", "zero", "null", },
673     { "1", "one", "first" },
674     { "2", "two", "second" },
675     { "3", "three", "third" },
676     { "4", "four", "fourth" },
677     { "5", "five", "fifth" },
678     { "6", "six", "sixth" },
679     { "7", "seven", "seventh" },
680     { "8", "eight", "eighth" },
681     { "9", "nine", "ninth" },
682     { "10", "ten", "tenth" },
683     { "11", "eleven", "eleventh" },
684     { "12", "twelve", "twelfth" },
685   };
686
687   int i, j;
688
689   for (i=0; i<13; i++)
690     for (j=0; j<3; j++)
691       if (strcmp(string_tolower(s), number_text[i][j]) == 0)
692         return i;
693
694   return atoi(s);
695 }
696
697 static boolean get_string_boolean_value(char *s)
698 {
699   if (strcmp(string_tolower(s), "true") == 0 ||
700       strcmp(string_tolower(s), "yes") == 0 ||
701       strcmp(string_tolower(s), "on") == 0 ||
702       get_string_integer_value(s) == 1)
703     return TRUE;
704   else
705     return FALSE;
706 }
707
708 static char *getFormattedSetupEntry(char *token, char *value)
709 {
710   int i;
711   static char entry[MAX_LINE_LEN];
712
713   sprintf(entry, "%s:", token);
714   for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
715     entry[i] = ' ';
716   entry[i] = '\0';
717
718   strcat(entry, value);
719
720   return entry;
721 }
722
723 static void freeSetupFileList(struct SetupFileList *setup_file_list)
724 {
725   if (!setup_file_list)
726     return;
727
728   if (setup_file_list->token)
729     free(setup_file_list->token);
730   if (setup_file_list->value)
731     free(setup_file_list->value);
732   if (setup_file_list->next)
733     freeSetupFileList(setup_file_list->next);
734   free(setup_file_list);
735 }
736
737 static struct SetupFileList *newSetupFileList(char *token, char *value)
738 {
739   struct SetupFileList *new = checked_malloc(sizeof(struct SetupFileList));
740
741   new->token = checked_malloc(strlen(token) + 1);
742   strcpy(new->token, token);
743
744   new->value = checked_malloc(strlen(value) + 1);
745   strcpy(new->value, value);
746
747   new->next = NULL;
748
749   return new;
750 }
751
752 static char *getTokenValue(struct SetupFileList *setup_file_list,
753                            char *token)
754 {
755   if (!setup_file_list)
756     return NULL;
757
758   if (strcmp(setup_file_list->token, token) == 0)
759     return setup_file_list->value;
760   else
761     return getTokenValue(setup_file_list->next, token);
762 }
763
764 static void setTokenValue(struct SetupFileList *setup_file_list,
765                           char *token, char *value)
766 {
767   if (!setup_file_list)
768     return;
769
770   if (strcmp(setup_file_list->token, token) == 0)
771   {
772     free(setup_file_list->value);
773     setup_file_list->value = checked_malloc(strlen(value) + 1);
774     strcpy(setup_file_list->value, value);
775   }
776   else if (setup_file_list->next == NULL)
777     setup_file_list->next = newSetupFileList(token, value);
778   else
779     setTokenValue(setup_file_list->next, token, value);
780 }
781
782 #ifdef DEBUG
783 static void printSetupFileList(struct SetupFileList *setup_file_list)
784 {
785   if (!setup_file_list)
786     return;
787
788   printf("token: '%s'\n", setup_file_list->token);
789   printf("value: '%s'\n", setup_file_list->value);
790
791   printSetupFileList(setup_file_list->next);
792 }
793 #endif
794
795 static struct SetupFileList *loadSetupFileList(char *filename)
796 {
797   int line_len;
798   char line[MAX_LINE_LEN];
799   char *token, *value, *line_ptr;
800   struct SetupFileList *setup_file_list = newSetupFileList("", "");
801   struct SetupFileList *first_valid_list_entry;
802
803   FILE *file;
804
805   if (!(file = fopen(filename, "r")))
806   {
807     Error(ERR_WARN, "cannot open setup/info file '%s'", filename);
808     return NULL;
809   }
810
811   while(!feof(file))
812   {
813     /* read next line of input file */
814     if (!fgets(line, MAX_LINE_LEN, file))
815       break;
816
817     /* cut trailing comment or whitespace from input line */
818     for (line_ptr = line; *line_ptr; line_ptr++)
819     {
820       if (*line_ptr == '#' || *line_ptr == '\n')
821       {
822         *line_ptr = '\0';
823         break;
824       }
825     }
826
827     /* cut trailing whitespaces from input line */
828     for (line_ptr = &line[strlen(line)]; line_ptr > line; line_ptr--)
829       if ((*line_ptr == ' ' || *line_ptr == '\t') && line_ptr[1] == '\0')
830         *line_ptr = '\0';
831
832     /* ignore empty lines */
833     if (*line == '\0')
834       continue;
835
836     line_len = strlen(line);
837
838     /* cut leading whitespaces from token */
839     for (token = line; *token; token++)
840       if (*token != ' ' && *token != '\t')
841         break;
842
843     /* find end of token */
844     for (line_ptr = token; *line_ptr; line_ptr++)
845     {
846       if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
847       {
848         *line_ptr = '\0';
849         break;
850       }
851     }
852
853     if (line_ptr < line + line_len)
854       value = line_ptr + 1;
855     else
856       value = "\0";
857
858     /* cut leading whitespaces from value */
859     for (; *value; value++)
860       if (*value != ' ' && *value != '\t')
861         break;
862
863     if (*token && *value)
864       setTokenValue(setup_file_list, token, value);
865   }
866
867   fclose(file);
868
869   first_valid_list_entry = setup_file_list->next;
870
871   /* free empty list header */
872   setup_file_list->next = NULL;
873   freeSetupFileList(setup_file_list);
874
875   if (first_valid_list_entry == NULL)
876     Error(ERR_WARN, "setup/info file '%s' is empty", filename);
877
878   return first_valid_list_entry;
879 }
880
881 static void checkSetupFileListIdentifier(struct SetupFileList *setup_file_list,
882                                          char *identifier)
883 {
884   if (!setup_file_list)
885     return;
886
887   if (strcmp(setup_file_list->token, TOKEN_STR_FILE_IDENTIFIER) == 0)
888   {
889     if (strcmp(setup_file_list->value, identifier) != 0)
890     {
891       Error(ERR_WARN, "setup/info file has wrong version");
892       return;
893     }
894     else
895       return;
896   }
897
898   if (setup_file_list->next)
899     checkSetupFileListIdentifier(setup_file_list->next, identifier);
900   else
901   {
902     Error(ERR_WARN, "setup/info file has no version information");
903     return;
904   }
905 }
906
907 static void setLevelDirInfoToDefaults(struct LevelDirInfo *ldi)
908 {
909   ldi->name = getStringCopy("non-existing");
910   ldi->levels = 0;
911   ldi->sort_priority = 999;     /* default: least priority */
912   ldi->readonly = TRUE;
913 }
914
915 static void setSetupInfoToDefaults(struct SetupInfo *si)
916 {
917   int i;
918
919   si->player_name = getStringCopy(getLoginName());
920
921   si->sound = TRUE;
922   si->sound_loops = FALSE;
923   si->sound_music = FALSE;
924   si->sound_simple = FALSE;
925   si->toons = TRUE;
926   si->double_buffering = TRUE;
927   si->direct_draw = !si->double_buffering;
928   si->scroll_delay = FALSE;
929   si->soft_scrolling = TRUE;
930   si->fading = FALSE;
931   si->autorecord = FALSE;
932   si->quick_doors = FALSE;
933
934   for (i=0; i<MAX_PLAYERS; i++)
935   {
936     si->input[i].use_joystick = FALSE;
937     si->input[i].joy.device_name = getStringCopy(joystick_device_name[i]);
938     si->input[i].joy.xleft   = JOYSTICK_XLEFT;
939     si->input[i].joy.xmiddle = JOYSTICK_XMIDDLE;
940     si->input[i].joy.xright  = JOYSTICK_XRIGHT;
941     si->input[i].joy.yupper  = JOYSTICK_YUPPER;
942     si->input[i].joy.ymiddle = JOYSTICK_YMIDDLE;
943     si->input[i].joy.ylower  = JOYSTICK_YLOWER;
944     si->input[i].joy.snap  = (i == 0 ? JOY_BUTTON_1 : 0);
945     si->input[i].joy.bomb  = (i == 0 ? JOY_BUTTON_2 : 0);
946     si->input[i].key.left  = (i == 0 ? DEFAULT_KEY_LEFT  : KEY_UNDEFINDED);
947     si->input[i].key.right = (i == 0 ? DEFAULT_KEY_RIGHT : KEY_UNDEFINDED);
948     si->input[i].key.up    = (i == 0 ? DEFAULT_KEY_UP    : KEY_UNDEFINDED);
949     si->input[i].key.down  = (i == 0 ? DEFAULT_KEY_DOWN  : KEY_UNDEFINDED);
950     si->input[i].key.snap  = (i == 0 ? DEFAULT_KEY_SNAP  : KEY_UNDEFINDED);
951     si->input[i].key.bomb  = (i == 0 ? DEFAULT_KEY_BOMB  : KEY_UNDEFINDED);
952   }
953 }
954
955 static void setSetupInfo(int token_nr, char *token_value)
956 {
957   int token_type = token_info[token_nr].type;
958   void *setup_value = token_info[token_nr].value;
959
960   if (token_value == NULL)
961     return;
962
963   /* set setup field to corresponding token value */
964   switch (token_type)
965   {
966     case TYPE_BOOLEAN:
967     case TYPE_SWITCH:
968       *(boolean *)setup_value = get_string_boolean_value(token_value);
969       break;
970
971     case TYPE_KEYSYM:
972       *(KeySym *)setup_value = getKeySymFromX11KeyName(token_value);
973       break;
974
975     case TYPE_INTEGER:
976       *(int *)setup_value = get_string_integer_value(token_value);
977       break;
978
979     case TYPE_STRING:
980       if (*(char **)setup_value != NULL)
981         free(*(char **)setup_value);
982       *(char **)setup_value = getStringCopy(token_value);
983       break;
984
985     default:
986       break;
987   }
988 }
989
990 static void decodeSetupFileList(struct SetupFileList *setup_file_list)
991 {
992   int i, pnr;
993
994   if (!setup_file_list)
995     return;
996
997   /* handle global setup values */
998   si = setup;
999   for (i=FIRST_GLOBAL_SETUP_TOKEN; i<=LAST_GLOBAL_SETUP_TOKEN; i++)
1000     setSetupInfo(i, getTokenValue(setup_file_list, token_info[i].text));
1001   setup = si;
1002
1003   /* handle player specific setup values */
1004   for (pnr=0; pnr<MAX_PLAYERS; pnr++)
1005   {
1006     char prefix[30];
1007
1008     sprintf(prefix, "%s%d", TOKEN_STR_PLAYER_PREFIX, pnr + 1);
1009
1010     sii = setup.input[pnr];
1011     for (i=FIRST_PLAYER_SETUP_TOKEN; i<=LAST_PLAYER_SETUP_TOKEN; i++)
1012     {
1013       char full_token[100];
1014
1015       sprintf(full_token, "%s%s", prefix, token_info[i].text);
1016       setSetupInfo(i, getTokenValue(setup_file_list, full_token));
1017     }
1018     setup.input[pnr] = sii;
1019   }
1020 }
1021
1022 int getLevelSeriesNrFromLevelSeriesName(char *level_series_name)
1023 {
1024   int i;
1025
1026   if (!level_series_name)
1027     return 0;
1028
1029   for (i=0; i<num_leveldirs; i++)
1030     if (strcmp(level_series_name, leveldir[i].filename) == 0)
1031       return i;
1032
1033   return 0;
1034 }
1035
1036 int getLastPlayedLevelOfLevelSeries(char *level_series_name)
1037 {
1038   char *token_value;
1039   int level_series_nr = getLevelSeriesNrFromLevelSeriesName(level_series_name);
1040   int last_level_nr = 0;
1041
1042   if (!level_series_name)
1043     return 0;
1044
1045   token_value = getTokenValue(level_setup_list, level_series_name);
1046
1047   if (token_value)
1048   {
1049     int highest_level_nr = leveldir[level_series_nr].levels - 1;
1050
1051     last_level_nr = atoi(token_value);
1052
1053     if (last_level_nr < 0)
1054       last_level_nr = 0;
1055     if (last_level_nr > highest_level_nr)
1056       last_level_nr = highest_level_nr;
1057   }
1058
1059   return last_level_nr;
1060 }
1061
1062 static int compareLevelDirInfoEntries(const void *object1, const void *object2)
1063 {
1064   const struct LevelDirInfo *entry1 = object1;
1065   const struct LevelDirInfo *entry2 = object2;
1066   int compare_result;
1067
1068   if (entry1->sort_priority != entry2->sort_priority)
1069     compare_result = entry1->sort_priority - entry2->sort_priority;
1070   else
1071   {
1072     char *name1 = getStringToLower(entry1->name);
1073     char *name2 = getStringToLower(entry2->name);
1074
1075     compare_result = strcmp(name1, name2);
1076
1077     free(name1);
1078     free(name2);
1079   }
1080
1081   return compare_result;
1082 }
1083
1084 static int LoadLevelInfoFromLevelDir(char *level_directory, int start_entry)
1085 {
1086   DIR *dir;
1087   struct stat file_status;
1088   char *directory = NULL;
1089   char *filename = NULL;
1090   struct SetupFileList *setup_file_list = NULL;
1091   struct dirent *dir_entry;
1092   int i, current_entry = start_entry;
1093
1094   if ((dir = opendir(level_directory)) == NULL)
1095     Error(ERR_EXIT, "cannot read level directory '%s'", level_directory);
1096
1097   while (current_entry < MAX_LEVDIR_ENTRIES)
1098   {
1099     if ((dir_entry = readdir(dir)) == NULL)     /* last directory entry */
1100       break;
1101
1102     /* skip entries for current and parent directory */
1103     if (strcmp(dir_entry->d_name, ".")  == 0 ||
1104         strcmp(dir_entry->d_name, "..") == 0)
1105       continue;
1106
1107     /* find out if directory entry is itself a directory */
1108     directory = getPath2(level_directory, dir_entry->d_name);
1109     if (stat(directory, &file_status) != 0 ||           /* cannot stat file */
1110         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
1111     {
1112       free(directory);
1113       continue;
1114     }
1115
1116     if (strlen(dir_entry->d_name) >= MAX_LEVDIR_FILENAME)
1117     {
1118       Error(ERR_WARN, "filename of level directory '%s' too long -- ignoring",
1119             dir_entry->d_name);
1120       continue;
1121     }
1122
1123     filename = getPath2(directory, LEVELINFO_FILENAME);
1124     setup_file_list = loadSetupFileList(filename);
1125
1126     if (setup_file_list)
1127     {
1128       checkSetupFileListIdentifier(setup_file_list, LEVELINFO_COOKIE);
1129       setLevelDirInfoToDefaults(&leveldir[current_entry]);
1130
1131       ldi = leveldir[current_entry];
1132       for (i=FIRST_LEVELINFO_TOKEN; i<=LAST_LEVELINFO_TOKEN; i++)
1133         setSetupInfo(i, getTokenValue(setup_file_list, token_info[i].text));
1134       leveldir[current_entry] = ldi;
1135
1136       leveldir[current_entry].filename = getStringCopy(dir_entry->d_name);
1137       leveldir[current_entry].user_defined =
1138         (level_directory == options.level_directory ? FALSE : TRUE);
1139
1140       freeSetupFileList(setup_file_list);
1141       current_entry++;
1142     }
1143     else
1144       Error(ERR_WARN, "ignoring level directory '%s'", directory);
1145
1146     free(directory);
1147     free(filename);
1148   }
1149
1150   if (current_entry == MAX_LEVDIR_ENTRIES)
1151     Error(ERR_WARN, "using %d level directories -- ignoring the rest",
1152           current_entry);
1153
1154   closedir(dir);
1155
1156   if (current_entry == start_entry && start_entry != -1)
1157     Error(ERR_EXIT, "cannot find any valid level series in directory '%s'",
1158           level_directory);
1159
1160   return current_entry;
1161 }
1162
1163 void LoadLevelInfo()
1164 {
1165   InitUserLevelDirectory(getLoginName());
1166
1167   num_leveldirs = 0;
1168   leveldir_nr = 0;
1169
1170   num_leveldirs = LoadLevelInfoFromLevelDir(options.level_directory,
1171                                             num_leveldirs);
1172   num_leveldirs = LoadLevelInfoFromLevelDir(getUserLevelDir(""),
1173                                             num_leveldirs);
1174   if (num_leveldirs > 1)
1175     qsort(leveldir, num_leveldirs, sizeof(struct LevelDirInfo),
1176           compareLevelDirInfoEntries);
1177 }
1178
1179 static void SaveUserLevelInfo()
1180 {
1181   char filename[MAX_FILENAME_LEN];
1182   FILE *file;
1183   int i;
1184
1185   sprintf(filename, "%s/%s",
1186           getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
1187
1188   if (!(file = fopen(filename, "w")))
1189   {
1190     Error(ERR_WARN, "cannot write level info file '%s'", filename);
1191     return;
1192   }
1193
1194   ldi.name = getLoginName();
1195   ldi.levels = 100;
1196   ldi.sort_priority = 300;
1197   ldi.readonly = FALSE;
1198
1199   fprintf(file, "%s\n\n",
1200           getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER, LEVELINFO_COOKIE));
1201
1202   for (i=FIRST_LEVELINFO_TOKEN; i<=LAST_LEVELINFO_TOKEN; i++)
1203     fprintf(file, "%s\n", getSetupLine("", i));
1204
1205   fclose(file);
1206
1207   chmod(filename, SETUP_PERMS);
1208 }
1209
1210 void LoadSetup()
1211 {
1212   char filename[MAX_FILENAME_LEN];
1213   struct SetupFileList *setup_file_list = NULL;
1214
1215   /* always start with reliable default setup values */
1216   setSetupInfoToDefaults(&setup);
1217
1218   sprintf(filename, "%s/%s", getSetupDir(), SETUP_FILENAME);
1219
1220   setup_file_list = loadSetupFileList(filename);
1221
1222   if (setup_file_list)
1223   {
1224     checkSetupFileListIdentifier(setup_file_list, SETUP_COOKIE);
1225     decodeSetupFileList(setup_file_list);
1226
1227     setup.direct_draw = !setup.double_buffering;
1228
1229     freeSetupFileList(setup_file_list);
1230
1231     /* needed to work around problems with fixed length strings */
1232     if (strlen(setup.player_name) >= MAX_NAMELEN)
1233       setup.player_name[MAX_NAMELEN - 1] = '\0';
1234     else if (strlen(setup.player_name) < MAX_NAMELEN - 1)
1235     {
1236       char *new_name = checked_malloc(MAX_NAMELEN);
1237
1238       strcpy(new_name, setup.player_name);
1239       free(setup.player_name);
1240       setup.player_name = new_name;
1241     }
1242   }
1243   else
1244     Error(ERR_WARN, "using default setup values");
1245 }
1246
1247 static char *getSetupLine(char *prefix, int token_nr)
1248 {
1249   int i;
1250   static char entry[MAX_LINE_LEN];
1251   int token_type = token_info[token_nr].type;
1252   void *setup_value = token_info[token_nr].value;
1253   char *token_text = token_info[token_nr].text;
1254
1255   /* start with the prefix, token and some spaces to format output line */
1256   sprintf(entry, "%s%s:", prefix, token_text);
1257   for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
1258     strcat(entry, " ");
1259
1260   /* continue with the token's value (which can have different types) */
1261   switch (token_type)
1262   {
1263     case TYPE_BOOLEAN:
1264       strcat(entry, (*(boolean *)setup_value ? "true" : "false"));
1265       break;
1266
1267     case TYPE_SWITCH:
1268       strcat(entry, (*(boolean *)setup_value ? "on" : "off"));
1269       break;
1270
1271     case TYPE_KEYSYM:
1272       {
1273         KeySym keysym = *(KeySym *)setup_value;
1274         char *keyname = getKeyNameFromKeySym(keysym);
1275
1276         strcat(entry, getX11KeyNameFromKeySym(keysym));
1277         for (i=strlen(entry); i<50; i++)
1278           strcat(entry, " ");
1279
1280         /* add comment, if useful */
1281         if (strcmp(keyname, "(undefined)") != 0 &&
1282             strcmp(keyname, "(unknown)") != 0)
1283         {
1284           strcat(entry, "# ");
1285           strcat(entry, keyname);
1286         }
1287       }
1288       break;
1289
1290     case TYPE_INTEGER:
1291       {
1292         char buffer[MAX_LINE_LEN];
1293
1294         sprintf(buffer, "%d", *(int *)setup_value);
1295         strcat(entry, buffer);
1296       }
1297       break;
1298
1299     case TYPE_STRING:
1300       strcat(entry, *(char **)setup_value);
1301       break;
1302
1303     default:
1304       break;
1305   }
1306
1307   return entry;
1308 }
1309
1310 void SaveSetup()
1311 {
1312   int i, pnr;
1313   char filename[MAX_FILENAME_LEN];
1314   FILE *file;
1315
1316   InitUserDataDirectory();
1317
1318   sprintf(filename, "%s/%s", getSetupDir(), SETUP_FILENAME);
1319
1320   if (!(file = fopen(filename, "w")))
1321   {
1322     Error(ERR_WARN, "cannot write setup file '%s'", filename);
1323     return;
1324   }
1325
1326   fprintf(file, "%s\n",
1327           getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER, SETUP_COOKIE));
1328   fprintf(file, "\n");
1329
1330   /* handle global setup values */
1331   si = setup;
1332   for (i=FIRST_GLOBAL_SETUP_TOKEN; i<=LAST_GLOBAL_SETUP_TOKEN; i++)
1333   {
1334     fprintf(file, "%s\n", getSetupLine("", i));
1335
1336     /* just to make things nicer :) */
1337     if (i == SETUP_TOKEN_PLAYER_NAME)
1338       fprintf(file, "\n");
1339   }
1340
1341   /* handle player specific setup values */
1342   for (pnr=0; pnr<MAX_PLAYERS; pnr++)
1343   {
1344     char prefix[30];
1345
1346     sprintf(prefix, "%s%d", TOKEN_STR_PLAYER_PREFIX, pnr + 1);
1347     fprintf(file, "\n");
1348
1349     sii = setup.input[pnr];
1350     for (i=FIRST_PLAYER_SETUP_TOKEN; i<=LAST_PLAYER_SETUP_TOKEN; i++)
1351       fprintf(file, "%s\n", getSetupLine(prefix, i));
1352   }
1353
1354   fclose(file);
1355
1356   chmod(filename, SETUP_PERMS);
1357 }
1358
1359 void LoadLevelSetup()
1360 {
1361   char filename[MAX_FILENAME_LEN];
1362
1363   /* always start with reliable default setup values */
1364
1365   leveldir_nr = 0;
1366   level_nr = 0;
1367
1368   sprintf(filename, "%s/%s", getSetupDir(), LEVELSETUP_FILENAME);
1369
1370   if (level_setup_list)
1371     freeSetupFileList(level_setup_list);
1372
1373   level_setup_list = loadSetupFileList(filename);
1374
1375   if (level_setup_list)
1376   {
1377     char *last_level_series =
1378       getTokenValue(level_setup_list, TOKEN_STR_LAST_LEVEL_SERIES);
1379
1380     leveldir_nr = getLevelSeriesNrFromLevelSeriesName(last_level_series);
1381     level_nr = getLastPlayedLevelOfLevelSeries(last_level_series);
1382
1383     checkSetupFileListIdentifier(level_setup_list, LEVELSETUP_COOKIE);
1384   }
1385   else
1386   {
1387     level_setup_list = newSetupFileList(TOKEN_STR_FILE_IDENTIFIER,
1388                                         LEVELSETUP_COOKIE);
1389     Error(ERR_WARN, "using default setup values");
1390   }
1391 }
1392
1393 void SaveLevelSetup()
1394 {
1395   char filename[MAX_FILENAME_LEN];
1396   struct SetupFileList *list_entry = level_setup_list;
1397   FILE *file;
1398
1399   InitUserDataDirectory();
1400
1401   setTokenValue(level_setup_list,
1402                 TOKEN_STR_LAST_LEVEL_SERIES, leveldir[leveldir_nr].filename);
1403
1404   setTokenValue(level_setup_list,
1405                 leveldir[leveldir_nr].filename, int2str(level_nr, 0));
1406
1407   sprintf(filename, "%s/%s", getSetupDir(), LEVELSETUP_FILENAME);
1408
1409   if (!(file = fopen(filename, "w")))
1410   {
1411     Error(ERR_WARN, "cannot write setup file '%s'", filename);
1412     return;
1413   }
1414
1415   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1416                                                  LEVELSETUP_COOKIE));
1417   while (list_entry)
1418   {
1419     if (strcmp(list_entry->token, TOKEN_STR_FILE_IDENTIFIER) != 0)
1420       fprintf(file, "%s\n",
1421               getFormattedSetupEntry(list_entry->token, list_entry->value));
1422
1423     /* just to make things nicer :) */
1424     if (strcmp(list_entry->token, TOKEN_STR_LAST_LEVEL_SERIES) == 0)
1425       fprintf(file, "\n");
1426
1427     list_entry = list_entry->next;
1428   }
1429
1430   fclose(file);
1431
1432   chmod(filename, SETUP_PERMS);
1433 }