bf02c2276e2a6a2f2227e993705fa64d26ad15f3
[rocksndiamonds.git] / src / libgame / setup.c
1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // setup.c
10 // ============================================================================
11
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <dirent.h>
15 #include <string.h>
16 #include <unistd.h>
17 #include <errno.h>
18
19 #include "platform.h"
20
21 #if !defined(PLATFORM_WIN32)
22 #include <pwd.h>
23 #include <sys/param.h>
24 #endif
25
26 #include "setup.h"
27 #include "joystick.h"
28 #include "text.h"
29 #include "misc.h"
30 #include "hash.h"
31
32
33 #define ENABLE_UNUSED_CODE      FALSE   /* for currently unused functions */
34
35 #define NUM_LEVELCLASS_DESC     8
36
37 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
38 {
39   "Tutorial Levels",
40   "Classic Originals",
41   "Contributions",
42   "Private Levels",
43   "Boulderdash",
44   "Emerald Mine",
45   "Supaplex",
46   "DX Boulderdash"
47 };
48
49
50 #define LEVELCOLOR(n)   (IS_LEVELCLASS_TUTORIAL(n) ?            FC_BLUE :    \
51                          IS_LEVELCLASS_CLASSICS(n) ?            FC_RED :     \
52                          IS_LEVELCLASS_BD(n) ?                  FC_YELLOW :  \
53                          IS_LEVELCLASS_EM(n) ?                  FC_YELLOW :  \
54                          IS_LEVELCLASS_SP(n) ?                  FC_YELLOW :  \
55                          IS_LEVELCLASS_DX(n) ?                  FC_YELLOW :  \
56                          IS_LEVELCLASS_SB(n) ?                  FC_YELLOW :  \
57                          IS_LEVELCLASS_CONTRIB(n) ?             FC_GREEN :   \
58                          IS_LEVELCLASS_PRIVATE(n) ?             FC_RED :     \
59                          FC_BLUE)
60
61 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ?            0 :     \
62                          IS_LEVELCLASS_CLASSICS(n) ?            1 :     \
63                          IS_LEVELCLASS_BD(n) ?                  2 :     \
64                          IS_LEVELCLASS_EM(n) ?                  3 :     \
65                          IS_LEVELCLASS_SP(n) ?                  4 :     \
66                          IS_LEVELCLASS_DX(n) ?                  5 :     \
67                          IS_LEVELCLASS_SB(n) ?                  6 :     \
68                          IS_LEVELCLASS_CONTRIB(n) ?             7 :     \
69                          IS_LEVELCLASS_PRIVATE(n) ?             8 :     \
70                          9)
71
72 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ?          FC_RED :     \
73                          IS_ARTWORKCLASS_CONTRIB(n) ?           FC_GREEN :   \
74                          IS_ARTWORKCLASS_PRIVATE(n) ?           FC_RED :     \
75                          IS_ARTWORKCLASS_LEVEL(n) ?             FC_YELLOW :  \
76                          FC_BLUE)
77
78 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ?        0 :     \
79                            IS_ARTWORKCLASS_LEVEL(n) ?           1 :     \
80                            IS_ARTWORKCLASS_CONTRIB(n) ?         2 :     \
81                            IS_ARTWORKCLASS_PRIVATE(n) ?         3 :     \
82                            9)
83
84 #define TOKEN_VALUE_POSITION_SHORT              32
85 #define TOKEN_VALUE_POSITION_DEFAULT            40
86 #define TOKEN_COMMENT_POSITION_DEFAULT          60
87
88 #define MAX_COOKIE_LEN                          256
89
90
91 static void setTreeInfoToDefaults(TreeInfo *, int);
92 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
93 static int compareTreeInfoEntries(const void *, const void *);
94
95 static int token_value_position   = TOKEN_VALUE_POSITION_DEFAULT;
96 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
97
98 static SetupFileHash *artworkinfo_cache_old = NULL;
99 static SetupFileHash *artworkinfo_cache_new = NULL;
100 static boolean use_artworkinfo_cache = TRUE;
101
102
103 /* ------------------------------------------------------------------------- */
104 /* file functions                                                            */
105 /* ------------------------------------------------------------------------- */
106
107 static char *getLevelClassDescription(TreeInfo *ti)
108 {
109   int position = ti->sort_priority / 100;
110
111   if (position >= 0 && position < NUM_LEVELCLASS_DESC)
112     return levelclass_desc[position];
113   else
114     return "Unknown Level Class";
115 }
116
117 static char *getScoreDir(char *level_subdir)
118 {
119   static char *score_dir = NULL;
120   static char *score_level_dir = NULL;
121   char *score_subdir = SCORES_DIRECTORY;
122
123   if (score_dir == NULL)
124   {
125     if (program.global_scores)
126       score_dir = getPath2(getCommonDataDir(),   score_subdir);
127     else
128       score_dir = getPath2(getUserGameDataDir(), score_subdir);
129   }
130
131   if (level_subdir != NULL)
132   {
133     checked_free(score_level_dir);
134
135     score_level_dir = getPath2(score_dir, level_subdir);
136
137     return score_level_dir;
138   }
139
140   return score_dir;
141 }
142
143 static char *getLevelSetupDir(char *level_subdir)
144 {
145   static char *levelsetup_dir = NULL;
146   char *data_dir = getUserGameDataDir();
147   char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
148
149   checked_free(levelsetup_dir);
150
151   if (level_subdir != NULL)
152     levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
153   else
154     levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
155
156   return levelsetup_dir;
157 }
158
159 static char *getCacheDir()
160 {
161   static char *cache_dir = NULL;
162
163   if (cache_dir == NULL)
164     cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
165
166   return cache_dir;
167 }
168
169 static char *getLevelDirFromTreeInfo(TreeInfo *node)
170 {
171   static char *level_dir = NULL;
172
173   if (node == NULL)
174     return options.level_directory;
175
176   checked_free(level_dir);
177
178   level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
179                         options.level_directory), node->fullpath);
180
181   return level_dir;
182 }
183
184 char *getUserLevelDir(char *level_subdir)
185 {
186   static char *userlevel_dir = NULL;
187   char *data_dir = getUserGameDataDir();
188   char *userlevel_subdir = LEVELS_DIRECTORY;
189
190   checked_free(userlevel_dir);
191
192   if (level_subdir != NULL)
193     userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
194   else
195     userlevel_dir = getPath2(data_dir, userlevel_subdir);
196
197   return userlevel_dir;
198 }
199
200 char *getCurrentLevelDir()
201 {
202   return getLevelDirFromTreeInfo(leveldir_current);
203 }
204
205 char *getNewUserLevelSubdir()
206 {
207   static char *new_level_subdir = NULL;
208   char *subdir_prefix = getLoginName();
209   char subdir_suffix[10];
210   int max_suffix_number = 1000;
211   int i = 0;
212
213   while (++i < max_suffix_number)
214   {
215     sprintf(subdir_suffix, "_%d", i);
216
217     checked_free(new_level_subdir);
218     new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
219
220     if (!directoryExists(getUserLevelDir(new_level_subdir)))
221       break;
222   }
223
224   return new_level_subdir;
225 }
226
227 static char *getTapeDir(char *level_subdir)
228 {
229   static char *tape_dir = NULL;
230   char *data_dir = getUserGameDataDir();
231   char *tape_subdir = TAPES_DIRECTORY;
232
233   checked_free(tape_dir);
234
235   if (level_subdir != NULL)
236     tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
237   else
238     tape_dir = getPath2(data_dir, tape_subdir);
239
240   return tape_dir;
241 }
242
243 static char *getSolutionTapeDir()
244 {
245   static char *tape_dir = NULL;
246   char *data_dir = getCurrentLevelDir();
247   char *tape_subdir = TAPES_DIRECTORY;
248
249   checked_free(tape_dir);
250
251   tape_dir = getPath2(data_dir, tape_subdir);
252
253   return tape_dir;
254 }
255
256 static char *getDefaultGraphicsDir(char *graphics_subdir)
257 {
258   static char *graphics_dir = NULL;
259
260   if (graphics_subdir == NULL)
261     return options.graphics_directory;
262
263   checked_free(graphics_dir);
264
265   graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
266
267   return graphics_dir;
268 }
269
270 static char *getDefaultSoundsDir(char *sounds_subdir)
271 {
272   static char *sounds_dir = NULL;
273
274   if (sounds_subdir == NULL)
275     return options.sounds_directory;
276
277   checked_free(sounds_dir);
278
279   sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
280
281   return sounds_dir;
282 }
283
284 static char *getDefaultMusicDir(char *music_subdir)
285 {
286   static char *music_dir = NULL;
287
288   if (music_subdir == NULL)
289     return options.music_directory;
290
291   checked_free(music_dir);
292
293   music_dir = getPath2(options.music_directory, music_subdir);
294
295   return music_dir;
296 }
297
298 static char *getClassicArtworkSet(int type)
299 {
300   return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
301           type == TREE_TYPE_SOUNDS_DIR   ? SND_CLASSIC_SUBDIR :
302           type == TREE_TYPE_MUSIC_DIR    ? MUS_CLASSIC_SUBDIR : "");
303 }
304
305 static char *getClassicArtworkDir(int type)
306 {
307   return (type == TREE_TYPE_GRAPHICS_DIR ?
308           getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
309           type == TREE_TYPE_SOUNDS_DIR ?
310           getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
311           type == TREE_TYPE_MUSIC_DIR ?
312           getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
313 }
314
315 static char *getUserGraphicsDir()
316 {
317   static char *usergraphics_dir = NULL;
318
319   if (usergraphics_dir == NULL)
320     usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
321
322   return usergraphics_dir;
323 }
324
325 static char *getUserSoundsDir()
326 {
327   static char *usersounds_dir = NULL;
328
329   if (usersounds_dir == NULL)
330     usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
331
332   return usersounds_dir;
333 }
334
335 static char *getUserMusicDir()
336 {
337   static char *usermusic_dir = NULL;
338
339   if (usermusic_dir == NULL)
340     usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
341
342   return usermusic_dir;
343 }
344
345 static char *getSetupArtworkDir(TreeInfo *ti)
346 {
347   static char *artwork_dir = NULL;
348
349   if (ti == NULL)
350     return NULL;
351
352   checked_free(artwork_dir);
353
354   artwork_dir = getPath2(ti->basepath, ti->fullpath);
355
356   return artwork_dir;
357 }
358
359 char *setLevelArtworkDir(TreeInfo *ti)
360 {
361   char **artwork_path_ptr, **artwork_set_ptr;
362   TreeInfo *level_artwork;
363
364   if (ti == NULL || leveldir_current == NULL)
365     return NULL;
366
367   artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
368   artwork_set_ptr  = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
369
370   checked_free(*artwork_path_ptr);
371
372   if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
373   {
374     *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
375   }
376   else
377   {
378     /*
379       No (or non-existing) artwork configured in "levelinfo.conf". This would
380       normally result in using the artwork configured in the setup menu. But
381       if an artwork subdirectory exists (which might contain custom artwork
382       or an artwork configuration file), this level artwork must be treated
383       as relative to the default "classic" artwork, not to the artwork that
384       is currently configured in the setup menu.
385
386       Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
387       the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
388       the real "classic" artwork from the original R'n'D (like "gfx_classic").
389     */
390
391     char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
392
393     checked_free(*artwork_set_ptr);
394
395     if (directoryExists(dir))
396     {
397       *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
398       *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
399     }
400     else
401     {
402       *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
403       *artwork_set_ptr = NULL;
404     }
405
406     free(dir);
407   }
408
409   return *artwork_set_ptr;
410 }
411
412 inline static char *getLevelArtworkSet(int type)
413 {
414   if (leveldir_current == NULL)
415     return NULL;
416
417   return LEVELDIR_ARTWORK_SET(leveldir_current, type);
418 }
419
420 inline static char *getLevelArtworkDir(int type)
421 {
422   if (leveldir_current == NULL)
423     return UNDEFINED_FILENAME;
424
425   return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
426 }
427
428 char *getProgramMainDataPath(char *command_filename, char *base_path)
429 {
430   /* check if the program's main data base directory is configured */
431   if (!strEqual(base_path, "."))
432     return base_path;
433
434   /* if the program is configured to start from current directory (default),
435      determine program package directory from program binary (some versions
436      of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
437      set the current working directory to the program package directory) */
438   char *main_data_path = getBasePath(command_filename);
439
440 #if defined(PLATFORM_MACOSX)
441   if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
442   {
443     char *main_data_path_old = main_data_path;
444
445     // cut relative path to Mac OS X application binary directory from path
446     main_data_path[strlen(main_data_path) -
447                    strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
448
449     // cut trailing path separator from path (but not if path is root directory)
450     if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
451       main_data_path[strlen(main_data_path) - 1] = '\0';
452
453     // replace empty path with current directory
454     if (strEqual(main_data_path, ""))
455       main_data_path = ".";
456
457     // add relative path to Mac OS X application resources directory to path
458     main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
459
460     free(main_data_path_old);
461   }
462 #endif
463
464   return main_data_path;
465 }
466
467 char *getProgramConfigFilename(char *command_filename)
468 {
469   char *command_filename_1 = getStringCopy(command_filename);
470
471   // strip trailing executable suffix from command filename
472   if (strSuffix(command_filename_1, ".exe"))
473     command_filename_1[strlen(command_filename_1) - 4] = '\0';
474
475   char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH);
476   char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY);
477
478   char *command_basepath = getBasePath(command_filename);
479   char *command_basename = getBaseNameNoSuffix(command_filename);
480   char *command_filename_2 = getPath2(command_basepath, command_basename);
481
482   char *config_filename_1 = getStringCat2(command_filename_1, ".conf");
483   char *config_filename_2 = getStringCat2(command_filename_2, ".conf");
484   char *config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
485
486   // 1st try: look for config file that exactly matches the binary filename
487   if (fileExists(config_filename_1))
488     return config_filename_1;
489
490   // 2nd try: look for config file that matches binary filename without suffix
491   if (fileExists(config_filename_2))
492     return config_filename_2;
493
494   // 3rd try: return setup config filename in global program config directory
495   return config_filename_3;
496 }
497
498 char *getTapeFilename(int nr)
499 {
500   static char *filename = NULL;
501   char basename[MAX_FILENAME_LEN];
502
503   checked_free(filename);
504
505   sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
506   filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
507
508   return filename;
509 }
510
511 char *getSolutionTapeFilename(int nr)
512 {
513   static char *filename = NULL;
514   char basename[MAX_FILENAME_LEN];
515
516   checked_free(filename);
517
518   sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
519   filename = getPath2(getSolutionTapeDir(), basename);
520
521   if (!fileExists(filename))
522   {
523     static char *filename_sln = NULL;
524
525     checked_free(filename_sln);
526
527     sprintf(basename, "%03d.sln", nr);
528     filename_sln = getPath2(getSolutionTapeDir(), basename);
529
530     if (fileExists(filename_sln))
531       return filename_sln;
532   }
533
534   return filename;
535 }
536
537 char *getScoreFilename(int nr)
538 {
539   static char *filename = NULL;
540   char basename[MAX_FILENAME_LEN];
541
542   checked_free(filename);
543
544   sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
545   filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
546
547   return filename;
548 }
549
550 char *getSetupFilename()
551 {
552   static char *filename = NULL;
553
554   checked_free(filename);
555
556   filename = getPath2(getSetupDir(), SETUP_FILENAME);
557
558   return filename;
559 }
560
561 char *getDefaultSetupFilename()
562 {
563   return program.config_filename;
564 }
565
566 char *getEditorSetupFilename()
567 {
568   static char *filename = NULL;
569
570   checked_free(filename);
571   filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
572
573   if (fileExists(filename))
574     return filename;
575
576   checked_free(filename);
577   filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
578
579   return filename;
580 }
581
582 char *getHelpAnimFilename()
583 {
584   static char *filename = NULL;
585
586   checked_free(filename);
587
588   filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
589
590   return filename;
591 }
592
593 char *getHelpTextFilename()
594 {
595   static char *filename = NULL;
596
597   checked_free(filename);
598
599   filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
600
601   return filename;
602 }
603
604 char *getLevelSetInfoFilename()
605 {
606   static char *filename = NULL;
607   char *basenames[] =
608   {
609     "README",
610     "README.TXT",
611     "README.txt",
612     "Readme",
613     "Readme.txt",
614     "readme",
615     "readme.txt",
616
617     NULL
618   };
619   int i;
620
621   for (i = 0; basenames[i] != NULL; i++)
622   {
623     checked_free(filename);
624     filename = getPath2(getCurrentLevelDir(), basenames[i]);
625
626     if (fileExists(filename))
627       return filename;
628   }
629
630   return NULL;
631 }
632
633 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
634 {
635   static char basename[32];
636
637   sprintf(basename, "%s_%d.txt",
638           (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
639
640   return basename;
641 }
642
643 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
644 {
645   static char *filename = NULL;
646   char *basename;
647   boolean skip_setup_artwork = FALSE;
648
649   checked_free(filename);
650
651   basename = getLevelSetTitleMessageBasename(nr, initial);
652
653   if (!gfx.override_level_graphics)
654   {
655     /* 1st try: look for special artwork in current level series directory */
656     filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
657     if (fileExists(filename))
658       return filename;
659
660     free(filename);
661
662     /* 2nd try: look for message file in current level set directory */
663     filename = getPath2(getCurrentLevelDir(), basename);
664     if (fileExists(filename))
665       return filename;
666
667     free(filename);
668
669     /* check if there is special artwork configured in level series config */
670     if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
671     {
672       /* 3rd try: look for special artwork configured in level series config */
673       filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
674       if (fileExists(filename))
675         return filename;
676
677       free(filename);
678
679       /* take missing artwork configured in level set config from default */
680       skip_setup_artwork = TRUE;
681     }
682   }
683
684   if (!skip_setup_artwork)
685   {
686     /* 4th try: look for special artwork in configured artwork directory */
687     filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
688     if (fileExists(filename))
689       return filename;
690
691     free(filename);
692   }
693
694   /* 5th try: look for default artwork in new default artwork directory */
695   filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
696   if (fileExists(filename))
697     return filename;
698
699   free(filename);
700
701   /* 6th try: look for default artwork in old default artwork directory */
702   filename = getPath2(options.graphics_directory, basename);
703   if (fileExists(filename))
704     return filename;
705
706   return NULL;          /* cannot find specified artwork file anywhere */
707 }
708
709 static char *getCorrectedArtworkBasename(char *basename)
710 {
711   return basename;
712 }
713
714 char *getCustomImageFilename(char *basename)
715 {
716   static char *filename = NULL;
717   boolean skip_setup_artwork = FALSE;
718
719   checked_free(filename);
720
721   basename = getCorrectedArtworkBasename(basename);
722
723   if (!gfx.override_level_graphics)
724   {
725     /* 1st try: look for special artwork in current level series directory */
726     filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
727     if (fileExists(filename))
728       return filename;
729
730     free(filename);
731
732     /* check if there is special artwork configured in level series config */
733     if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
734     {
735       /* 2nd try: look for special artwork configured in level series config */
736       filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
737       if (fileExists(filename))
738         return filename;
739
740       free(filename);
741
742       /* take missing artwork configured in level set config from default */
743       skip_setup_artwork = TRUE;
744     }
745   }
746
747   if (!skip_setup_artwork)
748   {
749     /* 3rd try: look for special artwork in configured artwork directory */
750     filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
751     if (fileExists(filename))
752       return filename;
753
754     free(filename);
755   }
756
757   /* 4th try: look for default artwork in new default artwork directory */
758   filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
759   if (fileExists(filename))
760     return filename;
761
762   free(filename);
763
764   /* 5th try: look for default artwork in old default artwork directory */
765   filename = getImg2(options.graphics_directory, basename);
766   if (fileExists(filename))
767     return filename;
768
769   if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
770   {
771     free(filename);
772
773     if (options.debug)
774       Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
775             basename);
776
777     /* 6th try: look for fallback artwork in old default artwork directory */
778     /* (needed to prevent errors when trying to access unused artwork files) */
779     filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
780     if (fileExists(filename))
781       return filename;
782   }
783
784   return NULL;          /* cannot find specified artwork file anywhere */
785 }
786
787 char *getCustomSoundFilename(char *basename)
788 {
789   static char *filename = NULL;
790   boolean skip_setup_artwork = FALSE;
791
792   checked_free(filename);
793
794   basename = getCorrectedArtworkBasename(basename);
795
796   if (!gfx.override_level_sounds)
797   {
798     /* 1st try: look for special artwork in current level series directory */
799     filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
800     if (fileExists(filename))
801       return filename;
802
803     free(filename);
804
805     /* check if there is special artwork configured in level series config */
806     if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
807     {
808       /* 2nd try: look for special artwork configured in level series config */
809       filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
810       if (fileExists(filename))
811         return filename;
812
813       free(filename);
814
815       /* take missing artwork configured in level set config from default */
816       skip_setup_artwork = TRUE;
817     }
818   }
819
820   if (!skip_setup_artwork)
821   {
822     /* 3rd try: look for special artwork in configured artwork directory */
823     filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
824     if (fileExists(filename))
825       return filename;
826
827     free(filename);
828   }
829
830   /* 4th try: look for default artwork in new default artwork directory */
831   filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
832   if (fileExists(filename))
833     return filename;
834
835   free(filename);
836
837   /* 5th try: look for default artwork in old default artwork directory */
838   filename = getPath2(options.sounds_directory, basename);
839   if (fileExists(filename))
840     return filename;
841
842   if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
843   {
844     free(filename);
845
846     if (options.debug)
847       Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
848             basename);
849
850     /* 6th try: look for fallback artwork in old default artwork directory */
851     /* (needed to prevent errors when trying to access unused artwork files) */
852     filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
853     if (fileExists(filename))
854       return filename;
855   }
856
857   return NULL;          /* cannot find specified artwork file anywhere */
858 }
859
860 char *getCustomMusicFilename(char *basename)
861 {
862   static char *filename = NULL;
863   boolean skip_setup_artwork = FALSE;
864
865   checked_free(filename);
866
867   basename = getCorrectedArtworkBasename(basename);
868
869   if (!gfx.override_level_music)
870   {
871     /* 1st try: look for special artwork in current level series directory */
872     filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
873     if (fileExists(filename))
874       return filename;
875
876     free(filename);
877
878     /* check if there is special artwork configured in level series config */
879     if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
880     {
881       /* 2nd try: look for special artwork configured in level series config */
882       filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
883       if (fileExists(filename))
884         return filename;
885
886       free(filename);
887
888       /* take missing artwork configured in level set config from default */
889       skip_setup_artwork = TRUE;
890     }
891   }
892
893   if (!skip_setup_artwork)
894   {
895     /* 3rd try: look for special artwork in configured artwork directory */
896     filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
897     if (fileExists(filename))
898       return filename;
899
900     free(filename);
901   }
902
903   /* 4th try: look for default artwork in new default artwork directory */
904   filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
905   if (fileExists(filename))
906     return filename;
907
908   free(filename);
909
910   /* 5th try: look for default artwork in old default artwork directory */
911   filename = getPath2(options.music_directory, basename);
912   if (fileExists(filename))
913     return filename;
914
915   if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
916   {
917     free(filename);
918
919     if (options.debug)
920       Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
921             basename);
922
923     /* 6th try: look for fallback artwork in old default artwork directory */
924     /* (needed to prevent errors when trying to access unused artwork files) */
925     filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
926     if (fileExists(filename))
927       return filename;
928   }
929
930   return NULL;          /* cannot find specified artwork file anywhere */
931 }
932
933 char *getCustomArtworkFilename(char *basename, int type)
934 {
935   if (type == ARTWORK_TYPE_GRAPHICS)
936     return getCustomImageFilename(basename);
937   else if (type == ARTWORK_TYPE_SOUNDS)
938     return getCustomSoundFilename(basename);
939   else if (type == ARTWORK_TYPE_MUSIC)
940     return getCustomMusicFilename(basename);
941   else
942     return UNDEFINED_FILENAME;
943 }
944
945 char *getCustomArtworkConfigFilename(int type)
946 {
947   return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
948 }
949
950 char *getCustomArtworkLevelConfigFilename(int type)
951 {
952   static char *filename = NULL;
953
954   checked_free(filename);
955
956   filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
957
958   return filename;
959 }
960
961 char *getCustomMusicDirectory(void)
962 {
963   static char *directory = NULL;
964   boolean skip_setup_artwork = FALSE;
965
966   checked_free(directory);
967
968   if (!gfx.override_level_music)
969   {
970     /* 1st try: look for special artwork in current level series directory */
971     directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
972     if (directoryExists(directory))
973       return directory;
974
975     free(directory);
976
977     /* check if there is special artwork configured in level series config */
978     if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
979     {
980       /* 2nd try: look for special artwork configured in level series config */
981       directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
982       if (directoryExists(directory))
983         return directory;
984
985       free(directory);
986
987       /* take missing artwork configured in level set config from default */
988       skip_setup_artwork = TRUE;
989     }
990   }
991
992   if (!skip_setup_artwork)
993   {
994     /* 3rd try: look for special artwork in configured artwork directory */
995     directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
996     if (directoryExists(directory))
997       return directory;
998
999     free(directory);
1000   }
1001
1002   /* 4th try: look for default artwork in new default artwork directory */
1003   directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1004   if (directoryExists(directory))
1005     return directory;
1006
1007   free(directory);
1008
1009   /* 5th try: look for default artwork in old default artwork directory */
1010   directory = getStringCopy(options.music_directory);
1011   if (directoryExists(directory))
1012     return directory;
1013
1014   return NULL;          /* cannot find specified artwork file anywhere */
1015 }
1016
1017 void InitTapeDirectory(char *level_subdir)
1018 {
1019   createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1020   createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1021   createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1022 }
1023
1024 void InitScoreDirectory(char *level_subdir)
1025 {
1026   int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
1027
1028   if (program.global_scores)
1029     createDirectory(getCommonDataDir(), "common data", permissions);
1030   else
1031     createDirectory(getUserGameDataDir(), "user data", permissions);
1032
1033   createDirectory(getScoreDir(NULL), "main score", permissions);
1034   createDirectory(getScoreDir(level_subdir), "level score", permissions);
1035 }
1036
1037 static void SaveUserLevelInfo();
1038
1039 void InitUserLevelDirectory(char *level_subdir)
1040 {
1041   if (!directoryExists(getUserLevelDir(level_subdir)))
1042   {
1043     createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1044     createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1045     createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1046
1047     SaveUserLevelInfo();
1048   }
1049 }
1050
1051 void InitLevelSetupDirectory(char *level_subdir)
1052 {
1053   createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1054   createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1055   createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1056 }
1057
1058 void InitCacheDirectory()
1059 {
1060   createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1061   createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1062 }
1063
1064
1065 /* ------------------------------------------------------------------------- */
1066 /* some functions to handle lists of level and artwork directories           */
1067 /* ------------------------------------------------------------------------- */
1068
1069 TreeInfo *newTreeInfo()
1070 {
1071   return checked_calloc(sizeof(TreeInfo));
1072 }
1073
1074 TreeInfo *newTreeInfo_setDefaults(int type)
1075 {
1076   TreeInfo *ti = newTreeInfo();
1077
1078   setTreeInfoToDefaults(ti, type);
1079
1080   return ti;
1081 }
1082
1083 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1084 {
1085   node_new->next = *node_first;
1086   *node_first = node_new;
1087 }
1088
1089 int numTreeInfo(TreeInfo *node)
1090 {
1091   int num = 0;
1092
1093   while (node)
1094   {
1095     num++;
1096     node = node->next;
1097   }
1098
1099   return num;
1100 }
1101
1102 boolean validLevelSeries(TreeInfo *node)
1103 {
1104   return (node != NULL && !node->node_group && !node->parent_link);
1105 }
1106
1107 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1108 {
1109   if (node == NULL)
1110     return NULL;
1111
1112   if (node->node_group)         /* enter level group (step down into tree) */
1113     return getFirstValidTreeInfoEntry(node->node_group);
1114   else if (node->parent_link)   /* skip start entry of level group */
1115   {
1116     if (node->next)             /* get first real level series entry */
1117       return getFirstValidTreeInfoEntry(node->next);
1118     else                        /* leave empty level group and go on */
1119       return getFirstValidTreeInfoEntry(node->node_parent->next);
1120   }
1121   else                          /* this seems to be a regular level series */
1122     return node;
1123 }
1124
1125 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1126 {
1127   if (node == NULL)
1128     return NULL;
1129
1130   if (node->node_parent == NULL)                /* top level group */
1131     return *node->node_top;
1132   else                                          /* sub level group */
1133     return node->node_parent->node_group;
1134 }
1135
1136 int numTreeInfoInGroup(TreeInfo *node)
1137 {
1138   return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1139 }
1140
1141 int posTreeInfo(TreeInfo *node)
1142 {
1143   TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1144   int pos = 0;
1145
1146   while (node_cmp)
1147   {
1148     if (node_cmp == node)
1149       return pos;
1150
1151     pos++;
1152     node_cmp = node_cmp->next;
1153   }
1154
1155   return 0;
1156 }
1157
1158 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1159 {
1160   TreeInfo *node_default = node;
1161   int pos_cmp = 0;
1162
1163   while (node)
1164   {
1165     if (pos_cmp == pos)
1166       return node;
1167
1168     pos_cmp++;
1169     node = node->next;
1170   }
1171
1172   return node_default;
1173 }
1174
1175 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1176 {
1177   if (identifier == NULL)
1178     return NULL;
1179
1180   while (node)
1181   {
1182     if (node->node_group)
1183     {
1184       TreeInfo *node_group;
1185
1186       node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1187
1188       if (node_group)
1189         return node_group;
1190     }
1191     else if (!node->parent_link)
1192     {
1193       if (strEqual(identifier, node->identifier))
1194         return node;
1195     }
1196
1197     node = node->next;
1198   }
1199
1200   return NULL;
1201 }
1202
1203 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1204                         TreeInfo *node, boolean skip_sets_without_levels)
1205 {
1206   TreeInfo *node_new;
1207
1208   if (node == NULL)
1209     return NULL;
1210
1211   if (!node->parent_link && !node->level_group &&
1212       skip_sets_without_levels && node->levels == 0)
1213     return cloneTreeNode(node_top, node_parent, node->next,
1214                          skip_sets_without_levels);
1215
1216   node_new = getTreeInfoCopy(node);             /* copy complete node */
1217
1218   node_new->node_top = node_top;                /* correct top node link */
1219   node_new->node_parent = node_parent;          /* correct parent node link */
1220
1221   if (node->level_group)
1222     node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1223                                          skip_sets_without_levels);
1224
1225   node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1226                                  skip_sets_without_levels);
1227   
1228   return node_new;
1229 }
1230
1231 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1232 {
1233   TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1234
1235   *ti_new = ti_cloned;
1236 }
1237
1238 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1239 {
1240   boolean settings_changed = FALSE;
1241
1242   while (node)
1243   {
1244     if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1245         !strEqual(node->graphics_set, node->graphics_set_ecs))
1246     {
1247       setString(&node->graphics_set, node->graphics_set_ecs);
1248       settings_changed = TRUE;
1249     }
1250     else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1251              !strEqual(node->graphics_set, node->graphics_set_aga))
1252     {
1253       setString(&node->graphics_set, node->graphics_set_aga);
1254       settings_changed = TRUE;
1255     }
1256
1257     if (node->node_group != NULL)
1258       settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1259
1260     node = node->next;
1261   }
1262
1263   return settings_changed;
1264 }
1265
1266 void dumpTreeInfo(TreeInfo *node, int depth)
1267 {
1268   int i;
1269
1270   printf("Dumping TreeInfo:\n");
1271
1272   while (node)
1273   {
1274     for (i = 0; i < (depth + 1) * 3; i++)
1275       printf(" ");
1276
1277     printf("'%s' / '%s'\n", node->identifier, node->name);
1278
1279     /*
1280     // use for dumping artwork info tree
1281     printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1282            node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1283     */
1284
1285     if (node->node_group != NULL)
1286       dumpTreeInfo(node->node_group, depth + 1);
1287
1288     node = node->next;
1289   }
1290 }
1291
1292 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1293                                 int (*compare_function)(const void *,
1294                                                         const void *))
1295 {
1296   int num_nodes = numTreeInfo(*node_first);
1297   TreeInfo **sort_array;
1298   TreeInfo *node = *node_first;
1299   int i = 0;
1300
1301   if (num_nodes == 0)
1302     return;
1303
1304   /* allocate array for sorting structure pointers */
1305   sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1306
1307   /* writing structure pointers to sorting array */
1308   while (i < num_nodes && node)         /* double boundary check... */
1309   {
1310     sort_array[i] = node;
1311
1312     i++;
1313     node = node->next;
1314   }
1315
1316   /* sorting the structure pointers in the sorting array */
1317   qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1318         compare_function);
1319
1320   /* update the linkage of list elements with the sorted node array */
1321   for (i = 0; i < num_nodes - 1; i++)
1322     sort_array[i]->next = sort_array[i + 1];
1323   sort_array[num_nodes - 1]->next = NULL;
1324
1325   /* update the linkage of the main list anchor pointer */
1326   *node_first = sort_array[0];
1327
1328   free(sort_array);
1329
1330   /* now recursively sort the level group structures */
1331   node = *node_first;
1332   while (node)
1333   {
1334     if (node->node_group != NULL)
1335       sortTreeInfoBySortFunction(&node->node_group, compare_function);
1336
1337     node = node->next;
1338   }
1339 }
1340
1341 void sortTreeInfo(TreeInfo **node_first)
1342 {
1343   sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1344 }
1345
1346
1347 /* ========================================================================= */
1348 /* some stuff from "files.c"                                                 */
1349 /* ========================================================================= */
1350
1351 #if defined(PLATFORM_WIN32)
1352 #ifndef S_IRGRP
1353 #define S_IRGRP S_IRUSR
1354 #endif
1355 #ifndef S_IROTH
1356 #define S_IROTH S_IRUSR
1357 #endif
1358 #ifndef S_IWGRP
1359 #define S_IWGRP S_IWUSR
1360 #endif
1361 #ifndef S_IWOTH
1362 #define S_IWOTH S_IWUSR
1363 #endif
1364 #ifndef S_IXGRP
1365 #define S_IXGRP S_IXUSR
1366 #endif
1367 #ifndef S_IXOTH
1368 #define S_IXOTH S_IXUSR
1369 #endif
1370 #ifndef S_IRWXG
1371 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1372 #endif
1373 #ifndef S_ISGID
1374 #define S_ISGID 0
1375 #endif
1376 #endif  /* PLATFORM_WIN32 */
1377
1378 /* file permissions for newly written files */
1379 #define MODE_R_ALL              (S_IRUSR | S_IRGRP | S_IROTH)
1380 #define MODE_W_ALL              (S_IWUSR | S_IWGRP | S_IWOTH)
1381 #define MODE_X_ALL              (S_IXUSR | S_IXGRP | S_IXOTH)
1382
1383 #define MODE_W_PRIVATE          (S_IWUSR)
1384 #define MODE_W_PUBLIC_FILE      (S_IWUSR | S_IWGRP)
1385 #define MODE_W_PUBLIC_DIR       (S_IWUSR | S_IWGRP | S_ISGID)
1386
1387 #define DIR_PERMS_PRIVATE       (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1388 #define DIR_PERMS_PUBLIC        (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1389 #define DIR_PERMS_PUBLIC_ALL    (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1390
1391 #define FILE_PERMS_PRIVATE      (MODE_R_ALL | MODE_W_PRIVATE)
1392 #define FILE_PERMS_PUBLIC       (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1393 #define FILE_PERMS_PUBLIC_ALL   (MODE_R_ALL | MODE_W_ALL)
1394
1395
1396 char *getHomeDir()
1397 {
1398   static char *dir = NULL;
1399
1400 #if defined(PLATFORM_WIN32)
1401   if (dir == NULL)
1402   {
1403     dir = checked_malloc(MAX_PATH + 1);
1404
1405     if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1406       strcpy(dir, ".");
1407   }
1408 #elif defined(PLATFORM_UNIX)
1409   if (dir == NULL)
1410   {
1411     if ((dir = getenv("HOME")) == NULL)
1412     {
1413       struct passwd *pwd;
1414
1415       if ((pwd = getpwuid(getuid())) != NULL)
1416         dir = getStringCopy(pwd->pw_dir);
1417       else
1418         dir = ".";
1419     }
1420   }
1421 #else
1422   dir = ".";
1423 #endif
1424
1425   return dir;
1426 }
1427
1428 char *getCommonDataDir(void)
1429 {
1430   static char *common_data_dir = NULL;
1431
1432 #if defined(PLATFORM_WIN32)
1433   if (common_data_dir == NULL)
1434   {
1435     char *dir = checked_malloc(MAX_PATH + 1);
1436
1437     if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1438         && !strEqual(dir, ""))          /* empty for Windows 95/98 */
1439       common_data_dir = getPath2(dir, program.userdata_subdir);
1440     else
1441       common_data_dir = options.rw_base_directory;
1442   }
1443 #else
1444   if (common_data_dir == NULL)
1445     common_data_dir = options.rw_base_directory;
1446 #endif
1447
1448   return common_data_dir;
1449 }
1450
1451 char *getPersonalDataDir(void)
1452 {
1453   static char *personal_data_dir = NULL;
1454
1455 #if defined(PLATFORM_MACOSX)
1456   if (personal_data_dir == NULL)
1457     personal_data_dir = getPath2(getHomeDir(), "Documents");
1458 #else
1459   if (personal_data_dir == NULL)
1460     personal_data_dir = getHomeDir();
1461 #endif
1462
1463   return personal_data_dir;
1464 }
1465
1466 char *getUserGameDataDir(void)
1467 {
1468   static char *user_game_data_dir = NULL;
1469
1470 #if defined(PLATFORM_ANDROID)
1471   if (user_game_data_dir == NULL)
1472     user_game_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1473                                   SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1474                                   SDL_AndroidGetExternalStoragePath() :
1475                                   SDL_AndroidGetInternalStoragePath());
1476 #else
1477   if (user_game_data_dir == NULL)
1478     user_game_data_dir = getPath2(getPersonalDataDir(),
1479                                   program.userdata_subdir);
1480 #endif
1481
1482   return user_game_data_dir;
1483 }
1484
1485 char *getSetupDir()
1486 {
1487   return getUserGameDataDir();
1488 }
1489
1490 static mode_t posix_umask(mode_t mask)
1491 {
1492 #if defined(PLATFORM_UNIX)
1493   return umask(mask);
1494 #else
1495   return 0;
1496 #endif
1497 }
1498
1499 static int posix_mkdir(const char *pathname, mode_t mode)
1500 {
1501 #if defined(PLATFORM_WIN32)
1502   return mkdir(pathname);
1503 #else
1504   return mkdir(pathname, mode);
1505 #endif
1506 }
1507
1508 static boolean posix_process_running_setgid()
1509 {
1510 #if defined(PLATFORM_UNIX)
1511   return (getgid() != getegid());
1512 #else
1513   return FALSE;
1514 #endif
1515 }
1516
1517 void createDirectory(char *dir, char *text, int permission_class)
1518 {
1519   if (directoryExists(dir))
1520     return;
1521
1522   /* leave "other" permissions in umask untouched, but ensure group parts
1523      of USERDATA_DIR_MODE are not masked */
1524   mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1525                      DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1526   mode_t last_umask = posix_umask(0);
1527   mode_t group_umask = ~(dir_mode & S_IRWXG);
1528   int running_setgid = posix_process_running_setgid();
1529
1530   if (permission_class == PERMS_PUBLIC)
1531   {
1532     /* if we're setgid, protect files against "other" */
1533     /* else keep umask(0) to make the dir world-writable */
1534
1535     if (running_setgid)
1536       posix_umask(last_umask & group_umask);
1537     else
1538       dir_mode = DIR_PERMS_PUBLIC_ALL;
1539   }
1540
1541   if (posix_mkdir(dir, dir_mode) != 0)
1542     Error(ERR_WARN, "cannot create %s directory '%s': %s",
1543           text, dir, strerror(errno));
1544
1545   if (permission_class == PERMS_PUBLIC && !running_setgid)
1546     chmod(dir, dir_mode);
1547
1548   posix_umask(last_umask);              /* restore previous umask */
1549 }
1550
1551 void InitUserDataDirectory()
1552 {
1553   createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1554 }
1555
1556 void SetFilePermissions(char *filename, int permission_class)
1557 {
1558   int running_setgid = posix_process_running_setgid();
1559   int perms = (permission_class == PERMS_PRIVATE ?
1560                FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1561
1562   if (permission_class == PERMS_PUBLIC && !running_setgid)
1563     perms = FILE_PERMS_PUBLIC_ALL;
1564
1565   chmod(filename, perms);
1566 }
1567
1568 char *getCookie(char *file_type)
1569 {
1570   static char cookie[MAX_COOKIE_LEN + 1];
1571
1572   if (strlen(program.cookie_prefix) + 1 +
1573       strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1574     return "[COOKIE ERROR]";    /* should never happen */
1575
1576   sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1577           program.cookie_prefix, file_type,
1578           program.version_super, program.version_major);
1579
1580   return cookie;
1581 }
1582
1583 void fprintFileHeader(FILE *file, char *basename)
1584 {
1585   char *prefix = "# ";
1586   char *sep1 = "=";
1587
1588   fprintf_line_with_prefix(file, prefix, sep1, 77);
1589   fprintf(file, "%s%s\n", prefix, basename);
1590   fprintf_line_with_prefix(file, prefix, sep1, 77);
1591   fprintf(file, "\n");
1592 }
1593
1594 int getFileVersionFromCookieString(const char *cookie)
1595 {
1596   const char *ptr_cookie1, *ptr_cookie2;
1597   const char *pattern1 = "_FILE_VERSION_";
1598   const char *pattern2 = "?.?";
1599   const int len_cookie = strlen(cookie);
1600   const int len_pattern1 = strlen(pattern1);
1601   const int len_pattern2 = strlen(pattern2);
1602   const int len_pattern = len_pattern1 + len_pattern2;
1603   int version_super, version_major;
1604
1605   if (len_cookie <= len_pattern)
1606     return -1;
1607
1608   ptr_cookie1 = &cookie[len_cookie - len_pattern];
1609   ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1610
1611   if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1612     return -1;
1613
1614   if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1615       ptr_cookie2[1] != '.' ||
1616       ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1617     return -1;
1618
1619   version_super = ptr_cookie2[0] - '0';
1620   version_major = ptr_cookie2[2] - '0';
1621
1622   return VERSION_IDENT(version_super, version_major, 0, 0);
1623 }
1624
1625 boolean checkCookieString(const char *cookie, const char *template)
1626 {
1627   const char *pattern = "_FILE_VERSION_?.?";
1628   const int len_cookie = strlen(cookie);
1629   const int len_template = strlen(template);
1630   const int len_pattern = strlen(pattern);
1631
1632   if (len_cookie != len_template)
1633     return FALSE;
1634
1635   if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1636     return FALSE;
1637
1638   return TRUE;
1639 }
1640
1641
1642 /* ------------------------------------------------------------------------- */
1643 /* setup file list and hash handling functions                               */
1644 /* ------------------------------------------------------------------------- */
1645
1646 char *getFormattedSetupEntry(char *token, char *value)
1647 {
1648   int i;
1649   static char entry[MAX_LINE_LEN];
1650
1651   /* if value is an empty string, just return token without value */
1652   if (*value == '\0')
1653     return token;
1654
1655   /* start with the token and some spaces to format output line */
1656   sprintf(entry, "%s:", token);
1657   for (i = strlen(entry); i < token_value_position; i++)
1658     strcat(entry, " ");
1659
1660   /* continue with the token's value */
1661   strcat(entry, value);
1662
1663   return entry;
1664 }
1665
1666 SetupFileList *newSetupFileList(char *token, char *value)
1667 {
1668   SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1669
1670   new->token = getStringCopy(token);
1671   new->value = getStringCopy(value);
1672
1673   new->next = NULL;
1674
1675   return new;
1676 }
1677
1678 void freeSetupFileList(SetupFileList *list)
1679 {
1680   if (list == NULL)
1681     return;
1682
1683   checked_free(list->token);
1684   checked_free(list->value);
1685
1686   if (list->next)
1687     freeSetupFileList(list->next);
1688
1689   free(list);
1690 }
1691
1692 char *getListEntry(SetupFileList *list, char *token)
1693 {
1694   if (list == NULL)
1695     return NULL;
1696
1697   if (strEqual(list->token, token))
1698     return list->value;
1699   else
1700     return getListEntry(list->next, token);
1701 }
1702
1703 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1704 {
1705   if (list == NULL)
1706     return NULL;
1707
1708   if (strEqual(list->token, token))
1709   {
1710     checked_free(list->value);
1711
1712     list->value = getStringCopy(value);
1713
1714     return list;
1715   }
1716   else if (list->next == NULL)
1717     return (list->next = newSetupFileList(token, value));
1718   else
1719     return setListEntry(list->next, token, value);
1720 }
1721
1722 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1723 {
1724   if (list == NULL)
1725     return NULL;
1726
1727   if (list->next == NULL)
1728     return (list->next = newSetupFileList(token, value));
1729   else
1730     return addListEntry(list->next, token, value);
1731 }
1732
1733 #if ENABLE_UNUSED_CODE
1734 #ifdef DEBUG
1735 static void printSetupFileList(SetupFileList *list)
1736 {
1737   if (!list)
1738     return;
1739
1740   printf("token: '%s'\n", list->token);
1741   printf("value: '%s'\n", list->value);
1742
1743   printSetupFileList(list->next);
1744 }
1745 #endif
1746 #endif
1747
1748 #ifdef DEBUG
1749 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1750 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1751 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1752 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1753 #else
1754 #define insert_hash_entry hashtable_insert
1755 #define search_hash_entry hashtable_search
1756 #define change_hash_entry hashtable_change
1757 #define remove_hash_entry hashtable_remove
1758 #endif
1759
1760 unsigned int get_hash_from_key(void *key)
1761 {
1762   /*
1763     djb2
1764
1765     This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1766     'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1767     uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1768     it works better than many other constants, prime or not) has never been
1769     adequately explained.
1770
1771     If you just want to have a good hash function, and cannot wait, djb2
1772     is one of the best string hash functions i know. It has excellent
1773     distribution and speed on many different sets of keys and table sizes.
1774     You are not likely to do better with one of the "well known" functions
1775     such as PJW, K&R, etc.
1776
1777     Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1778   */
1779
1780   char *str = (char *)key;
1781   unsigned int hash = 5381;
1782   int c;
1783
1784   while ((c = *str++))
1785     hash = ((hash << 5) + hash) + c;    /* hash * 33 + c */
1786
1787   return hash;
1788 }
1789
1790 static int keys_are_equal(void *key1, void *key2)
1791 {
1792   return (strEqual((char *)key1, (char *)key2));
1793 }
1794
1795 SetupFileHash *newSetupFileHash()
1796 {
1797   SetupFileHash *new_hash =
1798     create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1799
1800   if (new_hash == NULL)
1801     Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1802
1803   return new_hash;
1804 }
1805
1806 void freeSetupFileHash(SetupFileHash *hash)
1807 {
1808   if (hash == NULL)
1809     return;
1810
1811   hashtable_destroy(hash, 1);   /* 1 == also free values stored in hash */
1812 }
1813
1814 char *getHashEntry(SetupFileHash *hash, char *token)
1815 {
1816   if (hash == NULL)
1817     return NULL;
1818
1819   return search_hash_entry(hash, token);
1820 }
1821
1822 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1823 {
1824   char *value_copy;
1825
1826   if (hash == NULL)
1827     return;
1828
1829   value_copy = getStringCopy(value);
1830
1831   /* change value; if it does not exist, insert it as new */
1832   if (!change_hash_entry(hash, token, value_copy))
1833     if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1834       Error(ERR_EXIT, "cannot insert into hash -- aborting");
1835 }
1836
1837 char *removeHashEntry(SetupFileHash *hash, char *token)
1838 {
1839   if (hash == NULL)
1840     return NULL;
1841
1842   return remove_hash_entry(hash, token);
1843 }
1844
1845 #if ENABLE_UNUSED_CODE
1846 #if DEBUG
1847 static void printSetupFileHash(SetupFileHash *hash)
1848 {
1849   BEGIN_HASH_ITERATION(hash, itr)
1850   {
1851     printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1852     printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1853   }
1854   END_HASH_ITERATION(hash, itr)
1855 }
1856 #endif
1857 #endif
1858
1859 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE            1
1860 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING            0
1861 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH             0
1862
1863 static boolean token_value_separator_found = FALSE;
1864 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1865 static boolean token_value_separator_warning = FALSE;
1866 #endif
1867 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1868 static boolean token_already_exists_warning = FALSE;
1869 #endif
1870
1871 static boolean getTokenValueFromSetupLineExt(char *line,
1872                                              char **token_ptr, char **value_ptr,
1873                                              char *filename, char *line_raw,
1874                                              int line_nr,
1875                                              boolean separator_required)
1876 {
1877   static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1878   char *token, *value, *line_ptr;
1879
1880   /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1881   if (line_raw == NULL)
1882   {
1883     strncpy(line_copy, line, MAX_LINE_LEN);
1884     line_copy[MAX_LINE_LEN] = '\0';
1885     line = line_copy;
1886
1887     strcpy(line_raw_copy, line_copy);
1888     line_raw = line_raw_copy;
1889   }
1890
1891   /* cut trailing comment from input line */
1892   for (line_ptr = line; *line_ptr; line_ptr++)
1893   {
1894     if (*line_ptr == '#')
1895     {
1896       *line_ptr = '\0';
1897       break;
1898     }
1899   }
1900
1901   /* cut trailing whitespaces from input line */
1902   for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1903     if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1904       *line_ptr = '\0';
1905
1906   /* ignore empty lines */
1907   if (*line == '\0')
1908     return FALSE;
1909
1910   /* cut leading whitespaces from token */
1911   for (token = line; *token; token++)
1912     if (*token != ' ' && *token != '\t')
1913       break;
1914
1915   /* start with empty value as reliable default */
1916   value = "";
1917
1918   token_value_separator_found = FALSE;
1919
1920   /* find end of token to determine start of value */
1921   for (line_ptr = token; *line_ptr; line_ptr++)
1922   {
1923     /* first look for an explicit token/value separator, like ':' or '=' */
1924     if (*line_ptr == ':' || *line_ptr == '=')
1925     {
1926       *line_ptr = '\0';                 /* terminate token string */
1927       value = line_ptr + 1;             /* set beginning of value */
1928
1929       token_value_separator_found = TRUE;
1930
1931       break;
1932     }
1933   }
1934
1935 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1936   /* fallback: if no token/value separator found, also allow whitespaces */
1937   if (!token_value_separator_found && !separator_required)
1938   {
1939     for (line_ptr = token; *line_ptr; line_ptr++)
1940     {
1941       if (*line_ptr == ' ' || *line_ptr == '\t')
1942       {
1943         *line_ptr = '\0';               /* terminate token string */
1944         value = line_ptr + 1;           /* set beginning of value */
1945
1946         token_value_separator_found = TRUE;
1947
1948         break;
1949       }
1950     }
1951
1952 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1953     if (token_value_separator_found)
1954     {
1955       if (!token_value_separator_warning)
1956       {
1957         Error(ERR_INFO_LINE, "-");
1958
1959         if (filename != NULL)
1960         {
1961           Error(ERR_WARN, "missing token/value separator(s) in config file:");
1962           Error(ERR_INFO, "- config file: '%s'", filename);
1963         }
1964         else
1965         {
1966           Error(ERR_WARN, "missing token/value separator(s):");
1967         }
1968
1969         token_value_separator_warning = TRUE;
1970       }
1971
1972       if (filename != NULL)
1973         Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1974       else
1975         Error(ERR_INFO, "- line: '%s'", line_raw);
1976     }
1977 #endif
1978   }
1979 #endif
1980
1981   /* cut trailing whitespaces from token */
1982   for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1983     if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1984       *line_ptr = '\0';
1985
1986   /* cut leading whitespaces from value */
1987   for (; *value; value++)
1988     if (*value != ' ' && *value != '\t')
1989       break;
1990
1991   *token_ptr = token;
1992   *value_ptr = value;
1993
1994   return TRUE;
1995 }
1996
1997 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1998 {
1999   /* while the internal (old) interface does not require a token/value
2000      separator (for downwards compatibility with existing files which
2001      don't use them), it is mandatory for the external (new) interface */
2002
2003   return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2004 }
2005
2006 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2007                                  boolean top_recursion_level, boolean is_hash)
2008 {
2009   static SetupFileHash *include_filename_hash = NULL;
2010   char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2011   char *token, *value, *line_ptr;
2012   void *insert_ptr = NULL;
2013   boolean read_continued_line = FALSE;
2014   File *file;
2015   int line_nr = 0, token_count = 0, include_count = 0;
2016
2017 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2018   token_value_separator_warning = FALSE;
2019 #endif
2020
2021 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2022   token_already_exists_warning = FALSE;
2023 #endif
2024
2025   if (!(file = openFile(filename, MODE_READ)))
2026   {
2027     Error(ERR_DEBUG, "cannot open configuration file '%s'", filename);
2028
2029     return FALSE;
2030   }
2031
2032   /* use "insert pointer" to store list end for constant insertion complexity */
2033   if (!is_hash)
2034     insert_ptr = setup_file_data;
2035
2036   /* on top invocation, create hash to mark included files (to prevent loops) */
2037   if (top_recursion_level)
2038     include_filename_hash = newSetupFileHash();
2039
2040   /* mark this file as already included (to prevent including it again) */
2041   setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2042
2043   while (!checkEndOfFile(file))
2044   {
2045     /* read next line of input file */
2046     if (!getStringFromFile(file, line, MAX_LINE_LEN))
2047       break;
2048
2049     /* check if line was completely read and is terminated by line break */
2050     if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2051       line_nr++;
2052
2053     /* cut trailing line break (this can be newline and/or carriage return) */
2054     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2055       if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2056         *line_ptr = '\0';
2057
2058     /* copy raw input line for later use (mainly debugging output) */
2059     strcpy(line_raw, line);
2060
2061     if (read_continued_line)
2062     {
2063       /* append new line to existing line, if there is enough space */
2064       if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2065         strcat(previous_line, line_ptr);
2066
2067       strcpy(line, previous_line);      /* copy storage buffer to line */
2068
2069       read_continued_line = FALSE;
2070     }
2071
2072     /* if the last character is '\', continue at next line */
2073     if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2074     {
2075       line[strlen(line) - 1] = '\0';    /* cut off trailing backslash */
2076       strcpy(previous_line, line);      /* copy line to storage buffer */
2077
2078       read_continued_line = TRUE;
2079
2080       continue;
2081     }
2082
2083     if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2084                                        line_raw, line_nr, FALSE))
2085       continue;
2086
2087     if (*token)
2088     {
2089       if (strEqual(token, "include"))
2090       {
2091         if (getHashEntry(include_filename_hash, value) == NULL)
2092         {
2093           char *basepath = getBasePath(filename);
2094           char *basename = getBaseName(value);
2095           char *filename_include = getPath2(basepath, basename);
2096
2097           loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2098
2099           free(basepath);
2100           free(basename);
2101           free(filename_include);
2102
2103           include_count++;
2104         }
2105         else
2106         {
2107           Error(ERR_WARN, "ignoring already processed file '%s'", value);
2108         }
2109       }
2110       else
2111       {
2112         if (is_hash)
2113         {
2114 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2115           char *old_value =
2116             getHashEntry((SetupFileHash *)setup_file_data, token);
2117
2118           if (old_value != NULL)
2119           {
2120             if (!token_already_exists_warning)
2121             {
2122               Error(ERR_INFO_LINE, "-");
2123               Error(ERR_WARN, "duplicate token(s) found in config file:");
2124               Error(ERR_INFO, "- config file: '%s'", filename);
2125
2126               token_already_exists_warning = TRUE;
2127             }
2128
2129             Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2130             Error(ERR_INFO, "  old value: '%s'", old_value);
2131             Error(ERR_INFO, "  new value: '%s'", value);
2132           }
2133 #endif
2134
2135           setHashEntry((SetupFileHash *)setup_file_data, token, value);
2136         }
2137         else
2138         {
2139           insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2140         }
2141
2142         token_count++;
2143       }
2144     }
2145   }
2146
2147   closeFile(file);
2148
2149 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2150   if (token_value_separator_warning)
2151     Error(ERR_INFO_LINE, "-");
2152 #endif
2153
2154 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2155   if (token_already_exists_warning)
2156     Error(ERR_INFO_LINE, "-");
2157 #endif
2158
2159   if (token_count == 0 && include_count == 0)
2160     Error(ERR_WARN, "configuration file '%s' is empty", filename);
2161
2162   if (top_recursion_level)
2163     freeSetupFileHash(include_filename_hash);
2164
2165   return TRUE;
2166 }
2167
2168 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2169 {
2170   FILE *file;
2171
2172   if (!(file = fopen(filename, MODE_WRITE)))
2173   {
2174     Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2175
2176     return;
2177   }
2178
2179   BEGIN_HASH_ITERATION(hash, itr)
2180   {
2181     fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2182                                                  HASH_ITERATION_VALUE(itr)));
2183   }
2184   END_HASH_ITERATION(hash, itr)
2185
2186   fclose(file);
2187 }
2188
2189 SetupFileList *loadSetupFileList(char *filename)
2190 {
2191   SetupFileList *setup_file_list = newSetupFileList("", "");
2192   SetupFileList *first_valid_list_entry;
2193
2194   if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2195   {
2196     freeSetupFileList(setup_file_list);
2197
2198     return NULL;
2199   }
2200
2201   first_valid_list_entry = setup_file_list->next;
2202
2203   /* free empty list header */
2204   setup_file_list->next = NULL;
2205   freeSetupFileList(setup_file_list);
2206
2207   return first_valid_list_entry;
2208 }
2209
2210 SetupFileHash *loadSetupFileHash(char *filename)
2211 {
2212   SetupFileHash *setup_file_hash = newSetupFileHash();
2213
2214   if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2215   {
2216     freeSetupFileHash(setup_file_hash);
2217
2218     return NULL;
2219   }
2220
2221   return setup_file_hash;
2222 }
2223
2224
2225 /* ========================================================================= */
2226 /* setup file stuff                                                          */
2227 /* ========================================================================= */
2228
2229 #define TOKEN_STR_LAST_LEVEL_SERIES             "last_level_series"
2230 #define TOKEN_STR_LAST_PLAYED_LEVEL             "last_played_level"
2231 #define TOKEN_STR_HANDICAP_LEVEL                "handicap_level"
2232
2233 /* level directory info */
2234 #define LEVELINFO_TOKEN_IDENTIFIER              0
2235 #define LEVELINFO_TOKEN_NAME                    1
2236 #define LEVELINFO_TOKEN_NAME_SORTING            2
2237 #define LEVELINFO_TOKEN_AUTHOR                  3
2238 #define LEVELINFO_TOKEN_YEAR                    4
2239 #define LEVELINFO_TOKEN_PROGRAM_TITLE           5
2240 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT       6
2241 #define LEVELINFO_TOKEN_PROGRAM_COMPANY         7
2242 #define LEVELINFO_TOKEN_IMPORTED_FROM           8
2243 #define LEVELINFO_TOKEN_IMPORTED_BY             9
2244 #define LEVELINFO_TOKEN_TESTED_BY               10
2245 #define LEVELINFO_TOKEN_LEVELS                  11
2246 #define LEVELINFO_TOKEN_FIRST_LEVEL             12
2247 #define LEVELINFO_TOKEN_SORT_PRIORITY           13
2248 #define LEVELINFO_TOKEN_LATEST_ENGINE           14
2249 #define LEVELINFO_TOKEN_LEVEL_GROUP             15
2250 #define LEVELINFO_TOKEN_READONLY                16
2251 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS        17
2252 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA        18
2253 #define LEVELINFO_TOKEN_GRAPHICS_SET            19
2254 #define LEVELINFO_TOKEN_SOUNDS_SET              20
2255 #define LEVELINFO_TOKEN_MUSIC_SET               21
2256 #define LEVELINFO_TOKEN_FILENAME                22
2257 #define LEVELINFO_TOKEN_FILETYPE                23
2258 #define LEVELINFO_TOKEN_SPECIAL_FLAGS           24
2259 #define LEVELINFO_TOKEN_HANDICAP                25
2260 #define LEVELINFO_TOKEN_SKIP_LEVELS             26
2261
2262 #define NUM_LEVELINFO_TOKENS                    27
2263
2264 static LevelDirTree ldi;
2265
2266 static struct TokenInfo levelinfo_tokens[] =
2267 {
2268   /* level directory info */
2269   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
2270   { TYPE_STRING,        &ldi.name,              "name"                  },
2271   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
2272   { TYPE_STRING,        &ldi.author,            "author"                },
2273   { TYPE_STRING,        &ldi.year,              "year"                  },
2274   { TYPE_STRING,        &ldi.program_title,     "program_title"         },
2275   { TYPE_STRING,        &ldi.program_copyright, "program_copyright"     },
2276   { TYPE_STRING,        &ldi.program_company,   "program_company"       },
2277   { TYPE_STRING,        &ldi.imported_from,     "imported_from"         },
2278   { TYPE_STRING,        &ldi.imported_by,       "imported_by"           },
2279   { TYPE_STRING,        &ldi.tested_by,         "tested_by"             },
2280   { TYPE_INTEGER,       &ldi.levels,            "levels"                },
2281   { TYPE_INTEGER,       &ldi.first_level,       "first_level"           },
2282   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
2283   { TYPE_BOOLEAN,       &ldi.latest_engine,     "latest_engine"         },
2284   { TYPE_BOOLEAN,       &ldi.level_group,       "level_group"           },
2285   { TYPE_BOOLEAN,       &ldi.readonly,          "readonly"              },
2286   { TYPE_STRING,        &ldi.graphics_set_ecs,  "graphics_set.ecs"      },
2287   { TYPE_STRING,        &ldi.graphics_set_aga,  "graphics_set.aga"      },
2288   { TYPE_STRING,        &ldi.graphics_set,      "graphics_set"          },
2289   { TYPE_STRING,        &ldi.sounds_set,        "sounds_set"            },
2290   { TYPE_STRING,        &ldi.music_set,         "music_set"             },
2291   { TYPE_STRING,        &ldi.level_filename,    "filename"              },
2292   { TYPE_STRING,        &ldi.level_filetype,    "filetype"              },
2293   { TYPE_STRING,        &ldi.special_flags,     "special_flags"         },
2294   { TYPE_BOOLEAN,       &ldi.handicap,          "handicap"              },
2295   { TYPE_BOOLEAN,       &ldi.skip_levels,       "skip_levels"           }
2296 };
2297
2298 static struct TokenInfo artworkinfo_tokens[] =
2299 {
2300   /* artwork directory info */
2301   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
2302   { TYPE_STRING,        &ldi.subdir,            "subdir"                },
2303   { TYPE_STRING,        &ldi.name,              "name"                  },
2304   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
2305   { TYPE_STRING,        &ldi.author,            "author"                },
2306   { TYPE_STRING,        &ldi.program_title,     "program_title"         },
2307   { TYPE_STRING,        &ldi.program_copyright, "program_copyright"     },
2308   { TYPE_STRING,        &ldi.program_company,   "program_company"       },
2309   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
2310   { TYPE_STRING,        &ldi.basepath,          "basepath"              },
2311   { TYPE_STRING,        &ldi.fullpath,          "fullpath"              },
2312   { TYPE_BOOLEAN,       &ldi.in_user_dir,       "in_user_dir"           },
2313   { TYPE_INTEGER,       &ldi.color,             "color"                 },
2314   { TYPE_STRING,        &ldi.class_desc,        "class_desc"            },
2315
2316   { -1,                 NULL,                   NULL                    },
2317 };
2318
2319 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2320 {
2321   ti->type = type;
2322
2323   ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR    ? &leveldir_first :
2324                   ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2325                   ti->type == TREE_TYPE_SOUNDS_DIR   ? &artwork.snd_first :
2326                   ti->type == TREE_TYPE_MUSIC_DIR    ? &artwork.mus_first :
2327                   NULL);
2328
2329   ti->node_parent = NULL;
2330   ti->node_group = NULL;
2331   ti->next = NULL;
2332
2333   ti->cl_first = -1;
2334   ti->cl_cursor = -1;
2335
2336   ti->subdir = NULL;
2337   ti->fullpath = NULL;
2338   ti->basepath = NULL;
2339   ti->identifier = NULL;
2340   ti->name = getStringCopy(ANONYMOUS_NAME);
2341   ti->name_sorting = NULL;
2342   ti->author = getStringCopy(ANONYMOUS_NAME);
2343   ti->year = NULL;
2344
2345   ti->program_title = NULL;
2346   ti->program_copyright = NULL;
2347   ti->program_company = NULL;
2348
2349   ti->sort_priority = LEVELCLASS_UNDEFINED;     /* default: least priority */
2350   ti->latest_engine = FALSE;                    /* default: get from level */
2351   ti->parent_link = FALSE;
2352   ti->in_user_dir = FALSE;
2353   ti->user_defined = FALSE;
2354   ti->color = 0;
2355   ti->class_desc = NULL;
2356
2357   ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2358
2359   if (ti->type == TREE_TYPE_LEVEL_DIR)
2360   {
2361     ti->imported_from = NULL;
2362     ti->imported_by = NULL;
2363     ti->tested_by = NULL;
2364
2365     ti->graphics_set_ecs = NULL;
2366     ti->graphics_set_aga = NULL;
2367     ti->graphics_set = NULL;
2368     ti->sounds_set = NULL;
2369     ti->music_set = NULL;
2370     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2371     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2372     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2373
2374     ti->level_filename = NULL;
2375     ti->level_filetype = NULL;
2376
2377     ti->special_flags = NULL;
2378
2379     ti->levels = 0;
2380     ti->first_level = 0;
2381     ti->last_level = 0;
2382     ti->level_group = FALSE;
2383     ti->handicap_level = 0;
2384     ti->readonly = TRUE;
2385     ti->handicap = TRUE;
2386     ti->skip_levels = FALSE;
2387   }
2388 }
2389
2390 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2391 {
2392   if (parent == NULL)
2393   {
2394     Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2395
2396     setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2397
2398     return;
2399   }
2400
2401   /* copy all values from the parent structure */
2402
2403   ti->type = parent->type;
2404
2405   ti->node_top = parent->node_top;
2406   ti->node_parent = parent;
2407   ti->node_group = NULL;
2408   ti->next = NULL;
2409
2410   ti->cl_first = -1;
2411   ti->cl_cursor = -1;
2412
2413   ti->subdir = NULL;
2414   ti->fullpath = NULL;
2415   ti->basepath = NULL;
2416   ti->identifier = NULL;
2417   ti->name = getStringCopy(ANONYMOUS_NAME);
2418   ti->name_sorting = NULL;
2419   ti->author = getStringCopy(parent->author);
2420   ti->year = getStringCopy(parent->year);
2421
2422   ti->program_title = getStringCopy(parent->program_title);
2423   ti->program_copyright = getStringCopy(parent->program_copyright);
2424   ti->program_company = getStringCopy(parent->program_company);
2425
2426   ti->sort_priority = parent->sort_priority;
2427   ti->latest_engine = parent->latest_engine;
2428   ti->parent_link = FALSE;
2429   ti->in_user_dir = parent->in_user_dir;
2430   ti->user_defined = parent->user_defined;
2431   ti->color = parent->color;
2432   ti->class_desc = getStringCopy(parent->class_desc);
2433
2434   ti->infotext = getStringCopy(parent->infotext);
2435
2436   if (ti->type == TREE_TYPE_LEVEL_DIR)
2437   {
2438     ti->imported_from = getStringCopy(parent->imported_from);
2439     ti->imported_by = getStringCopy(parent->imported_by);
2440     ti->tested_by = getStringCopy(parent->tested_by);
2441
2442     ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2443     ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2444     ti->graphics_set = getStringCopy(parent->graphics_set);
2445     ti->sounds_set = getStringCopy(parent->sounds_set);
2446     ti->music_set = getStringCopy(parent->music_set);
2447     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2448     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2449     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2450
2451     ti->level_filename = getStringCopy(parent->level_filename);
2452     ti->level_filetype = getStringCopy(parent->level_filetype);
2453
2454     ti->special_flags = getStringCopy(parent->special_flags);
2455
2456     ti->levels = parent->levels;
2457     ti->first_level = parent->first_level;
2458     ti->last_level = parent->last_level;
2459     ti->level_group = FALSE;
2460     ti->handicap_level = parent->handicap_level;
2461     ti->readonly = parent->readonly;
2462     ti->handicap = parent->handicap;
2463     ti->skip_levels = parent->skip_levels;
2464   }
2465 }
2466
2467 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2468 {
2469   TreeInfo *ti_copy = newTreeInfo();
2470
2471   /* copy all values from the original structure */
2472
2473   ti_copy->type                 = ti->type;
2474
2475   ti_copy->node_top             = ti->node_top;
2476   ti_copy->node_parent          = ti->node_parent;
2477   ti_copy->node_group           = ti->node_group;
2478   ti_copy->next                 = ti->next;
2479
2480   ti_copy->cl_first             = ti->cl_first;
2481   ti_copy->cl_cursor            = ti->cl_cursor;
2482
2483   ti_copy->subdir               = getStringCopy(ti->subdir);
2484   ti_copy->fullpath             = getStringCopy(ti->fullpath);
2485   ti_copy->basepath             = getStringCopy(ti->basepath);
2486   ti_copy->identifier           = getStringCopy(ti->identifier);
2487   ti_copy->name                 = getStringCopy(ti->name);
2488   ti_copy->name_sorting         = getStringCopy(ti->name_sorting);
2489   ti_copy->author               = getStringCopy(ti->author);
2490   ti_copy->year                 = getStringCopy(ti->year);
2491
2492   ti_copy->program_title        = getStringCopy(ti->program_title);
2493   ti_copy->program_copyright    = getStringCopy(ti->program_copyright);
2494   ti_copy->program_company      = getStringCopy(ti->program_company);
2495
2496   ti_copy->imported_from        = getStringCopy(ti->imported_from);
2497   ti_copy->imported_by          = getStringCopy(ti->imported_by);
2498   ti_copy->tested_by            = getStringCopy(ti->tested_by);
2499
2500   ti_copy->graphics_set_ecs     = getStringCopy(ti->graphics_set_ecs);
2501   ti_copy->graphics_set_aga     = getStringCopy(ti->graphics_set_aga);
2502   ti_copy->graphics_set         = getStringCopy(ti->graphics_set);
2503   ti_copy->sounds_set           = getStringCopy(ti->sounds_set);
2504   ti_copy->music_set            = getStringCopy(ti->music_set);
2505   ti_copy->graphics_path        = getStringCopy(ti->graphics_path);
2506   ti_copy->sounds_path          = getStringCopy(ti->sounds_path);
2507   ti_copy->music_path           = getStringCopy(ti->music_path);
2508
2509   ti_copy->level_filename       = getStringCopy(ti->level_filename);
2510   ti_copy->level_filetype       = getStringCopy(ti->level_filetype);
2511
2512   ti_copy->special_flags        = getStringCopy(ti->special_flags);
2513
2514   ti_copy->levels               = ti->levels;
2515   ti_copy->first_level          = ti->first_level;
2516   ti_copy->last_level           = ti->last_level;
2517   ti_copy->sort_priority        = ti->sort_priority;
2518
2519   ti_copy->latest_engine        = ti->latest_engine;
2520
2521   ti_copy->level_group          = ti->level_group;
2522   ti_copy->parent_link          = ti->parent_link;
2523   ti_copy->in_user_dir          = ti->in_user_dir;
2524   ti_copy->user_defined         = ti->user_defined;
2525   ti_copy->readonly             = ti->readonly;
2526   ti_copy->handicap             = ti->handicap;
2527   ti_copy->skip_levels          = ti->skip_levels;
2528
2529   ti_copy->color                = ti->color;
2530   ti_copy->class_desc           = getStringCopy(ti->class_desc);
2531   ti_copy->handicap_level       = ti->handicap_level;
2532
2533   ti_copy->infotext             = getStringCopy(ti->infotext);
2534
2535   return ti_copy;
2536 }
2537
2538 void freeTreeInfo(TreeInfo *ti)
2539 {
2540   if (ti == NULL)
2541     return;
2542
2543   checked_free(ti->subdir);
2544   checked_free(ti->fullpath);
2545   checked_free(ti->basepath);
2546   checked_free(ti->identifier);
2547
2548   checked_free(ti->name);
2549   checked_free(ti->name_sorting);
2550   checked_free(ti->author);
2551   checked_free(ti->year);
2552
2553   checked_free(ti->program_title);
2554   checked_free(ti->program_copyright);
2555   checked_free(ti->program_company);
2556
2557   checked_free(ti->class_desc);
2558
2559   checked_free(ti->infotext);
2560
2561   if (ti->type == TREE_TYPE_LEVEL_DIR)
2562   {
2563     checked_free(ti->imported_from);
2564     checked_free(ti->imported_by);
2565     checked_free(ti->tested_by);
2566
2567     checked_free(ti->graphics_set_ecs);
2568     checked_free(ti->graphics_set_aga);
2569     checked_free(ti->graphics_set);
2570     checked_free(ti->sounds_set);
2571     checked_free(ti->music_set);
2572
2573     checked_free(ti->graphics_path);
2574     checked_free(ti->sounds_path);
2575     checked_free(ti->music_path);
2576
2577     checked_free(ti->level_filename);
2578     checked_free(ti->level_filetype);
2579
2580     checked_free(ti->special_flags);
2581   }
2582
2583   // recursively free child node
2584   if (ti->node_group)
2585     freeTreeInfo(ti->node_group);
2586
2587   // recursively free next node
2588   if (ti->next)
2589     freeTreeInfo(ti->next);
2590
2591   checked_free(ti);
2592 }
2593
2594 void setSetupInfo(struct TokenInfo *token_info,
2595                   int token_nr, char *token_value)
2596 {
2597   int token_type = token_info[token_nr].type;
2598   void *setup_value = token_info[token_nr].value;
2599
2600   if (token_value == NULL)
2601     return;
2602
2603   /* set setup field to corresponding token value */
2604   switch (token_type)
2605   {
2606     case TYPE_BOOLEAN:
2607     case TYPE_SWITCH:
2608       *(boolean *)setup_value = get_boolean_from_string(token_value);
2609       break;
2610
2611     case TYPE_SWITCH3:
2612       *(int *)setup_value = get_switch3_from_string(token_value);
2613       break;
2614
2615     case TYPE_KEY:
2616       *(Key *)setup_value = getKeyFromKeyName(token_value);
2617       break;
2618
2619     case TYPE_KEY_X11:
2620       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2621       break;
2622
2623     case TYPE_INTEGER:
2624       *(int *)setup_value = get_integer_from_string(token_value);
2625       break;
2626
2627     case TYPE_STRING:
2628       checked_free(*(char **)setup_value);
2629       *(char **)setup_value = getStringCopy(token_value);
2630       break;
2631
2632     default:
2633       break;
2634   }
2635 }
2636
2637 static int compareTreeInfoEntries(const void *object1, const void *object2)
2638 {
2639   const TreeInfo *entry1 = *((TreeInfo **)object1);
2640   const TreeInfo *entry2 = *((TreeInfo **)object2);
2641   int class_sorting1 = 0, class_sorting2 = 0;
2642   int compare_result;
2643
2644   if (entry1->type == TREE_TYPE_LEVEL_DIR)
2645   {
2646     class_sorting1 = LEVELSORTING(entry1);
2647     class_sorting2 = LEVELSORTING(entry2);
2648   }
2649   else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2650            entry1->type == TREE_TYPE_SOUNDS_DIR ||
2651            entry1->type == TREE_TYPE_MUSIC_DIR)
2652   {
2653     class_sorting1 = ARTWORKSORTING(entry1);
2654     class_sorting2 = ARTWORKSORTING(entry2);
2655   }
2656
2657   if (entry1->parent_link || entry2->parent_link)
2658     compare_result = (entry1->parent_link ? -1 : +1);
2659   else if (entry1->sort_priority == entry2->sort_priority)
2660   {
2661     char *name1 = getStringToLower(entry1->name_sorting);
2662     char *name2 = getStringToLower(entry2->name_sorting);
2663
2664     compare_result = strcmp(name1, name2);
2665
2666     free(name1);
2667     free(name2);
2668   }
2669   else if (class_sorting1 == class_sorting2)
2670     compare_result = entry1->sort_priority - entry2->sort_priority;
2671   else
2672     compare_result = class_sorting1 - class_sorting2;
2673
2674   return compare_result;
2675 }
2676
2677 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2678 {
2679   TreeInfo *ti_new;
2680
2681   if (node_parent == NULL)
2682     return NULL;
2683
2684   ti_new = newTreeInfo();
2685   setTreeInfoToDefaults(ti_new, node_parent->type);
2686
2687   ti_new->node_parent = node_parent;
2688   ti_new->parent_link = TRUE;
2689
2690   setString(&ti_new->identifier, node_parent->identifier);
2691   setString(&ti_new->name, ".. (parent directory)");
2692   setString(&ti_new->name_sorting, ti_new->name);
2693
2694   setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2695   setString(&ti_new->fullpath, node_parent->fullpath);
2696
2697   ti_new->sort_priority = node_parent->sort_priority;
2698   ti_new->latest_engine = node_parent->latest_engine;
2699
2700   setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2701
2702   pushTreeInfo(&node_parent->node_group, ti_new);
2703
2704   return ti_new;
2705 }
2706
2707 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2708 {
2709   TreeInfo *ti_new, *ti_new2;
2710
2711   if (node_first == NULL)
2712     return NULL;
2713
2714   ti_new = newTreeInfo();
2715   setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2716
2717   ti_new->node_parent = NULL;
2718   ti_new->parent_link = FALSE;
2719
2720   setString(&ti_new->identifier, node_first->identifier);
2721   setString(&ti_new->name, "level sets");
2722   setString(&ti_new->name_sorting, ti_new->name);
2723
2724   setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2725   setString(&ti_new->fullpath, ".");
2726
2727   ti_new->sort_priority = node_first->sort_priority;;
2728   ti_new->latest_engine = node_first->latest_engine;
2729
2730   setString(&ti_new->class_desc, "level sets");
2731
2732   ti_new->node_group = node_first;
2733   ti_new->level_group = TRUE;
2734
2735   ti_new2 = createParentTreeInfoNode(ti_new);
2736
2737   setString(&ti_new2->name, ".. (main menu)");
2738   setString(&ti_new2->name_sorting, ti_new2->name);
2739
2740   return ti_new;
2741 }
2742
2743
2744 /* -------------------------------------------------------------------------- */
2745 /* functions for handling level and custom artwork info cache                 */
2746 /* -------------------------------------------------------------------------- */
2747
2748 static void LoadArtworkInfoCache()
2749 {
2750   InitCacheDirectory();
2751
2752   if (artworkinfo_cache_old == NULL)
2753   {
2754     char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2755
2756     /* try to load artwork info hash from already existing cache file */
2757     artworkinfo_cache_old = loadSetupFileHash(filename);
2758
2759     /* if no artwork info cache file was found, start with empty hash */
2760     if (artworkinfo_cache_old == NULL)
2761       artworkinfo_cache_old = newSetupFileHash();
2762
2763     free(filename);
2764   }
2765
2766   if (artworkinfo_cache_new == NULL)
2767     artworkinfo_cache_new = newSetupFileHash();
2768 }
2769
2770 static void SaveArtworkInfoCache()
2771 {
2772   char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2773
2774   InitCacheDirectory();
2775
2776   saveSetupFileHash(artworkinfo_cache_new, filename);
2777
2778   free(filename);
2779 }
2780
2781 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2782 {
2783   static char *prefix = NULL;
2784
2785   checked_free(prefix);
2786
2787   prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2788
2789   return prefix;
2790 }
2791
2792 /* (identical to above function, but separate string buffer needed -- nasty) */
2793 static char *getCacheToken(char *prefix, char *suffix)
2794 {
2795   static char *token = NULL;
2796
2797   checked_free(token);
2798
2799   token = getStringCat2WithSeparator(prefix, suffix, ".");
2800
2801   return token;
2802 }
2803
2804 static char *getFileTimestampString(char *filename)
2805 {
2806   return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2807 }
2808
2809 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2810 {
2811   struct stat file_status;
2812
2813   if (timestamp_string == NULL)
2814     return TRUE;
2815
2816   if (stat(filename, &file_status) != 0)        /* cannot stat file */
2817     return TRUE;
2818
2819   return (file_status.st_mtime != atoi(timestamp_string));
2820 }
2821
2822 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2823 {
2824   char *identifier = level_node->subdir;
2825   char *type_string = ARTWORK_DIRECTORY(type);
2826   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2827   char *token_main = getCacheToken(token_prefix, "CACHED");
2828   char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2829   boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2830   TreeInfo *artwork_info = NULL;
2831
2832   if (!use_artworkinfo_cache)
2833     return NULL;
2834
2835   if (cached)
2836   {
2837     int i;
2838
2839     artwork_info = newTreeInfo();
2840     setTreeInfoToDefaults(artwork_info, type);
2841
2842     /* set all structure fields according to the token/value pairs */
2843     ldi = *artwork_info;
2844     for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2845     {
2846       char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2847       char *value = getHashEntry(artworkinfo_cache_old, token);
2848
2849       /* if defined, use value from cache, else keep default value */
2850       if (value != NULL)
2851         setSetupInfo(artworkinfo_tokens, i, value);
2852     }
2853
2854     *artwork_info = ldi;
2855
2856     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2857                                         LEVELINFO_FILENAME);
2858     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2859                                           ARTWORKINFO_FILENAME(type));
2860
2861     /* check if corresponding "levelinfo.conf" file has changed */
2862     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2863     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2864
2865     if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2866       cached = FALSE;
2867
2868     /* check if corresponding "<artworkinfo>.conf" file has changed */
2869     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2870     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2871
2872     if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2873       cached = FALSE;
2874
2875     checked_free(filename_levelinfo);
2876     checked_free(filename_artworkinfo);
2877   }
2878
2879   if (!cached && artwork_info != NULL)
2880   {
2881     freeTreeInfo(artwork_info);
2882
2883     return NULL;
2884   }
2885
2886   return artwork_info;
2887 }
2888
2889 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2890                                      LevelDirTree *level_node, int type)
2891 {
2892   char *identifier = level_node->subdir;
2893   char *type_string = ARTWORK_DIRECTORY(type);
2894   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2895   char *token_main = getCacheToken(token_prefix, "CACHED");
2896   boolean set_cache_timestamps = TRUE;
2897   int i;
2898
2899   setHashEntry(artworkinfo_cache_new, token_main, "true");
2900
2901   if (set_cache_timestamps)
2902   {
2903     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2904                                         LEVELINFO_FILENAME);
2905     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2906                                           ARTWORKINFO_FILENAME(type));
2907     char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
2908     char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
2909
2910     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2911     setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2912
2913     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2914     setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2915
2916     checked_free(filename_levelinfo);
2917     checked_free(filename_artworkinfo);
2918     checked_free(timestamp_levelinfo);
2919     checked_free(timestamp_artworkinfo);
2920   }
2921
2922   ldi = *artwork_info;
2923   for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2924   {
2925     char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2926     char *value = getSetupValue(artworkinfo_tokens[i].type,
2927                                 artworkinfo_tokens[i].value);
2928     if (value != NULL)
2929       setHashEntry(artworkinfo_cache_new, token, value);
2930   }
2931 }
2932
2933
2934 /* -------------------------------------------------------------------------- */
2935 /* functions for loading level info and custom artwork info                   */
2936 /* -------------------------------------------------------------------------- */
2937
2938 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2939 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2940
2941 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2942                                           TreeInfo *node_parent,
2943                                           char *level_directory,
2944                                           char *directory_name)
2945 {
2946   char *directory_path = getPath2(level_directory, directory_name);
2947   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2948   SetupFileHash *setup_file_hash;
2949   LevelDirTree *leveldir_new = NULL;
2950   int i;
2951
2952   /* unless debugging, silently ignore directories without "levelinfo.conf" */
2953   if (!options.debug && !fileExists(filename))
2954   {
2955     free(directory_path);
2956     free(filename);
2957
2958     return FALSE;
2959   }
2960
2961   setup_file_hash = loadSetupFileHash(filename);
2962
2963   if (setup_file_hash == NULL)
2964   {
2965     Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2966
2967     free(directory_path);
2968     free(filename);
2969
2970     return FALSE;
2971   }
2972
2973   leveldir_new = newTreeInfo();
2974
2975   if (node_parent)
2976     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2977   else
2978     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2979
2980   leveldir_new->subdir = getStringCopy(directory_name);
2981
2982   /* set all structure fields according to the token/value pairs */
2983   ldi = *leveldir_new;
2984   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2985     setSetupInfo(levelinfo_tokens, i,
2986                  getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2987   *leveldir_new = ldi;
2988
2989   if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2990     setString(&leveldir_new->name, leveldir_new->subdir);
2991
2992   if (leveldir_new->identifier == NULL)
2993     leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2994
2995   if (leveldir_new->name_sorting == NULL)
2996     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2997
2998   if (node_parent == NULL)              /* top level group */
2999   {
3000     leveldir_new->basepath = getStringCopy(level_directory);
3001     leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3002   }
3003   else                                  /* sub level group */
3004   {
3005     leveldir_new->basepath = getStringCopy(node_parent->basepath);
3006     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3007   }
3008
3009   leveldir_new->last_level =
3010     leveldir_new->first_level + leveldir_new->levels - 1;
3011
3012   leveldir_new->in_user_dir =
3013     (!strEqual(leveldir_new->basepath, options.level_directory));
3014
3015   /* adjust some settings if user's private level directory was detected */
3016   if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3017       leveldir_new->in_user_dir &&
3018       (strEqual(leveldir_new->subdir, getLoginName()) ||
3019        strEqual(leveldir_new->name,   getLoginName()) ||
3020        strEqual(leveldir_new->author, getRealName())))
3021   {
3022     leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3023     leveldir_new->readonly = FALSE;
3024   }
3025
3026   leveldir_new->user_defined =
3027     (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3028
3029   leveldir_new->color = LEVELCOLOR(leveldir_new);
3030
3031   setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3032
3033   leveldir_new->handicap_level =        /* set handicap to default value */
3034     (leveldir_new->user_defined || !leveldir_new->handicap ?
3035      leveldir_new->last_level : leveldir_new->first_level);
3036
3037   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3038
3039   pushTreeInfo(node_first, leveldir_new);
3040
3041   freeSetupFileHash(setup_file_hash);
3042
3043   if (leveldir_new->level_group)
3044   {
3045     /* create node to link back to current level directory */
3046     createParentTreeInfoNode(leveldir_new);
3047
3048     /* recursively step into sub-directory and look for more level series */
3049     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3050                               leveldir_new, directory_path);
3051   }
3052
3053   free(directory_path);
3054   free(filename);
3055
3056   return TRUE;
3057 }
3058
3059 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3060                                       TreeInfo *node_parent,
3061                                       char *level_directory)
3062 {
3063   Directory *dir;
3064   DirectoryEntry *dir_entry;
3065   boolean valid_entry_found = FALSE;
3066
3067   if ((dir = openDirectory(level_directory)) == NULL)
3068   {
3069     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3070
3071     return;
3072   }
3073
3074   while ((dir_entry = readDirectory(dir)) != NULL)      /* loop all entries */
3075   {
3076     char *directory_name = dir_entry->basename;
3077     char *directory_path = getPath2(level_directory, directory_name);
3078
3079     /* skip entries for current and parent directory */
3080     if (strEqual(directory_name, ".") ||
3081         strEqual(directory_name, ".."))
3082     {
3083       free(directory_path);
3084
3085       continue;
3086     }
3087
3088     /* find out if directory entry is itself a directory */
3089     if (!dir_entry->is_directory)                       /* not a directory */
3090     {
3091       free(directory_path);
3092
3093       continue;
3094     }
3095
3096     free(directory_path);
3097
3098     if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3099         strEqual(directory_name, SOUNDS_DIRECTORY) ||
3100         strEqual(directory_name, MUSIC_DIRECTORY))
3101       continue;
3102
3103     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3104                                                     level_directory,
3105                                                     directory_name);
3106   }
3107
3108   closeDirectory(dir);
3109
3110   /* special case: top level directory may directly contain "levelinfo.conf" */
3111   if (node_parent == NULL && !valid_entry_found)
3112   {
3113     /* check if this directory directly contains a file "levelinfo.conf" */
3114     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3115                                                     level_directory, ".");
3116   }
3117
3118   if (!valid_entry_found)
3119     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3120           level_directory);
3121 }
3122
3123 boolean AdjustGraphicsForEMC()
3124 {
3125   boolean settings_changed = FALSE;
3126
3127   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3128   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3129
3130   return settings_changed;
3131 }
3132
3133 void LoadLevelInfo()
3134 {
3135   InitUserLevelDirectory(getLoginName());
3136
3137   DrawInitText("Loading level series", 120, FC_GREEN);
3138
3139   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3140   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3141
3142   leveldir_first = createTopTreeInfoNode(leveldir_first);
3143
3144   /* after loading all level set information, clone the level directory tree
3145      and remove all level sets without levels (these may still contain artwork
3146      to be offered in the setup menu as "custom artwork", and are therefore
3147      checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3148   leveldir_first_all = leveldir_first;
3149   cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3150
3151   AdjustGraphicsForEMC();
3152
3153   /* before sorting, the first entries will be from the user directory */
3154   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3155
3156   if (leveldir_first == NULL)
3157     Error(ERR_EXIT, "cannot find any valid level series in any directory");
3158
3159   sortTreeInfo(&leveldir_first);
3160
3161 #if ENABLE_UNUSED_CODE
3162   dumpTreeInfo(leveldir_first, 0);
3163 #endif
3164 }
3165
3166 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3167                                               TreeInfo *node_parent,
3168                                               char *base_directory,
3169                                               char *directory_name, int type)
3170 {
3171   char *directory_path = getPath2(base_directory, directory_name);
3172   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3173   SetupFileHash *setup_file_hash = NULL;
3174   TreeInfo *artwork_new = NULL;
3175   int i;
3176
3177   if (fileExists(filename))
3178     setup_file_hash = loadSetupFileHash(filename);
3179
3180   if (setup_file_hash == NULL)  /* no config file -- look for artwork files */
3181   {
3182     Directory *dir;
3183     DirectoryEntry *dir_entry;
3184     boolean valid_file_found = FALSE;
3185
3186     if ((dir = openDirectory(directory_path)) != NULL)
3187     {
3188       while ((dir_entry = readDirectory(dir)) != NULL)
3189       {
3190         if (FileIsArtworkType(dir_entry->filename, type))
3191         {
3192           valid_file_found = TRUE;
3193
3194           break;
3195         }
3196       }
3197
3198       closeDirectory(dir);
3199     }
3200
3201     if (!valid_file_found)
3202     {
3203       if (!strEqual(directory_name, "."))
3204         Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3205
3206       free(directory_path);
3207       free(filename);
3208
3209       return FALSE;
3210     }
3211   }
3212
3213   artwork_new = newTreeInfo();
3214
3215   if (node_parent)
3216     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3217   else
3218     setTreeInfoToDefaults(artwork_new, type);
3219
3220   artwork_new->subdir = getStringCopy(directory_name);
3221
3222   if (setup_file_hash)  /* (before defining ".color" and ".class_desc") */
3223   {
3224     /* set all structure fields according to the token/value pairs */
3225     ldi = *artwork_new;
3226     for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3227       setSetupInfo(levelinfo_tokens, i,
3228                    getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3229     *artwork_new = ldi;
3230
3231     if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3232       setString(&artwork_new->name, artwork_new->subdir);
3233
3234     if (artwork_new->identifier == NULL)
3235       artwork_new->identifier = getStringCopy(artwork_new->subdir);
3236
3237     if (artwork_new->name_sorting == NULL)
3238       artwork_new->name_sorting = getStringCopy(artwork_new->name);
3239   }
3240
3241   if (node_parent == NULL)              /* top level group */
3242   {
3243     artwork_new->basepath = getStringCopy(base_directory);
3244     artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3245   }
3246   else                                  /* sub level group */
3247   {
3248     artwork_new->basepath = getStringCopy(node_parent->basepath);
3249     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3250   }
3251
3252   artwork_new->in_user_dir =
3253     (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3254
3255   /* (may use ".sort_priority" from "setup_file_hash" above) */
3256   artwork_new->color = ARTWORKCOLOR(artwork_new);
3257
3258   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3259
3260   if (setup_file_hash == NULL)  /* (after determining ".user_defined") */
3261   {
3262     if (strEqual(artwork_new->subdir, "."))
3263     {
3264       if (artwork_new->user_defined)
3265       {
3266         setString(&artwork_new->identifier, "private");
3267         artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3268       }
3269       else
3270       {
3271         setString(&artwork_new->identifier, "classic");
3272         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3273       }
3274
3275       /* set to new values after changing ".sort_priority" */
3276       artwork_new->color = ARTWORKCOLOR(artwork_new);
3277
3278       setString(&artwork_new->class_desc,
3279                 getLevelClassDescription(artwork_new));
3280     }
3281     else
3282     {
3283       setString(&artwork_new->identifier, artwork_new->subdir);
3284     }
3285
3286     setString(&artwork_new->name, artwork_new->identifier);
3287     setString(&artwork_new->name_sorting, artwork_new->name);
3288   }
3289
3290   pushTreeInfo(node_first, artwork_new);
3291
3292   freeSetupFileHash(setup_file_hash);
3293
3294   free(directory_path);
3295   free(filename);
3296
3297   return TRUE;
3298 }
3299
3300 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3301                                           TreeInfo *node_parent,
3302                                           char *base_directory, int type)
3303 {
3304   Directory *dir;
3305   DirectoryEntry *dir_entry;
3306   boolean valid_entry_found = FALSE;
3307
3308   if ((dir = openDirectory(base_directory)) == NULL)
3309   {
3310     /* display error if directory is main "options.graphics_directory" etc. */
3311     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3312       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3313
3314     return;
3315   }
3316
3317   while ((dir_entry = readDirectory(dir)) != NULL)      /* loop all entries */
3318   {
3319     char *directory_name = dir_entry->basename;
3320     char *directory_path = getPath2(base_directory, directory_name);
3321
3322     /* skip directory entries for current and parent directory */
3323     if (strEqual(directory_name, ".") ||
3324         strEqual(directory_name, ".."))
3325     {
3326       free(directory_path);
3327
3328       continue;
3329     }
3330
3331     /* skip directory entries which are not a directory */
3332     if (!dir_entry->is_directory)                       /* not a directory */
3333     {
3334       free(directory_path);
3335
3336       continue;
3337     }
3338
3339     free(directory_path);
3340
3341     /* check if this directory contains artwork with or without config file */
3342     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3343                                                         base_directory,
3344                                                         directory_name, type);
3345   }
3346
3347   closeDirectory(dir);
3348
3349   /* check if this directory directly contains artwork itself */
3350   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3351                                                       base_directory, ".",
3352                                                       type);
3353   if (!valid_entry_found)
3354     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3355           base_directory);
3356 }
3357
3358 static TreeInfo *getDummyArtworkInfo(int type)
3359 {
3360   /* this is only needed when there is completely no artwork available */
3361   TreeInfo *artwork_new = newTreeInfo();
3362
3363   setTreeInfoToDefaults(artwork_new, type);
3364
3365   setString(&artwork_new->subdir,   UNDEFINED_FILENAME);
3366   setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3367   setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3368
3369   setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
3370   setString(&artwork_new->name,         UNDEFINED_FILENAME);
3371   setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3372
3373   return artwork_new;
3374 }
3375
3376 void LoadArtworkInfo()
3377 {
3378   LoadArtworkInfoCache();
3379
3380   DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3381
3382   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3383                                 options.graphics_directory,
3384                                 TREE_TYPE_GRAPHICS_DIR);
3385   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3386                                 getUserGraphicsDir(),
3387                                 TREE_TYPE_GRAPHICS_DIR);
3388
3389   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3390                                 options.sounds_directory,
3391                                 TREE_TYPE_SOUNDS_DIR);
3392   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3393                                 getUserSoundsDir(),
3394                                 TREE_TYPE_SOUNDS_DIR);
3395
3396   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3397                                 options.music_directory,
3398                                 TREE_TYPE_MUSIC_DIR);
3399   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3400                                 getUserMusicDir(),
3401                                 TREE_TYPE_MUSIC_DIR);
3402
3403   if (artwork.gfx_first == NULL)
3404     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3405   if (artwork.snd_first == NULL)
3406     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3407   if (artwork.mus_first == NULL)
3408     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3409
3410   /* before sorting, the first entries will be from the user directory */
3411   artwork.gfx_current =
3412     getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3413   if (artwork.gfx_current == NULL)
3414     artwork.gfx_current =
3415       getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3416   if (artwork.gfx_current == NULL)
3417     artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3418
3419   artwork.snd_current =
3420     getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3421   if (artwork.snd_current == NULL)
3422     artwork.snd_current =
3423       getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3424   if (artwork.snd_current == NULL)
3425     artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3426
3427   artwork.mus_current =
3428     getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3429   if (artwork.mus_current == NULL)
3430     artwork.mus_current =
3431       getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3432   if (artwork.mus_current == NULL)
3433     artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3434
3435   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3436   artwork.snd_current_identifier = artwork.snd_current->identifier;
3437   artwork.mus_current_identifier = artwork.mus_current->identifier;
3438
3439 #if ENABLE_UNUSED_CODE
3440   printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3441   printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3442   printf("music set == %s\n\n", artwork.mus_current_identifier);
3443 #endif
3444
3445   sortTreeInfo(&artwork.gfx_first);
3446   sortTreeInfo(&artwork.snd_first);
3447   sortTreeInfo(&artwork.mus_first);
3448
3449 #if ENABLE_UNUSED_CODE
3450   dumpTreeInfo(artwork.gfx_first, 0);
3451   dumpTreeInfo(artwork.snd_first, 0);
3452   dumpTreeInfo(artwork.mus_first, 0);
3453 #endif
3454 }
3455
3456 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3457                                   LevelDirTree *level_node)
3458 {
3459   int type = (*artwork_node)->type;
3460
3461   /* recursively check all level directories for artwork sub-directories */
3462
3463   while (level_node)
3464   {
3465     /* check all tree entries for artwork, but skip parent link entries */
3466     if (!level_node->parent_link)
3467     {
3468       TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3469       boolean cached = (artwork_new != NULL);
3470
3471       if (cached)
3472       {
3473         pushTreeInfo(artwork_node, artwork_new);
3474       }
3475       else
3476       {
3477         TreeInfo *topnode_last = *artwork_node;
3478         char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3479                               ARTWORK_DIRECTORY(type));
3480
3481         LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3482
3483         if (topnode_last != *artwork_node)      /* check for newly added node */
3484         {
3485           artwork_new = *artwork_node;
3486
3487           setString(&artwork_new->identifier,   level_node->subdir);
3488           setString(&artwork_new->name,         level_node->name);
3489           setString(&artwork_new->name_sorting, level_node->name_sorting);
3490
3491           artwork_new->sort_priority = level_node->sort_priority;
3492           artwork_new->color = LEVELCOLOR(artwork_new);
3493         }
3494
3495         free(path);
3496       }
3497
3498       /* insert artwork info (from old cache or filesystem) into new cache */
3499       if (artwork_new != NULL)
3500         setArtworkInfoCacheEntry(artwork_new, level_node, type);
3501     }
3502
3503     DrawInitText(level_node->name, 150, FC_YELLOW);
3504
3505     if (level_node->node_group != NULL)
3506       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3507
3508     level_node = level_node->next;
3509   }
3510 }
3511
3512 void LoadLevelArtworkInfo()
3513 {
3514   print_timestamp_init("LoadLevelArtworkInfo");
3515
3516   DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3517
3518   print_timestamp_time("DrawTimeText");
3519
3520   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3521   print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3522   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3523   print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3524   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3525   print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3526
3527   SaveArtworkInfoCache();
3528
3529   print_timestamp_time("SaveArtworkInfoCache");
3530
3531   /* needed for reloading level artwork not known at ealier stage */
3532
3533   if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3534   {
3535     artwork.gfx_current =
3536       getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3537     if (artwork.gfx_current == NULL)
3538       artwork.gfx_current =
3539         getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3540     if (artwork.gfx_current == NULL)
3541       artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3542   }
3543
3544   if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3545   {
3546     artwork.snd_current =
3547       getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3548     if (artwork.snd_current == NULL)
3549       artwork.snd_current =
3550         getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3551     if (artwork.snd_current == NULL)
3552       artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3553   }
3554
3555   if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3556   {
3557     artwork.mus_current =
3558       getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3559     if (artwork.mus_current == NULL)
3560       artwork.mus_current =
3561         getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3562     if (artwork.mus_current == NULL)
3563       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3564   }
3565
3566   print_timestamp_time("getTreeInfoFromIdentifier");
3567
3568   sortTreeInfo(&artwork.gfx_first);
3569   sortTreeInfo(&artwork.snd_first);
3570   sortTreeInfo(&artwork.mus_first);
3571
3572   print_timestamp_time("sortTreeInfo");
3573
3574 #if ENABLE_UNUSED_CODE
3575   dumpTreeInfo(artwork.gfx_first, 0);
3576   dumpTreeInfo(artwork.snd_first, 0);
3577   dumpTreeInfo(artwork.mus_first, 0);
3578 #endif
3579
3580   print_timestamp_done("LoadLevelArtworkInfo");
3581 }
3582
3583 static boolean AddUserLevelSetToLevelInfoExt(char *level_subdir_new)
3584 {
3585   // get level info tree node of first (original) user level set
3586   char *level_subdir_old = getLoginName();
3587   LevelDirTree *leveldir_old = getTreeInfoFromIdentifier(leveldir_first,
3588                                                          level_subdir_old);
3589   if (leveldir_old == NULL)             // should not happen
3590     return FALSE;
3591
3592   int draw_deactivation_mask = GetDrawDeactivationMask();
3593
3594   // override draw deactivation mask (temporarily disable drawing)
3595   SetDrawDeactivationMask(REDRAW_ALL);
3596
3597   // load new level set config and add it next to first user level set
3598   LoadLevelInfoFromLevelConf(&leveldir_old->next, NULL,
3599                              leveldir_old->basepath, level_subdir_new);
3600
3601   // set draw deactivation mask to previous value
3602   SetDrawDeactivationMask(draw_deactivation_mask);
3603
3604   // get level info tree node of newly added user level set
3605   LevelDirTree *leveldir_new = getTreeInfoFromIdentifier(leveldir_first,
3606                                                          level_subdir_new);
3607   if (leveldir_new == NULL)             // should not happen
3608     return FALSE;
3609
3610   // correct top link and parent node link of newly created tree node
3611   leveldir_new->node_top    = leveldir_old->node_top;
3612   leveldir_new->node_parent = leveldir_old->node_parent;
3613
3614   // sort level info tree to adjust position of newly added level set
3615   sortTreeInfo(&leveldir_first);
3616
3617   return TRUE;
3618 }
3619
3620 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
3621 {
3622   if (!AddUserLevelSetToLevelInfoExt(level_subdir_new))
3623     Error(ERR_EXIT, "internal level set structure corrupted -- aborting");
3624 }
3625
3626 char *getArtworkIdentifierForUserLevelSet(int type)
3627 {
3628   char *classic_artwork_set = getClassicArtworkSet(type);
3629
3630   /* check for custom artwork configured in "levelinfo.conf" */
3631   char *leveldir_artwork_set =
3632     *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
3633   boolean has_leveldir_artwork_set =
3634     (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
3635                                                classic_artwork_set));
3636
3637   /* check for custom artwork in sub-directory "graphics" etc. */
3638   TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
3639   char *leveldir_identifier = leveldir_current->identifier;
3640   boolean has_artwork_subdir =
3641     (getTreeInfoFromIdentifier(artwork_first_node,
3642                                leveldir_identifier) != NULL);
3643
3644   return (has_leveldir_artwork_set ? leveldir_artwork_set :
3645           has_artwork_subdir       ? leveldir_identifier :
3646           classic_artwork_set);
3647 }
3648
3649 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
3650 {
3651   char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
3652   TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
3653
3654   return getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
3655 }
3656
3657 boolean checkIfCustomArtworkExistsForCurrentLevelSet()
3658 {
3659   char *graphics_set =
3660     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
3661   char *sounds_set =
3662     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
3663   char *music_set =
3664     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
3665
3666   return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
3667           !strEqual(sounds_set,   SND_CLASSIC_SUBDIR) ||
3668           !strEqual(music_set,    MUS_CLASSIC_SUBDIR));
3669 }
3670
3671 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
3672                            char *level_author, int num_levels)
3673 {
3674   char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
3675   char *filename_tmp = getStringCat2(filename, ".tmp");
3676   FILE *file = NULL;
3677   FILE *file_tmp = NULL;
3678   char line[MAX_LINE_LEN];
3679   boolean success = FALSE;
3680   LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
3681                                                      level_subdir);
3682   // update values in level directory tree
3683
3684   if (level_name != NULL)
3685     setString(&leveldir->name, level_name);
3686
3687   if (level_author != NULL)
3688     setString(&leveldir->author, level_author);
3689
3690   if (num_levels != -1)
3691     leveldir->levels = num_levels;
3692
3693   // update values that depend on other values
3694
3695   setString(&leveldir->name_sorting, leveldir->name);
3696
3697   leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
3698
3699   // sort order of level sets may have changed
3700   sortTreeInfo(&leveldir_first);
3701
3702   if ((file     = fopen(filename,     MODE_READ)) &&
3703       (file_tmp = fopen(filename_tmp, MODE_WRITE)))
3704   {
3705     while (fgets(line, MAX_LINE_LEN, file))
3706     {
3707       if (strPrefix(line, "name:") && level_name != NULL)
3708         fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
3709       else if (strPrefix(line, "author:") && level_author != NULL)
3710         fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
3711       else if (strPrefix(line, "levels:") && num_levels != -1)
3712         fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
3713       else
3714         fputs(line, file_tmp);
3715     }
3716
3717     success = TRUE;
3718   }
3719
3720   if (file)
3721     fclose(file);
3722
3723   if (file_tmp)
3724     fclose(file_tmp);
3725
3726   if (success)
3727     success = (rename(filename_tmp, filename) == 0);
3728
3729   free(filename);
3730   free(filename_tmp);
3731
3732   return success;
3733 }
3734
3735 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
3736                            char *level_author, int num_levels,
3737                            boolean use_artwork_set)
3738 {
3739   LevelDirTree *level_info;
3740   char *filename;
3741   FILE *file;
3742   int i;
3743
3744   // create user level sub-directory, if needed
3745   createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
3746
3747   filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
3748
3749   if (!(file = fopen(filename, MODE_WRITE)))
3750   {
3751     Error(ERR_WARN, "cannot write level info file '%s'", filename);
3752     free(filename);
3753
3754     return FALSE;
3755   }
3756
3757   level_info = newTreeInfo();
3758
3759   /* always start with reliable default values */
3760   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3761
3762   setString(&level_info->name, level_name);
3763   setString(&level_info->author, level_author);
3764   level_info->levels = num_levels;
3765   level_info->first_level = 1;
3766   level_info->sort_priority = LEVELCLASS_PRIVATE_START;
3767   level_info->readonly = FALSE;
3768
3769   if (use_artwork_set)
3770   {
3771     level_info->graphics_set =
3772       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
3773     level_info->sounds_set =
3774       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
3775     level_info->music_set =
3776       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
3777   }
3778
3779   token_value_position = TOKEN_VALUE_POSITION_SHORT;
3780
3781   fprintFileHeader(file, LEVELINFO_FILENAME);
3782
3783   ldi = *level_info;
3784   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3785   {
3786     if (i == LEVELINFO_TOKEN_NAME ||
3787         i == LEVELINFO_TOKEN_AUTHOR ||
3788         i == LEVELINFO_TOKEN_LEVELS ||
3789         i == LEVELINFO_TOKEN_FIRST_LEVEL ||
3790         i == LEVELINFO_TOKEN_SORT_PRIORITY ||
3791         i == LEVELINFO_TOKEN_READONLY ||
3792         (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
3793                              i == LEVELINFO_TOKEN_SOUNDS_SET ||
3794                              i == LEVELINFO_TOKEN_MUSIC_SET)))
3795       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3796
3797     /* just to make things nicer :) */
3798     if (i == LEVELINFO_TOKEN_AUTHOR ||
3799         i == LEVELINFO_TOKEN_FIRST_LEVEL ||
3800         (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
3801       fprintf(file, "\n");      
3802   }
3803
3804   token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3805
3806   fclose(file);
3807
3808   SetFilePermissions(filename, PERMS_PRIVATE);
3809
3810   freeTreeInfo(level_info);
3811   free(filename);
3812
3813   return TRUE;
3814 }
3815
3816 static void SaveUserLevelInfo()
3817 {
3818   CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
3819 }
3820
3821 char *getSetupValue(int type, void *value)
3822 {
3823   static char value_string[MAX_LINE_LEN];
3824
3825   if (value == NULL)
3826     return NULL;
3827
3828   switch (type)
3829   {
3830     case TYPE_BOOLEAN:
3831       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3832       break;
3833
3834     case TYPE_SWITCH:
3835       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3836       break;
3837
3838     case TYPE_SWITCH3:
3839       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
3840                             *(int *)value == FALSE ? "off" : "on"));
3841       break;
3842
3843     case TYPE_YES_NO:
3844       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3845       break;
3846
3847     case TYPE_YES_NO_AUTO:
3848       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
3849                             *(int *)value == FALSE ? "no" : "yes"));
3850       break;
3851
3852     case TYPE_ECS_AGA:
3853       strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3854       break;
3855
3856     case TYPE_KEY:
3857       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3858       break;
3859
3860     case TYPE_KEY_X11:
3861       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3862       break;
3863
3864     case TYPE_INTEGER:
3865       sprintf(value_string, "%d", *(int *)value);
3866       break;
3867
3868     case TYPE_STRING:
3869       if (*(char **)value == NULL)
3870         return NULL;
3871
3872       strcpy(value_string, *(char **)value);
3873       break;
3874
3875     default:
3876       value_string[0] = '\0';
3877       break;
3878   }
3879
3880   if (type & TYPE_GHOSTED)
3881     strcpy(value_string, "n/a");
3882
3883   return value_string;
3884 }
3885
3886 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3887 {
3888   int i;
3889   char *line;
3890   static char token_string[MAX_LINE_LEN];
3891   int token_type = token_info[token_nr].type;
3892   void *setup_value = token_info[token_nr].value;
3893   char *token_text = token_info[token_nr].text;
3894   char *value_string = getSetupValue(token_type, setup_value);
3895
3896   /* build complete token string */
3897   sprintf(token_string, "%s%s", prefix, token_text);
3898
3899   /* build setup entry line */
3900   line = getFormattedSetupEntry(token_string, value_string);
3901
3902   if (token_type == TYPE_KEY_X11)
3903   {
3904     Key key = *(Key *)setup_value;
3905     char *keyname = getKeyNameFromKey(key);
3906
3907     /* add comment, if useful */
3908     if (!strEqual(keyname, "(undefined)") &&
3909         !strEqual(keyname, "(unknown)"))
3910     {
3911       /* add at least one whitespace */
3912       strcat(line, " ");
3913       for (i = strlen(line); i < token_comment_position; i++)
3914         strcat(line, " ");
3915
3916       strcat(line, "# ");
3917       strcat(line, keyname);
3918     }
3919   }
3920
3921   return line;
3922 }
3923
3924 void LoadLevelSetup_LastSeries()
3925 {
3926   /* ----------------------------------------------------------------------- */
3927   /* ~/.<program>/levelsetup.conf                                            */
3928   /* ----------------------------------------------------------------------- */
3929
3930   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3931   SetupFileHash *level_setup_hash = NULL;
3932
3933   /* always start with reliable default values */
3934   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3935
3936   if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
3937   {
3938     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3939                                                  DEFAULT_LEVELSET);
3940     if (leveldir_current == NULL)
3941       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3942   }
3943
3944   if ((level_setup_hash = loadSetupFileHash(filename)))
3945   {
3946     char *last_level_series =
3947       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3948
3949     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3950                                                  last_level_series);
3951     if (leveldir_current == NULL)
3952       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3953
3954     freeSetupFileHash(level_setup_hash);
3955   }
3956   else
3957   {
3958     Error(ERR_DEBUG, "using default setup values");
3959   }
3960
3961   free(filename);
3962 }
3963
3964 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
3965 {
3966   /* ----------------------------------------------------------------------- */
3967   /* ~/.<program>/levelsetup.conf                                            */
3968   /* ----------------------------------------------------------------------- */
3969
3970   // check if the current level directory structure is available at this point
3971   if (leveldir_current == NULL)
3972     return;
3973
3974   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3975   char *level_subdir = leveldir_current->subdir;
3976   FILE *file;
3977
3978   InitUserDataDirectory();
3979
3980   if (!(file = fopen(filename, MODE_WRITE)))
3981   {
3982     Error(ERR_WARN, "cannot write setup file '%s'", filename);
3983
3984     free(filename);
3985
3986     return;
3987   }
3988
3989   fprintFileHeader(file, LEVELSETUP_FILENAME);
3990
3991   if (deactivate_last_level_series)
3992     fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
3993
3994   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3995                                                level_subdir));
3996
3997   fclose(file);
3998
3999   SetFilePermissions(filename, PERMS_PRIVATE);
4000
4001   free(filename);
4002 }
4003
4004 void SaveLevelSetup_LastSeries()
4005 {
4006   SaveLevelSetup_LastSeries_Ext(FALSE);
4007 }
4008
4009 void SaveLevelSetup_LastSeries_Deactivate()
4010 {
4011   SaveLevelSetup_LastSeries_Ext(TRUE);
4012 }
4013
4014 static void checkSeriesInfo()
4015 {
4016   static char *level_directory = NULL;
4017   Directory *dir;
4018
4019   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
4020
4021   level_directory = getPath2((leveldir_current->in_user_dir ?
4022                               getUserLevelDir(NULL) :
4023                               options.level_directory),
4024                              leveldir_current->fullpath);
4025
4026   if ((dir = openDirectory(level_directory)) == NULL)
4027   {
4028     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4029
4030     return;
4031   }
4032
4033   closeDirectory(dir);
4034 }
4035
4036 void LoadLevelSetup_SeriesInfo()
4037 {
4038   char *filename;
4039   SetupFileHash *level_setup_hash = NULL;
4040   char *level_subdir = leveldir_current->subdir;
4041   int i;
4042
4043   /* always start with reliable default values */
4044   level_nr = leveldir_current->first_level;
4045
4046   for (i = 0; i < MAX_LEVELS; i++)
4047   {
4048     LevelStats_setPlayed(i, 0);
4049     LevelStats_setSolved(i, 0);
4050   }
4051
4052   checkSeriesInfo();
4053
4054   /* ----------------------------------------------------------------------- */
4055   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
4056   /* ----------------------------------------------------------------------- */
4057
4058   level_subdir = leveldir_current->subdir;
4059
4060   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4061
4062   if ((level_setup_hash = loadSetupFileHash(filename)))
4063   {
4064     char *token_value;
4065
4066     /* get last played level in this level set */
4067
4068     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4069
4070     if (token_value)
4071     {
4072       level_nr = atoi(token_value);
4073
4074       if (level_nr < leveldir_current->first_level)
4075         level_nr = leveldir_current->first_level;
4076       if (level_nr > leveldir_current->last_level)
4077         level_nr = leveldir_current->last_level;
4078     }
4079
4080     /* get handicap level in this level set */
4081
4082     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4083
4084     if (token_value)
4085     {
4086       int level_nr = atoi(token_value);
4087
4088       if (level_nr < leveldir_current->first_level)
4089         level_nr = leveldir_current->first_level;
4090       if (level_nr > leveldir_current->last_level + 1)
4091         level_nr = leveldir_current->last_level;
4092
4093       if (leveldir_current->user_defined || !leveldir_current->handicap)
4094         level_nr = leveldir_current->last_level;
4095
4096       leveldir_current->handicap_level = level_nr;
4097     }
4098
4099     /* get number of played and solved levels in this level set */
4100
4101     BEGIN_HASH_ITERATION(level_setup_hash, itr)
4102     {
4103       char *token = HASH_ITERATION_TOKEN(itr);
4104       char *value = HASH_ITERATION_VALUE(itr);
4105
4106       if (strlen(token) == 3 &&
4107           token[0] >= '0' && token[0] <= '9' &&
4108           token[1] >= '0' && token[1] <= '9' &&
4109           token[2] >= '0' && token[2] <= '9')
4110       {
4111         int level_nr = atoi(token);
4112
4113         if (value != NULL)
4114           LevelStats_setPlayed(level_nr, atoi(value));  /* read 1st column */
4115
4116         value = strchr(value, ' ');
4117
4118         if (value != NULL)
4119           LevelStats_setSolved(level_nr, atoi(value));  /* read 2nd column */
4120       }
4121     }
4122     END_HASH_ITERATION(hash, itr)
4123
4124     freeSetupFileHash(level_setup_hash);
4125   }
4126   else
4127   {
4128     Error(ERR_DEBUG, "using default setup values");
4129   }
4130
4131   free(filename);
4132 }
4133
4134 void SaveLevelSetup_SeriesInfo()
4135 {
4136   char *filename;
4137   char *level_subdir = leveldir_current->subdir;
4138   char *level_nr_str = int2str(level_nr, 0);
4139   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4140   FILE *file;
4141   int i;
4142
4143   /* ----------------------------------------------------------------------- */
4144   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
4145   /* ----------------------------------------------------------------------- */
4146
4147   InitLevelSetupDirectory(level_subdir);
4148
4149   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4150
4151   if (!(file = fopen(filename, MODE_WRITE)))
4152   {
4153     Error(ERR_WARN, "cannot write setup file '%s'", filename);
4154     free(filename);
4155     return;
4156   }
4157
4158   fprintFileHeader(file, LEVELSETUP_FILENAME);
4159
4160   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4161                                                level_nr_str));
4162   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4163                                                  handicap_level_str));
4164
4165   for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4166        i++)
4167   {
4168     if (LevelStats_getPlayed(i) > 0 ||
4169         LevelStats_getSolved(i) > 0)
4170     {
4171       char token[16];
4172       char value[16];
4173
4174       sprintf(token, "%03d", i);
4175       sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4176
4177       fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4178     }
4179   }
4180
4181   fclose(file);
4182
4183   SetFilePermissions(filename, PERMS_PRIVATE);
4184
4185   free(filename);
4186 }
4187
4188 int LevelStats_getPlayed(int nr)
4189 {
4190   return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4191 }
4192
4193 int LevelStats_getSolved(int nr)
4194 {
4195   return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4196 }
4197
4198 void LevelStats_setPlayed(int nr, int value)
4199 {
4200   if (nr >= 0 && nr < MAX_LEVELS)
4201     level_stats[nr].played = value;
4202 }
4203
4204 void LevelStats_setSolved(int nr, int value)
4205 {
4206   if (nr >= 0 && nr < MAX_LEVELS)
4207     level_stats[nr].solved = value;
4208 }
4209
4210 void LevelStats_incPlayed(int nr)
4211 {
4212   if (nr >= 0 && nr < MAX_LEVELS)
4213     level_stats[nr].played++;
4214 }
4215
4216 void LevelStats_incSolved(int nr)
4217 {
4218   if (nr >= 0 && nr < MAX_LEVELS)
4219     level_stats[nr].solved++;
4220 }