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