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