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