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