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