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