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