added storing if last level set was selected from "last played" menu
[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       if (directoryExists_CheckMusic(directory, check_music))
1330         return directory;
1331
1332       free(directory);
1333
1334       // take missing artwork configured in level set config from default
1335       skip_setup_artwork = TRUE;
1336     }
1337   }
1338
1339   if (!skip_setup_artwork)
1340   {
1341     // 3rd try: look for special artwork in configured artwork directory
1342     directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1343     if (directoryExists_CheckMusic(directory, check_music))
1344       return directory;
1345
1346     free(directory);
1347   }
1348
1349   // 4th try: look for default artwork in new default artwork directory
1350   directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1351   if (directoryExists_CheckMusic(directory, check_music))
1352     return directory;
1353
1354   free(directory);
1355
1356   // 5th try: look for default artwork in old default artwork directory
1357   directory = getStringCopy(options.music_directory);
1358   if (directoryExists_CheckMusic(directory, check_music))
1359     return directory;
1360
1361   return NULL;          // cannot find specified artwork file anywhere
1362 }
1363
1364 char *getCustomMusicDirectory(void)
1365 {
1366   return getCustomMusicDirectoryExt(FALSE);
1367 }
1368
1369 char *getCustomMusicDirectory_NoConf(void)
1370 {
1371   return getCustomMusicDirectoryExt(TRUE);
1372 }
1373
1374 void MarkTapeDirectoryUploadsAsComplete(char *level_subdir)
1375 {
1376   char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1377
1378   touchFile(filename);
1379
1380   checked_free(filename);
1381 }
1382
1383 void MarkTapeDirectoryUploadsAsIncomplete(char *level_subdir)
1384 {
1385   char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1386
1387   unlink(filename);
1388
1389   checked_free(filename);
1390 }
1391
1392 boolean CheckTapeDirectoryUploadsComplete(char *level_subdir)
1393 {
1394   char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1395   boolean success = fileExists(filename);
1396
1397   checked_free(filename);
1398
1399   return success;
1400 }
1401
1402 void InitMissingFileHash(void)
1403 {
1404   if (missing_file_hash == NULL)
1405     freeSetupFileHash(missing_file_hash);
1406
1407   missing_file_hash = newSetupFileHash();
1408 }
1409
1410 void InitTapeDirectory(char *level_subdir)
1411 {
1412   boolean new_tape_dir = !directoryExists(getTapeDir(level_subdir));
1413
1414   createDirectory(getUserGameDataDir(), "user data");
1415   createDirectory(getTapeDir(NULL), "main tape");
1416   createDirectory(getTapeDir(level_subdir), "level tape");
1417
1418   if (new_tape_dir)
1419     MarkTapeDirectoryUploadsAsComplete(level_subdir);
1420 }
1421
1422 void InitScoreDirectory(char *level_subdir)
1423 {
1424   createDirectory(getMainUserGameDataDir(), "main user data");
1425   createDirectory(getScoreDir(NULL), "main score");
1426   createDirectory(getScoreDir(level_subdir), "level score");
1427 }
1428
1429 void InitScoreCacheDirectory(char *level_subdir)
1430 {
1431   createDirectory(getMainUserGameDataDir(), "main user data");
1432   createDirectory(getCacheDir(), "cache data");
1433   createDirectory(getScoreCacheDir(NULL), "main score");
1434   createDirectory(getScoreCacheDir(level_subdir), "level score");
1435 }
1436
1437 void InitScoreTapeDirectory(char *level_subdir, int nr)
1438 {
1439   InitScoreDirectory(level_subdir);
1440
1441   createDirectory(getScoreTapeDir(level_subdir, nr), "score tape");
1442 }
1443
1444 void InitScoreCacheTapeDirectory(char *level_subdir, int nr)
1445 {
1446   InitScoreCacheDirectory(level_subdir);
1447
1448   createDirectory(getScoreCacheTapeDir(level_subdir, nr), "score tape");
1449 }
1450
1451 static void SaveUserLevelInfo(void);
1452
1453 void InitUserLevelDirectory(char *level_subdir)
1454 {
1455   if (!directoryExists(getUserLevelDir(level_subdir)))
1456   {
1457     createDirectory(getMainUserGameDataDir(), "main user data");
1458     createDirectory(getUserLevelDir(NULL), "main user level");
1459
1460     if (setup.internal.create_user_levelset)
1461     {
1462       createDirectory(getUserLevelDir(level_subdir), "user level");
1463
1464       SaveUserLevelInfo();
1465     }
1466   }
1467 }
1468
1469 void InitNetworkLevelDirectory(char *level_subdir)
1470 {
1471   if (!directoryExists(getNetworkLevelDir(level_subdir)))
1472   {
1473     createDirectory(getMainUserGameDataDir(), "main user data");
1474     createDirectory(getNetworkDir(), "network data");
1475     createDirectory(getNetworkLevelDir(NULL), "main network level");
1476     createDirectory(getNetworkLevelDir(level_subdir), "network level");
1477   }
1478 }
1479
1480 void InitLevelSetupDirectory(char *level_subdir)
1481 {
1482   createDirectory(getUserGameDataDir(), "user data");
1483   createDirectory(getLevelSetupDir(NULL), "main level setup");
1484   createDirectory(getLevelSetupDir(level_subdir), "level setup");
1485 }
1486
1487 static void InitCacheDirectory(void)
1488 {
1489   createDirectory(getMainUserGameDataDir(), "main user data");
1490   createDirectory(getCacheDir(), "cache data");
1491 }
1492
1493
1494 // ----------------------------------------------------------------------------
1495 // some functions to handle lists of level and artwork directories
1496 // ----------------------------------------------------------------------------
1497
1498 TreeInfo *newTreeInfo(void)
1499 {
1500   return checked_calloc(sizeof(TreeInfo));
1501 }
1502
1503 TreeInfo *newTreeInfo_setDefaults(int type)
1504 {
1505   TreeInfo *ti = newTreeInfo();
1506
1507   setTreeInfoToDefaults(ti, type);
1508
1509   return ti;
1510 }
1511
1512 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1513 {
1514   node_new->next = *node_first;
1515   *node_first = node_new;
1516 }
1517
1518 void removeTreeInfo(TreeInfo **node_first)
1519 {
1520   TreeInfo *node_old = *node_first;
1521
1522   *node_first = node_old->next;
1523   node_old->next = NULL;
1524
1525   freeTreeInfo(node_old);
1526 }
1527
1528 int numTreeInfo(TreeInfo *node)
1529 {
1530   int num = 0;
1531
1532   while (node)
1533   {
1534     num++;
1535     node = node->next;
1536   }
1537
1538   return num;
1539 }
1540
1541 boolean validLevelSeries(TreeInfo *node)
1542 {
1543   // in a number of cases, tree node is no valid level set
1544   if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1545     return FALSE;
1546
1547   return TRUE;
1548 }
1549
1550 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1551 {
1552   if (validLevelSeries(node))
1553     return node;
1554   else if (node->is_copy)
1555     return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1556   else
1557     return getFirstValidTreeInfoEntry(default_node);
1558 }
1559
1560 static TreeInfo *getValidTreeInfoEntryExt(TreeInfo *node, boolean get_next_node)
1561 {
1562   if (node == NULL)
1563     return NULL;
1564
1565   if (node->node_group)         // enter node group (step down into tree)
1566     return getFirstValidTreeInfoEntry(node->node_group);
1567
1568   if (node->parent_link)        // skip first node (back link) of node group
1569     get_next_node = TRUE;
1570
1571   if (!get_next_node)           // get current regular tree node
1572     return node;
1573
1574   // get next regular tree node, or step up until one is found
1575   while (node->next == NULL && node->node_parent != NULL)
1576     node = node->node_parent;
1577
1578   return getFirstValidTreeInfoEntry(node->next);
1579 }
1580
1581 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1582 {
1583   return getValidTreeInfoEntryExt(node, FALSE);
1584 }
1585
1586 TreeInfo *getNextValidTreeInfoEntry(TreeInfo *node)
1587 {
1588   return getValidTreeInfoEntryExt(node, TRUE);
1589 }
1590
1591 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1592 {
1593   if (node == NULL)
1594     return NULL;
1595
1596   if (node->node_parent == NULL)                // top level group
1597     return *node->node_top;
1598   else                                          // sub level group
1599     return node->node_parent->node_group;
1600 }
1601
1602 int numTreeInfoInGroup(TreeInfo *node)
1603 {
1604   return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1605 }
1606
1607 int getPosFromTreeInfo(TreeInfo *node)
1608 {
1609   TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1610   int pos = 0;
1611
1612   while (node_cmp)
1613   {
1614     if (node_cmp == node)
1615       return pos;
1616
1617     pos++;
1618     node_cmp = node_cmp->next;
1619   }
1620
1621   return 0;
1622 }
1623
1624 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1625 {
1626   TreeInfo *node_default = node;
1627   int pos_cmp = 0;
1628
1629   while (node)
1630   {
1631     if (pos_cmp == pos)
1632       return node;
1633
1634     pos_cmp++;
1635     node = node->next;
1636   }
1637
1638   return node_default;
1639 }
1640
1641 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1642                                               int node_type_wanted)
1643 {
1644   if (identifier == NULL)
1645     return NULL;
1646
1647   while (node)
1648   {
1649     if (TREE_NODE_TYPE(node) == node_type_wanted &&
1650         strEqual(identifier, node->identifier))
1651       return node;
1652
1653     if (node->node_group)
1654     {
1655       TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1656                                                           identifier,
1657                                                           node_type_wanted);
1658       if (node_group)
1659         return node_group;
1660     }
1661
1662     node = node->next;
1663   }
1664
1665   return NULL;
1666 }
1667
1668 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1669 {
1670   return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1671 }
1672
1673 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1674                                TreeInfo *node, boolean skip_sets_without_levels)
1675 {
1676   TreeInfo *node_new;
1677
1678   if (node == NULL)
1679     return NULL;
1680
1681   if (!node->parent_link && !node->level_group &&
1682       skip_sets_without_levels && node->levels == 0)
1683     return cloneTreeNode(node_top, node_parent, node->next,
1684                          skip_sets_without_levels);
1685
1686   node_new = getTreeInfoCopy(node);             // copy complete node
1687
1688   node_new->node_top = node_top;                // correct top node link
1689   node_new->node_parent = node_parent;          // correct parent node link
1690
1691   if (node->level_group)
1692     node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1693                                          skip_sets_without_levels);
1694
1695   node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1696                                  skip_sets_without_levels);
1697   
1698   return node_new;
1699 }
1700
1701 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1702 {
1703   TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1704
1705   *ti_new = ti_cloned;
1706 }
1707
1708 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1709 {
1710   boolean settings_changed = FALSE;
1711
1712   while (node)
1713   {
1714     boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1715     boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1716     boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1717     boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1718     char *graphics_set = NULL;
1719
1720     if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1721       graphics_set = node->graphics_set_ecs;
1722
1723     if (node->graphics_set_aga && (want_aga || has_only_aga))
1724       graphics_set = node->graphics_set_aga;
1725
1726     if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1727     {
1728       setString(&node->graphics_set, graphics_set);
1729       settings_changed = TRUE;
1730     }
1731
1732     if (node->node_group != NULL)
1733       settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1734
1735     node = node->next;
1736   }
1737
1738   return settings_changed;
1739 }
1740
1741 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1742 {
1743   boolean settings_changed = FALSE;
1744
1745   while (node)
1746   {
1747     boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1748     boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1749     boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1750     boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1751     char *sounds_set = NULL;
1752
1753     if (node->sounds_set_default && (want_default || has_only_default))
1754       sounds_set = node->sounds_set_default;
1755
1756     if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1757       sounds_set = node->sounds_set_lowpass;
1758
1759     if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1760     {
1761       setString(&node->sounds_set, sounds_set);
1762       settings_changed = TRUE;
1763     }
1764
1765     if (node->node_group != NULL)
1766       settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1767
1768     node = node->next;
1769   }
1770
1771   return settings_changed;
1772 }
1773
1774 int dumpTreeInfo(TreeInfo *node, int depth)
1775 {
1776   char bullet_list[] = { '-', '*', 'o' };
1777   int num_leaf_nodes = 0;
1778   int i;
1779
1780   if (depth == 0)
1781     Debug("tree", "Dumping TreeInfo:");
1782
1783   while (node)
1784   {
1785     char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1786
1787     for (i = 0; i < depth * 2; i++)
1788       DebugContinued("", " ");
1789
1790     DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1791                    bullet, node->name, node->identifier,
1792                    (node->node_parent ? node->node_parent->identifier : "-"),
1793                    (node->node_group ? "[GROUP]" :
1794                     node->is_copy ? "[COPY]" : ""));
1795
1796     if (!node->node_group && !node->parent_link)
1797       num_leaf_nodes++;
1798
1799     /*
1800     // use for dumping artwork info tree
1801     Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1802           node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1803     */
1804
1805     if (node->node_group != NULL)
1806       num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1);
1807
1808     node = node->next;
1809   }
1810
1811   if (depth == 0)
1812     Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes);
1813
1814   return num_leaf_nodes;
1815 }
1816
1817 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1818                                 int (*compare_function)(const void *,
1819                                                         const void *))
1820 {
1821   int num_nodes = numTreeInfo(*node_first);
1822   TreeInfo **sort_array;
1823   TreeInfo *node = *node_first;
1824   int i = 0;
1825
1826   if (num_nodes == 0)
1827     return;
1828
1829   // allocate array for sorting structure pointers
1830   sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1831
1832   // writing structure pointers to sorting array
1833   while (i < num_nodes && node)         // double boundary check...
1834   {
1835     sort_array[i] = node;
1836
1837     i++;
1838     node = node->next;
1839   }
1840
1841   // sorting the structure pointers in the sorting array
1842   qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1843         compare_function);
1844
1845   // update the linkage of list elements with the sorted node array
1846   for (i = 0; i < num_nodes - 1; i++)
1847     sort_array[i]->next = sort_array[i + 1];
1848   sort_array[num_nodes - 1]->next = NULL;
1849
1850   // update the linkage of the main list anchor pointer
1851   *node_first = sort_array[0];
1852
1853   free(sort_array);
1854
1855   // now recursively sort the level group structures
1856   node = *node_first;
1857   while (node)
1858   {
1859     if (node->node_group != NULL)
1860       sortTreeInfoBySortFunction(&node->node_group, compare_function);
1861
1862     node = node->next;
1863   }
1864 }
1865
1866 void sortTreeInfo(TreeInfo **node_first)
1867 {
1868   sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1869 }
1870
1871
1872 // ============================================================================
1873 // some stuff from "files.c"
1874 // ============================================================================
1875
1876 #if defined(PLATFORM_WINDOWS)
1877 #ifndef S_IRGRP
1878 #define S_IRGRP S_IRUSR
1879 #endif
1880 #ifndef S_IROTH
1881 #define S_IROTH S_IRUSR
1882 #endif
1883 #ifndef S_IWGRP
1884 #define S_IWGRP S_IWUSR
1885 #endif
1886 #ifndef S_IWOTH
1887 #define S_IWOTH S_IWUSR
1888 #endif
1889 #ifndef S_IXGRP
1890 #define S_IXGRP S_IXUSR
1891 #endif
1892 #ifndef S_IXOTH
1893 #define S_IXOTH S_IXUSR
1894 #endif
1895 #ifndef S_IRWXG
1896 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1897 #endif
1898 #ifndef S_ISGID
1899 #define S_ISGID 0
1900 #endif
1901 #endif  // PLATFORM_WINDOWS
1902
1903 // file permissions for newly written files
1904 #define MODE_R_ALL              (S_IRUSR | S_IRGRP | S_IROTH)
1905 #define MODE_W_ALL              (S_IWUSR | S_IWGRP | S_IWOTH)
1906 #define MODE_X_ALL              (S_IXUSR | S_IXGRP | S_IXOTH)
1907
1908 #define MODE_W_PRIVATE          (S_IWUSR)
1909 #define MODE_W_PUBLIC_FILE      (S_IWUSR | S_IWGRP)
1910 #define MODE_W_PUBLIC_DIR       (S_IWUSR | S_IWGRP | S_ISGID)
1911
1912 #define DIR_PERMS_PRIVATE       (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1913 #define DIR_PERMS_PUBLIC        (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1914 #define DIR_PERMS_PUBLIC_ALL    (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1915
1916 #define FILE_PERMS_PRIVATE      (MODE_R_ALL | MODE_W_PRIVATE)
1917 #define FILE_PERMS_PUBLIC       (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1918 #define FILE_PERMS_PUBLIC_ALL   (MODE_R_ALL | MODE_W_ALL)
1919
1920
1921 char *getHomeDir(void)
1922 {
1923   static char *dir = NULL;
1924
1925 #if defined(PLATFORM_WINDOWS)
1926   if (dir == NULL)
1927   {
1928     dir = checked_malloc(MAX_PATH + 1);
1929
1930     if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1931       strcpy(dir, ".");
1932   }
1933 #elif defined(PLATFORM_EMSCRIPTEN)
1934   dir = PERSISTENT_DIRECTORY;
1935 #elif defined(PLATFORM_UNIX)
1936   if (dir == NULL)
1937   {
1938     if ((dir = getenv("HOME")) == NULL)
1939     {
1940       dir = getUnixHomeDir();
1941
1942       if (dir != NULL)
1943         dir = getStringCopy(dir);
1944       else
1945         dir = ".";
1946     }
1947   }
1948 #else
1949   dir = ".";
1950 #endif
1951
1952   return dir;
1953 }
1954
1955 char *getPersonalDataDir(void)
1956 {
1957   static char *personal_data_dir = NULL;
1958
1959 #if defined(PLATFORM_MAC)
1960   if (personal_data_dir == NULL)
1961     personal_data_dir = getPath2(getHomeDir(), "Documents");
1962 #else
1963   if (personal_data_dir == NULL)
1964     personal_data_dir = getHomeDir();
1965 #endif
1966
1967   return personal_data_dir;
1968 }
1969
1970 char *getMainUserGameDataDir(void)
1971 {
1972   static char *main_user_data_dir = NULL;
1973
1974 #if defined(PLATFORM_ANDROID)
1975   if (main_user_data_dir == NULL)
1976     main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1977                                   SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1978                                   SDL_AndroidGetExternalStoragePath() :
1979                                   SDL_AndroidGetInternalStoragePath());
1980 #else
1981   if (main_user_data_dir == NULL)
1982     main_user_data_dir = getPath2(getPersonalDataDir(),
1983                                   program.userdata_subdir);
1984 #endif
1985
1986   return main_user_data_dir;
1987 }
1988
1989 char *getUserGameDataDir(void)
1990 {
1991   if (user.nr == 0)
1992     return getMainUserGameDataDir();
1993   else
1994     return getUserDir(user.nr);
1995 }
1996
1997 char *getSetupDir(void)
1998 {
1999   return getUserGameDataDir();
2000 }
2001
2002 static mode_t posix_umask(mode_t mask)
2003 {
2004 #if defined(PLATFORM_UNIX)
2005   return umask(mask);
2006 #else
2007   return 0;
2008 #endif
2009 }
2010
2011 static int posix_mkdir(const char *pathname, mode_t mode)
2012 {
2013 #if defined(PLATFORM_WINDOWS)
2014   return mkdir(pathname);
2015 #else
2016   return mkdir(pathname, mode);
2017 #endif
2018 }
2019
2020 static boolean posix_process_running_setgid(void)
2021 {
2022 #if defined(PLATFORM_UNIX)
2023   return (getgid() != getegid());
2024 #else
2025   return FALSE;
2026 #endif
2027 }
2028
2029 void createDirectory(char *dir, char *text)
2030 {
2031   if (directoryExists(dir))
2032     return;
2033
2034   // leave "other" permissions in umask untouched, but ensure group parts
2035   // of USERDATA_DIR_MODE are not masked
2036   int permission_class = PERMS_PRIVATE;
2037   mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
2038                      DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
2039   mode_t last_umask = posix_umask(0);
2040   mode_t group_umask = ~(dir_mode & S_IRWXG);
2041   int running_setgid = posix_process_running_setgid();
2042
2043   if (permission_class == PERMS_PUBLIC)
2044   {
2045     // if we're setgid, protect files against "other"
2046     // else keep umask(0) to make the dir world-writable
2047
2048     if (running_setgid)
2049       posix_umask(last_umask & group_umask);
2050     else
2051       dir_mode = DIR_PERMS_PUBLIC_ALL;
2052   }
2053
2054   if (posix_mkdir(dir, dir_mode) != 0)
2055     Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
2056
2057   if (permission_class == PERMS_PUBLIC && !running_setgid)
2058     chmod(dir, dir_mode);
2059
2060   posix_umask(last_umask);              // restore previous umask
2061 }
2062
2063 void InitMainUserDataDirectory(void)
2064 {
2065   createDirectory(getMainUserGameDataDir(), "main user data");
2066 }
2067
2068 void InitUserDataDirectory(void)
2069 {
2070   createDirectory(getMainUserGameDataDir(), "main user data");
2071
2072   if (user.nr != 0)
2073   {
2074     createDirectory(getUserDir(-1), "users");
2075     createDirectory(getUserDir(user.nr), "user data");
2076   }
2077 }
2078
2079 void SetFilePermissions(char *filename, int permission_class)
2080 {
2081   int running_setgid = posix_process_running_setgid();
2082   int perms = (permission_class == PERMS_PRIVATE ?
2083                FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
2084
2085   if (permission_class == PERMS_PUBLIC && !running_setgid)
2086     perms = FILE_PERMS_PUBLIC_ALL;
2087
2088   chmod(filename, perms);
2089 }
2090
2091 void fprintFileHeader(FILE *file, char *basename)
2092 {
2093   char *prefix = "# ";
2094   char *sep1 = "=";
2095
2096   fprintf_line_with_prefix(file, prefix, sep1, 77);
2097   fprintf(file, "%s%s\n", prefix, basename);
2098   fprintf_line_with_prefix(file, prefix, sep1, 77);
2099   fprintf(file, "\n");
2100 }
2101
2102 int getFileVersionFromCookieString(const char *cookie)
2103 {
2104   const char *ptr_cookie1, *ptr_cookie2;
2105   const char *pattern1 = "_FILE_VERSION_";
2106   const char *pattern2 = "?.?";
2107   const int len_cookie = strlen(cookie);
2108   const int len_pattern1 = strlen(pattern1);
2109   const int len_pattern2 = strlen(pattern2);
2110   const int len_pattern = len_pattern1 + len_pattern2;
2111   int version_super, version_major;
2112
2113   if (len_cookie <= len_pattern)
2114     return -1;
2115
2116   ptr_cookie1 = &cookie[len_cookie - len_pattern];
2117   ptr_cookie2 = &cookie[len_cookie - len_pattern2];
2118
2119   if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
2120     return -1;
2121
2122   if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
2123       ptr_cookie2[1] != '.' ||
2124       ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
2125     return -1;
2126
2127   version_super = ptr_cookie2[0] - '0';
2128   version_major = ptr_cookie2[2] - '0';
2129
2130   return VERSION_IDENT(version_super, version_major, 0, 0);
2131 }
2132
2133 boolean checkCookieString(const char *cookie, const char *template)
2134 {
2135   const char *pattern = "_FILE_VERSION_?.?";
2136   const int len_cookie = strlen(cookie);
2137   const int len_template = strlen(template);
2138   const int len_pattern = strlen(pattern);
2139
2140   if (len_cookie != len_template)
2141     return FALSE;
2142
2143   if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
2144     return FALSE;
2145
2146   return TRUE;
2147 }
2148
2149
2150 // ----------------------------------------------------------------------------
2151 // setup file list and hash handling functions
2152 // ----------------------------------------------------------------------------
2153
2154 char *getFormattedSetupEntry(char *token, char *value)
2155 {
2156   int i;
2157   static char entry[MAX_LINE_LEN];
2158
2159   // if value is an empty string, just return token without value
2160   if (*value == '\0')
2161     return token;
2162
2163   // start with the token and some spaces to format output line
2164   sprintf(entry, "%s:", token);
2165   for (i = strlen(entry); i < token_value_position; i++)
2166     strcat(entry, " ");
2167
2168   // continue with the token's value
2169   strcat(entry, value);
2170
2171   return entry;
2172 }
2173
2174 SetupFileList *newSetupFileList(char *token, char *value)
2175 {
2176   SetupFileList *new = checked_malloc(sizeof(SetupFileList));
2177
2178   new->token = getStringCopy(token);
2179   new->value = getStringCopy(value);
2180
2181   new->next = NULL;
2182
2183   return new;
2184 }
2185
2186 void freeSetupFileList(SetupFileList *list)
2187 {
2188   if (list == NULL)
2189     return;
2190
2191   checked_free(list->token);
2192   checked_free(list->value);
2193
2194   if (list->next)
2195     freeSetupFileList(list->next);
2196
2197   free(list);
2198 }
2199
2200 char *getListEntry(SetupFileList *list, char *token)
2201 {
2202   if (list == NULL)
2203     return NULL;
2204
2205   if (strEqual(list->token, token))
2206     return list->value;
2207   else
2208     return getListEntry(list->next, token);
2209 }
2210
2211 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
2212 {
2213   if (list == NULL)
2214     return NULL;
2215
2216   if (strEqual(list->token, token))
2217   {
2218     checked_free(list->value);
2219
2220     list->value = getStringCopy(value);
2221
2222     return list;
2223   }
2224   else if (list->next == NULL)
2225     return (list->next = newSetupFileList(token, value));
2226   else
2227     return setListEntry(list->next, token, value);
2228 }
2229
2230 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
2231 {
2232   if (list == NULL)
2233     return NULL;
2234
2235   if (list->next == NULL)
2236     return (list->next = newSetupFileList(token, value));
2237   else
2238     return addListEntry(list->next, token, value);
2239 }
2240
2241 #if ENABLE_UNUSED_CODE
2242 #ifdef DEBUG
2243 static void printSetupFileList(SetupFileList *list)
2244 {
2245   if (!list)
2246     return;
2247
2248   Debug("setup:printSetupFileList", "token: '%s'", list->token);
2249   Debug("setup:printSetupFileList", "value: '%s'", list->value);
2250
2251   printSetupFileList(list->next);
2252 }
2253 #endif
2254 #endif
2255
2256 #ifdef DEBUG
2257 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
2258 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
2259 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
2260 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
2261 #else
2262 #define insert_hash_entry hashtable_insert
2263 #define search_hash_entry hashtable_search
2264 #define change_hash_entry hashtable_change
2265 #define remove_hash_entry hashtable_remove
2266 #endif
2267
2268 unsigned int get_hash_from_key(void *key)
2269 {
2270   /*
2271     djb2
2272
2273     This algorithm (k=33) was first reported by Dan Bernstein many years ago in
2274     'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
2275     uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
2276     it works better than many other constants, prime or not) has never been
2277     adequately explained.
2278
2279     If you just want to have a good hash function, and cannot wait, djb2
2280     is one of the best string hash functions i know. It has excellent
2281     distribution and speed on many different sets of keys and table sizes.
2282     You are not likely to do better with one of the "well known" functions
2283     such as PJW, K&R, etc.
2284
2285     Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2286   */
2287
2288   char *str = (char *)key;
2289   unsigned int hash = 5381;
2290   int c;
2291
2292   while ((c = *str++))
2293     hash = ((hash << 5) + hash) + c;    // hash * 33 + c
2294
2295   return hash;
2296 }
2297
2298 int hash_keys_are_equal(void *key1, void *key2)
2299 {
2300   return (strEqual((char *)key1, (char *)key2));
2301 }
2302
2303 SetupFileHash *newSetupFileHash(void)
2304 {
2305   SetupFileHash *new_hash =
2306     create_hashtable(16, 0.75, get_hash_from_key, hash_keys_are_equal);
2307
2308   if (new_hash == NULL)
2309     Fail("create_hashtable() failed -- out of memory");
2310
2311   return new_hash;
2312 }
2313
2314 void freeSetupFileHash(SetupFileHash *hash)
2315 {
2316   if (hash == NULL)
2317     return;
2318
2319   hashtable_destroy(hash, 1);   // 1 == also free values stored in hash
2320 }
2321
2322 char *getHashEntry(SetupFileHash *hash, char *token)
2323 {
2324   if (hash == NULL)
2325     return NULL;
2326
2327   return search_hash_entry(hash, token);
2328 }
2329
2330 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2331 {
2332   char *value_copy;
2333
2334   if (hash == NULL)
2335     return;
2336
2337   value_copy = getStringCopy(value);
2338
2339   // change value; if it does not exist, insert it as new
2340   if (!change_hash_entry(hash, token, value_copy))
2341     if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2342       Fail("cannot insert into hash -- aborting");
2343 }
2344
2345 char *removeHashEntry(SetupFileHash *hash, char *token)
2346 {
2347   if (hash == NULL)
2348     return NULL;
2349
2350   return remove_hash_entry(hash, token);
2351 }
2352
2353 #if ENABLE_UNUSED_CODE
2354 #if DEBUG
2355 static void printSetupFileHash(SetupFileHash *hash)
2356 {
2357   BEGIN_HASH_ITERATION(hash, itr)
2358   {
2359     Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2360     Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2361   }
2362   END_HASH_ITERATION(hash, itr)
2363 }
2364 #endif
2365 #endif
2366
2367 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE            1
2368 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING            0
2369 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH             0
2370
2371 static boolean token_value_separator_found = FALSE;
2372 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2373 static boolean token_value_separator_warning = FALSE;
2374 #endif
2375 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2376 static boolean token_already_exists_warning = FALSE;
2377 #endif
2378
2379 static boolean getTokenValueFromSetupLineExt(char *line,
2380                                              char **token_ptr, char **value_ptr,
2381                                              char *filename, char *line_raw,
2382                                              int line_nr,
2383                                              boolean separator_required)
2384 {
2385   static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2386   char *token, *value, *line_ptr;
2387
2388   // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2389   if (line_raw == NULL)
2390   {
2391     strncpy(line_copy, line, MAX_LINE_LEN);
2392     line_copy[MAX_LINE_LEN] = '\0';
2393     line = line_copy;
2394
2395     strcpy(line_raw_copy, line_copy);
2396     line_raw = line_raw_copy;
2397   }
2398
2399   // cut trailing comment from input line
2400   for (line_ptr = line; *line_ptr; line_ptr++)
2401   {
2402     if (*line_ptr == '#')
2403     {
2404       *line_ptr = '\0';
2405       break;
2406     }
2407   }
2408
2409   // cut trailing whitespaces from input line
2410   for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2411     if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2412       *line_ptr = '\0';
2413
2414   // ignore empty lines
2415   if (*line == '\0')
2416     return FALSE;
2417
2418   // cut leading whitespaces from token
2419   for (token = line; *token; token++)
2420     if (*token != ' ' && *token != '\t')
2421       break;
2422
2423   // start with empty value as reliable default
2424   value = "";
2425
2426   token_value_separator_found = FALSE;
2427
2428   // find end of token to determine start of value
2429   for (line_ptr = token; *line_ptr; line_ptr++)
2430   {
2431     // first look for an explicit token/value separator, like ':' or '='
2432     if (*line_ptr == ':' || *line_ptr == '=')
2433     {
2434       *line_ptr = '\0';                 // terminate token string
2435       value = line_ptr + 1;             // set beginning of value
2436
2437       token_value_separator_found = TRUE;
2438
2439       break;
2440     }
2441   }
2442
2443 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2444   // fallback: if no token/value separator found, also allow whitespaces
2445   if (!token_value_separator_found && !separator_required)
2446   {
2447     for (line_ptr = token; *line_ptr; line_ptr++)
2448     {
2449       if (*line_ptr == ' ' || *line_ptr == '\t')
2450       {
2451         *line_ptr = '\0';               // terminate token string
2452         value = line_ptr + 1;           // set beginning of value
2453
2454         token_value_separator_found = TRUE;
2455
2456         break;
2457       }
2458     }
2459
2460 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2461     if (token_value_separator_found)
2462     {
2463       if (!token_value_separator_warning)
2464       {
2465         Debug("setup", "---");
2466
2467         if (filename != NULL)
2468         {
2469           Debug("setup", "missing token/value separator(s) in config file:");
2470           Debug("setup", "- config file: '%s'", filename);
2471         }
2472         else
2473         {
2474           Debug("setup", "missing token/value separator(s):");
2475         }
2476
2477         token_value_separator_warning = TRUE;
2478       }
2479
2480       if (filename != NULL)
2481         Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2482       else
2483         Debug("setup", "- line: '%s'", line_raw);
2484     }
2485 #endif
2486   }
2487 #endif
2488
2489   // cut trailing whitespaces from token
2490   for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2491     if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2492       *line_ptr = '\0';
2493
2494   // cut leading whitespaces from value
2495   for (; *value; value++)
2496     if (*value != ' ' && *value != '\t')
2497       break;
2498
2499   *token_ptr = token;
2500   *value_ptr = value;
2501
2502   return TRUE;
2503 }
2504
2505 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2506 {
2507   // while the internal (old) interface does not require a token/value
2508   // separator (for downwards compatibility with existing files which
2509   // don't use them), it is mandatory for the external (new) interface
2510
2511   return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2512 }
2513
2514 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2515                                  boolean top_recursion_level, boolean is_hash)
2516 {
2517   static SetupFileHash *include_filename_hash = NULL;
2518   char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2519   char *token, *value, *line_ptr;
2520   void *insert_ptr = NULL;
2521   boolean read_continued_line = FALSE;
2522   File *file;
2523   int line_nr = 0, token_count = 0, include_count = 0;
2524
2525 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2526   token_value_separator_warning = FALSE;
2527 #endif
2528
2529 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2530   token_already_exists_warning = FALSE;
2531 #endif
2532
2533   if (!(file = openFile(filename, MODE_READ)))
2534   {
2535 #if DEBUG_NO_CONFIG_FILE
2536     Debug("setup", "cannot open configuration file '%s'", filename);
2537 #endif
2538
2539     return FALSE;
2540   }
2541
2542   // use "insert pointer" to store list end for constant insertion complexity
2543   if (!is_hash)
2544     insert_ptr = setup_file_data;
2545
2546   // on top invocation, create hash to mark included files (to prevent loops)
2547   if (top_recursion_level)
2548     include_filename_hash = newSetupFileHash();
2549
2550   // mark this file as already included (to prevent including it again)
2551   setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2552
2553   while (!checkEndOfFile(file))
2554   {
2555     // read next line of input file
2556     if (!getStringFromFile(file, line, MAX_LINE_LEN))
2557       break;
2558
2559     // check if line was completely read and is terminated by line break
2560     if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2561       line_nr++;
2562
2563     // cut trailing line break (this can be newline and/or carriage return)
2564     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2565       if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2566         *line_ptr = '\0';
2567
2568     // copy raw input line for later use (mainly debugging output)
2569     strcpy(line_raw, line);
2570
2571     if (read_continued_line)
2572     {
2573       // append new line to existing line, if there is enough space
2574       if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2575         strcat(previous_line, line_ptr);
2576
2577       strcpy(line, previous_line);      // copy storage buffer to line
2578
2579       read_continued_line = FALSE;
2580     }
2581
2582     // if the last character is '\', continue at next line
2583     if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2584     {
2585       line[strlen(line) - 1] = '\0';    // cut off trailing backslash
2586       strcpy(previous_line, line);      // copy line to storage buffer
2587
2588       read_continued_line = TRUE;
2589
2590       continue;
2591     }
2592
2593     if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2594                                        line_raw, line_nr, FALSE))
2595       continue;
2596
2597     if (*token)
2598     {
2599       if (strEqual(token, "include"))
2600       {
2601         if (getHashEntry(include_filename_hash, value) == NULL)
2602         {
2603           char *basepath = getBasePath(filename);
2604           char *basename = getBaseName(value);
2605           char *filename_include = getPath2(basepath, basename);
2606
2607           loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2608
2609           free(basepath);
2610           free(basename);
2611           free(filename_include);
2612
2613           include_count++;
2614         }
2615         else
2616         {
2617           Warn("ignoring already processed file '%s'", value);
2618         }
2619       }
2620       else
2621       {
2622         if (is_hash)
2623         {
2624 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2625           char *old_value =
2626             getHashEntry((SetupFileHash *)setup_file_data, token);
2627
2628           if (old_value != NULL)
2629           {
2630             if (!token_already_exists_warning)
2631             {
2632               Debug("setup", "---");
2633               Debug("setup", "duplicate token(s) found in config file:");
2634               Debug("setup", "- config file: '%s'", filename);
2635
2636               token_already_exists_warning = TRUE;
2637             }
2638
2639             Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2640             Debug("setup", "  old value: '%s'", old_value);
2641             Debug("setup", "  new value: '%s'", value);
2642           }
2643 #endif
2644
2645           setHashEntry((SetupFileHash *)setup_file_data, token, value);
2646         }
2647         else
2648         {
2649           insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2650         }
2651
2652         token_count++;
2653       }
2654     }
2655   }
2656
2657   closeFile(file);
2658
2659 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2660   if (token_value_separator_warning)
2661     Debug("setup", "---");
2662 #endif
2663
2664 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2665   if (token_already_exists_warning)
2666     Debug("setup", "---");
2667 #endif
2668
2669   if (token_count == 0 && include_count == 0)
2670     Warn("configuration file '%s' is empty", filename);
2671
2672   if (top_recursion_level)
2673     freeSetupFileHash(include_filename_hash);
2674
2675   return TRUE;
2676 }
2677
2678 static int compareSetupFileData(const void *object1, const void *object2)
2679 {
2680   const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2681   const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2682
2683   return strcmp(entry1->token, entry2->token);
2684 }
2685
2686 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2687 {
2688   int item_count = hashtable_count(hash);
2689   int item_size = sizeof(struct ConfigInfo);
2690   struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2691   FILE *file;
2692   int i = 0;
2693
2694   // copy string pointers from hash to array
2695   BEGIN_HASH_ITERATION(hash, itr)
2696   {
2697     sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2698     sort_array[i].value = HASH_ITERATION_VALUE(itr);
2699
2700     i++;
2701
2702     if (i > item_count)         // should never happen
2703       break;
2704   }
2705   END_HASH_ITERATION(hash, itr)
2706
2707   // sort string pointers from hash in array
2708   qsort(sort_array, item_count, item_size, compareSetupFileData);
2709
2710   if (!(file = fopen(filename, MODE_WRITE)))
2711   {
2712     Warn("cannot write configuration file '%s'", filename);
2713
2714     return;
2715   }
2716
2717   fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2718                                                  program.version_string));
2719   for (i = 0; i < item_count; i++)
2720     fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2721                                                  sort_array[i].value));
2722   fclose(file);
2723
2724   checked_free(sort_array);
2725 }
2726
2727 SetupFileList *loadSetupFileList(char *filename)
2728 {
2729   SetupFileList *setup_file_list = newSetupFileList("", "");
2730   SetupFileList *first_valid_list_entry;
2731
2732   if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2733   {
2734     freeSetupFileList(setup_file_list);
2735
2736     return NULL;
2737   }
2738
2739   first_valid_list_entry = setup_file_list->next;
2740
2741   // free empty list header
2742   setup_file_list->next = NULL;
2743   freeSetupFileList(setup_file_list);
2744
2745   return first_valid_list_entry;
2746 }
2747
2748 SetupFileHash *loadSetupFileHash(char *filename)
2749 {
2750   SetupFileHash *setup_file_hash = newSetupFileHash();
2751
2752   if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2753   {
2754     freeSetupFileHash(setup_file_hash);
2755
2756     return NULL;
2757   }
2758
2759   return setup_file_hash;
2760 }
2761
2762
2763 // ============================================================================
2764 // setup file stuff
2765 // ============================================================================
2766
2767 #define TOKEN_STR_LAST_LEVEL_SERIES             "last_level_series"
2768 #define TOKEN_STR_LAST_PLAYED_MENU_USED         "last_played_menu_used"
2769 #define TOKEN_STR_LAST_PLAYED_LEVEL             "last_played_level"
2770 #define TOKEN_STR_HANDICAP_LEVEL                "handicap_level"
2771 #define TOKEN_STR_LAST_USER                     "last_user"
2772
2773 // level directory info
2774 #define LEVELINFO_TOKEN_IDENTIFIER              0
2775 #define LEVELINFO_TOKEN_NAME                    1
2776 #define LEVELINFO_TOKEN_NAME_SORTING            2
2777 #define LEVELINFO_TOKEN_AUTHOR                  3
2778 #define LEVELINFO_TOKEN_YEAR                    4
2779 #define LEVELINFO_TOKEN_PROGRAM_TITLE           5
2780 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT       6
2781 #define LEVELINFO_TOKEN_PROGRAM_COMPANY         7
2782 #define LEVELINFO_TOKEN_IMPORTED_FROM           8
2783 #define LEVELINFO_TOKEN_IMPORTED_BY             9
2784 #define LEVELINFO_TOKEN_TESTED_BY               10
2785 #define LEVELINFO_TOKEN_LEVELS                  11
2786 #define LEVELINFO_TOKEN_FIRST_LEVEL             12
2787 #define LEVELINFO_TOKEN_SORT_PRIORITY           13
2788 #define LEVELINFO_TOKEN_LATEST_ENGINE           14
2789 #define LEVELINFO_TOKEN_LEVEL_GROUP             15
2790 #define LEVELINFO_TOKEN_READONLY                16
2791 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS        17
2792 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA        18
2793 #define LEVELINFO_TOKEN_GRAPHICS_SET            19
2794 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT      20
2795 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS      21
2796 #define LEVELINFO_TOKEN_SOUNDS_SET              22
2797 #define LEVELINFO_TOKEN_MUSIC_SET               23
2798 #define LEVELINFO_TOKEN_FILENAME                24
2799 #define LEVELINFO_TOKEN_FILETYPE                25
2800 #define LEVELINFO_TOKEN_SPECIAL_FLAGS           26
2801 #define LEVELINFO_TOKEN_EMPTY_LEVEL_NAME        27
2802 #define LEVELINFO_TOKEN_FORCE_LEVEL_NAME        28
2803 #define LEVELINFO_TOKEN_HANDICAP                29
2804 #define LEVELINFO_TOKEN_TIME_LIMIT              30
2805 #define LEVELINFO_TOKEN_SKIP_LEVELS             31
2806 #define LEVELINFO_TOKEN_USE_EMC_TILES           32
2807 #define LEVELINFO_TOKEN_INFO_SCREENS_FROM_MAIN  33
2808
2809 #define NUM_LEVELINFO_TOKENS                    34
2810
2811 static LevelDirTree ldi;
2812
2813 static struct TokenInfo levelinfo_tokens[] =
2814 {
2815   // level directory info
2816   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
2817   { TYPE_STRING,        &ldi.name,              "name"                  },
2818   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
2819   { TYPE_STRING,        &ldi.author,            "author"                },
2820   { TYPE_STRING,        &ldi.year,              "year"                  },
2821   { TYPE_STRING,        &ldi.program_title,     "program_title"         },
2822   { TYPE_STRING,        &ldi.program_copyright, "program_copyright"     },
2823   { TYPE_STRING,        &ldi.program_company,   "program_company"       },
2824   { TYPE_STRING,        &ldi.imported_from,     "imported_from"         },
2825   { TYPE_STRING,        &ldi.imported_by,       "imported_by"           },
2826   { TYPE_STRING,        &ldi.tested_by,         "tested_by"             },
2827   { TYPE_INTEGER,       &ldi.levels,            "levels"                },
2828   { TYPE_INTEGER,       &ldi.first_level,       "first_level"           },
2829   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
2830   { TYPE_BOOLEAN,       &ldi.latest_engine,     "latest_engine"         },
2831   { TYPE_BOOLEAN,       &ldi.level_group,       "level_group"           },
2832   { TYPE_BOOLEAN,       &ldi.readonly,          "readonly"              },
2833   { TYPE_STRING,        &ldi.graphics_set_ecs,  "graphics_set.ecs"      },
2834   { TYPE_STRING,        &ldi.graphics_set_aga,  "graphics_set.aga"      },
2835   { TYPE_STRING,        &ldi.graphics_set,      "graphics_set"          },
2836   { TYPE_STRING,        &ldi.sounds_set_default,"sounds_set.default"    },
2837   { TYPE_STRING,        &ldi.sounds_set_lowpass,"sounds_set.lowpass"    },
2838   { TYPE_STRING,        &ldi.sounds_set,        "sounds_set"            },
2839   { TYPE_STRING,        &ldi.music_set,         "music_set"             },
2840   { TYPE_STRING,        &ldi.level_filename,    "filename"              },
2841   { TYPE_STRING,        &ldi.level_filetype,    "filetype"              },
2842   { TYPE_STRING,        &ldi.special_flags,     "special_flags"         },
2843   { TYPE_STRING,        &ldi.empty_level_name,  "empty_level_name"      },
2844   { TYPE_BOOLEAN,       &ldi.force_level_name,  "force_level_name"      },
2845   { TYPE_BOOLEAN,       &ldi.handicap,          "handicap"              },
2846   { TYPE_BOOLEAN,       &ldi.time_limit,        "time_limit"            },
2847   { TYPE_BOOLEAN,       &ldi.skip_levels,       "skip_levels"           },
2848   { TYPE_BOOLEAN,       &ldi.use_emc_tiles,     "use_emc_tiles"         },
2849   { TYPE_BOOLEAN,       &ldi.info_screens_from_main, "info_screens_from_main" }
2850 };
2851
2852 static struct TokenInfo artworkinfo_tokens[] =
2853 {
2854   // artwork directory info
2855   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
2856   { TYPE_STRING,        &ldi.subdir,            "subdir"                },
2857   { TYPE_STRING,        &ldi.name,              "name"                  },
2858   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
2859   { TYPE_STRING,        &ldi.author,            "author"                },
2860   { TYPE_STRING,        &ldi.program_title,     "program_title"         },
2861   { TYPE_STRING,        &ldi.program_copyright, "program_copyright"     },
2862   { TYPE_STRING,        &ldi.program_company,   "program_company"       },
2863   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
2864   { TYPE_STRING,        &ldi.basepath,          "basepath"              },
2865   { TYPE_STRING,        &ldi.fullpath,          "fullpath"              },
2866   { TYPE_BOOLEAN,       &ldi.in_user_dir,       "in_user_dir"           },
2867   { TYPE_STRING,        &ldi.class_desc,        "class_desc"            },
2868
2869   { -1,                 NULL,                   NULL                    },
2870 };
2871
2872 static char *optional_tokens[] =
2873 {
2874   "program_title",
2875   "program_copyright",
2876   "program_company",
2877
2878   NULL
2879 };
2880
2881 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2882 {
2883   ti->type = type;
2884
2885   ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR    ? &leveldir_first :
2886                   ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2887                   ti->type == TREE_TYPE_SOUNDS_DIR   ? &artwork.snd_first :
2888                   ti->type == TREE_TYPE_MUSIC_DIR    ? &artwork.mus_first :
2889                   NULL);
2890
2891   ti->node_parent = NULL;
2892   ti->node_group = NULL;
2893   ti->next = NULL;
2894
2895   ti->cl_first = -1;
2896   ti->cl_cursor = -1;
2897
2898   ti->subdir = NULL;
2899   ti->fullpath = NULL;
2900   ti->basepath = NULL;
2901   ti->identifier = NULL;
2902   ti->name = getStringCopy(ANONYMOUS_NAME);
2903   ti->name_sorting = NULL;
2904   ti->author = getStringCopy(ANONYMOUS_NAME);
2905   ti->year = NULL;
2906
2907   ti->program_title = NULL;
2908   ti->program_copyright = NULL;
2909   ti->program_company = NULL;
2910
2911   ti->sort_priority = LEVELCLASS_UNDEFINED;     // default: least priority
2912   ti->latest_engine = FALSE;                    // default: get from level
2913   ti->parent_link = FALSE;
2914   ti->is_copy = FALSE;
2915   ti->in_user_dir = FALSE;
2916   ti->user_defined = FALSE;
2917   ti->color = 0;
2918   ti->class_desc = NULL;
2919
2920   ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2921
2922   if (ti->type == TREE_TYPE_LEVEL_DIR)
2923   {
2924     ti->imported_from = NULL;
2925     ti->imported_by = NULL;
2926     ti->tested_by = NULL;
2927
2928     ti->graphics_set_ecs = NULL;
2929     ti->graphics_set_aga = NULL;
2930     ti->graphics_set = NULL;
2931     ti->sounds_set_default = NULL;
2932     ti->sounds_set_lowpass = NULL;
2933     ti->sounds_set = NULL;
2934     ti->music_set = NULL;
2935     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2936     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2937     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2938
2939     ti->level_filename = NULL;
2940     ti->level_filetype = NULL;
2941
2942     ti->special_flags = NULL;
2943
2944     ti->empty_level_name = NULL;
2945     ti->force_level_name = FALSE;
2946
2947     ti->levels = 0;
2948     ti->first_level = 0;
2949     ti->last_level = 0;
2950     ti->level_group = FALSE;
2951     ti->handicap_level = 0;
2952     ti->readonly = TRUE;
2953     ti->handicap = TRUE;
2954     ti->time_limit = TRUE;
2955     ti->skip_levels = FALSE;
2956
2957     ti->use_emc_tiles = FALSE;
2958     ti->info_screens_from_main = FALSE;
2959   }
2960 }
2961
2962 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2963 {
2964   if (parent == NULL)
2965   {
2966     Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2967
2968     setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2969
2970     return;
2971   }
2972
2973   // copy all values from the parent structure
2974
2975   ti->type = parent->type;
2976
2977   ti->node_top = parent->node_top;
2978   ti->node_parent = parent;
2979   ti->node_group = NULL;
2980   ti->next = NULL;
2981
2982   ti->cl_first = -1;
2983   ti->cl_cursor = -1;
2984
2985   ti->subdir = NULL;
2986   ti->fullpath = NULL;
2987   ti->basepath = NULL;
2988   ti->identifier = NULL;
2989   ti->name = getStringCopy(ANONYMOUS_NAME);
2990   ti->name_sorting = NULL;
2991   ti->author = getStringCopy(parent->author);
2992   ti->year = getStringCopy(parent->year);
2993
2994   ti->program_title = getStringCopy(parent->program_title);
2995   ti->program_copyright = getStringCopy(parent->program_copyright);
2996   ti->program_company = getStringCopy(parent->program_company);
2997
2998   ti->sort_priority = parent->sort_priority;
2999   ti->latest_engine = parent->latest_engine;
3000   ti->parent_link = FALSE;
3001   ti->is_copy = FALSE;
3002   ti->in_user_dir = parent->in_user_dir;
3003   ti->user_defined = parent->user_defined;
3004   ti->color = parent->color;
3005   ti->class_desc = getStringCopy(parent->class_desc);
3006
3007   ti->infotext = getStringCopy(parent->infotext);
3008
3009   if (ti->type == TREE_TYPE_LEVEL_DIR)
3010   {
3011     ti->imported_from = getStringCopy(parent->imported_from);
3012     ti->imported_by = getStringCopy(parent->imported_by);
3013     ti->tested_by = getStringCopy(parent->tested_by);
3014
3015     ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
3016     ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
3017     ti->graphics_set = getStringCopy(parent->graphics_set);
3018     ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
3019     ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
3020     ti->sounds_set = getStringCopy(parent->sounds_set);
3021     ti->music_set = getStringCopy(parent->music_set);
3022     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
3023     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
3024     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
3025
3026     ti->level_filename = getStringCopy(parent->level_filename);
3027     ti->level_filetype = getStringCopy(parent->level_filetype);
3028
3029     ti->special_flags = getStringCopy(parent->special_flags);
3030
3031     ti->empty_level_name = getStringCopy(parent->empty_level_name);
3032     ti->force_level_name = parent->force_level_name;
3033
3034     ti->levels = parent->levels;
3035     ti->first_level = parent->first_level;
3036     ti->last_level = parent->last_level;
3037     ti->level_group = FALSE;
3038     ti->handicap_level = parent->handicap_level;
3039     ti->readonly = parent->readonly;
3040     ti->handicap = parent->handicap;
3041     ti->time_limit = parent->time_limit;
3042     ti->skip_levels = parent->skip_levels;
3043
3044     ti->use_emc_tiles = parent->use_emc_tiles;
3045     ti->info_screens_from_main = parent->info_screens_from_main;
3046   }
3047 }
3048
3049 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
3050 {
3051   TreeInfo *ti_copy = newTreeInfo();
3052
3053   // copy all values from the original structure
3054
3055   ti_copy->type                 = ti->type;
3056
3057   ti_copy->node_top             = ti->node_top;
3058   ti_copy->node_parent          = ti->node_parent;
3059   ti_copy->node_group           = ti->node_group;
3060   ti_copy->next                 = ti->next;
3061
3062   ti_copy->cl_first             = ti->cl_first;
3063   ti_copy->cl_cursor            = ti->cl_cursor;
3064
3065   ti_copy->subdir               = getStringCopy(ti->subdir);
3066   ti_copy->fullpath             = getStringCopy(ti->fullpath);
3067   ti_copy->basepath             = getStringCopy(ti->basepath);
3068   ti_copy->identifier           = getStringCopy(ti->identifier);
3069   ti_copy->name                 = getStringCopy(ti->name);
3070   ti_copy->name_sorting         = getStringCopy(ti->name_sorting);
3071   ti_copy->author               = getStringCopy(ti->author);
3072   ti_copy->year                 = getStringCopy(ti->year);
3073
3074   ti_copy->program_title        = getStringCopy(ti->program_title);
3075   ti_copy->program_copyright    = getStringCopy(ti->program_copyright);
3076   ti_copy->program_company      = getStringCopy(ti->program_company);
3077
3078   ti_copy->imported_from        = getStringCopy(ti->imported_from);
3079   ti_copy->imported_by          = getStringCopy(ti->imported_by);
3080   ti_copy->tested_by            = getStringCopy(ti->tested_by);
3081
3082   ti_copy->graphics_set_ecs     = getStringCopy(ti->graphics_set_ecs);
3083   ti_copy->graphics_set_aga     = getStringCopy(ti->graphics_set_aga);
3084   ti_copy->graphics_set         = getStringCopy(ti->graphics_set);
3085   ti_copy->sounds_set_default   = getStringCopy(ti->sounds_set_default);
3086   ti_copy->sounds_set_lowpass   = getStringCopy(ti->sounds_set_lowpass);
3087   ti_copy->sounds_set           = getStringCopy(ti->sounds_set);
3088   ti_copy->music_set            = getStringCopy(ti->music_set);
3089   ti_copy->graphics_path        = getStringCopy(ti->graphics_path);
3090   ti_copy->sounds_path          = getStringCopy(ti->sounds_path);
3091   ti_copy->music_path           = getStringCopy(ti->music_path);
3092
3093   ti_copy->level_filename       = getStringCopy(ti->level_filename);
3094   ti_copy->level_filetype       = getStringCopy(ti->level_filetype);
3095
3096   ti_copy->special_flags        = getStringCopy(ti->special_flags);
3097
3098   ti_copy->empty_level_name     = getStringCopy(ti->empty_level_name);
3099   ti_copy->force_level_name     = ti->force_level_name;
3100
3101   ti_copy->levels               = ti->levels;
3102   ti_copy->first_level          = ti->first_level;
3103   ti_copy->last_level           = ti->last_level;
3104   ti_copy->sort_priority        = ti->sort_priority;
3105
3106   ti_copy->latest_engine        = ti->latest_engine;
3107
3108   ti_copy->level_group          = ti->level_group;
3109   ti_copy->parent_link          = ti->parent_link;
3110   ti_copy->is_copy              = ti->is_copy;
3111   ti_copy->in_user_dir          = ti->in_user_dir;
3112   ti_copy->user_defined         = ti->user_defined;
3113   ti_copy->readonly             = ti->readonly;
3114   ti_copy->handicap             = ti->handicap;
3115   ti_copy->time_limit           = ti->time_limit;
3116   ti_copy->skip_levels          = ti->skip_levels;
3117
3118   ti_copy->use_emc_tiles        = ti->use_emc_tiles;
3119   ti_copy->info_screens_from_main = ti->info_screens_from_main;
3120
3121   ti_copy->color                = ti->color;
3122   ti_copy->class_desc           = getStringCopy(ti->class_desc);
3123   ti_copy->handicap_level       = ti->handicap_level;
3124
3125   ti_copy->infotext             = getStringCopy(ti->infotext);
3126
3127   return ti_copy;
3128 }
3129
3130 void freeTreeInfo(TreeInfo *ti)
3131 {
3132   if (ti == NULL)
3133     return;
3134
3135   checked_free(ti->subdir);
3136   checked_free(ti->fullpath);
3137   checked_free(ti->basepath);
3138   checked_free(ti->identifier);
3139
3140   checked_free(ti->name);
3141   checked_free(ti->name_sorting);
3142   checked_free(ti->author);
3143   checked_free(ti->year);
3144
3145   checked_free(ti->program_title);
3146   checked_free(ti->program_copyright);
3147   checked_free(ti->program_company);
3148
3149   checked_free(ti->class_desc);
3150
3151   checked_free(ti->infotext);
3152
3153   if (ti->type == TREE_TYPE_LEVEL_DIR)
3154   {
3155     checked_free(ti->imported_from);
3156     checked_free(ti->imported_by);
3157     checked_free(ti->tested_by);
3158
3159     checked_free(ti->graphics_set_ecs);
3160     checked_free(ti->graphics_set_aga);
3161     checked_free(ti->graphics_set);
3162     checked_free(ti->sounds_set_default);
3163     checked_free(ti->sounds_set_lowpass);
3164     checked_free(ti->sounds_set);
3165     checked_free(ti->music_set);
3166
3167     checked_free(ti->graphics_path);
3168     checked_free(ti->sounds_path);
3169     checked_free(ti->music_path);
3170
3171     checked_free(ti->level_filename);
3172     checked_free(ti->level_filetype);
3173
3174     checked_free(ti->special_flags);
3175   }
3176
3177   // recursively free child node
3178   if (ti->node_group)
3179     freeTreeInfo(ti->node_group);
3180
3181   // recursively free next node
3182   if (ti->next)
3183     freeTreeInfo(ti->next);
3184
3185   checked_free(ti);
3186 }
3187
3188 void setSetupInfo(struct TokenInfo *token_info,
3189                   int token_nr, char *token_value)
3190 {
3191   int token_type = token_info[token_nr].type;
3192   void *setup_value = token_info[token_nr].value;
3193
3194   if (token_value == NULL)
3195     return;
3196
3197   // set setup field to corresponding token value
3198   switch (token_type)
3199   {
3200     case TYPE_BOOLEAN:
3201     case TYPE_SWITCH:
3202       *(boolean *)setup_value = get_boolean_from_string(token_value);
3203       break;
3204
3205     case TYPE_SWITCH3:
3206       *(int *)setup_value = get_switch3_from_string(token_value);
3207       break;
3208
3209     case TYPE_KEY:
3210       *(Key *)setup_value = getKeyFromKeyName(token_value);
3211       break;
3212
3213     case TYPE_KEY_X11:
3214       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
3215       break;
3216
3217     case TYPE_INTEGER:
3218       *(int *)setup_value = get_integer_from_string(token_value);
3219       break;
3220
3221     case TYPE_STRING:
3222       checked_free(*(char **)setup_value);
3223       *(char **)setup_value = getStringCopy(token_value);
3224       break;
3225
3226     case TYPE_PLAYER:
3227       *(int *)setup_value = get_player_nr_from_string(token_value);
3228       break;
3229
3230     default:
3231       break;
3232   }
3233 }
3234
3235 static int compareTreeInfoEntries(const void *object1, const void *object2)
3236 {
3237   const TreeInfo *entry1 = *((TreeInfo **)object1);
3238   const TreeInfo *entry2 = *((TreeInfo **)object2);
3239   int tree_sorting1 = TREE_SORTING(entry1);
3240   int tree_sorting2 = TREE_SORTING(entry2);
3241
3242   if (tree_sorting1 != tree_sorting2)
3243     return (tree_sorting1 - tree_sorting2);
3244   else
3245     return strcasecmp(entry1->name_sorting, entry2->name_sorting);
3246 }
3247
3248 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
3249 {
3250   TreeInfo *ti_new;
3251
3252   if (node_parent == NULL)
3253     return NULL;
3254
3255   ti_new = newTreeInfo();
3256   setTreeInfoToDefaults(ti_new, node_parent->type);
3257
3258   ti_new->node_parent = node_parent;
3259   ti_new->parent_link = TRUE;
3260
3261   setString(&ti_new->identifier, node_parent->identifier);
3262   setString(&ti_new->name, BACKLINK_TEXT_PARENT);
3263   setString(&ti_new->name_sorting, ti_new->name);
3264
3265   setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
3266   setString(&ti_new->fullpath, node_parent->fullpath);
3267
3268   ti_new->sort_priority = LEVELCLASS_PARENT;
3269   ti_new->latest_engine = node_parent->latest_engine;
3270
3271   setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3272
3273   pushTreeInfo(&node_parent->node_group, ti_new);
3274
3275   return ti_new;
3276 }
3277
3278 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
3279 {
3280   if (node_first == NULL)
3281     return NULL;
3282
3283   TreeInfo *ti_new = newTreeInfo();
3284   int type = node_first->type;
3285
3286   setTreeInfoToDefaults(ti_new, type);
3287
3288   ti_new->node_parent = NULL;
3289   ti_new->parent_link = FALSE;
3290
3291   setString(&ti_new->identifier, "top_tree_node");
3292   setString(&ti_new->name, TREE_INFOTEXT(type));
3293   setString(&ti_new->name_sorting, ti_new->name);
3294
3295   setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3296   setString(&ti_new->fullpath, ".");
3297
3298   ti_new->sort_priority = LEVELCLASS_TOP;
3299   ti_new->latest_engine = node_first->latest_engine;
3300
3301   setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3302
3303   ti_new->node_group = node_first;
3304   ti_new->level_group = TRUE;
3305
3306   TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3307
3308   setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3309   setString(&ti_new2->name_sorting, ti_new2->name);
3310
3311   return ti_new;
3312 }
3313
3314 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3315 {
3316   while (node)
3317   {
3318     if (node->node_group)
3319       setTreeInfoParentNodes(node->node_group, node);
3320
3321     node->node_parent = node_parent;
3322
3323     node = node->next;
3324   }
3325 }
3326
3327 TreeInfo *addTopTreeInfoNode(TreeInfo *node_first)
3328 {
3329   // add top tree node with back link node in previous tree
3330   node_first = createTopTreeInfoNode(node_first);
3331
3332   // set all parent links (back links) in complete tree
3333   setTreeInfoParentNodes(node_first, NULL);
3334
3335   return node_first;
3336 }
3337
3338
3339 // ----------------------------------------------------------------------------
3340 // functions for handling level and custom artwork info cache
3341 // ----------------------------------------------------------------------------
3342
3343 static void LoadArtworkInfoCache(void)
3344 {
3345   InitCacheDirectory();
3346
3347   if (artworkinfo_cache_old == NULL)
3348   {
3349     char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3350
3351     // try to load artwork info hash from already existing cache file
3352     artworkinfo_cache_old = loadSetupFileHash(filename);
3353
3354     // try to get program version that artwork info cache was written with
3355     char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3356
3357     // check program version of artwork info cache against current version
3358     if (!strEqual(version, program.version_string))
3359     {
3360       freeSetupFileHash(artworkinfo_cache_old);
3361
3362       artworkinfo_cache_old = NULL;
3363     }
3364
3365     // if no artwork info cache file was found, start with empty hash
3366     if (artworkinfo_cache_old == NULL)
3367       artworkinfo_cache_old = newSetupFileHash();
3368
3369     free(filename);
3370   }
3371
3372   if (artworkinfo_cache_new == NULL)
3373     artworkinfo_cache_new = newSetupFileHash();
3374
3375   update_artworkinfo_cache = FALSE;
3376 }
3377
3378 static void SaveArtworkInfoCache(void)
3379 {
3380   if (!update_artworkinfo_cache)
3381     return;
3382
3383   char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3384
3385   InitCacheDirectory();
3386
3387   saveSetupFileHash(artworkinfo_cache_new, filename);
3388
3389   free(filename);
3390 }
3391
3392 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3393 {
3394   static char *prefix = NULL;
3395
3396   checked_free(prefix);
3397
3398   prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3399
3400   return prefix;
3401 }
3402
3403 // (identical to above function, but separate string buffer needed -- nasty)
3404 static char *getCacheToken(char *prefix, char *suffix)
3405 {
3406   static char *token = NULL;
3407
3408   checked_free(token);
3409
3410   token = getStringCat2WithSeparator(prefix, suffix, ".");
3411
3412   return token;
3413 }
3414
3415 static char *getFileTimestampString(char *filename)
3416 {
3417   return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3418 }
3419
3420 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3421 {
3422   struct stat file_status;
3423
3424   if (timestamp_string == NULL)
3425     return TRUE;
3426
3427   if (!fileExists(filename))                    // file does not exist
3428     return (atoi(timestamp_string) != 0);
3429
3430   if (stat(filename, &file_status) != 0)        // cannot stat file
3431     return TRUE;
3432
3433   return (file_status.st_mtime != atoi(timestamp_string));
3434 }
3435
3436 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3437 {
3438   char *identifier = level_node->subdir;
3439   char *type_string = ARTWORK_DIRECTORY(type);
3440   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3441   char *token_main = getCacheToken(token_prefix, "CACHED");
3442   char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3443   boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3444   TreeInfo *artwork_info = NULL;
3445
3446   if (!use_artworkinfo_cache)
3447     return NULL;
3448
3449   if (optional_tokens_hash == NULL)
3450   {
3451     int i;
3452
3453     // create hash from list of optional tokens (for quick access)
3454     optional_tokens_hash = newSetupFileHash();
3455     for (i = 0; optional_tokens[i] != NULL; i++)
3456       setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3457   }
3458
3459   if (cached)
3460   {
3461     int i;
3462
3463     artwork_info = newTreeInfo();
3464     setTreeInfoToDefaults(artwork_info, type);
3465
3466     // set all structure fields according to the token/value pairs
3467     ldi = *artwork_info;
3468     for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3469     {
3470       char *token_suffix = artworkinfo_tokens[i].text;
3471       char *token = getCacheToken(token_prefix, token_suffix);
3472       char *value = getHashEntry(artworkinfo_cache_old, token);
3473       boolean optional =
3474         (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3475
3476       setSetupInfo(artworkinfo_tokens, i, value);
3477
3478       // check if cache entry for this item is mandatory, but missing
3479       if (value == NULL && !optional)
3480       {
3481         Warn("missing cache entry '%s'", token);
3482
3483         cached = FALSE;
3484       }
3485     }
3486
3487     *artwork_info = ldi;
3488   }
3489
3490   if (cached)
3491   {
3492     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3493                                         LEVELINFO_FILENAME);
3494     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3495                                           ARTWORKINFO_FILENAME(type));
3496
3497     // check if corresponding "levelinfo.conf" file has changed
3498     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3499     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3500
3501     if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3502       cached = FALSE;
3503
3504     // check if corresponding "<artworkinfo>.conf" file has changed
3505     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3506     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3507
3508     if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3509       cached = FALSE;
3510
3511     checked_free(filename_levelinfo);
3512     checked_free(filename_artworkinfo);
3513   }
3514
3515   if (!cached && artwork_info != NULL)
3516   {
3517     freeTreeInfo(artwork_info);
3518
3519     return NULL;
3520   }
3521
3522   return artwork_info;
3523 }
3524
3525 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3526                                      LevelDirTree *level_node, int type)
3527 {
3528   char *identifier = level_node->subdir;
3529   char *type_string = ARTWORK_DIRECTORY(type);
3530   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3531   char *token_main = getCacheToken(token_prefix, "CACHED");
3532   boolean set_cache_timestamps = TRUE;
3533   int i;
3534
3535   setHashEntry(artworkinfo_cache_new, token_main, "true");
3536
3537   if (set_cache_timestamps)
3538   {
3539     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3540                                         LEVELINFO_FILENAME);
3541     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3542                                           ARTWORKINFO_FILENAME(type));
3543     char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3544     char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3545
3546     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3547     setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3548
3549     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3550     setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3551
3552     checked_free(filename_levelinfo);
3553     checked_free(filename_artworkinfo);
3554     checked_free(timestamp_levelinfo);
3555     checked_free(timestamp_artworkinfo);
3556   }
3557
3558   ldi = *artwork_info;
3559   for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3560   {
3561     char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3562     char *value = getSetupValue(artworkinfo_tokens[i].type,
3563                                 artworkinfo_tokens[i].value);
3564     if (value != NULL)
3565       setHashEntry(artworkinfo_cache_new, token, value);
3566   }
3567 }
3568
3569
3570 // ----------------------------------------------------------------------------
3571 // functions for loading level info and custom artwork info
3572 // ----------------------------------------------------------------------------
3573
3574 int GetZipFileTreeType(char *zip_filename)
3575 {
3576   static char *top_dir_path = NULL;
3577   static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3578   static char *conf_basename[NUM_BASE_TREE_TYPES] =
3579   {
3580     GRAPHICSINFO_FILENAME,
3581     SOUNDSINFO_FILENAME,
3582     MUSICINFO_FILENAME,
3583     LEVELINFO_FILENAME
3584   };
3585   int j;
3586
3587   checked_free(top_dir_path);
3588   top_dir_path = NULL;
3589
3590   for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3591   {
3592     checked_free(top_dir_conf_filename[j]);
3593     top_dir_conf_filename[j] = NULL;
3594   }
3595
3596   char **zip_entries = zip_list(zip_filename);
3597
3598   // check if zip file successfully opened
3599   if (zip_entries == NULL || zip_entries[0] == NULL)
3600     return TREE_TYPE_UNDEFINED;
3601
3602   // first zip file entry is expected to be top level directory
3603   char *top_dir = zip_entries[0];
3604
3605   // check if valid top level directory found in zip file
3606   if (!strSuffix(top_dir, "/"))
3607     return TREE_TYPE_UNDEFINED;
3608
3609   // get filenames of valid configuration files in top level directory
3610   for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3611     top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3612
3613   int tree_type = TREE_TYPE_UNDEFINED;
3614   int e = 0;
3615
3616   while (zip_entries[e] != NULL)
3617   {
3618     // check if every zip file entry is below top level directory
3619     if (!strPrefix(zip_entries[e], top_dir))
3620       return TREE_TYPE_UNDEFINED;
3621
3622     // check if this zip file entry is a valid configuration filename
3623     for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3624     {
3625       if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3626       {
3627         // only exactly one valid configuration file allowed
3628         if (tree_type != TREE_TYPE_UNDEFINED)
3629           return TREE_TYPE_UNDEFINED;
3630
3631         tree_type = j;
3632       }
3633     }
3634
3635     e++;
3636   }
3637
3638   return tree_type;
3639 }
3640
3641 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3642                                         int tree_type)
3643 {
3644   static char *top_dir_path = NULL;
3645   static char *top_dir_conf_filename = NULL;
3646
3647   checked_free(top_dir_path);
3648   checked_free(top_dir_conf_filename);
3649
3650   top_dir_path = NULL;
3651   top_dir_conf_filename = NULL;
3652
3653   char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3654                          ARTWORKINFO_FILENAME(tree_type));
3655
3656   // check if valid configuration filename determined
3657   if (conf_basename == NULL || strEqual(conf_basename, ""))
3658     return FALSE;
3659
3660   char **zip_entries = zip_list(zip_filename);
3661
3662   // check if zip file successfully opened
3663   if (zip_entries == NULL || zip_entries[0] == NULL)
3664     return FALSE;
3665
3666   // first zip file entry is expected to be top level directory
3667   char *top_dir = zip_entries[0];
3668
3669   // check if valid top level directory found in zip file
3670   if (!strSuffix(top_dir, "/"))
3671     return FALSE;
3672
3673   // get path of extracted top level directory
3674   top_dir_path = getPath2(directory, top_dir);
3675
3676   // remove trailing directory separator from top level directory path
3677   // (required to be able to check for file and directory in next step)
3678   top_dir_path[strlen(top_dir_path) - 1] = '\0';
3679
3680   // check if zip file's top level directory already exists in target directory
3681   if (fileExists(top_dir_path))         // (checks for file and directory)
3682     return FALSE;
3683
3684   // get filename of configuration file in top level directory
3685   top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3686
3687   boolean found_top_dir_conf_filename = FALSE;
3688   int i = 0;
3689
3690   while (zip_entries[i] != NULL)
3691   {
3692     // check if every zip file entry is below top level directory
3693     if (!strPrefix(zip_entries[i], top_dir))
3694       return FALSE;
3695
3696     // check if this zip file entry is the configuration filename
3697     if (strEqual(zip_entries[i], top_dir_conf_filename))
3698       found_top_dir_conf_filename = TRUE;
3699
3700     i++;
3701   }
3702
3703   // check if valid configuration filename was found in zip file
3704   if (!found_top_dir_conf_filename)
3705     return FALSE;
3706
3707   return TRUE;
3708 }
3709
3710 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3711                                   int tree_type)
3712 {
3713   boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3714                                                     tree_type);
3715
3716   if (!zip_file_valid)
3717   {
3718     Warn("zip file '%s' rejected!", zip_filename);
3719
3720     return NULL;
3721   }
3722
3723   char **zip_entries = zip_extract(zip_filename, directory);
3724
3725   if (zip_entries == NULL)
3726   {
3727     Warn("zip file '%s' could not be extracted!", zip_filename);
3728
3729     return NULL;
3730   }
3731
3732   Info("zip file '%s' successfully extracted!", zip_filename);
3733
3734   // first zip file entry contains top level directory
3735   char *top_dir = zip_entries[0];
3736
3737   // remove trailing directory separator from top level directory
3738   top_dir[strlen(top_dir) - 1] = '\0';
3739
3740   return top_dir;
3741 }
3742
3743 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3744 {
3745   Directory *dir;
3746   DirectoryEntry *dir_entry;
3747
3748   if ((dir = openDirectory(directory)) == NULL)
3749   {
3750     // display error if directory is main "options.graphics_directory" etc.
3751     if (tree_type == TREE_TYPE_LEVEL_DIR ||
3752         directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3753       Warn("cannot read directory '%s'", directory);
3754
3755     return;
3756   }
3757
3758   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
3759   {
3760     // skip non-zip files (and also directories with zip extension)
3761     if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3762       continue;
3763
3764     char *zip_filename = getPath2(directory, dir_entry->basename);
3765     char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3766     char *zip_filename_rejected  = getStringCat2(zip_filename, ".rejected");
3767
3768     // check if zip file hasn't already been extracted or rejected
3769     if (!fileExists(zip_filename_extracted) &&
3770         !fileExists(zip_filename_rejected))
3771     {
3772       char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3773                                                   tree_type);
3774       char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3775                                zip_filename_rejected);
3776       FILE *marker_file;
3777
3778       // create empty file to mark zip file as extracted or rejected
3779       if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3780         fclose(marker_file);
3781
3782       free(zip_filename);
3783       free(zip_filename_extracted);
3784       free(zip_filename_rejected);
3785     }
3786   }
3787
3788   closeDirectory(dir);
3789 }
3790
3791 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3792 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3793
3794 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3795                                           TreeInfo *node_parent,
3796                                           char *level_directory,
3797                                           char *directory_name)
3798 {
3799   char *directory_path = getPath2(level_directory, directory_name);
3800   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3801   SetupFileHash *setup_file_hash;
3802   LevelDirTree *leveldir_new = NULL;
3803   int i;
3804
3805   // unless debugging, silently ignore directories without "levelinfo.conf"
3806   if (!options.debug && !fileExists(filename))
3807   {
3808     free(directory_path);
3809     free(filename);
3810
3811     return FALSE;
3812   }
3813
3814   setup_file_hash = loadSetupFileHash(filename);
3815
3816   if (setup_file_hash == NULL)
3817   {
3818 #if DEBUG_NO_CONFIG_FILE
3819     Debug("setup", "ignoring level directory '%s'", directory_path);
3820 #endif
3821
3822     free(directory_path);
3823     free(filename);
3824
3825     return FALSE;
3826   }
3827
3828   leveldir_new = newTreeInfo();
3829
3830   if (node_parent)
3831     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3832   else
3833     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3834
3835   leveldir_new->subdir = getStringCopy(directory_name);
3836
3837   // set all structure fields according to the token/value pairs
3838   ldi = *leveldir_new;
3839   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3840     setSetupInfo(levelinfo_tokens, i,
3841                  getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3842   *leveldir_new = ldi;
3843
3844   if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3845     setString(&leveldir_new->name, leveldir_new->subdir);
3846
3847   if (leveldir_new->identifier == NULL)
3848     leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3849
3850   if (leveldir_new->name_sorting == NULL)
3851     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3852
3853   if (node_parent == NULL)              // top level group
3854   {
3855     leveldir_new->basepath = getStringCopy(level_directory);
3856     leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3857   }
3858   else                                  // sub level group
3859   {
3860     leveldir_new->basepath = getStringCopy(node_parent->basepath);
3861     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3862   }
3863
3864   leveldir_new->last_level =
3865     leveldir_new->first_level + leveldir_new->levels - 1;
3866
3867   leveldir_new->in_user_dir =
3868     (!strEqual(leveldir_new->basepath, options.level_directory));
3869
3870   // adjust some settings if user's private level directory was detected
3871   if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3872       leveldir_new->in_user_dir &&
3873       (strEqual(leveldir_new->subdir, getLoginName()) ||
3874        strEqual(leveldir_new->name,   getLoginName()) ||
3875        strEqual(leveldir_new->author, getRealName())))
3876   {
3877     leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3878     leveldir_new->readonly = FALSE;
3879   }
3880
3881   leveldir_new->user_defined =
3882     (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3883
3884   setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3885
3886   leveldir_new->handicap_level =        // set handicap to default value
3887     (leveldir_new->user_defined || !leveldir_new->handicap ?
3888      leveldir_new->last_level : leveldir_new->first_level);
3889
3890   DrawInitTextItem(leveldir_new->name);
3891
3892   pushTreeInfo(node_first, leveldir_new);
3893
3894   freeSetupFileHash(setup_file_hash);
3895
3896   if (leveldir_new->level_group)
3897   {
3898     // create node to link back to current level directory
3899     createParentTreeInfoNode(leveldir_new);
3900
3901     // recursively step into sub-directory and look for more level series
3902     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3903                               leveldir_new, directory_path);
3904   }
3905
3906   free(directory_path);
3907   free(filename);
3908
3909   return TRUE;
3910 }
3911
3912 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3913                                       TreeInfo *node_parent,
3914                                       char *level_directory)
3915 {
3916   // ---------- 1st stage: process any level set zip files ----------
3917
3918   ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3919
3920   // ---------- 2nd stage: check for level set directories ----------
3921
3922   Directory *dir;
3923   DirectoryEntry *dir_entry;
3924   boolean valid_entry_found = FALSE;
3925
3926   if ((dir = openDirectory(level_directory)) == NULL)
3927   {
3928     Warn("cannot read level directory '%s'", level_directory);
3929
3930     return;
3931   }
3932
3933   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
3934   {
3935     char *directory_name = dir_entry->basename;
3936     char *directory_path = getPath2(level_directory, directory_name);
3937
3938     // skip entries for current and parent directory
3939     if (strEqual(directory_name, ".") ||
3940         strEqual(directory_name, ".."))
3941     {
3942       free(directory_path);
3943
3944       continue;
3945     }
3946
3947     // find out if directory entry is itself a directory
3948     if (!dir_entry->is_directory)                       // not a directory
3949     {
3950       free(directory_path);
3951
3952       continue;
3953     }
3954
3955     free(directory_path);
3956
3957     if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3958         strEqual(directory_name, SOUNDS_DIRECTORY) ||
3959         strEqual(directory_name, MUSIC_DIRECTORY))
3960       continue;
3961
3962     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3963                                                     level_directory,
3964                                                     directory_name);
3965   }
3966
3967   closeDirectory(dir);
3968
3969   // special case: top level directory may directly contain "levelinfo.conf"
3970   if (node_parent == NULL && !valid_entry_found)
3971   {
3972     // check if this directory directly contains a file "levelinfo.conf"
3973     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3974                                                     level_directory, ".");
3975   }
3976
3977   boolean valid_entry_expected =
3978     (strEqual(level_directory, options.level_directory) ||
3979      setup.internal.create_user_levelset);
3980
3981   if (valid_entry_expected && !valid_entry_found)
3982     Warn("cannot find any valid level series in directory '%s'",
3983          level_directory);
3984 }
3985
3986 boolean AdjustGraphicsForEMC(void)
3987 {
3988   boolean settings_changed = FALSE;
3989
3990   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3991   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3992
3993   return settings_changed;
3994 }
3995
3996 boolean AdjustSoundsForEMC(void)
3997 {
3998   boolean settings_changed = FALSE;
3999
4000   settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
4001   settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
4002
4003   return settings_changed;
4004 }
4005
4006 void LoadLevelInfo(void)
4007 {
4008   InitUserLevelDirectory(getLoginName());
4009
4010   DrawInitTextHead("Loading level series");
4011
4012   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
4013   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
4014
4015   leveldir_first = createTopTreeInfoNode(leveldir_first);
4016
4017   /* after loading all level set information, clone the level directory tree
4018      and remove all level sets without levels (these may still contain artwork
4019      to be offered in the setup menu as "custom artwork", and are therefore
4020      checked for existing artwork in the function "LoadLevelArtworkInfo()") */
4021   leveldir_first_all = leveldir_first;
4022   cloneTree(&leveldir_first, leveldir_first_all, TRUE);
4023
4024   AdjustGraphicsForEMC();
4025   AdjustSoundsForEMC();
4026
4027   // before sorting, the first entries will be from the user directory
4028   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4029
4030   if (leveldir_first == NULL)
4031     Fail("cannot find any valid level series in any directory");
4032
4033   sortTreeInfo(&leveldir_first);
4034
4035 #if ENABLE_UNUSED_CODE
4036   dumpTreeInfo(leveldir_first, 0);
4037 #endif
4038 }
4039
4040 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
4041                                               TreeInfo *node_parent,
4042                                               char *base_directory,
4043                                               char *directory_name, int type)
4044 {
4045   char *directory_path = getPath2(base_directory, directory_name);
4046   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
4047   SetupFileHash *setup_file_hash = NULL;
4048   TreeInfo *artwork_new = NULL;
4049   int i;
4050
4051   if (fileExists(filename))
4052     setup_file_hash = loadSetupFileHash(filename);
4053
4054   if (setup_file_hash == NULL)  // no config file -- look for artwork files
4055   {
4056     Directory *dir;
4057     DirectoryEntry *dir_entry;
4058     boolean valid_file_found = FALSE;
4059
4060     if ((dir = openDirectory(directory_path)) != NULL)
4061     {
4062       while ((dir_entry = readDirectory(dir)) != NULL)
4063       {
4064         if (FileIsArtworkType(dir_entry->filename, type))
4065         {
4066           valid_file_found = TRUE;
4067
4068           break;
4069         }
4070       }
4071
4072       closeDirectory(dir);
4073     }
4074
4075     if (!valid_file_found)
4076     {
4077 #if DEBUG_NO_CONFIG_FILE
4078       if (!strEqual(directory_name, "."))
4079         Debug("setup", "ignoring artwork directory '%s'", directory_path);
4080 #endif
4081
4082       free(directory_path);
4083       free(filename);
4084
4085       return FALSE;
4086     }
4087   }
4088
4089   artwork_new = newTreeInfo();
4090
4091   if (node_parent)
4092     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4093   else
4094     setTreeInfoToDefaults(artwork_new, type);
4095
4096   artwork_new->subdir = getStringCopy(directory_name);
4097
4098   if (setup_file_hash)  // (before defining ".color" and ".class_desc")
4099   {
4100     // set all structure fields according to the token/value pairs
4101     ldi = *artwork_new;
4102     for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4103       setSetupInfo(levelinfo_tokens, i,
4104                    getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
4105     *artwork_new = ldi;
4106
4107     if (strEqual(artwork_new->name, ANONYMOUS_NAME))
4108       setString(&artwork_new->name, artwork_new->subdir);
4109
4110     if (artwork_new->identifier == NULL)
4111       artwork_new->identifier = getStringCopy(artwork_new->subdir);
4112
4113     if (artwork_new->name_sorting == NULL)
4114       artwork_new->name_sorting = getStringCopy(artwork_new->name);
4115   }
4116
4117   if (node_parent == NULL)              // top level group
4118   {
4119     artwork_new->basepath = getStringCopy(base_directory);
4120     artwork_new->fullpath = getStringCopy(artwork_new->subdir);
4121   }
4122   else                                  // sub level group
4123   {
4124     artwork_new->basepath = getStringCopy(node_parent->basepath);
4125     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
4126   }
4127
4128   artwork_new->in_user_dir =
4129     (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
4130
4131   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
4132
4133   if (setup_file_hash == NULL)  // (after determining ".user_defined")
4134   {
4135     if (strEqual(artwork_new->subdir, "."))
4136     {
4137       if (artwork_new->user_defined)
4138       {
4139         setString(&artwork_new->identifier, "private");
4140         artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
4141       }
4142       else
4143       {
4144         setString(&artwork_new->identifier, "classic");
4145         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
4146       }
4147
4148       setString(&artwork_new->class_desc,
4149                 getLevelClassDescription(artwork_new));
4150     }
4151     else
4152     {
4153       setString(&artwork_new->identifier, artwork_new->subdir);
4154     }
4155
4156     setString(&artwork_new->name, artwork_new->identifier);
4157     setString(&artwork_new->name_sorting, artwork_new->name);
4158   }
4159
4160   pushTreeInfo(node_first, artwork_new);
4161
4162   freeSetupFileHash(setup_file_hash);
4163
4164   free(directory_path);
4165   free(filename);
4166
4167   return TRUE;
4168 }
4169
4170 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
4171                                           TreeInfo *node_parent,
4172                                           char *base_directory, int type)
4173 {
4174   // ---------- 1st stage: process any artwork set zip files ----------
4175
4176   ProcessZipFilesInDirectory(base_directory, type);
4177
4178   // ---------- 2nd stage: check for artwork set directories ----------
4179
4180   Directory *dir;
4181   DirectoryEntry *dir_entry;
4182   boolean valid_entry_found = FALSE;
4183
4184   if ((dir = openDirectory(base_directory)) == NULL)
4185   {
4186     // display error if directory is main "options.graphics_directory" etc.
4187     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
4188       Warn("cannot read directory '%s'", base_directory);
4189
4190     return;
4191   }
4192
4193   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
4194   {
4195     char *directory_name = dir_entry->basename;
4196     char *directory_path = getPath2(base_directory, directory_name);
4197
4198     // skip directory entries for current and parent directory
4199     if (strEqual(directory_name, ".") ||
4200         strEqual(directory_name, ".."))
4201     {
4202       free(directory_path);
4203
4204       continue;
4205     }
4206
4207     // skip directory entries which are not a directory
4208     if (!dir_entry->is_directory)                       // not a directory
4209     {
4210       free(directory_path);
4211
4212       continue;
4213     }
4214
4215     free(directory_path);
4216
4217     // check if this directory contains artwork with or without config file
4218     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4219                                                         base_directory,
4220                                                         directory_name, type);
4221   }
4222
4223   closeDirectory(dir);
4224
4225   // check if this directory directly contains artwork itself
4226   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4227                                                       base_directory, ".",
4228                                                       type);
4229   if (!valid_entry_found)
4230     Warn("cannot find any valid artwork in directory '%s'", base_directory);
4231 }
4232
4233 static TreeInfo *getDummyArtworkInfo(int type)
4234 {
4235   // this is only needed when there is completely no artwork available
4236   TreeInfo *artwork_new = newTreeInfo();
4237
4238   setTreeInfoToDefaults(artwork_new, type);
4239
4240   setString(&artwork_new->subdir,   UNDEFINED_FILENAME);
4241   setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
4242   setString(&artwork_new->basepath, UNDEFINED_FILENAME);
4243
4244   setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
4245   setString(&artwork_new->name,         UNDEFINED_FILENAME);
4246   setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4247
4248   return artwork_new;
4249 }
4250
4251 void SetCurrentArtwork(int type)
4252 {
4253   ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
4254   ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
4255   char *setup_set = SETUP_ARTWORK_SET(setup, type);
4256   char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
4257
4258   // set current artwork to artwork configured in setup menu
4259   *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
4260
4261   // if not found, set current artwork to default artwork
4262   if (*current_ptr == NULL)
4263     *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
4264
4265   // if not found, set current artwork to first artwork in tree
4266   if (*current_ptr == NULL)
4267     *current_ptr = getFirstValidTreeInfoEntry(first_node);
4268 }
4269
4270 void ChangeCurrentArtworkIfNeeded(int type)
4271 {
4272   char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
4273   char *setup_set = SETUP_ARTWORK_SET(setup, type);
4274
4275   if (!strEqual(current_identifier, setup_set))
4276     SetCurrentArtwork(type);
4277 }
4278
4279 void LoadArtworkInfo(void)
4280 {
4281   LoadArtworkInfoCache();
4282
4283   DrawInitTextHead("Looking for custom artwork");
4284
4285   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4286                                 options.graphics_directory,
4287                                 TREE_TYPE_GRAPHICS_DIR);
4288   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4289                                 getUserGraphicsDir(),
4290                                 TREE_TYPE_GRAPHICS_DIR);
4291
4292   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4293                                 options.sounds_directory,
4294                                 TREE_TYPE_SOUNDS_DIR);
4295   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4296                                 getUserSoundsDir(),
4297                                 TREE_TYPE_SOUNDS_DIR);
4298
4299   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4300                                 options.music_directory,
4301                                 TREE_TYPE_MUSIC_DIR);
4302   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4303                                 getUserMusicDir(),
4304                                 TREE_TYPE_MUSIC_DIR);
4305
4306   if (artwork.gfx_first == NULL)
4307     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4308   if (artwork.snd_first == NULL)
4309     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4310   if (artwork.mus_first == NULL)
4311     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4312
4313   // before sorting, the first entries will be from the user directory
4314   SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4315   SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4316   SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4317
4318   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4319   artwork.snd_current_identifier = artwork.snd_current->identifier;
4320   artwork.mus_current_identifier = artwork.mus_current->identifier;
4321
4322 #if ENABLE_UNUSED_CODE
4323   Debug("setup:LoadArtworkInfo", "graphics set == %s",
4324         artwork.gfx_current_identifier);
4325   Debug("setup:LoadArtworkInfo", "sounds set == %s",
4326         artwork.snd_current_identifier);
4327   Debug("setup:LoadArtworkInfo", "music set == %s",
4328         artwork.mus_current_identifier);
4329 #endif
4330
4331   sortTreeInfo(&artwork.gfx_first);
4332   sortTreeInfo(&artwork.snd_first);
4333   sortTreeInfo(&artwork.mus_first);
4334
4335 #if ENABLE_UNUSED_CODE
4336   dumpTreeInfo(artwork.gfx_first, 0);
4337   dumpTreeInfo(artwork.snd_first, 0);
4338   dumpTreeInfo(artwork.mus_first, 0);
4339 #endif
4340 }
4341
4342 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4343 {
4344   ArtworkDirTree *artwork_new = newTreeInfo();
4345   char *top_node_name = "standalone artwork";
4346
4347   setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4348
4349   artwork_new->level_group = TRUE;
4350
4351   setString(&artwork_new->identifier,   top_node_name);
4352   setString(&artwork_new->name,         top_node_name);
4353   setString(&artwork_new->name_sorting, top_node_name);
4354
4355   // create node to link back to current custom artwork directory
4356   createParentTreeInfoNode(artwork_new);
4357
4358   // move existing custom artwork tree into newly created sub-tree
4359   artwork_new->node_group->next = *artwork_node;
4360
4361   // change custom artwork tree to contain only newly created node
4362   *artwork_node = artwork_new;
4363 }
4364
4365 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4366                                             ArtworkDirTree *node_parent,
4367                                             LevelDirTree *level_node,
4368                                             boolean empty_level_set_mode)
4369 {
4370   int type = (*artwork_node)->type;
4371
4372   // recursively check all level directories for artwork sub-directories
4373
4374   while (level_node)
4375   {
4376     boolean empty_level_set = (level_node->levels == 0);
4377
4378     // check all tree entries for artwork, but skip parent link entries
4379     if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4380     {
4381       TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4382       boolean cached = (artwork_new != NULL);
4383
4384       if (cached)
4385       {
4386         pushTreeInfo(artwork_node, artwork_new);
4387       }
4388       else
4389       {
4390         TreeInfo *topnode_last = *artwork_node;
4391         char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4392                               ARTWORK_DIRECTORY(type));
4393
4394         LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4395
4396         if (topnode_last != *artwork_node)      // check for newly added node
4397         {
4398           artwork_new = *artwork_node;
4399
4400           setString(&artwork_new->identifier,   level_node->subdir);
4401           setString(&artwork_new->name,         level_node->name);
4402           setString(&artwork_new->name_sorting, level_node->name_sorting);
4403
4404           artwork_new->sort_priority = level_node->sort_priority;
4405           artwork_new->in_user_dir = level_node->in_user_dir;
4406
4407           update_artworkinfo_cache = TRUE;
4408         }
4409
4410         free(path);
4411       }
4412
4413       // insert artwork info (from old cache or filesystem) into new cache
4414       if (artwork_new != NULL)
4415         setArtworkInfoCacheEntry(artwork_new, level_node, type);
4416     }
4417
4418     DrawInitTextItem(level_node->name);
4419
4420     if (level_node->node_group != NULL)
4421     {
4422       TreeInfo *artwork_new = newTreeInfo();
4423
4424       if (node_parent)
4425         setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4426       else
4427         setTreeInfoToDefaults(artwork_new, type);
4428
4429       artwork_new->level_group = TRUE;
4430
4431       setString(&artwork_new->identifier,   level_node->subdir);
4432
4433       if (node_parent == NULL)          // check for top tree node
4434       {
4435         char *top_node_name = (empty_level_set_mode ?
4436                                "artwork for certain level sets" :
4437                                "artwork included in level sets");
4438
4439         setString(&artwork_new->name,         top_node_name);
4440         setString(&artwork_new->name_sorting, top_node_name);
4441       }
4442       else
4443       {
4444         setString(&artwork_new->name,         level_node->name);
4445         setString(&artwork_new->name_sorting, level_node->name_sorting);
4446       }
4447
4448       pushTreeInfo(artwork_node, artwork_new);
4449
4450       // create node to link back to current custom artwork directory
4451       createParentTreeInfoNode(artwork_new);
4452
4453       // recursively step into sub-directory and look for more custom artwork
4454       LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4455                                       level_node->node_group,
4456                                       empty_level_set_mode);
4457
4458       // if sub-tree has no custom artwork at all, remove it
4459       if (artwork_new->node_group->next == NULL)
4460         removeTreeInfo(artwork_node);
4461     }
4462
4463     level_node = level_node->next;
4464   }
4465 }
4466
4467 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4468 {
4469   // move peviously loaded artwork tree into separate sub-tree
4470   MoveArtworkInfoIntoSubTree(artwork_node);
4471
4472   // load artwork from level sets into separate sub-trees
4473   LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4474   LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4475
4476   // add top tree node over all sub-trees and set parent links
4477   *artwork_node = addTopTreeInfoNode(*artwork_node);
4478 }
4479
4480 void LoadLevelArtworkInfo(void)
4481 {
4482   print_timestamp_init("LoadLevelArtworkInfo");
4483
4484   DrawInitTextHead("Looking for custom level artwork");
4485
4486   print_timestamp_time("DrawTimeText");
4487
4488   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4489   print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4490   LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4491   print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4492   LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4493   print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4494
4495   SaveArtworkInfoCache();
4496
4497   print_timestamp_time("SaveArtworkInfoCache");
4498
4499   // needed for reloading level artwork not known at ealier stage
4500   ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4501   ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4502   ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4503
4504   print_timestamp_time("getTreeInfoFromIdentifier");
4505
4506   sortTreeInfo(&artwork.gfx_first);
4507   sortTreeInfo(&artwork.snd_first);
4508   sortTreeInfo(&artwork.mus_first);
4509
4510   print_timestamp_time("sortTreeInfo");
4511
4512 #if ENABLE_UNUSED_CODE
4513   dumpTreeInfo(artwork.gfx_first, 0);
4514   dumpTreeInfo(artwork.snd_first, 0);
4515   dumpTreeInfo(artwork.mus_first, 0);
4516 #endif
4517
4518   print_timestamp_done("LoadLevelArtworkInfo");
4519 }
4520
4521 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4522                                        char *tree_subdir_new, int type)
4523 {
4524   if (tree_node_old == NULL)
4525   {
4526     if (type == TREE_TYPE_LEVEL_DIR)
4527     {
4528       // get level info tree node of personal user level set
4529       tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4530
4531       // this may happen if "setup.internal.create_user_levelset" is FALSE
4532       // or if file "levelinfo.conf" is missing in personal user level set
4533       if (tree_node_old == NULL)
4534         tree_node_old = leveldir_first->node_group;
4535     }
4536     else
4537     {
4538       // get artwork info tree node of first artwork set
4539       tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4540     }
4541   }
4542
4543   if (tree_dir == NULL)
4544     tree_dir = TREE_USERDIR(type);
4545
4546   if (tree_node_old   == NULL ||
4547       tree_dir        == NULL ||
4548       tree_subdir_new == NULL)          // should not happen
4549     return FALSE;
4550
4551   int draw_deactivation_mask = GetDrawDeactivationMask();
4552
4553   // override draw deactivation mask (temporarily disable drawing)
4554   SetDrawDeactivationMask(REDRAW_ALL);
4555
4556   if (type == TREE_TYPE_LEVEL_DIR)
4557   {
4558     // load new level set config and add it next to first user level set
4559     LoadLevelInfoFromLevelConf(&tree_node_old->next,
4560                                tree_node_old->node_parent,
4561                                tree_dir, tree_subdir_new);
4562   }
4563   else
4564   {
4565     // load new artwork set config and add it next to first artwork set
4566     LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4567                                    tree_node_old->node_parent,
4568                                    tree_dir, tree_subdir_new, type);
4569   }
4570
4571   // set draw deactivation mask to previous value
4572   SetDrawDeactivationMask(draw_deactivation_mask);
4573
4574   // get first node of level or artwork info tree
4575   TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4576
4577   // get tree info node of newly added level or artwork set
4578   TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4579                                                       tree_subdir_new);
4580
4581   if (tree_node_new == NULL)            // should not happen
4582     return FALSE;
4583
4584   // correct top link and parent node link of newly created tree node
4585   tree_node_new->node_top    = tree_node_old->node_top;
4586   tree_node_new->node_parent = tree_node_old->node_parent;
4587
4588   // sort tree info to adjust position of newly added tree set
4589   sortTreeInfo(tree_node_first);
4590
4591   return TRUE;
4592 }
4593
4594 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4595                           char *tree_subdir_new, int type)
4596 {
4597   if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4598     Fail("internal tree info structure corrupted -- aborting");
4599 }
4600
4601 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4602 {
4603   AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4604 }
4605
4606 char *getArtworkIdentifierForUserLevelSet(int type)
4607 {
4608   char *classic_artwork_set = getClassicArtworkSet(type);
4609
4610   // check for custom artwork configured in "levelinfo.conf"
4611   char *leveldir_artwork_set =
4612     *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4613   boolean has_leveldir_artwork_set =
4614     (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4615                                                classic_artwork_set));
4616
4617   // check for custom artwork in sub-directory "graphics" etc.
4618   TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4619   char *leveldir_identifier = leveldir_current->identifier;
4620   boolean has_artwork_subdir =
4621     (getTreeInfoFromIdentifier(artwork_first_node,
4622                                leveldir_identifier) != NULL);
4623
4624   return (has_leveldir_artwork_set ? leveldir_artwork_set :
4625           has_artwork_subdir       ? leveldir_identifier :
4626           classic_artwork_set);
4627 }
4628
4629 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4630 {
4631   char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4632   TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4633   TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4634
4635   if (ti == NULL)
4636   {
4637     ti = getTreeInfoFromIdentifier(artwork_first_node,
4638                                    ARTWORK_DEFAULT_SUBDIR(type));
4639     if (ti == NULL)
4640       Fail("cannot find default graphics -- should not happen");
4641   }
4642
4643   return ti;
4644 }
4645
4646 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4647 {
4648   char *graphics_set =
4649     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4650   char *sounds_set =
4651     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4652   char *music_set =
4653     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4654
4655   return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4656           !strEqual(sounds_set,   SND_CLASSIC_SUBDIR) ||
4657           !strEqual(music_set,    MUS_CLASSIC_SUBDIR));
4658 }
4659
4660 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4661                            char *level_author, int num_levels)
4662 {
4663   char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4664   char *filename_tmp = getStringCat2(filename, ".tmp");
4665   FILE *file = NULL;
4666   FILE *file_tmp = NULL;
4667   char line[MAX_LINE_LEN];
4668   boolean success = FALSE;
4669   LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4670                                                      level_subdir);
4671   // update values in level directory tree
4672
4673   if (level_name != NULL)
4674     setString(&leveldir->name, level_name);
4675
4676   if (level_author != NULL)
4677     setString(&leveldir->author, level_author);
4678
4679   if (num_levels != -1)
4680     leveldir->levels = num_levels;
4681
4682   // update values that depend on other values
4683
4684   setString(&leveldir->name_sorting, leveldir->name);
4685
4686   leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4687
4688   // sort order of level sets may have changed
4689   sortTreeInfo(&leveldir_first);
4690
4691   if ((file     = fopen(filename,     MODE_READ)) &&
4692       (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4693   {
4694     while (fgets(line, MAX_LINE_LEN, file))
4695     {
4696       if (strPrefix(line, "name:") && level_name != NULL)
4697         fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4698       else if (strPrefix(line, "author:") && level_author != NULL)
4699         fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4700       else if (strPrefix(line, "levels:") && num_levels != -1)
4701         fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4702       else
4703         fputs(line, file_tmp);
4704     }
4705
4706     success = TRUE;
4707   }
4708
4709   if (file)
4710     fclose(file);
4711
4712   if (file_tmp)
4713     fclose(file_tmp);
4714
4715   if (success)
4716     success = (rename(filename_tmp, filename) == 0);
4717
4718   free(filename);
4719   free(filename_tmp);
4720
4721   return success;
4722 }
4723
4724 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4725                            char *level_author, int num_levels,
4726                            boolean use_artwork_set)
4727 {
4728   LevelDirTree *level_info;
4729   char *filename;
4730   FILE *file;
4731   int i;
4732
4733   // create user level sub-directory, if needed
4734   createDirectory(getUserLevelDir(level_subdir), "user level");
4735
4736   filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4737
4738   if (!(file = fopen(filename, MODE_WRITE)))
4739   {
4740     Warn("cannot write level info file '%s'", filename);
4741
4742     free(filename);
4743
4744     return FALSE;
4745   }
4746
4747   level_info = newTreeInfo();
4748
4749   // always start with reliable default values
4750   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4751
4752   setString(&level_info->name, level_name);
4753   setString(&level_info->author, level_author);
4754   level_info->levels = num_levels;
4755   level_info->first_level = 1;
4756   level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4757   level_info->readonly = FALSE;
4758
4759   if (use_artwork_set)
4760   {
4761     level_info->graphics_set =
4762       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4763     level_info->sounds_set =
4764       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4765     level_info->music_set =
4766       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4767   }
4768
4769   token_value_position = TOKEN_VALUE_POSITION_SHORT;
4770
4771   fprintFileHeader(file, LEVELINFO_FILENAME);
4772
4773   ldi = *level_info;
4774   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4775   {
4776     if (i == LEVELINFO_TOKEN_NAME ||
4777         i == LEVELINFO_TOKEN_AUTHOR ||
4778         i == LEVELINFO_TOKEN_LEVELS ||
4779         i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4780         i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4781         i == LEVELINFO_TOKEN_READONLY ||
4782         (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4783                              i == LEVELINFO_TOKEN_SOUNDS_SET ||
4784                              i == LEVELINFO_TOKEN_MUSIC_SET)))
4785       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4786
4787     // just to make things nicer :)
4788     if (i == LEVELINFO_TOKEN_AUTHOR ||
4789         i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4790         (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4791       fprintf(file, "\n");      
4792   }
4793
4794   token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4795
4796   fclose(file);
4797
4798   SetFilePermissions(filename, PERMS_PRIVATE);
4799
4800   freeTreeInfo(level_info);
4801   free(filename);
4802
4803   return TRUE;
4804 }
4805
4806 static void SaveUserLevelInfo(void)
4807 {
4808   CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4809 }
4810
4811 char *getSetupValue(int type, void *value)
4812 {
4813   static char value_string[MAX_LINE_LEN];
4814
4815   if (value == NULL)
4816     return NULL;
4817
4818   switch (type)
4819   {
4820     case TYPE_BOOLEAN:
4821       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4822       break;
4823
4824     case TYPE_SWITCH:
4825       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4826       break;
4827
4828     case TYPE_SWITCH3:
4829       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
4830                             *(int *)value == FALSE ? "off" : "on"));
4831       break;
4832
4833     case TYPE_YES_NO:
4834       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4835       break;
4836
4837     case TYPE_YES_NO_AUTO:
4838       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
4839                             *(int *)value == FALSE ? "no" : "yes"));
4840       break;
4841
4842     case TYPE_ECS_AGA:
4843       strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4844       break;
4845
4846     case TYPE_KEY:
4847       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4848       break;
4849
4850     case TYPE_KEY_X11:
4851       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4852       break;
4853
4854     case TYPE_INTEGER:
4855       sprintf(value_string, "%d", *(int *)value);
4856       break;
4857
4858     case TYPE_STRING:
4859       if (*(char **)value == NULL)
4860         return NULL;
4861
4862       strcpy(value_string, *(char **)value);
4863       break;
4864
4865     case TYPE_PLAYER:
4866       sprintf(value_string, "player_%d", *(int *)value + 1);
4867       break;
4868
4869     default:
4870       value_string[0] = '\0';
4871       break;
4872   }
4873
4874   if (type & TYPE_GHOSTED)
4875     strcpy(value_string, "n/a");
4876
4877   return value_string;
4878 }
4879
4880 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4881 {
4882   int i;
4883   char *line;
4884   static char token_string[MAX_LINE_LEN];
4885   int token_type = token_info[token_nr].type;
4886   void *setup_value = token_info[token_nr].value;
4887   char *token_text = token_info[token_nr].text;
4888   char *value_string = getSetupValue(token_type, setup_value);
4889
4890   // build complete token string
4891   sprintf(token_string, "%s%s", prefix, token_text);
4892
4893   // build setup entry line
4894   line = getFormattedSetupEntry(token_string, value_string);
4895
4896   if (token_type == TYPE_KEY_X11)
4897   {
4898     Key key = *(Key *)setup_value;
4899     char *keyname = getKeyNameFromKey(key);
4900
4901     // add comment, if useful
4902     if (!strEqual(keyname, "(undefined)") &&
4903         !strEqual(keyname, "(unknown)"))
4904     {
4905       // add at least one whitespace
4906       strcat(line, " ");
4907       for (i = strlen(line); i < token_comment_position; i++)
4908         strcat(line, " ");
4909
4910       strcat(line, "# ");
4911       strcat(line, keyname);
4912     }
4913   }
4914
4915   return line;
4916 }
4917
4918 static void InitLastPlayedLevels_ParentNode(void)
4919 {
4920   LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4921   LevelDirTree *leveldir_new = NULL;
4922
4923   // check if parent node for last played levels already exists
4924   if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4925     return;
4926
4927   leveldir_new = newTreeInfo();
4928
4929   setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4930
4931   leveldir_new->level_group = TRUE;
4932   leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4933
4934   setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4935   setString(&leveldir_new->name, "<< (last played level sets)");
4936   setString(&leveldir_new->name_sorting, leveldir_new->name);
4937
4938   pushTreeInfo(leveldir_top, leveldir_new);
4939
4940   // create node to link back to current level directory
4941   createParentTreeInfoNode(leveldir_new);
4942 }
4943
4944 void UpdateLastPlayedLevels_TreeInfo(void)
4945 {
4946   char **last_level_series = setup.level_setup.last_level_series;
4947   LevelDirTree *leveldir_last;
4948   TreeInfo **node_new = NULL;
4949   int i;
4950
4951   if (last_level_series[0] == NULL)
4952     return;
4953
4954   InitLastPlayedLevels_ParentNode();
4955
4956   leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4957                                                TOKEN_STR_LAST_LEVEL_SERIES,
4958                                                TREE_NODE_TYPE_GROUP);
4959   if (leveldir_last == NULL)
4960     return;
4961
4962   node_new = &leveldir_last->node_group->next;
4963
4964   freeTreeInfo(*node_new);
4965
4966   *node_new = NULL;
4967
4968   for (i = 0; last_level_series[i] != NULL; i++)
4969   {
4970     LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4971                                                         last_level_series[i]);
4972     if (node_last == NULL)
4973       continue;
4974
4975     *node_new = getTreeInfoCopy(node_last);     // copy complete node
4976
4977     (*node_new)->node_top = &leveldir_first;    // correct top node link
4978     (*node_new)->node_parent = leveldir_last;   // correct parent node link
4979
4980     (*node_new)->is_copy = TRUE;                // mark entry as node copy
4981
4982     (*node_new)->node_group = NULL;
4983     (*node_new)->next = NULL;
4984
4985     (*node_new)->cl_first = -1;                 // force setting tree cursor
4986
4987     node_new = &((*node_new)->next);
4988   }
4989 }
4990
4991 static void UpdateLastPlayedLevels_List(void)
4992 {
4993   char **last_level_series = setup.level_setup.last_level_series;
4994   int pos = MAX_LEVELDIR_HISTORY - 1;
4995   int i;
4996
4997   // search for potentially already existing entry in list of level sets
4998   for (i = 0; last_level_series[i] != NULL; i++)
4999     if (strEqual(last_level_series[i], leveldir_current->identifier))
5000       pos = i;
5001
5002   // move list of level sets one entry down (using potentially free entry)
5003   for (i = pos; i > 0; i--)
5004     setString(&last_level_series[i], last_level_series[i - 1]);
5005
5006   // put last played level set at top position
5007   setString(&last_level_series[0], leveldir_current->identifier);
5008 }
5009
5010 #define LAST_PLAYED_MODE_SET                    1
5011 #define LAST_PLAYED_MODE_SET_FORCED             2
5012 #define LAST_PLAYED_MODE_GET                    3
5013
5014 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, int mode)
5015 {
5016   static char *identifier = NULL;
5017
5018   if (mode == LAST_PLAYED_MODE_SET)
5019   {
5020     setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
5021   }
5022   else if (mode == LAST_PLAYED_MODE_SET_FORCED)
5023   {
5024     setString(&identifier, (node ? node->identifier : NULL));
5025   }
5026   else if (mode == LAST_PLAYED_MODE_GET)
5027   {
5028     TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
5029                                                       identifier,
5030                                                       TREE_NODE_TYPE_COPY);
5031     return (node_new != NULL ? node_new : node);
5032   }
5033
5034   return NULL;          // not used
5035 }
5036
5037 void StoreLastPlayedLevels(TreeInfo *node)
5038 {
5039   StoreOrRestoreLastPlayedLevels(node, LAST_PLAYED_MODE_SET);
5040 }
5041
5042 void ForcedStoreLastPlayedLevels(TreeInfo *node)
5043 {
5044   StoreOrRestoreLastPlayedLevels(node, LAST_PLAYED_MODE_SET_FORCED);
5045 }
5046
5047 void RestoreLastPlayedLevels(TreeInfo **node)
5048 {
5049   *node = StoreOrRestoreLastPlayedLevels(*node, LAST_PLAYED_MODE_GET);
5050 }
5051
5052 boolean CheckLastPlayedLevels(void)
5053 {
5054   return (StoreOrRestoreLastPlayedLevels(NULL, LAST_PLAYED_MODE_GET) != NULL);
5055 }
5056
5057 void LoadLevelSetup_LastSeries(void)
5058 {
5059   // --------------------------------------------------------------------------
5060   // ~/.<program>/levelsetup.conf
5061   // --------------------------------------------------------------------------
5062
5063   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
5064   SetupFileHash *level_setup_hash = NULL;
5065   int pos = 0;
5066   int i;
5067
5068   // always start with reliable default values
5069   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
5070
5071   // start with empty history of last played level sets
5072   setString(&setup.level_setup.last_level_series[0], NULL);
5073
5074   if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
5075   {
5076     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
5077                                                  DEFAULT_LEVELSET);
5078     if (leveldir_current == NULL)
5079       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
5080   }
5081
5082   if ((level_setup_hash = loadSetupFileHash(filename)))
5083   {
5084     char *last_level_series =
5085       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
5086
5087     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
5088                                                  last_level_series);
5089     if (leveldir_current == NULL)
5090       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
5091
5092     char *last_played_menu_used =
5093       getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_MENU_USED);
5094
5095     // store if last level set was selected from "last played" menu
5096     if (strEqual(last_played_menu_used, "true"))
5097       ForcedStoreLastPlayedLevels(leveldir_current);
5098
5099     for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
5100     {
5101       char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
5102       LevelDirTree *leveldir_last;
5103
5104       sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
5105
5106       last_level_series = getHashEntry(level_setup_hash, token);
5107
5108       leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
5109                                                 last_level_series);
5110       if (leveldir_last != NULL)
5111         setString(&setup.level_setup.last_level_series[pos++],
5112                   last_level_series);
5113     }
5114
5115     setString(&setup.level_setup.last_level_series[pos], NULL);
5116
5117     freeSetupFileHash(level_setup_hash);
5118   }
5119   else
5120   {
5121     Debug("setup", "using default setup values");
5122   }
5123
5124   free(filename);
5125 }
5126
5127 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
5128 {
5129   // --------------------------------------------------------------------------
5130   // ~/.<program>/levelsetup.conf
5131   // --------------------------------------------------------------------------
5132
5133   // check if the current level directory structure is available at this point
5134   if (leveldir_current == NULL)
5135     return;
5136
5137   char **last_level_series = setup.level_setup.last_level_series;
5138   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
5139   FILE *file;
5140   int i;
5141
5142   InitUserDataDirectory();
5143
5144   UpdateLastPlayedLevels_List();
5145
5146   if (!(file = fopen(filename, MODE_WRITE)))
5147   {
5148     Warn("cannot write setup file '%s'", filename);
5149
5150     free(filename);
5151
5152     return;
5153   }
5154
5155   fprintFileHeader(file, LEVELSETUP_FILENAME);
5156
5157   if (deactivate_last_level_series)
5158     fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
5159
5160   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
5161                                                  leveldir_current->identifier));
5162
5163   // store if last level set was selected from "last played" menu
5164   boolean last_played_menu_used = CheckLastPlayedLevels();
5165   char *setup_value = getSetupValue(TYPE_BOOLEAN, &last_played_menu_used);
5166
5167   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_MENU_USED,
5168                                                  setup_value));
5169
5170   for (i = 0; last_level_series[i] != NULL; i++)
5171   {
5172     char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 1 + 10 + 1];
5173
5174     sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
5175
5176     fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
5177   }
5178
5179   fclose(file);
5180
5181   SetFilePermissions(filename, PERMS_PRIVATE);
5182
5183   free(filename);
5184 }
5185
5186 void SaveLevelSetup_LastSeries(void)
5187 {
5188   SaveLevelSetup_LastSeries_Ext(FALSE);
5189 }
5190
5191 void SaveLevelSetup_LastSeries_Deactivate(void)
5192 {
5193   SaveLevelSetup_LastSeries_Ext(TRUE);
5194 }
5195
5196 static void checkSeriesInfo(void)
5197 {
5198   static char *level_directory = NULL;
5199   Directory *dir;
5200 #if 0
5201   DirectoryEntry *dir_entry;
5202 #endif
5203
5204   checked_free(level_directory);
5205
5206   // check for more levels besides the 'levels' field of 'levelinfo.conf'
5207
5208   level_directory = getPath2((leveldir_current->in_user_dir ?
5209                               getUserLevelDir(NULL) :
5210                               options.level_directory),
5211                              leveldir_current->fullpath);
5212
5213   if ((dir = openDirectory(level_directory)) == NULL)
5214   {
5215     Warn("cannot read level directory '%s'", level_directory);
5216
5217     return;
5218   }
5219
5220 #if 0
5221   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
5222   {
5223     if (strlen(dir_entry->basename) > 4 &&
5224         dir_entry->basename[3] == '.' &&
5225         strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
5226     {
5227       char levelnum_str[4];
5228       int levelnum_value;
5229
5230       strncpy(levelnum_str, dir_entry->basename, 3);
5231       levelnum_str[3] = '\0';
5232
5233       levelnum_value = atoi(levelnum_str);
5234
5235       if (levelnum_value < leveldir_current->first_level)
5236       {
5237         Warn("additional level %d found", levelnum_value);
5238
5239         leveldir_current->first_level = levelnum_value;
5240       }
5241       else if (levelnum_value > leveldir_current->last_level)
5242       {
5243         Warn("additional level %d found", levelnum_value);
5244
5245         leveldir_current->last_level = levelnum_value;
5246       }
5247     }
5248   }
5249 #endif
5250
5251   closeDirectory(dir);
5252 }
5253
5254 void LoadLevelSetup_SeriesInfo(void)
5255 {
5256   char *filename;
5257   SetupFileHash *level_setup_hash = NULL;
5258   char *level_subdir = leveldir_current->subdir;
5259   int i;
5260
5261   // always start with reliable default values
5262   level_nr = leveldir_current->first_level;
5263
5264   for (i = 0; i < MAX_LEVELS; i++)
5265   {
5266     LevelStats_setPlayed(i, 0);
5267     LevelStats_setSolved(i, 0);
5268   }
5269
5270   checkSeriesInfo();
5271
5272   // --------------------------------------------------------------------------
5273   // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5274   // --------------------------------------------------------------------------
5275
5276   level_subdir = leveldir_current->subdir;
5277
5278   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5279
5280   if ((level_setup_hash = loadSetupFileHash(filename)))
5281   {
5282     char *token_value;
5283
5284     // get last played level in this level set
5285
5286     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
5287
5288     if (token_value)
5289     {
5290       level_nr = atoi(token_value);
5291
5292       if (level_nr < leveldir_current->first_level)
5293         level_nr = leveldir_current->first_level;
5294       if (level_nr > leveldir_current->last_level)
5295         level_nr = leveldir_current->last_level;
5296     }
5297
5298     // get handicap level in this level set
5299
5300     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
5301
5302     if (token_value)
5303     {
5304       int level_nr = atoi(token_value);
5305
5306       if (level_nr < leveldir_current->first_level)
5307         level_nr = leveldir_current->first_level;
5308       if (level_nr > leveldir_current->last_level + 1)
5309         level_nr = leveldir_current->last_level;
5310
5311       if (leveldir_current->user_defined || !leveldir_current->handicap)
5312         level_nr = leveldir_current->last_level;
5313
5314       leveldir_current->handicap_level = level_nr;
5315     }
5316
5317     // get number of played and solved levels in this level set
5318
5319     BEGIN_HASH_ITERATION(level_setup_hash, itr)
5320     {
5321       char *token = HASH_ITERATION_TOKEN(itr);
5322       char *value = HASH_ITERATION_VALUE(itr);
5323
5324       if (strlen(token) == 3 &&
5325           token[0] >= '0' && token[0] <= '9' &&
5326           token[1] >= '0' && token[1] <= '9' &&
5327           token[2] >= '0' && token[2] <= '9')
5328       {
5329         int level_nr = atoi(token);
5330
5331         if (value != NULL)
5332           LevelStats_setPlayed(level_nr, atoi(value));  // read 1st column
5333
5334         value = strchr(value, ' ');
5335
5336         if (value != NULL)
5337           LevelStats_setSolved(level_nr, atoi(value));  // read 2nd column
5338       }
5339     }
5340     END_HASH_ITERATION(hash, itr)
5341
5342     freeSetupFileHash(level_setup_hash);
5343   }
5344   else
5345   {
5346     Debug("setup", "using default setup values");
5347   }
5348
5349   free(filename);
5350 }
5351
5352 void SaveLevelSetup_SeriesInfo(void)
5353 {
5354   char *filename;
5355   char *level_subdir = leveldir_current->subdir;
5356   char *level_nr_str = int2str(level_nr, 0);
5357   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5358   FILE *file;
5359   int i;
5360
5361   // --------------------------------------------------------------------------
5362   // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5363   // --------------------------------------------------------------------------
5364
5365   InitLevelSetupDirectory(level_subdir);
5366
5367   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5368
5369   if (!(file = fopen(filename, MODE_WRITE)))
5370   {
5371     Warn("cannot write setup file '%s'", filename);
5372
5373     free(filename);
5374
5375     return;
5376   }
5377
5378   fprintFileHeader(file, LEVELSETUP_FILENAME);
5379
5380   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5381                                                level_nr_str));
5382   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5383                                                  handicap_level_str));
5384
5385   for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5386        i++)
5387   {
5388     if (LevelStats_getPlayed(i) > 0 ||
5389         LevelStats_getSolved(i) > 0)
5390     {
5391       char token[16];
5392       char value[16];
5393
5394       sprintf(token, "%03d", i);
5395       sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5396
5397       fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5398     }
5399   }
5400
5401   fclose(file);
5402
5403   SetFilePermissions(filename, PERMS_PRIVATE);
5404
5405   free(filename);
5406 }
5407
5408 int LevelStats_getPlayed(int nr)
5409 {
5410   return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5411 }
5412
5413 int LevelStats_getSolved(int nr)
5414 {
5415   return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5416 }
5417
5418 void LevelStats_setPlayed(int nr, int value)
5419 {
5420   if (nr >= 0 && nr < MAX_LEVELS)
5421     level_stats[nr].played = value;
5422 }
5423
5424 void LevelStats_setSolved(int nr, int value)
5425 {
5426   if (nr >= 0 && nr < MAX_LEVELS)
5427     level_stats[nr].solved = value;
5428 }
5429
5430 void LevelStats_incPlayed(int nr)
5431 {
5432   if (nr >= 0 && nr < MAX_LEVELS)
5433     level_stats[nr].played++;
5434 }
5435
5436 void LevelStats_incSolved(int nr)
5437 {
5438   if (nr >= 0 && nr < MAX_LEVELS)
5439     level_stats[nr].solved++;
5440 }
5441
5442 void LoadUserSetup(void)
5443 {
5444   // --------------------------------------------------------------------------
5445   // ~/.<program>/usersetup.conf
5446   // --------------------------------------------------------------------------
5447
5448   char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5449   SetupFileHash *user_setup_hash = NULL;
5450
5451   // always start with reliable default values
5452   user.nr = 0;
5453
5454   if ((user_setup_hash = loadSetupFileHash(filename)))
5455   {
5456     char *token_value;
5457
5458     // get last selected user number
5459     token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5460
5461     if (token_value)
5462       user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5463
5464     freeSetupFileHash(user_setup_hash);
5465   }
5466   else
5467   {
5468     Debug("setup", "using default setup values");
5469   }
5470
5471   free(filename);
5472 }
5473
5474 void SaveUserSetup(void)
5475 {
5476   // --------------------------------------------------------------------------
5477   // ~/.<program>/usersetup.conf
5478   // --------------------------------------------------------------------------
5479
5480   char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5481   FILE *file;
5482
5483   InitMainUserDataDirectory();
5484
5485   if (!(file = fopen(filename, MODE_WRITE)))
5486   {
5487     Warn("cannot write setup file '%s'", filename);
5488
5489     free(filename);
5490
5491     return;
5492   }
5493
5494   fprintFileHeader(file, USERSETUP_FILENAME);
5495
5496   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5497                                                i_to_a(user.nr)));
5498   fclose(file);
5499
5500   SetFilePermissions(filename, PERMS_PRIVATE);
5501
5502   free(filename);
5503 }