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