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