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