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