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