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