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