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