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