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