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