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