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