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