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