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