added options to use artwork and to copy template when creating level set
[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_major, program.version_minor);
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_major, version_minor;
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_major = ptr_cookie2[0] - '0';
1620   version_minor = ptr_cookie2[2] - '0';
1621
1622   return VERSION_IDENT(version_major, version_minor, 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_IMPORTED_FROM           5
2240 #define LEVELINFO_TOKEN_IMPORTED_BY             6
2241 #define LEVELINFO_TOKEN_TESTED_BY               7
2242 #define LEVELINFO_TOKEN_LEVELS                  8
2243 #define LEVELINFO_TOKEN_FIRST_LEVEL             9
2244 #define LEVELINFO_TOKEN_SORT_PRIORITY           10
2245 #define LEVELINFO_TOKEN_LATEST_ENGINE           11
2246 #define LEVELINFO_TOKEN_LEVEL_GROUP             12
2247 #define LEVELINFO_TOKEN_READONLY                13
2248 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS        14
2249 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA        15
2250 #define LEVELINFO_TOKEN_GRAPHICS_SET            16
2251 #define LEVELINFO_TOKEN_SOUNDS_SET              17
2252 #define LEVELINFO_TOKEN_MUSIC_SET               18
2253 #define LEVELINFO_TOKEN_FILENAME                19
2254 #define LEVELINFO_TOKEN_FILETYPE                20
2255 #define LEVELINFO_TOKEN_SPECIAL_FLAGS           21
2256 #define LEVELINFO_TOKEN_HANDICAP                22
2257 #define LEVELINFO_TOKEN_SKIP_LEVELS             23
2258
2259 #define NUM_LEVELINFO_TOKENS                    24
2260
2261 static LevelDirTree ldi;
2262
2263 static struct TokenInfo levelinfo_tokens[] =
2264 {
2265   /* level directory info */
2266   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
2267   { TYPE_STRING,        &ldi.name,              "name"                  },
2268   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
2269   { TYPE_STRING,        &ldi.author,            "author"                },
2270   { TYPE_STRING,        &ldi.year,              "year"                  },
2271   { TYPE_STRING,        &ldi.imported_from,     "imported_from"         },
2272   { TYPE_STRING,        &ldi.imported_by,       "imported_by"           },
2273   { TYPE_STRING,        &ldi.tested_by,         "tested_by"             },
2274   { TYPE_INTEGER,       &ldi.levels,            "levels"                },
2275   { TYPE_INTEGER,       &ldi.first_level,       "first_level"           },
2276   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
2277   { TYPE_BOOLEAN,       &ldi.latest_engine,     "latest_engine"         },
2278   { TYPE_BOOLEAN,       &ldi.level_group,       "level_group"           },
2279   { TYPE_BOOLEAN,       &ldi.readonly,          "readonly"              },
2280   { TYPE_STRING,        &ldi.graphics_set_ecs,  "graphics_set.ecs"      },
2281   { TYPE_STRING,        &ldi.graphics_set_aga,  "graphics_set.aga"      },
2282   { TYPE_STRING,        &ldi.graphics_set,      "graphics_set"          },
2283   { TYPE_STRING,        &ldi.sounds_set,        "sounds_set"            },
2284   { TYPE_STRING,        &ldi.music_set,         "music_set"             },
2285   { TYPE_STRING,        &ldi.level_filename,    "filename"              },
2286   { TYPE_STRING,        &ldi.level_filetype,    "filetype"              },
2287   { TYPE_STRING,        &ldi.special_flags,     "special_flags"         },
2288   { TYPE_BOOLEAN,       &ldi.handicap,          "handicap"              },
2289   { TYPE_BOOLEAN,       &ldi.skip_levels,       "skip_levels"           }
2290 };
2291
2292 static struct TokenInfo artworkinfo_tokens[] =
2293 {
2294   /* artwork directory info */
2295   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
2296   { TYPE_STRING,        &ldi.subdir,            "subdir"                },
2297   { TYPE_STRING,        &ldi.name,              "name"                  },
2298   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
2299   { TYPE_STRING,        &ldi.author,            "author"                },
2300   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
2301   { TYPE_STRING,        &ldi.basepath,          "basepath"              },
2302   { TYPE_STRING,        &ldi.fullpath,          "fullpath"              },
2303   { TYPE_BOOLEAN,       &ldi.in_user_dir,       "in_user_dir"           },
2304   { TYPE_INTEGER,       &ldi.color,             "color"                 },
2305   { TYPE_STRING,        &ldi.class_desc,        "class_desc"            },
2306
2307   { -1,                 NULL,                   NULL                    },
2308 };
2309
2310 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2311 {
2312   ti->type = type;
2313
2314   ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR    ? &leveldir_first :
2315                   ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2316                   ti->type == TREE_TYPE_SOUNDS_DIR   ? &artwork.snd_first :
2317                   ti->type == TREE_TYPE_MUSIC_DIR    ? &artwork.mus_first :
2318                   NULL);
2319
2320   ti->node_parent = NULL;
2321   ti->node_group = NULL;
2322   ti->next = NULL;
2323
2324   ti->cl_first = -1;
2325   ti->cl_cursor = -1;
2326
2327   ti->subdir = NULL;
2328   ti->fullpath = NULL;
2329   ti->basepath = NULL;
2330   ti->identifier = NULL;
2331   ti->name = getStringCopy(ANONYMOUS_NAME);
2332   ti->name_sorting = NULL;
2333   ti->author = getStringCopy(ANONYMOUS_NAME);
2334   ti->year = NULL;
2335
2336   ti->sort_priority = LEVELCLASS_UNDEFINED;     /* default: least priority */
2337   ti->latest_engine = FALSE;                    /* default: get from level */
2338   ti->parent_link = FALSE;
2339   ti->in_user_dir = FALSE;
2340   ti->user_defined = FALSE;
2341   ti->color = 0;
2342   ti->class_desc = NULL;
2343
2344   ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2345
2346   if (ti->type == TREE_TYPE_LEVEL_DIR)
2347   {
2348     ti->imported_from = NULL;
2349     ti->imported_by = NULL;
2350     ti->tested_by = NULL;
2351
2352     ti->graphics_set_ecs = NULL;
2353     ti->graphics_set_aga = NULL;
2354     ti->graphics_set = NULL;
2355     ti->sounds_set = NULL;
2356     ti->music_set = NULL;
2357     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2358     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2359     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2360
2361     ti->level_filename = NULL;
2362     ti->level_filetype = NULL;
2363
2364     ti->special_flags = NULL;
2365
2366     ti->levels = 0;
2367     ti->first_level = 0;
2368     ti->last_level = 0;
2369     ti->level_group = FALSE;
2370     ti->handicap_level = 0;
2371     ti->readonly = TRUE;
2372     ti->handicap = TRUE;
2373     ti->skip_levels = FALSE;
2374   }
2375 }
2376
2377 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2378 {
2379   if (parent == NULL)
2380   {
2381     Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2382
2383     setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2384
2385     return;
2386   }
2387
2388   /* copy all values from the parent structure */
2389
2390   ti->type = parent->type;
2391
2392   ti->node_top = parent->node_top;
2393   ti->node_parent = parent;
2394   ti->node_group = NULL;
2395   ti->next = NULL;
2396
2397   ti->cl_first = -1;
2398   ti->cl_cursor = -1;
2399
2400   ti->subdir = NULL;
2401   ti->fullpath = NULL;
2402   ti->basepath = NULL;
2403   ti->identifier = NULL;
2404   ti->name = getStringCopy(ANONYMOUS_NAME);
2405   ti->name_sorting = NULL;
2406   ti->author = getStringCopy(parent->author);
2407   ti->year = getStringCopy(parent->year);
2408
2409   ti->sort_priority = parent->sort_priority;
2410   ti->latest_engine = parent->latest_engine;
2411   ti->parent_link = FALSE;
2412   ti->in_user_dir = parent->in_user_dir;
2413   ti->user_defined = parent->user_defined;
2414   ti->color = parent->color;
2415   ti->class_desc = getStringCopy(parent->class_desc);
2416
2417   ti->infotext = getStringCopy(parent->infotext);
2418
2419   if (ti->type == TREE_TYPE_LEVEL_DIR)
2420   {
2421     ti->imported_from = getStringCopy(parent->imported_from);
2422     ti->imported_by = getStringCopy(parent->imported_by);
2423     ti->tested_by = getStringCopy(parent->tested_by);
2424
2425     ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2426     ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2427     ti->graphics_set = getStringCopy(parent->graphics_set);
2428     ti->sounds_set = getStringCopy(parent->sounds_set);
2429     ti->music_set = getStringCopy(parent->music_set);
2430     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2431     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2432     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2433
2434     ti->level_filename = getStringCopy(parent->level_filename);
2435     ti->level_filetype = getStringCopy(parent->level_filetype);
2436
2437     ti->special_flags = getStringCopy(parent->special_flags);
2438
2439     ti->levels = parent->levels;
2440     ti->first_level = parent->first_level;
2441     ti->last_level = parent->last_level;
2442     ti->level_group = FALSE;
2443     ti->handicap_level = parent->handicap_level;
2444     ti->readonly = parent->readonly;
2445     ti->handicap = parent->handicap;
2446     ti->skip_levels = parent->skip_levels;
2447   }
2448 }
2449
2450 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2451 {
2452   TreeInfo *ti_copy = newTreeInfo();
2453
2454   /* copy all values from the original structure */
2455
2456   ti_copy->type                 = ti->type;
2457
2458   ti_copy->node_top             = ti->node_top;
2459   ti_copy->node_parent          = ti->node_parent;
2460   ti_copy->node_group           = ti->node_group;
2461   ti_copy->next                 = ti->next;
2462
2463   ti_copy->cl_first             = ti->cl_first;
2464   ti_copy->cl_cursor            = ti->cl_cursor;
2465
2466   ti_copy->subdir               = getStringCopy(ti->subdir);
2467   ti_copy->fullpath             = getStringCopy(ti->fullpath);
2468   ti_copy->basepath             = getStringCopy(ti->basepath);
2469   ti_copy->identifier           = getStringCopy(ti->identifier);
2470   ti_copy->name                 = getStringCopy(ti->name);
2471   ti_copy->name_sorting         = getStringCopy(ti->name_sorting);
2472   ti_copy->author               = getStringCopy(ti->author);
2473   ti_copy->year                 = getStringCopy(ti->year);
2474   ti_copy->imported_from        = getStringCopy(ti->imported_from);
2475   ti_copy->imported_by          = getStringCopy(ti->imported_by);
2476   ti_copy->tested_by            = getStringCopy(ti->tested_by);
2477
2478   ti_copy->graphics_set_ecs     = getStringCopy(ti->graphics_set_ecs);
2479   ti_copy->graphics_set_aga     = getStringCopy(ti->graphics_set_aga);
2480   ti_copy->graphics_set         = getStringCopy(ti->graphics_set);
2481   ti_copy->sounds_set           = getStringCopy(ti->sounds_set);
2482   ti_copy->music_set            = getStringCopy(ti->music_set);
2483   ti_copy->graphics_path        = getStringCopy(ti->graphics_path);
2484   ti_copy->sounds_path          = getStringCopy(ti->sounds_path);
2485   ti_copy->music_path           = getStringCopy(ti->music_path);
2486
2487   ti_copy->level_filename       = getStringCopy(ti->level_filename);
2488   ti_copy->level_filetype       = getStringCopy(ti->level_filetype);
2489
2490   ti_copy->special_flags        = getStringCopy(ti->special_flags);
2491
2492   ti_copy->levels               = ti->levels;
2493   ti_copy->first_level          = ti->first_level;
2494   ti_copy->last_level           = ti->last_level;
2495   ti_copy->sort_priority        = ti->sort_priority;
2496
2497   ti_copy->latest_engine        = ti->latest_engine;
2498
2499   ti_copy->level_group          = ti->level_group;
2500   ti_copy->parent_link          = ti->parent_link;
2501   ti_copy->in_user_dir          = ti->in_user_dir;
2502   ti_copy->user_defined         = ti->user_defined;
2503   ti_copy->readonly             = ti->readonly;
2504   ti_copy->handicap             = ti->handicap;
2505   ti_copy->skip_levels          = ti->skip_levels;
2506
2507   ti_copy->color                = ti->color;
2508   ti_copy->class_desc           = getStringCopy(ti->class_desc);
2509   ti_copy->handicap_level       = ti->handicap_level;
2510
2511   ti_copy->infotext             = getStringCopy(ti->infotext);
2512
2513   return ti_copy;
2514 }
2515
2516 void freeTreeInfo(TreeInfo *ti)
2517 {
2518   if (ti == NULL)
2519     return;
2520
2521   checked_free(ti->subdir);
2522   checked_free(ti->fullpath);
2523   checked_free(ti->basepath);
2524   checked_free(ti->identifier);
2525
2526   checked_free(ti->name);
2527   checked_free(ti->name_sorting);
2528   checked_free(ti->author);
2529   checked_free(ti->year);
2530
2531   checked_free(ti->class_desc);
2532
2533   checked_free(ti->infotext);
2534
2535   if (ti->type == TREE_TYPE_LEVEL_DIR)
2536   {
2537     checked_free(ti->imported_from);
2538     checked_free(ti->imported_by);
2539     checked_free(ti->tested_by);
2540
2541     checked_free(ti->graphics_set_ecs);
2542     checked_free(ti->graphics_set_aga);
2543     checked_free(ti->graphics_set);
2544     checked_free(ti->sounds_set);
2545     checked_free(ti->music_set);
2546
2547     checked_free(ti->graphics_path);
2548     checked_free(ti->sounds_path);
2549     checked_free(ti->music_path);
2550
2551     checked_free(ti->level_filename);
2552     checked_free(ti->level_filetype);
2553
2554     checked_free(ti->special_flags);
2555   }
2556
2557   // recursively free child node
2558   if (ti->node_group)
2559     freeTreeInfo(ti->node_group);
2560
2561   // recursively free next node
2562   if (ti->next)
2563     freeTreeInfo(ti->next);
2564
2565   checked_free(ti);
2566 }
2567
2568 void setSetupInfo(struct TokenInfo *token_info,
2569                   int token_nr, char *token_value)
2570 {
2571   int token_type = token_info[token_nr].type;
2572   void *setup_value = token_info[token_nr].value;
2573
2574   if (token_value == NULL)
2575     return;
2576
2577   /* set setup field to corresponding token value */
2578   switch (token_type)
2579   {
2580     case TYPE_BOOLEAN:
2581     case TYPE_SWITCH:
2582       *(boolean *)setup_value = get_boolean_from_string(token_value);
2583       break;
2584
2585     case TYPE_SWITCH3:
2586       *(int *)setup_value = get_switch3_from_string(token_value);
2587       break;
2588
2589     case TYPE_KEY:
2590       *(Key *)setup_value = getKeyFromKeyName(token_value);
2591       break;
2592
2593     case TYPE_KEY_X11:
2594       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2595       break;
2596
2597     case TYPE_INTEGER:
2598       *(int *)setup_value = get_integer_from_string(token_value);
2599       break;
2600
2601     case TYPE_STRING:
2602       checked_free(*(char **)setup_value);
2603       *(char **)setup_value = getStringCopy(token_value);
2604       break;
2605
2606     default:
2607       break;
2608   }
2609 }
2610
2611 static int compareTreeInfoEntries(const void *object1, const void *object2)
2612 {
2613   const TreeInfo *entry1 = *((TreeInfo **)object1);
2614   const TreeInfo *entry2 = *((TreeInfo **)object2);
2615   int class_sorting1 = 0, class_sorting2 = 0;
2616   int compare_result;
2617
2618   if (entry1->type == TREE_TYPE_LEVEL_DIR)
2619   {
2620     class_sorting1 = LEVELSORTING(entry1);
2621     class_sorting2 = LEVELSORTING(entry2);
2622   }
2623   else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2624            entry1->type == TREE_TYPE_SOUNDS_DIR ||
2625            entry1->type == TREE_TYPE_MUSIC_DIR)
2626   {
2627     class_sorting1 = ARTWORKSORTING(entry1);
2628     class_sorting2 = ARTWORKSORTING(entry2);
2629   }
2630
2631   if (entry1->parent_link || entry2->parent_link)
2632     compare_result = (entry1->parent_link ? -1 : +1);
2633   else if (entry1->sort_priority == entry2->sort_priority)
2634   {
2635     char *name1 = getStringToLower(entry1->name_sorting);
2636     char *name2 = getStringToLower(entry2->name_sorting);
2637
2638     compare_result = strcmp(name1, name2);
2639
2640     free(name1);
2641     free(name2);
2642   }
2643   else if (class_sorting1 == class_sorting2)
2644     compare_result = entry1->sort_priority - entry2->sort_priority;
2645   else
2646     compare_result = class_sorting1 - class_sorting2;
2647
2648   return compare_result;
2649 }
2650
2651 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2652 {
2653   TreeInfo *ti_new;
2654
2655   if (node_parent == NULL)
2656     return NULL;
2657
2658   ti_new = newTreeInfo();
2659   setTreeInfoToDefaults(ti_new, node_parent->type);
2660
2661   ti_new->node_parent = node_parent;
2662   ti_new->parent_link = TRUE;
2663
2664   setString(&ti_new->identifier, node_parent->identifier);
2665   setString(&ti_new->name, ".. (parent directory)");
2666   setString(&ti_new->name_sorting, ti_new->name);
2667
2668   setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2669   setString(&ti_new->fullpath, node_parent->fullpath);
2670
2671   ti_new->sort_priority = node_parent->sort_priority;
2672   ti_new->latest_engine = node_parent->latest_engine;
2673
2674   setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2675
2676   pushTreeInfo(&node_parent->node_group, ti_new);
2677
2678   return ti_new;
2679 }
2680
2681 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2682 {
2683   TreeInfo *ti_new, *ti_new2;
2684
2685   if (node_first == NULL)
2686     return NULL;
2687
2688   ti_new = newTreeInfo();
2689   setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2690
2691   ti_new->node_parent = NULL;
2692   ti_new->parent_link = FALSE;
2693
2694   setString(&ti_new->identifier, node_first->identifier);
2695   setString(&ti_new->name, "level sets");
2696   setString(&ti_new->name_sorting, ti_new->name);
2697
2698   setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2699   setString(&ti_new->fullpath, ".");
2700
2701   ti_new->sort_priority = node_first->sort_priority;;
2702   ti_new->latest_engine = node_first->latest_engine;
2703
2704   setString(&ti_new->class_desc, "level sets");
2705
2706   ti_new->node_group = node_first;
2707   ti_new->level_group = TRUE;
2708
2709   ti_new2 = createParentTreeInfoNode(ti_new);
2710
2711   setString(&ti_new2->name, ".. (main menu)");
2712   setString(&ti_new2->name_sorting, ti_new2->name);
2713
2714   return ti_new;
2715 }
2716
2717
2718 /* -------------------------------------------------------------------------- */
2719 /* functions for handling level and custom artwork info cache                 */
2720 /* -------------------------------------------------------------------------- */
2721
2722 static void LoadArtworkInfoCache()
2723 {
2724   InitCacheDirectory();
2725
2726   if (artworkinfo_cache_old == NULL)
2727   {
2728     char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2729
2730     /* try to load artwork info hash from already existing cache file */
2731     artworkinfo_cache_old = loadSetupFileHash(filename);
2732
2733     /* if no artwork info cache file was found, start with empty hash */
2734     if (artworkinfo_cache_old == NULL)
2735       artworkinfo_cache_old = newSetupFileHash();
2736
2737     free(filename);
2738   }
2739
2740   if (artworkinfo_cache_new == NULL)
2741     artworkinfo_cache_new = newSetupFileHash();
2742 }
2743
2744 static void SaveArtworkInfoCache()
2745 {
2746   char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2747
2748   InitCacheDirectory();
2749
2750   saveSetupFileHash(artworkinfo_cache_new, filename);
2751
2752   free(filename);
2753 }
2754
2755 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2756 {
2757   static char *prefix = NULL;
2758
2759   checked_free(prefix);
2760
2761   prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2762
2763   return prefix;
2764 }
2765
2766 /* (identical to above function, but separate string buffer needed -- nasty) */
2767 static char *getCacheToken(char *prefix, char *suffix)
2768 {
2769   static char *token = NULL;
2770
2771   checked_free(token);
2772
2773   token = getStringCat2WithSeparator(prefix, suffix, ".");
2774
2775   return token;
2776 }
2777
2778 static char *getFileTimestampString(char *filename)
2779 {
2780   return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2781 }
2782
2783 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2784 {
2785   struct stat file_status;
2786
2787   if (timestamp_string == NULL)
2788     return TRUE;
2789
2790   if (stat(filename, &file_status) != 0)        /* cannot stat file */
2791     return TRUE;
2792
2793   return (file_status.st_mtime != atoi(timestamp_string));
2794 }
2795
2796 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2797 {
2798   char *identifier = level_node->subdir;
2799   char *type_string = ARTWORK_DIRECTORY(type);
2800   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2801   char *token_main = getCacheToken(token_prefix, "CACHED");
2802   char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2803   boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2804   TreeInfo *artwork_info = NULL;
2805
2806   if (!use_artworkinfo_cache)
2807     return NULL;
2808
2809   if (cached)
2810   {
2811     int i;
2812
2813     artwork_info = newTreeInfo();
2814     setTreeInfoToDefaults(artwork_info, type);
2815
2816     /* set all structure fields according to the token/value pairs */
2817     ldi = *artwork_info;
2818     for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2819     {
2820       char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2821       char *value = getHashEntry(artworkinfo_cache_old, token);
2822
2823       setSetupInfo(artworkinfo_tokens, i, value);
2824
2825       /* check if cache entry for this item is invalid or incomplete */
2826       if (value == NULL)
2827       {
2828         Error(ERR_WARN, "cache entry '%s' invalid", token);
2829
2830         cached = FALSE;
2831       }
2832     }
2833
2834     *artwork_info = ldi;
2835   }
2836
2837   if (cached)
2838   {
2839     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2840                                         LEVELINFO_FILENAME);
2841     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2842                                           ARTWORKINFO_FILENAME(type));
2843
2844     /* check if corresponding "levelinfo.conf" file has changed */
2845     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2846     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2847
2848     if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2849       cached = FALSE;
2850
2851     /* check if corresponding "<artworkinfo>.conf" file has changed */
2852     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2853     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2854
2855     if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2856       cached = FALSE;
2857
2858     checked_free(filename_levelinfo);
2859     checked_free(filename_artworkinfo);
2860   }
2861
2862   if (!cached && artwork_info != NULL)
2863   {
2864     freeTreeInfo(artwork_info);
2865
2866     return NULL;
2867   }
2868
2869   return artwork_info;
2870 }
2871
2872 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2873                                      LevelDirTree *level_node, int type)
2874 {
2875   char *identifier = level_node->subdir;
2876   char *type_string = ARTWORK_DIRECTORY(type);
2877   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2878   char *token_main = getCacheToken(token_prefix, "CACHED");
2879   boolean set_cache_timestamps = TRUE;
2880   int i;
2881
2882   setHashEntry(artworkinfo_cache_new, token_main, "true");
2883
2884   if (set_cache_timestamps)
2885   {
2886     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2887                                         LEVELINFO_FILENAME);
2888     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2889                                           ARTWORKINFO_FILENAME(type));
2890     char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
2891     char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
2892
2893     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2894     setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2895
2896     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2897     setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2898
2899     checked_free(filename_levelinfo);
2900     checked_free(filename_artworkinfo);
2901     checked_free(timestamp_levelinfo);
2902     checked_free(timestamp_artworkinfo);
2903   }
2904
2905   ldi = *artwork_info;
2906   for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2907   {
2908     char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2909     char *value = getSetupValue(artworkinfo_tokens[i].type,
2910                                 artworkinfo_tokens[i].value);
2911     if (value != NULL)
2912       setHashEntry(artworkinfo_cache_new, token, value);
2913   }
2914 }
2915
2916
2917 /* -------------------------------------------------------------------------- */
2918 /* functions for loading level info and custom artwork info                   */
2919 /* -------------------------------------------------------------------------- */
2920
2921 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2922 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2923
2924 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2925                                           TreeInfo *node_parent,
2926                                           char *level_directory,
2927                                           char *directory_name)
2928 {
2929   char *directory_path = getPath2(level_directory, directory_name);
2930   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2931   SetupFileHash *setup_file_hash;
2932   LevelDirTree *leveldir_new = NULL;
2933   int i;
2934
2935   /* unless debugging, silently ignore directories without "levelinfo.conf" */
2936   if (!options.debug && !fileExists(filename))
2937   {
2938     free(directory_path);
2939     free(filename);
2940
2941     return FALSE;
2942   }
2943
2944   setup_file_hash = loadSetupFileHash(filename);
2945
2946   if (setup_file_hash == NULL)
2947   {
2948     Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2949
2950     free(directory_path);
2951     free(filename);
2952
2953     return FALSE;
2954   }
2955
2956   leveldir_new = newTreeInfo();
2957
2958   if (node_parent)
2959     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2960   else
2961     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2962
2963   leveldir_new->subdir = getStringCopy(directory_name);
2964
2965   /* set all structure fields according to the token/value pairs */
2966   ldi = *leveldir_new;
2967   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2968     setSetupInfo(levelinfo_tokens, i,
2969                  getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2970   *leveldir_new = ldi;
2971
2972   if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2973     setString(&leveldir_new->name, leveldir_new->subdir);
2974
2975   if (leveldir_new->identifier == NULL)
2976     leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2977
2978   if (leveldir_new->name_sorting == NULL)
2979     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2980
2981   if (node_parent == NULL)              /* top level group */
2982   {
2983     leveldir_new->basepath = getStringCopy(level_directory);
2984     leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2985   }
2986   else                                  /* sub level group */
2987   {
2988     leveldir_new->basepath = getStringCopy(node_parent->basepath);
2989     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2990   }
2991
2992   leveldir_new->last_level =
2993     leveldir_new->first_level + leveldir_new->levels - 1;
2994
2995   leveldir_new->in_user_dir =
2996     (!strEqual(leveldir_new->basepath, options.level_directory));
2997
2998   /* adjust some settings if user's private level directory was detected */
2999   if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3000       leveldir_new->in_user_dir &&
3001       (strEqual(leveldir_new->subdir, getLoginName()) ||
3002        strEqual(leveldir_new->name,   getLoginName()) ||
3003        strEqual(leveldir_new->author, getRealName())))
3004   {
3005     leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3006     leveldir_new->readonly = FALSE;
3007   }
3008
3009   leveldir_new->user_defined =
3010     (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3011
3012   leveldir_new->color = LEVELCOLOR(leveldir_new);
3013
3014   setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3015
3016   leveldir_new->handicap_level =        /* set handicap to default value */
3017     (leveldir_new->user_defined || !leveldir_new->handicap ?
3018      leveldir_new->last_level : leveldir_new->first_level);
3019
3020   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3021
3022   pushTreeInfo(node_first, leveldir_new);
3023
3024   freeSetupFileHash(setup_file_hash);
3025
3026   if (leveldir_new->level_group)
3027   {
3028     /* create node to link back to current level directory */
3029     createParentTreeInfoNode(leveldir_new);
3030
3031     /* recursively step into sub-directory and look for more level series */
3032     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3033                               leveldir_new, directory_path);
3034   }
3035
3036   free(directory_path);
3037   free(filename);
3038
3039   return TRUE;
3040 }
3041
3042 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3043                                       TreeInfo *node_parent,
3044                                       char *level_directory)
3045 {
3046   Directory *dir;
3047   DirectoryEntry *dir_entry;
3048   boolean valid_entry_found = FALSE;
3049
3050   if ((dir = openDirectory(level_directory)) == NULL)
3051   {
3052     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3053
3054     return;
3055   }
3056
3057   while ((dir_entry = readDirectory(dir)) != NULL)      /* loop all entries */
3058   {
3059     char *directory_name = dir_entry->basename;
3060     char *directory_path = getPath2(level_directory, directory_name);
3061
3062     /* skip entries for current and parent directory */
3063     if (strEqual(directory_name, ".") ||
3064         strEqual(directory_name, ".."))
3065     {
3066       free(directory_path);
3067
3068       continue;
3069     }
3070
3071     /* find out if directory entry is itself a directory */
3072     if (!dir_entry->is_directory)                       /* not a directory */
3073     {
3074       free(directory_path);
3075
3076       continue;
3077     }
3078
3079     free(directory_path);
3080
3081     if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3082         strEqual(directory_name, SOUNDS_DIRECTORY) ||
3083         strEqual(directory_name, MUSIC_DIRECTORY))
3084       continue;
3085
3086     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3087                                                     level_directory,
3088                                                     directory_name);
3089   }
3090
3091   closeDirectory(dir);
3092
3093   /* special case: top level directory may directly contain "levelinfo.conf" */
3094   if (node_parent == NULL && !valid_entry_found)
3095   {
3096     /* check if this directory directly contains a file "levelinfo.conf" */
3097     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3098                                                     level_directory, ".");
3099   }
3100
3101   if (!valid_entry_found)
3102     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3103           level_directory);
3104 }
3105
3106 boolean AdjustGraphicsForEMC()
3107 {
3108   boolean settings_changed = FALSE;
3109
3110   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3111   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3112
3113   return settings_changed;
3114 }
3115
3116 void LoadLevelInfo()
3117 {
3118   InitUserLevelDirectory(getLoginName());
3119
3120   DrawInitText("Loading level series", 120, FC_GREEN);
3121
3122   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3123   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3124
3125   leveldir_first = createTopTreeInfoNode(leveldir_first);
3126
3127   /* after loading all level set information, clone the level directory tree
3128      and remove all level sets without levels (these may still contain artwork
3129      to be offered in the setup menu as "custom artwork", and are therefore
3130      checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3131   leveldir_first_all = leveldir_first;
3132   cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3133
3134   AdjustGraphicsForEMC();
3135
3136   /* before sorting, the first entries will be from the user directory */
3137   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3138
3139   if (leveldir_first == NULL)
3140     Error(ERR_EXIT, "cannot find any valid level series in any directory");
3141
3142   sortTreeInfo(&leveldir_first);
3143
3144 #if ENABLE_UNUSED_CODE
3145   dumpTreeInfo(leveldir_first, 0);
3146 #endif
3147 }
3148
3149 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3150                                               TreeInfo *node_parent,
3151                                               char *base_directory,
3152                                               char *directory_name, int type)
3153 {
3154   char *directory_path = getPath2(base_directory, directory_name);
3155   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3156   SetupFileHash *setup_file_hash = NULL;
3157   TreeInfo *artwork_new = NULL;
3158   int i;
3159
3160   if (fileExists(filename))
3161     setup_file_hash = loadSetupFileHash(filename);
3162
3163   if (setup_file_hash == NULL)  /* no config file -- look for artwork files */
3164   {
3165     Directory *dir;
3166     DirectoryEntry *dir_entry;
3167     boolean valid_file_found = FALSE;
3168
3169     if ((dir = openDirectory(directory_path)) != NULL)
3170     {
3171       while ((dir_entry = readDirectory(dir)) != NULL)
3172       {
3173         if (FileIsArtworkType(dir_entry->filename, type))
3174         {
3175           valid_file_found = TRUE;
3176
3177           break;
3178         }
3179       }
3180
3181       closeDirectory(dir);
3182     }
3183
3184     if (!valid_file_found)
3185     {
3186       if (!strEqual(directory_name, "."))
3187         Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3188
3189       free(directory_path);
3190       free(filename);
3191
3192       return FALSE;
3193     }
3194   }
3195
3196   artwork_new = newTreeInfo();
3197
3198   if (node_parent)
3199     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3200   else
3201     setTreeInfoToDefaults(artwork_new, type);
3202
3203   artwork_new->subdir = getStringCopy(directory_name);
3204
3205   if (setup_file_hash)  /* (before defining ".color" and ".class_desc") */
3206   {
3207     /* set all structure fields according to the token/value pairs */
3208     ldi = *artwork_new;
3209     for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3210       setSetupInfo(levelinfo_tokens, i,
3211                    getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3212     *artwork_new = ldi;
3213
3214     if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3215       setString(&artwork_new->name, artwork_new->subdir);
3216
3217     if (artwork_new->identifier == NULL)
3218       artwork_new->identifier = getStringCopy(artwork_new->subdir);
3219
3220     if (artwork_new->name_sorting == NULL)
3221       artwork_new->name_sorting = getStringCopy(artwork_new->name);
3222   }
3223
3224   if (node_parent == NULL)              /* top level group */
3225   {
3226     artwork_new->basepath = getStringCopy(base_directory);
3227     artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3228   }
3229   else                                  /* sub level group */
3230   {
3231     artwork_new->basepath = getStringCopy(node_parent->basepath);
3232     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3233   }
3234
3235   artwork_new->in_user_dir =
3236     (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3237
3238   /* (may use ".sort_priority" from "setup_file_hash" above) */
3239   artwork_new->color = ARTWORKCOLOR(artwork_new);
3240
3241   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3242
3243   if (setup_file_hash == NULL)  /* (after determining ".user_defined") */
3244   {
3245     if (strEqual(artwork_new->subdir, "."))
3246     {
3247       if (artwork_new->user_defined)
3248       {
3249         setString(&artwork_new->identifier, "private");
3250         artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3251       }
3252       else
3253       {
3254         setString(&artwork_new->identifier, "classic");
3255         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3256       }
3257
3258       /* set to new values after changing ".sort_priority" */
3259       artwork_new->color = ARTWORKCOLOR(artwork_new);
3260
3261       setString(&artwork_new->class_desc,
3262                 getLevelClassDescription(artwork_new));
3263     }
3264     else
3265     {
3266       setString(&artwork_new->identifier, artwork_new->subdir);
3267     }
3268
3269     setString(&artwork_new->name, artwork_new->identifier);
3270     setString(&artwork_new->name_sorting, artwork_new->name);
3271   }
3272
3273   pushTreeInfo(node_first, artwork_new);
3274
3275   freeSetupFileHash(setup_file_hash);
3276
3277   free(directory_path);
3278   free(filename);
3279
3280   return TRUE;
3281 }
3282
3283 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3284                                           TreeInfo *node_parent,
3285                                           char *base_directory, int type)
3286 {
3287   Directory *dir;
3288   DirectoryEntry *dir_entry;
3289   boolean valid_entry_found = FALSE;
3290
3291   if ((dir = openDirectory(base_directory)) == NULL)
3292   {
3293     /* display error if directory is main "options.graphics_directory" etc. */
3294     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3295       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3296
3297     return;
3298   }
3299
3300   while ((dir_entry = readDirectory(dir)) != NULL)      /* loop all entries */
3301   {
3302     char *directory_name = dir_entry->basename;
3303     char *directory_path = getPath2(base_directory, directory_name);
3304
3305     /* skip directory entries for current and parent directory */
3306     if (strEqual(directory_name, ".") ||
3307         strEqual(directory_name, ".."))
3308     {
3309       free(directory_path);
3310
3311       continue;
3312     }
3313
3314     /* skip directory entries which are not a directory */
3315     if (!dir_entry->is_directory)                       /* not a directory */
3316     {
3317       free(directory_path);
3318
3319       continue;
3320     }
3321
3322     free(directory_path);
3323
3324     /* check if this directory contains artwork with or without config file */
3325     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3326                                                         base_directory,
3327                                                         directory_name, type);
3328   }
3329
3330   closeDirectory(dir);
3331
3332   /* check if this directory directly contains artwork itself */
3333   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3334                                                       base_directory, ".",
3335                                                       type);
3336   if (!valid_entry_found)
3337     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3338           base_directory);
3339 }
3340
3341 static TreeInfo *getDummyArtworkInfo(int type)
3342 {
3343   /* this is only needed when there is completely no artwork available */
3344   TreeInfo *artwork_new = newTreeInfo();
3345
3346   setTreeInfoToDefaults(artwork_new, type);
3347
3348   setString(&artwork_new->subdir,   UNDEFINED_FILENAME);
3349   setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3350   setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3351
3352   setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
3353   setString(&artwork_new->name,         UNDEFINED_FILENAME);
3354   setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3355
3356   return artwork_new;
3357 }
3358
3359 void LoadArtworkInfo()
3360 {
3361   LoadArtworkInfoCache();
3362
3363   DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3364
3365   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3366                                 options.graphics_directory,
3367                                 TREE_TYPE_GRAPHICS_DIR);
3368   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3369                                 getUserGraphicsDir(),
3370                                 TREE_TYPE_GRAPHICS_DIR);
3371
3372   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3373                                 options.sounds_directory,
3374                                 TREE_TYPE_SOUNDS_DIR);
3375   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3376                                 getUserSoundsDir(),
3377                                 TREE_TYPE_SOUNDS_DIR);
3378
3379   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3380                                 options.music_directory,
3381                                 TREE_TYPE_MUSIC_DIR);
3382   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3383                                 getUserMusicDir(),
3384                                 TREE_TYPE_MUSIC_DIR);
3385
3386   if (artwork.gfx_first == NULL)
3387     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3388   if (artwork.snd_first == NULL)
3389     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3390   if (artwork.mus_first == NULL)
3391     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3392
3393   /* before sorting, the first entries will be from the user directory */
3394   artwork.gfx_current =
3395     getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3396   if (artwork.gfx_current == NULL)
3397     artwork.gfx_current =
3398       getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3399   if (artwork.gfx_current == NULL)
3400     artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3401
3402   artwork.snd_current =
3403     getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3404   if (artwork.snd_current == NULL)
3405     artwork.snd_current =
3406       getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3407   if (artwork.snd_current == NULL)
3408     artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3409
3410   artwork.mus_current =
3411     getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3412   if (artwork.mus_current == NULL)
3413     artwork.mus_current =
3414       getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3415   if (artwork.mus_current == NULL)
3416     artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3417
3418   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3419   artwork.snd_current_identifier = artwork.snd_current->identifier;
3420   artwork.mus_current_identifier = artwork.mus_current->identifier;
3421
3422 #if ENABLE_UNUSED_CODE
3423   printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3424   printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3425   printf("music set == %s\n\n", artwork.mus_current_identifier);
3426 #endif
3427
3428   sortTreeInfo(&artwork.gfx_first);
3429   sortTreeInfo(&artwork.snd_first);
3430   sortTreeInfo(&artwork.mus_first);
3431
3432 #if ENABLE_UNUSED_CODE
3433   dumpTreeInfo(artwork.gfx_first, 0);
3434   dumpTreeInfo(artwork.snd_first, 0);
3435   dumpTreeInfo(artwork.mus_first, 0);
3436 #endif
3437 }
3438
3439 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3440                                   LevelDirTree *level_node)
3441 {
3442   int type = (*artwork_node)->type;
3443
3444   /* recursively check all level directories for artwork sub-directories */
3445
3446   while (level_node)
3447   {
3448     /* check all tree entries for artwork, but skip parent link entries */
3449     if (!level_node->parent_link)
3450     {
3451       TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3452       boolean cached = (artwork_new != NULL);
3453
3454       if (cached)
3455       {
3456         pushTreeInfo(artwork_node, artwork_new);
3457       }
3458       else
3459       {
3460         TreeInfo *topnode_last = *artwork_node;
3461         char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3462                               ARTWORK_DIRECTORY(type));
3463
3464         LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3465
3466         if (topnode_last != *artwork_node)      /* check for newly added node */
3467         {
3468           artwork_new = *artwork_node;
3469
3470           setString(&artwork_new->identifier,   level_node->subdir);
3471           setString(&artwork_new->name,         level_node->name);
3472           setString(&artwork_new->name_sorting, level_node->name_sorting);
3473
3474           artwork_new->sort_priority = level_node->sort_priority;
3475           artwork_new->color = LEVELCOLOR(artwork_new);
3476         }
3477
3478         free(path);
3479       }
3480
3481       /* insert artwork info (from old cache or filesystem) into new cache */
3482       if (artwork_new != NULL)
3483         setArtworkInfoCacheEntry(artwork_new, level_node, type);
3484     }
3485
3486     DrawInitText(level_node->name, 150, FC_YELLOW);
3487
3488     if (level_node->node_group != NULL)
3489       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3490
3491     level_node = level_node->next;
3492   }
3493 }
3494
3495 void LoadLevelArtworkInfo()
3496 {
3497   print_timestamp_init("LoadLevelArtworkInfo");
3498
3499   DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3500
3501   print_timestamp_time("DrawTimeText");
3502
3503   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3504   print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3505   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3506   print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3507   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3508   print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3509
3510   SaveArtworkInfoCache();
3511
3512   print_timestamp_time("SaveArtworkInfoCache");
3513
3514   /* needed for reloading level artwork not known at ealier stage */
3515
3516   if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3517   {
3518     artwork.gfx_current =
3519       getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3520     if (artwork.gfx_current == NULL)
3521       artwork.gfx_current =
3522         getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3523     if (artwork.gfx_current == NULL)
3524       artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3525   }
3526
3527   if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3528   {
3529     artwork.snd_current =
3530       getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3531     if (artwork.snd_current == NULL)
3532       artwork.snd_current =
3533         getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3534     if (artwork.snd_current == NULL)
3535       artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3536   }
3537
3538   if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3539   {
3540     artwork.mus_current =
3541       getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3542     if (artwork.mus_current == NULL)
3543       artwork.mus_current =
3544         getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3545     if (artwork.mus_current == NULL)
3546       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3547   }
3548
3549   print_timestamp_time("getTreeInfoFromIdentifier");
3550
3551   sortTreeInfo(&artwork.gfx_first);
3552   sortTreeInfo(&artwork.snd_first);
3553   sortTreeInfo(&artwork.mus_first);
3554
3555   print_timestamp_time("sortTreeInfo");
3556
3557 #if ENABLE_UNUSED_CODE
3558   dumpTreeInfo(artwork.gfx_first, 0);
3559   dumpTreeInfo(artwork.snd_first, 0);
3560   dumpTreeInfo(artwork.mus_first, 0);
3561 #endif
3562
3563   print_timestamp_done("LoadLevelArtworkInfo");
3564 }
3565
3566 static boolean AddUserLevelSetToLevelInfoExt(char *level_subdir_new)
3567 {
3568   // get level info tree node of first (original) user level set
3569   char *level_subdir_old = getLoginName();
3570   LevelDirTree *leveldir_old = getTreeInfoFromIdentifier(leveldir_first,
3571                                                          level_subdir_old);
3572   if (leveldir_old == NULL)             // should not happen
3573     return FALSE;
3574
3575   int draw_deactivation_mask = GetDrawDeactivationMask();
3576
3577   // override draw deactivation mask (temporarily disable drawing)
3578   SetDrawDeactivationMask(REDRAW_ALL);
3579
3580   // load new level set config and add it next to first user level set
3581   LoadLevelInfoFromLevelConf(&leveldir_old->next, NULL,
3582                              leveldir_old->basepath, level_subdir_new);
3583
3584   // set draw deactivation mask to previous value
3585   SetDrawDeactivationMask(draw_deactivation_mask);
3586
3587   // get level info tree node of newly added user level set
3588   LevelDirTree *leveldir_new = getTreeInfoFromIdentifier(leveldir_first,
3589                                                          level_subdir_new);
3590   if (leveldir_new == NULL)             // should not happen
3591     return FALSE;
3592
3593   // correct top link and parent node link of newly created tree node
3594   leveldir_new->node_top    = leveldir_old->node_top;
3595   leveldir_new->node_parent = leveldir_old->node_parent;
3596
3597   // sort level info tree to adjust position of newly added level set
3598   sortTreeInfo(&leveldir_first);
3599
3600   return TRUE;
3601 }
3602
3603 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
3604 {
3605   if (!AddUserLevelSetToLevelInfoExt(level_subdir_new))
3606     Error(ERR_EXIT, "internal level set structure corrupted -- aborting");
3607 }
3608
3609 char *getArtworkIdentifierForUserLevelSet(int type)
3610 {
3611   char *classic_artwork_set = getClassicArtworkSet(type);
3612
3613   /* check for custom artwork configured in "levelinfo.conf" */
3614   char *leveldir_artwork_set =
3615     *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
3616   boolean has_leveldir_artwork_set =
3617     (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
3618                                                classic_artwork_set));
3619
3620   /* check for custom artwork in sub-directory "graphics" etc. */
3621   TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
3622   char *leveldir_identifier = leveldir_current->identifier;
3623   boolean has_artwork_subdir =
3624     (getTreeInfoFromIdentifier(artwork_first_node,
3625                                leveldir_identifier) != NULL);
3626
3627   return (has_leveldir_artwork_set ? leveldir_artwork_set :
3628           has_artwork_subdir       ? leveldir_identifier :
3629           classic_artwork_set);
3630 }
3631
3632 boolean checkIfCustomArtworkExistsForCurrentLevelSet()
3633 {
3634   char *graphics_set =
3635     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
3636   char *sounds_set =
3637     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
3638   char *music_set =
3639     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
3640
3641   return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
3642           !strEqual(sounds_set,   SND_CLASSIC_SUBDIR) ||
3643           !strEqual(music_set,    MUS_CLASSIC_SUBDIR));
3644 }
3645
3646 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
3647                            char *level_author, int num_levels)
3648 {
3649   char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
3650   char *filename_tmp = getStringCat2(filename, ".tmp");
3651   FILE *file = NULL;
3652   FILE *file_tmp = NULL;
3653   char line[MAX_LINE_LEN];
3654   boolean success = FALSE;
3655   LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
3656                                                      level_subdir);
3657   // update values in level directory tree
3658
3659   if (level_name != NULL)
3660     setString(&leveldir->name, level_name);
3661
3662   if (level_author != NULL)
3663     setString(&leveldir->author, level_author);
3664
3665   if (num_levels != -1)
3666     leveldir->levels = num_levels;
3667
3668   // update values that depend on other values
3669
3670   setString(&leveldir->name_sorting, leveldir->name);
3671
3672   leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
3673
3674   // sort order of level sets may have changed
3675   sortTreeInfo(&leveldir_first);
3676
3677   if ((file     = fopen(filename,     MODE_READ)) &&
3678       (file_tmp = fopen(filename_tmp, MODE_WRITE)))
3679   {
3680     while (fgets(line, MAX_LINE_LEN, file))
3681     {
3682       if (strPrefix(line, "name:") && level_name != NULL)
3683         fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
3684       else if (strPrefix(line, "author:") && level_author != NULL)
3685         fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
3686       else if (strPrefix(line, "levels:") && num_levels != -1)
3687         fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
3688       else
3689         fputs(line, file_tmp);
3690     }
3691
3692     success = TRUE;
3693   }
3694
3695   if (file)
3696     fclose(file);
3697
3698   if (file_tmp)
3699     fclose(file_tmp);
3700
3701   if (success)
3702     success = (rename(filename_tmp, filename) == 0);
3703
3704   free(filename);
3705   free(filename_tmp);
3706
3707   return success;
3708 }
3709
3710 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
3711                            char *level_author, int num_levels,
3712                            boolean use_artwork_set)
3713 {
3714   LevelDirTree *level_info;
3715   char *filename;
3716   FILE *file;
3717   int i;
3718
3719   // create user level sub-directory, if needed
3720   createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
3721
3722   filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
3723
3724   if (!(file = fopen(filename, MODE_WRITE)))
3725   {
3726     Error(ERR_WARN, "cannot write level info file '%s'", filename);
3727     free(filename);
3728
3729     return FALSE;
3730   }
3731
3732   level_info = newTreeInfo();
3733
3734   /* always start with reliable default values */
3735   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3736
3737   setString(&level_info->name, level_name);
3738   setString(&level_info->author, level_author);
3739   level_info->levels = num_levels;
3740   level_info->first_level = 1;
3741   level_info->sort_priority = LEVELCLASS_PRIVATE_START;
3742   level_info->readonly = FALSE;
3743
3744   if (use_artwork_set)
3745   {
3746     level_info->graphics_set =
3747       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
3748     level_info->sounds_set =
3749       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
3750     level_info->music_set =
3751       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
3752   }
3753
3754   token_value_position = TOKEN_VALUE_POSITION_SHORT;
3755
3756   fprintFileHeader(file, LEVELINFO_FILENAME);
3757
3758   ldi = *level_info;
3759   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3760   {
3761     if (i == LEVELINFO_TOKEN_NAME ||
3762         i == LEVELINFO_TOKEN_AUTHOR ||
3763         i == LEVELINFO_TOKEN_LEVELS ||
3764         i == LEVELINFO_TOKEN_FIRST_LEVEL ||
3765         i == LEVELINFO_TOKEN_SORT_PRIORITY ||
3766         i == LEVELINFO_TOKEN_READONLY ||
3767         (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
3768                              i == LEVELINFO_TOKEN_SOUNDS_SET ||
3769                              i == LEVELINFO_TOKEN_MUSIC_SET)))
3770       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3771
3772     /* just to make things nicer :) */
3773     if (i == LEVELINFO_TOKEN_AUTHOR ||
3774         i == LEVELINFO_TOKEN_FIRST_LEVEL ||
3775         (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
3776       fprintf(file, "\n");      
3777   }
3778
3779   token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3780
3781   fclose(file);
3782
3783   SetFilePermissions(filename, PERMS_PRIVATE);
3784
3785   freeTreeInfo(level_info);
3786   free(filename);
3787
3788   return TRUE;
3789 }
3790
3791 static void SaveUserLevelInfo()
3792 {
3793   CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
3794 }
3795
3796 char *getSetupValue(int type, void *value)
3797 {
3798   static char value_string[MAX_LINE_LEN];
3799
3800   if (value == NULL)
3801     return NULL;
3802
3803   switch (type)
3804   {
3805     case TYPE_BOOLEAN:
3806       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3807       break;
3808
3809     case TYPE_SWITCH:
3810       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3811       break;
3812
3813     case TYPE_SWITCH3:
3814       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
3815                             *(int *)value == FALSE ? "off" : "on"));
3816       break;
3817
3818     case TYPE_YES_NO:
3819       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3820       break;
3821
3822     case TYPE_YES_NO_AUTO:
3823       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
3824                             *(int *)value == FALSE ? "no" : "yes"));
3825       break;
3826
3827     case TYPE_ECS_AGA:
3828       strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3829       break;
3830
3831     case TYPE_KEY:
3832       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3833       break;
3834
3835     case TYPE_KEY_X11:
3836       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3837       break;
3838
3839     case TYPE_INTEGER:
3840       sprintf(value_string, "%d", *(int *)value);
3841       break;
3842
3843     case TYPE_STRING:
3844       if (*(char **)value == NULL)
3845         return NULL;
3846
3847       strcpy(value_string, *(char **)value);
3848       break;
3849
3850     default:
3851       value_string[0] = '\0';
3852       break;
3853   }
3854
3855   if (type & TYPE_GHOSTED)
3856     strcpy(value_string, "n/a");
3857
3858   return value_string;
3859 }
3860
3861 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3862 {
3863   int i;
3864   char *line;
3865   static char token_string[MAX_LINE_LEN];
3866   int token_type = token_info[token_nr].type;
3867   void *setup_value = token_info[token_nr].value;
3868   char *token_text = token_info[token_nr].text;
3869   char *value_string = getSetupValue(token_type, setup_value);
3870
3871   /* build complete token string */
3872   sprintf(token_string, "%s%s", prefix, token_text);
3873
3874   /* build setup entry line */
3875   line = getFormattedSetupEntry(token_string, value_string);
3876
3877   if (token_type == TYPE_KEY_X11)
3878   {
3879     Key key = *(Key *)setup_value;
3880     char *keyname = getKeyNameFromKey(key);
3881
3882     /* add comment, if useful */
3883     if (!strEqual(keyname, "(undefined)") &&
3884         !strEqual(keyname, "(unknown)"))
3885     {
3886       /* add at least one whitespace */
3887       strcat(line, " ");
3888       for (i = strlen(line); i < token_comment_position; i++)
3889         strcat(line, " ");
3890
3891       strcat(line, "# ");
3892       strcat(line, keyname);
3893     }
3894   }
3895
3896   return line;
3897 }
3898
3899 void LoadLevelSetup_LastSeries()
3900 {
3901   /* ----------------------------------------------------------------------- */
3902   /* ~/.<program>/levelsetup.conf                                            */
3903   /* ----------------------------------------------------------------------- */
3904
3905   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3906   SetupFileHash *level_setup_hash = NULL;
3907
3908   /* always start with reliable default values */
3909   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3910
3911   if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
3912   {
3913     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3914                                                  DEFAULT_LEVELSET);
3915     if (leveldir_current == NULL)
3916       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3917   }
3918
3919   if ((level_setup_hash = loadSetupFileHash(filename)))
3920   {
3921     char *last_level_series =
3922       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3923
3924     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3925                                                  last_level_series);
3926     if (leveldir_current == NULL)
3927       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3928
3929     freeSetupFileHash(level_setup_hash);
3930   }
3931   else
3932   {
3933     Error(ERR_DEBUG, "using default setup values");
3934   }
3935
3936   free(filename);
3937 }
3938
3939 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
3940 {
3941   /* ----------------------------------------------------------------------- */
3942   /* ~/.<program>/levelsetup.conf                                            */
3943   /* ----------------------------------------------------------------------- */
3944
3945   // check if the current level directory structure is available at this point
3946   if (leveldir_current == NULL)
3947     return;
3948
3949   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3950   char *level_subdir = leveldir_current->subdir;
3951   FILE *file;
3952
3953   InitUserDataDirectory();
3954
3955   if (!(file = fopen(filename, MODE_WRITE)))
3956   {
3957     Error(ERR_WARN, "cannot write setup file '%s'", filename);
3958
3959     free(filename);
3960
3961     return;
3962   }
3963
3964   fprintFileHeader(file, LEVELSETUP_FILENAME);
3965
3966   if (deactivate_last_level_series)
3967     fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
3968
3969   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3970                                                level_subdir));
3971
3972   fclose(file);
3973
3974   SetFilePermissions(filename, PERMS_PRIVATE);
3975
3976   free(filename);
3977 }
3978
3979 void SaveLevelSetup_LastSeries()
3980 {
3981   SaveLevelSetup_LastSeries_Ext(FALSE);
3982 }
3983
3984 void SaveLevelSetup_LastSeries_Deactivate()
3985 {
3986   SaveLevelSetup_LastSeries_Ext(TRUE);
3987 }
3988
3989 static void checkSeriesInfo()
3990 {
3991   static char *level_directory = NULL;
3992   Directory *dir;
3993
3994   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3995
3996   level_directory = getPath2((leveldir_current->in_user_dir ?
3997                               getUserLevelDir(NULL) :
3998                               options.level_directory),
3999                              leveldir_current->fullpath);
4000
4001   if ((dir = openDirectory(level_directory)) == NULL)
4002   {
4003     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4004
4005     return;
4006   }
4007
4008   closeDirectory(dir);
4009 }
4010
4011 void LoadLevelSetup_SeriesInfo()
4012 {
4013   char *filename;
4014   SetupFileHash *level_setup_hash = NULL;
4015   char *level_subdir = leveldir_current->subdir;
4016   int i;
4017
4018   /* always start with reliable default values */
4019   level_nr = leveldir_current->first_level;
4020
4021   for (i = 0; i < MAX_LEVELS; i++)
4022   {
4023     LevelStats_setPlayed(i, 0);
4024     LevelStats_setSolved(i, 0);
4025   }
4026
4027   checkSeriesInfo();
4028
4029   /* ----------------------------------------------------------------------- */
4030   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
4031   /* ----------------------------------------------------------------------- */
4032
4033   level_subdir = leveldir_current->subdir;
4034
4035   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4036
4037   if ((level_setup_hash = loadSetupFileHash(filename)))
4038   {
4039     char *token_value;
4040
4041     /* get last played level in this level set */
4042
4043     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4044
4045     if (token_value)
4046     {
4047       level_nr = atoi(token_value);
4048
4049       if (level_nr < leveldir_current->first_level)
4050         level_nr = leveldir_current->first_level;
4051       if (level_nr > leveldir_current->last_level)
4052         level_nr = leveldir_current->last_level;
4053     }
4054
4055     /* get handicap level in this level set */
4056
4057     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4058
4059     if (token_value)
4060     {
4061       int level_nr = atoi(token_value);
4062
4063       if (level_nr < leveldir_current->first_level)
4064         level_nr = leveldir_current->first_level;
4065       if (level_nr > leveldir_current->last_level + 1)
4066         level_nr = leveldir_current->last_level;
4067
4068       if (leveldir_current->user_defined || !leveldir_current->handicap)
4069         level_nr = leveldir_current->last_level;
4070
4071       leveldir_current->handicap_level = level_nr;
4072     }
4073
4074     /* get number of played and solved levels in this level set */
4075
4076     BEGIN_HASH_ITERATION(level_setup_hash, itr)
4077     {
4078       char *token = HASH_ITERATION_TOKEN(itr);
4079       char *value = HASH_ITERATION_VALUE(itr);
4080
4081       if (strlen(token) == 3 &&
4082           token[0] >= '0' && token[0] <= '9' &&
4083           token[1] >= '0' && token[1] <= '9' &&
4084           token[2] >= '0' && token[2] <= '9')
4085       {
4086         int level_nr = atoi(token);
4087
4088         if (value != NULL)
4089           LevelStats_setPlayed(level_nr, atoi(value));  /* read 1st column */
4090
4091         value = strchr(value, ' ');
4092
4093         if (value != NULL)
4094           LevelStats_setSolved(level_nr, atoi(value));  /* read 2nd column */
4095       }
4096     }
4097     END_HASH_ITERATION(hash, itr)
4098
4099     freeSetupFileHash(level_setup_hash);
4100   }
4101   else
4102   {
4103     Error(ERR_DEBUG, "using default setup values");
4104   }
4105
4106   free(filename);
4107 }
4108
4109 void SaveLevelSetup_SeriesInfo()
4110 {
4111   char *filename;
4112   char *level_subdir = leveldir_current->subdir;
4113   char *level_nr_str = int2str(level_nr, 0);
4114   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4115   FILE *file;
4116   int i;
4117
4118   /* ----------------------------------------------------------------------- */
4119   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
4120   /* ----------------------------------------------------------------------- */
4121
4122   InitLevelSetupDirectory(level_subdir);
4123
4124   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4125
4126   if (!(file = fopen(filename, MODE_WRITE)))
4127   {
4128     Error(ERR_WARN, "cannot write setup file '%s'", filename);
4129     free(filename);
4130     return;
4131   }
4132
4133   fprintFileHeader(file, LEVELSETUP_FILENAME);
4134
4135   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4136                                                level_nr_str));
4137   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4138                                                  handicap_level_str));
4139
4140   for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4141        i++)
4142   {
4143     if (LevelStats_getPlayed(i) > 0 ||
4144         LevelStats_getSolved(i) > 0)
4145     {
4146       char token[16];
4147       char value[16];
4148
4149       sprintf(token, "%03d", i);
4150       sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4151
4152       fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4153     }
4154   }
4155
4156   fclose(file);
4157
4158   SetFilePermissions(filename, PERMS_PRIVATE);
4159
4160   free(filename);
4161 }
4162
4163 int LevelStats_getPlayed(int nr)
4164 {
4165   return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4166 }
4167
4168 int LevelStats_getSolved(int nr)
4169 {
4170   return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4171 }
4172
4173 void LevelStats_setPlayed(int nr, int value)
4174 {
4175   if (nr >= 0 && nr < MAX_LEVELS)
4176     level_stats[nr].played = value;
4177 }
4178
4179 void LevelStats_setSolved(int nr, int value)
4180 {
4181   if (nr >= 0 && nr < MAX_LEVELS)
4182     level_stats[nr].solved = value;
4183 }
4184
4185 void LevelStats_incPlayed(int nr)
4186 {
4187   if (nr >= 0 && nr < MAX_LEVELS)
4188     level_stats[nr].played++;
4189 }
4190
4191 void LevelStats_incSolved(int nr)
4192 {
4193   if (nr >= 0 && nr < MAX_LEVELS)
4194     level_stats[nr].solved++;
4195 }