added definition for persistent directory name for Emscripten platform
[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_HANDICAP                27
2640 #define LEVELINFO_TOKEN_SKIP_LEVELS             28
2641 #define LEVELINFO_TOKEN_USE_EMC_TILES           29
2642
2643 #define NUM_LEVELINFO_TOKENS                    30
2644
2645 static LevelDirTree ldi;
2646
2647 static struct TokenInfo levelinfo_tokens[] =
2648 {
2649   // level directory info
2650   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
2651   { TYPE_STRING,        &ldi.name,              "name"                  },
2652   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
2653   { TYPE_STRING,        &ldi.author,            "author"                },
2654   { TYPE_STRING,        &ldi.year,              "year"                  },
2655   { TYPE_STRING,        &ldi.program_title,     "program_title"         },
2656   { TYPE_STRING,        &ldi.program_copyright, "program_copyright"     },
2657   { TYPE_STRING,        &ldi.program_company,   "program_company"       },
2658   { TYPE_STRING,        &ldi.imported_from,     "imported_from"         },
2659   { TYPE_STRING,        &ldi.imported_by,       "imported_by"           },
2660   { TYPE_STRING,        &ldi.tested_by,         "tested_by"             },
2661   { TYPE_INTEGER,       &ldi.levels,            "levels"                },
2662   { TYPE_INTEGER,       &ldi.first_level,       "first_level"           },
2663   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
2664   { TYPE_BOOLEAN,       &ldi.latest_engine,     "latest_engine"         },
2665   { TYPE_BOOLEAN,       &ldi.level_group,       "level_group"           },
2666   { TYPE_BOOLEAN,       &ldi.readonly,          "readonly"              },
2667   { TYPE_STRING,        &ldi.graphics_set_ecs,  "graphics_set.ecs"      },
2668   { TYPE_STRING,        &ldi.graphics_set_aga,  "graphics_set.aga"      },
2669   { TYPE_STRING,        &ldi.graphics_set,      "graphics_set"          },
2670   { TYPE_STRING,        &ldi.sounds_set_default,"sounds_set.default"    },
2671   { TYPE_STRING,        &ldi.sounds_set_lowpass,"sounds_set.lowpass"    },
2672   { TYPE_STRING,        &ldi.sounds_set,        "sounds_set"            },
2673   { TYPE_STRING,        &ldi.music_set,         "music_set"             },
2674   { TYPE_STRING,        &ldi.level_filename,    "filename"              },
2675   { TYPE_STRING,        &ldi.level_filetype,    "filetype"              },
2676   { TYPE_STRING,        &ldi.special_flags,     "special_flags"         },
2677   { TYPE_BOOLEAN,       &ldi.handicap,          "handicap"              },
2678   { TYPE_BOOLEAN,       &ldi.skip_levels,       "skip_levels"           },
2679   { TYPE_BOOLEAN,       &ldi.use_emc_tiles,     "use_emc_tiles"         }
2680 };
2681
2682 static struct TokenInfo artworkinfo_tokens[] =
2683 {
2684   // artwork directory info
2685   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
2686   { TYPE_STRING,        &ldi.subdir,            "subdir"                },
2687   { TYPE_STRING,        &ldi.name,              "name"                  },
2688   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
2689   { TYPE_STRING,        &ldi.author,            "author"                },
2690   { TYPE_STRING,        &ldi.program_title,     "program_title"         },
2691   { TYPE_STRING,        &ldi.program_copyright, "program_copyright"     },
2692   { TYPE_STRING,        &ldi.program_company,   "program_company"       },
2693   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
2694   { TYPE_STRING,        &ldi.basepath,          "basepath"              },
2695   { TYPE_STRING,        &ldi.fullpath,          "fullpath"              },
2696   { TYPE_BOOLEAN,       &ldi.in_user_dir,       "in_user_dir"           },
2697   { TYPE_STRING,        &ldi.class_desc,        "class_desc"            },
2698
2699   { -1,                 NULL,                   NULL                    },
2700 };
2701
2702 static char *optional_tokens[] =
2703 {
2704   "program_title",
2705   "program_copyright",
2706   "program_company",
2707
2708   NULL
2709 };
2710
2711 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2712 {
2713   ti->type = type;
2714
2715   ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR    ? &leveldir_first :
2716                   ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2717                   ti->type == TREE_TYPE_SOUNDS_DIR   ? &artwork.snd_first :
2718                   ti->type == TREE_TYPE_MUSIC_DIR    ? &artwork.mus_first :
2719                   NULL);
2720
2721   ti->node_parent = NULL;
2722   ti->node_group = NULL;
2723   ti->next = NULL;
2724
2725   ti->cl_first = -1;
2726   ti->cl_cursor = -1;
2727
2728   ti->subdir = NULL;
2729   ti->fullpath = NULL;
2730   ti->basepath = NULL;
2731   ti->identifier = NULL;
2732   ti->name = getStringCopy(ANONYMOUS_NAME);
2733   ti->name_sorting = NULL;
2734   ti->author = getStringCopy(ANONYMOUS_NAME);
2735   ti->year = NULL;
2736
2737   ti->program_title = NULL;
2738   ti->program_copyright = NULL;
2739   ti->program_company = NULL;
2740
2741   ti->sort_priority = LEVELCLASS_UNDEFINED;     // default: least priority
2742   ti->latest_engine = FALSE;                    // default: get from level
2743   ti->parent_link = FALSE;
2744   ti->is_copy = FALSE;
2745   ti->in_user_dir = FALSE;
2746   ti->user_defined = FALSE;
2747   ti->color = 0;
2748   ti->class_desc = NULL;
2749
2750   ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2751
2752   if (ti->type == TREE_TYPE_LEVEL_DIR)
2753   {
2754     ti->imported_from = NULL;
2755     ti->imported_by = NULL;
2756     ti->tested_by = NULL;
2757
2758     ti->graphics_set_ecs = NULL;
2759     ti->graphics_set_aga = NULL;
2760     ti->graphics_set = NULL;
2761     ti->sounds_set_default = NULL;
2762     ti->sounds_set_lowpass = NULL;
2763     ti->sounds_set = NULL;
2764     ti->music_set = NULL;
2765     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2766     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2767     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2768
2769     ti->level_filename = NULL;
2770     ti->level_filetype = NULL;
2771
2772     ti->special_flags = NULL;
2773
2774     ti->levels = 0;
2775     ti->first_level = 0;
2776     ti->last_level = 0;
2777     ti->level_group = FALSE;
2778     ti->handicap_level = 0;
2779     ti->readonly = TRUE;
2780     ti->handicap = TRUE;
2781     ti->skip_levels = FALSE;
2782
2783     ti->use_emc_tiles = FALSE;
2784   }
2785 }
2786
2787 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2788 {
2789   if (parent == NULL)
2790   {
2791     Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2792
2793     setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2794
2795     return;
2796   }
2797
2798   // copy all values from the parent structure
2799
2800   ti->type = parent->type;
2801
2802   ti->node_top = parent->node_top;
2803   ti->node_parent = parent;
2804   ti->node_group = NULL;
2805   ti->next = NULL;
2806
2807   ti->cl_first = -1;
2808   ti->cl_cursor = -1;
2809
2810   ti->subdir = NULL;
2811   ti->fullpath = NULL;
2812   ti->basepath = NULL;
2813   ti->identifier = NULL;
2814   ti->name = getStringCopy(ANONYMOUS_NAME);
2815   ti->name_sorting = NULL;
2816   ti->author = getStringCopy(parent->author);
2817   ti->year = getStringCopy(parent->year);
2818
2819   ti->program_title = getStringCopy(parent->program_title);
2820   ti->program_copyright = getStringCopy(parent->program_copyright);
2821   ti->program_company = getStringCopy(parent->program_company);
2822
2823   ti->sort_priority = parent->sort_priority;
2824   ti->latest_engine = parent->latest_engine;
2825   ti->parent_link = FALSE;
2826   ti->is_copy = FALSE;
2827   ti->in_user_dir = parent->in_user_dir;
2828   ti->user_defined = parent->user_defined;
2829   ti->color = parent->color;
2830   ti->class_desc = getStringCopy(parent->class_desc);
2831
2832   ti->infotext = getStringCopy(parent->infotext);
2833
2834   if (ti->type == TREE_TYPE_LEVEL_DIR)
2835   {
2836     ti->imported_from = getStringCopy(parent->imported_from);
2837     ti->imported_by = getStringCopy(parent->imported_by);
2838     ti->tested_by = getStringCopy(parent->tested_by);
2839
2840     ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2841     ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2842     ti->graphics_set = getStringCopy(parent->graphics_set);
2843     ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2844     ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2845     ti->sounds_set = getStringCopy(parent->sounds_set);
2846     ti->music_set = getStringCopy(parent->music_set);
2847     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2848     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2849     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2850
2851     ti->level_filename = getStringCopy(parent->level_filename);
2852     ti->level_filetype = getStringCopy(parent->level_filetype);
2853
2854     ti->special_flags = getStringCopy(parent->special_flags);
2855
2856     ti->levels = parent->levels;
2857     ti->first_level = parent->first_level;
2858     ti->last_level = parent->last_level;
2859     ti->level_group = FALSE;
2860     ti->handicap_level = parent->handicap_level;
2861     ti->readonly = parent->readonly;
2862     ti->handicap = parent->handicap;
2863     ti->skip_levels = parent->skip_levels;
2864
2865     ti->use_emc_tiles = parent->use_emc_tiles;
2866   }
2867 }
2868
2869 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2870 {
2871   TreeInfo *ti_copy = newTreeInfo();
2872
2873   // copy all values from the original structure
2874
2875   ti_copy->type                 = ti->type;
2876
2877   ti_copy->node_top             = ti->node_top;
2878   ti_copy->node_parent          = ti->node_parent;
2879   ti_copy->node_group           = ti->node_group;
2880   ti_copy->next                 = ti->next;
2881
2882   ti_copy->cl_first             = ti->cl_first;
2883   ti_copy->cl_cursor            = ti->cl_cursor;
2884
2885   ti_copy->subdir               = getStringCopy(ti->subdir);
2886   ti_copy->fullpath             = getStringCopy(ti->fullpath);
2887   ti_copy->basepath             = getStringCopy(ti->basepath);
2888   ti_copy->identifier           = getStringCopy(ti->identifier);
2889   ti_copy->name                 = getStringCopy(ti->name);
2890   ti_copy->name_sorting         = getStringCopy(ti->name_sorting);
2891   ti_copy->author               = getStringCopy(ti->author);
2892   ti_copy->year                 = getStringCopy(ti->year);
2893
2894   ti_copy->program_title        = getStringCopy(ti->program_title);
2895   ti_copy->program_copyright    = getStringCopy(ti->program_copyright);
2896   ti_copy->program_company      = getStringCopy(ti->program_company);
2897
2898   ti_copy->imported_from        = getStringCopy(ti->imported_from);
2899   ti_copy->imported_by          = getStringCopy(ti->imported_by);
2900   ti_copy->tested_by            = getStringCopy(ti->tested_by);
2901
2902   ti_copy->graphics_set_ecs     = getStringCopy(ti->graphics_set_ecs);
2903   ti_copy->graphics_set_aga     = getStringCopy(ti->graphics_set_aga);
2904   ti_copy->graphics_set         = getStringCopy(ti->graphics_set);
2905   ti_copy->sounds_set_default   = getStringCopy(ti->sounds_set_default);
2906   ti_copy->sounds_set_lowpass   = getStringCopy(ti->sounds_set_lowpass);
2907   ti_copy->sounds_set           = getStringCopy(ti->sounds_set);
2908   ti_copy->music_set            = getStringCopy(ti->music_set);
2909   ti_copy->graphics_path        = getStringCopy(ti->graphics_path);
2910   ti_copy->sounds_path          = getStringCopy(ti->sounds_path);
2911   ti_copy->music_path           = getStringCopy(ti->music_path);
2912
2913   ti_copy->level_filename       = getStringCopy(ti->level_filename);
2914   ti_copy->level_filetype       = getStringCopy(ti->level_filetype);
2915
2916   ti_copy->special_flags        = getStringCopy(ti->special_flags);
2917
2918   ti_copy->levels               = ti->levels;
2919   ti_copy->first_level          = ti->first_level;
2920   ti_copy->last_level           = ti->last_level;
2921   ti_copy->sort_priority        = ti->sort_priority;
2922
2923   ti_copy->latest_engine        = ti->latest_engine;
2924
2925   ti_copy->level_group          = ti->level_group;
2926   ti_copy->parent_link          = ti->parent_link;
2927   ti_copy->is_copy              = ti->is_copy;
2928   ti_copy->in_user_dir          = ti->in_user_dir;
2929   ti_copy->user_defined         = ti->user_defined;
2930   ti_copy->readonly             = ti->readonly;
2931   ti_copy->handicap             = ti->handicap;
2932   ti_copy->skip_levels          = ti->skip_levels;
2933
2934   ti_copy->use_emc_tiles        = ti->use_emc_tiles;
2935
2936   ti_copy->color                = ti->color;
2937   ti_copy->class_desc           = getStringCopy(ti->class_desc);
2938   ti_copy->handicap_level       = ti->handicap_level;
2939
2940   ti_copy->infotext             = getStringCopy(ti->infotext);
2941
2942   return ti_copy;
2943 }
2944
2945 void freeTreeInfo(TreeInfo *ti)
2946 {
2947   if (ti == NULL)
2948     return;
2949
2950   checked_free(ti->subdir);
2951   checked_free(ti->fullpath);
2952   checked_free(ti->basepath);
2953   checked_free(ti->identifier);
2954
2955   checked_free(ti->name);
2956   checked_free(ti->name_sorting);
2957   checked_free(ti->author);
2958   checked_free(ti->year);
2959
2960   checked_free(ti->program_title);
2961   checked_free(ti->program_copyright);
2962   checked_free(ti->program_company);
2963
2964   checked_free(ti->class_desc);
2965
2966   checked_free(ti->infotext);
2967
2968   if (ti->type == TREE_TYPE_LEVEL_DIR)
2969   {
2970     checked_free(ti->imported_from);
2971     checked_free(ti->imported_by);
2972     checked_free(ti->tested_by);
2973
2974     checked_free(ti->graphics_set_ecs);
2975     checked_free(ti->graphics_set_aga);
2976     checked_free(ti->graphics_set);
2977     checked_free(ti->sounds_set_default);
2978     checked_free(ti->sounds_set_lowpass);
2979     checked_free(ti->sounds_set);
2980     checked_free(ti->music_set);
2981
2982     checked_free(ti->graphics_path);
2983     checked_free(ti->sounds_path);
2984     checked_free(ti->music_path);
2985
2986     checked_free(ti->level_filename);
2987     checked_free(ti->level_filetype);
2988
2989     checked_free(ti->special_flags);
2990   }
2991
2992   // recursively free child node
2993   if (ti->node_group)
2994     freeTreeInfo(ti->node_group);
2995
2996   // recursively free next node
2997   if (ti->next)
2998     freeTreeInfo(ti->next);
2999
3000   checked_free(ti);
3001 }
3002
3003 void setSetupInfo(struct TokenInfo *token_info,
3004                   int token_nr, char *token_value)
3005 {
3006   int token_type = token_info[token_nr].type;
3007   void *setup_value = token_info[token_nr].value;
3008
3009   if (token_value == NULL)
3010     return;
3011
3012   // set setup field to corresponding token value
3013   switch (token_type)
3014   {
3015     case TYPE_BOOLEAN:
3016     case TYPE_SWITCH:
3017       *(boolean *)setup_value = get_boolean_from_string(token_value);
3018       break;
3019
3020     case TYPE_SWITCH3:
3021       *(int *)setup_value = get_switch3_from_string(token_value);
3022       break;
3023
3024     case TYPE_KEY:
3025       *(Key *)setup_value = getKeyFromKeyName(token_value);
3026       break;
3027
3028     case TYPE_KEY_X11:
3029       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
3030       break;
3031
3032     case TYPE_INTEGER:
3033       *(int *)setup_value = get_integer_from_string(token_value);
3034       break;
3035
3036     case TYPE_STRING:
3037       checked_free(*(char **)setup_value);
3038       *(char **)setup_value = getStringCopy(token_value);
3039       break;
3040
3041     case TYPE_PLAYER:
3042       *(int *)setup_value = get_player_nr_from_string(token_value);
3043       break;
3044
3045     default:
3046       break;
3047   }
3048 }
3049
3050 static int compareTreeInfoEntries(const void *object1, const void *object2)
3051 {
3052   const TreeInfo *entry1 = *((TreeInfo **)object1);
3053   const TreeInfo *entry2 = *((TreeInfo **)object2);
3054   int tree_sorting1 = TREE_SORTING(entry1);
3055   int tree_sorting2 = TREE_SORTING(entry2);
3056
3057   if (tree_sorting1 != tree_sorting2)
3058     return (tree_sorting1 - tree_sorting2);
3059   else
3060     return strcasecmp(entry1->name_sorting, entry2->name_sorting);
3061 }
3062
3063 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
3064 {
3065   TreeInfo *ti_new;
3066
3067   if (node_parent == NULL)
3068     return NULL;
3069
3070   ti_new = newTreeInfo();
3071   setTreeInfoToDefaults(ti_new, node_parent->type);
3072
3073   ti_new->node_parent = node_parent;
3074   ti_new->parent_link = TRUE;
3075
3076   setString(&ti_new->identifier, node_parent->identifier);
3077   setString(&ti_new->name, BACKLINK_TEXT_PARENT);
3078   setString(&ti_new->name_sorting, ti_new->name);
3079
3080   setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
3081   setString(&ti_new->fullpath, node_parent->fullpath);
3082
3083   ti_new->sort_priority = LEVELCLASS_PARENT;
3084   ti_new->latest_engine = node_parent->latest_engine;
3085
3086   setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3087
3088   pushTreeInfo(&node_parent->node_group, ti_new);
3089
3090   return ti_new;
3091 }
3092
3093 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
3094 {
3095   if (node_first == NULL)
3096     return NULL;
3097
3098   TreeInfo *ti_new = newTreeInfo();
3099   int type = node_first->type;
3100
3101   setTreeInfoToDefaults(ti_new, type);
3102
3103   ti_new->node_parent = NULL;
3104   ti_new->parent_link = FALSE;
3105
3106   setString(&ti_new->identifier, "top_tree_node");
3107   setString(&ti_new->name, TREE_INFOTEXT(type));
3108   setString(&ti_new->name_sorting, ti_new->name);
3109
3110   setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3111   setString(&ti_new->fullpath, ".");
3112
3113   ti_new->sort_priority = LEVELCLASS_TOP;
3114   ti_new->latest_engine = node_first->latest_engine;
3115
3116   setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3117
3118   ti_new->node_group = node_first;
3119   ti_new->level_group = TRUE;
3120
3121   TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3122
3123   setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3124   setString(&ti_new2->name_sorting, ti_new2->name);
3125
3126   return ti_new;
3127 }
3128
3129 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3130 {
3131   while (node)
3132   {
3133     if (node->node_group)
3134       setTreeInfoParentNodes(node->node_group, node);
3135
3136     node->node_parent = node_parent;
3137
3138     node = node->next;
3139   }
3140 }
3141
3142 TreeInfo *addTopTreeInfoNode(TreeInfo *node_first)
3143 {
3144   // add top tree node with back link node in previous tree
3145   node_first = createTopTreeInfoNode(node_first);
3146
3147   // set all parent links (back links) in complete tree
3148   setTreeInfoParentNodes(node_first, NULL);
3149
3150   return node_first;
3151 }
3152
3153
3154 // ----------------------------------------------------------------------------
3155 // functions for handling level and custom artwork info cache
3156 // ----------------------------------------------------------------------------
3157
3158 static void LoadArtworkInfoCache(void)
3159 {
3160   InitCacheDirectory();
3161
3162   if (artworkinfo_cache_old == NULL)
3163   {
3164     char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3165
3166     // try to load artwork info hash from already existing cache file
3167     artworkinfo_cache_old = loadSetupFileHash(filename);
3168
3169     // try to get program version that artwork info cache was written with
3170     char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3171
3172     // check program version of artwork info cache against current version
3173     if (!strEqual(version, program.version_string))
3174     {
3175       freeSetupFileHash(artworkinfo_cache_old);
3176
3177       artworkinfo_cache_old = NULL;
3178     }
3179
3180     // if no artwork info cache file was found, start with empty hash
3181     if (artworkinfo_cache_old == NULL)
3182       artworkinfo_cache_old = newSetupFileHash();
3183
3184     free(filename);
3185   }
3186
3187   if (artworkinfo_cache_new == NULL)
3188     artworkinfo_cache_new = newSetupFileHash();
3189
3190   update_artworkinfo_cache = FALSE;
3191 }
3192
3193 static void SaveArtworkInfoCache(void)
3194 {
3195   if (!update_artworkinfo_cache)
3196     return;
3197
3198   char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3199
3200   InitCacheDirectory();
3201
3202   saveSetupFileHash(artworkinfo_cache_new, filename);
3203
3204   free(filename);
3205 }
3206
3207 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3208 {
3209   static char *prefix = NULL;
3210
3211   checked_free(prefix);
3212
3213   prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3214
3215   return prefix;
3216 }
3217
3218 // (identical to above function, but separate string buffer needed -- nasty)
3219 static char *getCacheToken(char *prefix, char *suffix)
3220 {
3221   static char *token = NULL;
3222
3223   checked_free(token);
3224
3225   token = getStringCat2WithSeparator(prefix, suffix, ".");
3226
3227   return token;
3228 }
3229
3230 static char *getFileTimestampString(char *filename)
3231 {
3232   return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3233 }
3234
3235 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3236 {
3237   struct stat file_status;
3238
3239   if (timestamp_string == NULL)
3240     return TRUE;
3241
3242   if (!fileExists(filename))                    // file does not exist
3243     return (atoi(timestamp_string) != 0);
3244
3245   if (stat(filename, &file_status) != 0)        // cannot stat file
3246     return TRUE;
3247
3248   return (file_status.st_mtime != atoi(timestamp_string));
3249 }
3250
3251 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3252 {
3253   char *identifier = level_node->subdir;
3254   char *type_string = ARTWORK_DIRECTORY(type);
3255   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3256   char *token_main = getCacheToken(token_prefix, "CACHED");
3257   char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3258   boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3259   TreeInfo *artwork_info = NULL;
3260
3261   if (!use_artworkinfo_cache)
3262     return NULL;
3263
3264   if (optional_tokens_hash == NULL)
3265   {
3266     int i;
3267
3268     // create hash from list of optional tokens (for quick access)
3269     optional_tokens_hash = newSetupFileHash();
3270     for (i = 0; optional_tokens[i] != NULL; i++)
3271       setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3272   }
3273
3274   if (cached)
3275   {
3276     int i;
3277
3278     artwork_info = newTreeInfo();
3279     setTreeInfoToDefaults(artwork_info, type);
3280
3281     // set all structure fields according to the token/value pairs
3282     ldi = *artwork_info;
3283     for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3284     {
3285       char *token_suffix = artworkinfo_tokens[i].text;
3286       char *token = getCacheToken(token_prefix, token_suffix);
3287       char *value = getHashEntry(artworkinfo_cache_old, token);
3288       boolean optional =
3289         (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3290
3291       setSetupInfo(artworkinfo_tokens, i, value);
3292
3293       // check if cache entry for this item is mandatory, but missing
3294       if (value == NULL && !optional)
3295       {
3296         Warn("missing cache entry '%s'", token);
3297
3298         cached = FALSE;
3299       }
3300     }
3301
3302     *artwork_info = ldi;
3303   }
3304
3305   if (cached)
3306   {
3307     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3308                                         LEVELINFO_FILENAME);
3309     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3310                                           ARTWORKINFO_FILENAME(type));
3311
3312     // check if corresponding "levelinfo.conf" file has changed
3313     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3314     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3315
3316     if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3317       cached = FALSE;
3318
3319     // check if corresponding "<artworkinfo>.conf" file has changed
3320     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3321     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3322
3323     if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3324       cached = FALSE;
3325
3326     checked_free(filename_levelinfo);
3327     checked_free(filename_artworkinfo);
3328   }
3329
3330   if (!cached && artwork_info != NULL)
3331   {
3332     freeTreeInfo(artwork_info);
3333
3334     return NULL;
3335   }
3336
3337   return artwork_info;
3338 }
3339
3340 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3341                                      LevelDirTree *level_node, int type)
3342 {
3343   char *identifier = level_node->subdir;
3344   char *type_string = ARTWORK_DIRECTORY(type);
3345   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3346   char *token_main = getCacheToken(token_prefix, "CACHED");
3347   boolean set_cache_timestamps = TRUE;
3348   int i;
3349
3350   setHashEntry(artworkinfo_cache_new, token_main, "true");
3351
3352   if (set_cache_timestamps)
3353   {
3354     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3355                                         LEVELINFO_FILENAME);
3356     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3357                                           ARTWORKINFO_FILENAME(type));
3358     char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3359     char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3360
3361     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3362     setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3363
3364     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3365     setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3366
3367     checked_free(filename_levelinfo);
3368     checked_free(filename_artworkinfo);
3369     checked_free(timestamp_levelinfo);
3370     checked_free(timestamp_artworkinfo);
3371   }
3372
3373   ldi = *artwork_info;
3374   for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3375   {
3376     char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3377     char *value = getSetupValue(artworkinfo_tokens[i].type,
3378                                 artworkinfo_tokens[i].value);
3379     if (value != NULL)
3380       setHashEntry(artworkinfo_cache_new, token, value);
3381   }
3382 }
3383
3384
3385 // ----------------------------------------------------------------------------
3386 // functions for loading level info and custom artwork info
3387 // ----------------------------------------------------------------------------
3388
3389 int GetZipFileTreeType(char *zip_filename)
3390 {
3391   static char *top_dir_path = NULL;
3392   static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3393   static char *conf_basename[NUM_BASE_TREE_TYPES] =
3394   {
3395     GRAPHICSINFO_FILENAME,
3396     SOUNDSINFO_FILENAME,
3397     MUSICINFO_FILENAME,
3398     LEVELINFO_FILENAME
3399   };
3400   int j;
3401
3402   checked_free(top_dir_path);
3403   top_dir_path = NULL;
3404
3405   for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3406   {
3407     checked_free(top_dir_conf_filename[j]);
3408     top_dir_conf_filename[j] = NULL;
3409   }
3410
3411   char **zip_entries = zip_list(zip_filename);
3412
3413   // check if zip file successfully opened
3414   if (zip_entries == NULL || zip_entries[0] == NULL)
3415     return TREE_TYPE_UNDEFINED;
3416
3417   // first zip file entry is expected to be top level directory
3418   char *top_dir = zip_entries[0];
3419
3420   // check if valid top level directory found in zip file
3421   if (!strSuffix(top_dir, "/"))
3422     return TREE_TYPE_UNDEFINED;
3423
3424   // get filenames of valid configuration files in top level directory
3425   for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3426     top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3427
3428   int tree_type = TREE_TYPE_UNDEFINED;
3429   int e = 0;
3430
3431   while (zip_entries[e] != NULL)
3432   {
3433     // check if every zip file entry is below top level directory
3434     if (!strPrefix(zip_entries[e], top_dir))
3435       return TREE_TYPE_UNDEFINED;
3436
3437     // check if this zip file entry is a valid configuration filename
3438     for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3439     {
3440       if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3441       {
3442         // only exactly one valid configuration file allowed
3443         if (tree_type != TREE_TYPE_UNDEFINED)
3444           return TREE_TYPE_UNDEFINED;
3445
3446         tree_type = j;
3447       }
3448     }
3449
3450     e++;
3451   }
3452
3453   return tree_type;
3454 }
3455
3456 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3457                                         int tree_type)
3458 {
3459   static char *top_dir_path = NULL;
3460   static char *top_dir_conf_filename = NULL;
3461
3462   checked_free(top_dir_path);
3463   checked_free(top_dir_conf_filename);
3464
3465   top_dir_path = NULL;
3466   top_dir_conf_filename = NULL;
3467
3468   char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3469                          ARTWORKINFO_FILENAME(tree_type));
3470
3471   // check if valid configuration filename determined
3472   if (conf_basename == NULL || strEqual(conf_basename, ""))
3473     return FALSE;
3474
3475   char **zip_entries = zip_list(zip_filename);
3476
3477   // check if zip file successfully opened
3478   if (zip_entries == NULL || zip_entries[0] == NULL)
3479     return FALSE;
3480
3481   // first zip file entry is expected to be top level directory
3482   char *top_dir = zip_entries[0];
3483
3484   // check if valid top level directory found in zip file
3485   if (!strSuffix(top_dir, "/"))
3486     return FALSE;
3487
3488   // get path of extracted top level directory
3489   top_dir_path = getPath2(directory, top_dir);
3490
3491   // remove trailing directory separator from top level directory path
3492   // (required to be able to check for file and directory in next step)
3493   top_dir_path[strlen(top_dir_path) - 1] = '\0';
3494
3495   // check if zip file's top level directory already exists in target directory
3496   if (fileExists(top_dir_path))         // (checks for file and directory)
3497     return FALSE;
3498
3499   // get filename of configuration file in top level directory
3500   top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3501
3502   boolean found_top_dir_conf_filename = FALSE;
3503   int i = 0;
3504
3505   while (zip_entries[i] != NULL)
3506   {
3507     // check if every zip file entry is below top level directory
3508     if (!strPrefix(zip_entries[i], top_dir))
3509       return FALSE;
3510
3511     // check if this zip file entry is the configuration filename
3512     if (strEqual(zip_entries[i], top_dir_conf_filename))
3513       found_top_dir_conf_filename = TRUE;
3514
3515     i++;
3516   }
3517
3518   // check if valid configuration filename was found in zip file
3519   if (!found_top_dir_conf_filename)
3520     return FALSE;
3521
3522   return TRUE;
3523 }
3524
3525 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3526                                   int tree_type)
3527 {
3528   boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3529                                                     tree_type);
3530
3531   if (!zip_file_valid)
3532   {
3533     Warn("zip file '%s' rejected!", zip_filename);
3534
3535     return NULL;
3536   }
3537
3538   char **zip_entries = zip_extract(zip_filename, directory);
3539
3540   if (zip_entries == NULL)
3541   {
3542     Warn("zip file '%s' could not be extracted!", zip_filename);
3543
3544     return NULL;
3545   }
3546
3547   Info("zip file '%s' successfully extracted!", zip_filename);
3548
3549   // first zip file entry contains top level directory
3550   char *top_dir = zip_entries[0];
3551
3552   // remove trailing directory separator from top level directory
3553   top_dir[strlen(top_dir) - 1] = '\0';
3554
3555   return top_dir;
3556 }
3557
3558 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3559 {
3560   Directory *dir;
3561   DirectoryEntry *dir_entry;
3562
3563   if ((dir = openDirectory(directory)) == NULL)
3564   {
3565     // display error if directory is main "options.graphics_directory" etc.
3566     if (tree_type == TREE_TYPE_LEVEL_DIR ||
3567         directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3568       Warn("cannot read directory '%s'", directory);
3569
3570     return;
3571   }
3572
3573   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
3574   {
3575     // skip non-zip files (and also directories with zip extension)
3576     if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3577       continue;
3578
3579     char *zip_filename = getPath2(directory, dir_entry->basename);
3580     char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3581     char *zip_filename_rejected  = getStringCat2(zip_filename, ".rejected");
3582
3583     // check if zip file hasn't already been extracted or rejected
3584     if (!fileExists(zip_filename_extracted) &&
3585         !fileExists(zip_filename_rejected))
3586     {
3587       char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3588                                                   tree_type);
3589       char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3590                                zip_filename_rejected);
3591       FILE *marker_file;
3592
3593       // create empty file to mark zip file as extracted or rejected
3594       if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3595         fclose(marker_file);
3596
3597       free(zip_filename);
3598       free(zip_filename_extracted);
3599       free(zip_filename_rejected);
3600     }
3601   }
3602
3603   closeDirectory(dir);
3604 }
3605
3606 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3607 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3608
3609 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3610                                           TreeInfo *node_parent,
3611                                           char *level_directory,
3612                                           char *directory_name)
3613 {
3614   char *directory_path = getPath2(level_directory, directory_name);
3615   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3616   SetupFileHash *setup_file_hash;
3617   LevelDirTree *leveldir_new = NULL;
3618   int i;
3619
3620   // unless debugging, silently ignore directories without "levelinfo.conf"
3621   if (!options.debug && !fileExists(filename))
3622   {
3623     free(directory_path);
3624     free(filename);
3625
3626     return FALSE;
3627   }
3628
3629   setup_file_hash = loadSetupFileHash(filename);
3630
3631   if (setup_file_hash == NULL)
3632   {
3633 #if DEBUG_NO_CONFIG_FILE
3634     Debug("setup", "ignoring level directory '%s'", directory_path);
3635 #endif
3636
3637     free(directory_path);
3638     free(filename);
3639
3640     return FALSE;
3641   }
3642
3643   leveldir_new = newTreeInfo();
3644
3645   if (node_parent)
3646     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3647   else
3648     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3649
3650   leveldir_new->subdir = getStringCopy(directory_name);
3651
3652   // set all structure fields according to the token/value pairs
3653   ldi = *leveldir_new;
3654   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3655     setSetupInfo(levelinfo_tokens, i,
3656                  getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3657   *leveldir_new = ldi;
3658
3659   if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3660     setString(&leveldir_new->name, leveldir_new->subdir);
3661
3662   if (leveldir_new->identifier == NULL)
3663     leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3664
3665   if (leveldir_new->name_sorting == NULL)
3666     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3667
3668   if (node_parent == NULL)              // top level group
3669   {
3670     leveldir_new->basepath = getStringCopy(level_directory);
3671     leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3672   }
3673   else                                  // sub level group
3674   {
3675     leveldir_new->basepath = getStringCopy(node_parent->basepath);
3676     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3677   }
3678
3679   leveldir_new->last_level =
3680     leveldir_new->first_level + leveldir_new->levels - 1;
3681
3682   leveldir_new->in_user_dir =
3683     (!strEqual(leveldir_new->basepath, options.level_directory));
3684
3685   // adjust some settings if user's private level directory was detected
3686   if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3687       leveldir_new->in_user_dir &&
3688       (strEqual(leveldir_new->subdir, getLoginName()) ||
3689        strEqual(leveldir_new->name,   getLoginName()) ||
3690        strEqual(leveldir_new->author, getRealName())))
3691   {
3692     leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3693     leveldir_new->readonly = FALSE;
3694   }
3695
3696   leveldir_new->user_defined =
3697     (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3698
3699   setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3700
3701   leveldir_new->handicap_level =        // set handicap to default value
3702     (leveldir_new->user_defined || !leveldir_new->handicap ?
3703      leveldir_new->last_level : leveldir_new->first_level);
3704
3705   DrawInitTextItem(leveldir_new->name);
3706
3707   pushTreeInfo(node_first, leveldir_new);
3708
3709   freeSetupFileHash(setup_file_hash);
3710
3711   if (leveldir_new->level_group)
3712   {
3713     // create node to link back to current level directory
3714     createParentTreeInfoNode(leveldir_new);
3715
3716     // recursively step into sub-directory and look for more level series
3717     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3718                               leveldir_new, directory_path);
3719   }
3720
3721   free(directory_path);
3722   free(filename);
3723
3724   return TRUE;
3725 }
3726
3727 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3728                                       TreeInfo *node_parent,
3729                                       char *level_directory)
3730 {
3731   // ---------- 1st stage: process any level set zip files ----------
3732
3733   ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3734
3735   // ---------- 2nd stage: check for level set directories ----------
3736
3737   Directory *dir;
3738   DirectoryEntry *dir_entry;
3739   boolean valid_entry_found = FALSE;
3740
3741   if ((dir = openDirectory(level_directory)) == NULL)
3742   {
3743     Warn("cannot read level directory '%s'", level_directory);
3744
3745     return;
3746   }
3747
3748   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
3749   {
3750     char *directory_name = dir_entry->basename;
3751     char *directory_path = getPath2(level_directory, directory_name);
3752
3753     // skip entries for current and parent directory
3754     if (strEqual(directory_name, ".") ||
3755         strEqual(directory_name, ".."))
3756     {
3757       free(directory_path);
3758
3759       continue;
3760     }
3761
3762     // find out if directory entry is itself a directory
3763     if (!dir_entry->is_directory)                       // not a directory
3764     {
3765       free(directory_path);
3766
3767       continue;
3768     }
3769
3770     free(directory_path);
3771
3772     if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3773         strEqual(directory_name, SOUNDS_DIRECTORY) ||
3774         strEqual(directory_name, MUSIC_DIRECTORY))
3775       continue;
3776
3777     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3778                                                     level_directory,
3779                                                     directory_name);
3780   }
3781
3782   closeDirectory(dir);
3783
3784   // special case: top level directory may directly contain "levelinfo.conf"
3785   if (node_parent == NULL && !valid_entry_found)
3786   {
3787     // check if this directory directly contains a file "levelinfo.conf"
3788     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3789                                                     level_directory, ".");
3790   }
3791
3792   if (!valid_entry_found)
3793     Warn("cannot find any valid level series in directory '%s'",
3794           level_directory);
3795 }
3796
3797 boolean AdjustGraphicsForEMC(void)
3798 {
3799   boolean settings_changed = FALSE;
3800
3801   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3802   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3803
3804   return settings_changed;
3805 }
3806
3807 boolean AdjustSoundsForEMC(void)
3808 {
3809   boolean settings_changed = FALSE;
3810
3811   settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3812   settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3813
3814   return settings_changed;
3815 }
3816
3817 void LoadLevelInfo(void)
3818 {
3819   InitUserLevelDirectory(getLoginName());
3820
3821   DrawInitTextHead("Loading level series");
3822
3823   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3824   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3825
3826   leveldir_first = createTopTreeInfoNode(leveldir_first);
3827
3828   /* after loading all level set information, clone the level directory tree
3829      and remove all level sets without levels (these may still contain artwork
3830      to be offered in the setup menu as "custom artwork", and are therefore
3831      checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3832   leveldir_first_all = leveldir_first;
3833   cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3834
3835   AdjustGraphicsForEMC();
3836   AdjustSoundsForEMC();
3837
3838   // before sorting, the first entries will be from the user directory
3839   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3840
3841   if (leveldir_first == NULL)
3842     Fail("cannot find any valid level series in any directory");
3843
3844   sortTreeInfo(&leveldir_first);
3845
3846 #if ENABLE_UNUSED_CODE
3847   dumpTreeInfo(leveldir_first, 0);
3848 #endif
3849 }
3850
3851 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3852                                               TreeInfo *node_parent,
3853                                               char *base_directory,
3854                                               char *directory_name, int type)
3855 {
3856   char *directory_path = getPath2(base_directory, directory_name);
3857   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3858   SetupFileHash *setup_file_hash = NULL;
3859   TreeInfo *artwork_new = NULL;
3860   int i;
3861
3862   if (fileExists(filename))
3863     setup_file_hash = loadSetupFileHash(filename);
3864
3865   if (setup_file_hash == NULL)  // no config file -- look for artwork files
3866   {
3867     Directory *dir;
3868     DirectoryEntry *dir_entry;
3869     boolean valid_file_found = FALSE;
3870
3871     if ((dir = openDirectory(directory_path)) != NULL)
3872     {
3873       while ((dir_entry = readDirectory(dir)) != NULL)
3874       {
3875         if (FileIsArtworkType(dir_entry->filename, type))
3876         {
3877           valid_file_found = TRUE;
3878
3879           break;
3880         }
3881       }
3882
3883       closeDirectory(dir);
3884     }
3885
3886     if (!valid_file_found)
3887     {
3888 #if DEBUG_NO_CONFIG_FILE
3889       if (!strEqual(directory_name, "."))
3890         Debug("setup", "ignoring artwork directory '%s'", directory_path);
3891 #endif
3892
3893       free(directory_path);
3894       free(filename);
3895
3896       return FALSE;
3897     }
3898   }
3899
3900   artwork_new = newTreeInfo();
3901
3902   if (node_parent)
3903     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3904   else
3905     setTreeInfoToDefaults(artwork_new, type);
3906
3907   artwork_new->subdir = getStringCopy(directory_name);
3908
3909   if (setup_file_hash)  // (before defining ".color" and ".class_desc")
3910   {
3911     // set all structure fields according to the token/value pairs
3912     ldi = *artwork_new;
3913     for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3914       setSetupInfo(levelinfo_tokens, i,
3915                    getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3916     *artwork_new = ldi;
3917
3918     if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3919       setString(&artwork_new->name, artwork_new->subdir);
3920
3921     if (artwork_new->identifier == NULL)
3922       artwork_new->identifier = getStringCopy(artwork_new->subdir);
3923
3924     if (artwork_new->name_sorting == NULL)
3925       artwork_new->name_sorting = getStringCopy(artwork_new->name);
3926   }
3927
3928   if (node_parent == NULL)              // top level group
3929   {
3930     artwork_new->basepath = getStringCopy(base_directory);
3931     artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3932   }
3933   else                                  // sub level group
3934   {
3935     artwork_new->basepath = getStringCopy(node_parent->basepath);
3936     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3937   }
3938
3939   artwork_new->in_user_dir =
3940     (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3941
3942   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3943
3944   if (setup_file_hash == NULL)  // (after determining ".user_defined")
3945   {
3946     if (strEqual(artwork_new->subdir, "."))
3947     {
3948       if (artwork_new->user_defined)
3949       {
3950         setString(&artwork_new->identifier, "private");
3951         artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3952       }
3953       else
3954       {
3955         setString(&artwork_new->identifier, "classic");
3956         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3957       }
3958
3959       setString(&artwork_new->class_desc,
3960                 getLevelClassDescription(artwork_new));
3961     }
3962     else
3963     {
3964       setString(&artwork_new->identifier, artwork_new->subdir);
3965     }
3966
3967     setString(&artwork_new->name, artwork_new->identifier);
3968     setString(&artwork_new->name_sorting, artwork_new->name);
3969   }
3970
3971   pushTreeInfo(node_first, artwork_new);
3972
3973   freeSetupFileHash(setup_file_hash);
3974
3975   free(directory_path);
3976   free(filename);
3977
3978   return TRUE;
3979 }
3980
3981 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3982                                           TreeInfo *node_parent,
3983                                           char *base_directory, int type)
3984 {
3985   // ---------- 1st stage: process any artwork set zip files ----------
3986
3987   ProcessZipFilesInDirectory(base_directory, type);
3988
3989   // ---------- 2nd stage: check for artwork set directories ----------
3990
3991   Directory *dir;
3992   DirectoryEntry *dir_entry;
3993   boolean valid_entry_found = FALSE;
3994
3995   if ((dir = openDirectory(base_directory)) == NULL)
3996   {
3997     // display error if directory is main "options.graphics_directory" etc.
3998     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3999       Warn("cannot read directory '%s'", base_directory);
4000
4001     return;
4002   }
4003
4004   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
4005   {
4006     char *directory_name = dir_entry->basename;
4007     char *directory_path = getPath2(base_directory, directory_name);
4008
4009     // skip directory entries for current and parent directory
4010     if (strEqual(directory_name, ".") ||
4011         strEqual(directory_name, ".."))
4012     {
4013       free(directory_path);
4014
4015       continue;
4016     }
4017
4018     // skip directory entries which are not a directory
4019     if (!dir_entry->is_directory)                       // not a directory
4020     {
4021       free(directory_path);
4022
4023       continue;
4024     }
4025
4026     free(directory_path);
4027
4028     // check if this directory contains artwork with or without config file
4029     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4030                                                         base_directory,
4031                                                         directory_name, type);
4032   }
4033
4034   closeDirectory(dir);
4035
4036   // check if this directory directly contains artwork itself
4037   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4038                                                       base_directory, ".",
4039                                                       type);
4040   if (!valid_entry_found)
4041     Warn("cannot find any valid artwork in directory '%s'", base_directory);
4042 }
4043
4044 static TreeInfo *getDummyArtworkInfo(int type)
4045 {
4046   // this is only needed when there is completely no artwork available
4047   TreeInfo *artwork_new = newTreeInfo();
4048
4049   setTreeInfoToDefaults(artwork_new, type);
4050
4051   setString(&artwork_new->subdir,   UNDEFINED_FILENAME);
4052   setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
4053   setString(&artwork_new->basepath, UNDEFINED_FILENAME);
4054
4055   setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
4056   setString(&artwork_new->name,         UNDEFINED_FILENAME);
4057   setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4058
4059   return artwork_new;
4060 }
4061
4062 void SetCurrentArtwork(int type)
4063 {
4064   ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
4065   ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
4066   char *setup_set = SETUP_ARTWORK_SET(setup, type);
4067   char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
4068
4069   // set current artwork to artwork configured in setup menu
4070   *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
4071
4072   // if not found, set current artwork to default artwork
4073   if (*current_ptr == NULL)
4074     *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
4075
4076   // if not found, set current artwork to first artwork in tree
4077   if (*current_ptr == NULL)
4078     *current_ptr = getFirstValidTreeInfoEntry(first_node);
4079 }
4080
4081 void ChangeCurrentArtworkIfNeeded(int type)
4082 {
4083   char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
4084   char *setup_set = SETUP_ARTWORK_SET(setup, type);
4085
4086   if (!strEqual(current_identifier, setup_set))
4087     SetCurrentArtwork(type);
4088 }
4089
4090 void LoadArtworkInfo(void)
4091 {
4092   LoadArtworkInfoCache();
4093
4094   DrawInitTextHead("Looking for custom artwork");
4095
4096   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4097                                 options.graphics_directory,
4098                                 TREE_TYPE_GRAPHICS_DIR);
4099   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4100                                 getUserGraphicsDir(),
4101                                 TREE_TYPE_GRAPHICS_DIR);
4102
4103   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4104                                 options.sounds_directory,
4105                                 TREE_TYPE_SOUNDS_DIR);
4106   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4107                                 getUserSoundsDir(),
4108                                 TREE_TYPE_SOUNDS_DIR);
4109
4110   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4111                                 options.music_directory,
4112                                 TREE_TYPE_MUSIC_DIR);
4113   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4114                                 getUserMusicDir(),
4115                                 TREE_TYPE_MUSIC_DIR);
4116
4117   if (artwork.gfx_first == NULL)
4118     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4119   if (artwork.snd_first == NULL)
4120     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4121   if (artwork.mus_first == NULL)
4122     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4123
4124   // before sorting, the first entries will be from the user directory
4125   SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4126   SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4127   SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4128
4129   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4130   artwork.snd_current_identifier = artwork.snd_current->identifier;
4131   artwork.mus_current_identifier = artwork.mus_current->identifier;
4132
4133 #if ENABLE_UNUSED_CODE
4134   Debug("setup:LoadArtworkInfo", "graphics set == %s",
4135         artwork.gfx_current_identifier);
4136   Debug("setup:LoadArtworkInfo", "sounds set == %s",
4137         artwork.snd_current_identifier);
4138   Debug("setup:LoadArtworkInfo", "music set == %s",
4139         artwork.mus_current_identifier);
4140 #endif
4141
4142   sortTreeInfo(&artwork.gfx_first);
4143   sortTreeInfo(&artwork.snd_first);
4144   sortTreeInfo(&artwork.mus_first);
4145
4146 #if ENABLE_UNUSED_CODE
4147   dumpTreeInfo(artwork.gfx_first, 0);
4148   dumpTreeInfo(artwork.snd_first, 0);
4149   dumpTreeInfo(artwork.mus_first, 0);
4150 #endif
4151 }
4152
4153 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4154 {
4155   ArtworkDirTree *artwork_new = newTreeInfo();
4156   char *top_node_name = "standalone artwork";
4157
4158   setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4159
4160   artwork_new->level_group = TRUE;
4161
4162   setString(&artwork_new->identifier,   top_node_name);
4163   setString(&artwork_new->name,         top_node_name);
4164   setString(&artwork_new->name_sorting, top_node_name);
4165
4166   // create node to link back to current custom artwork directory
4167   createParentTreeInfoNode(artwork_new);
4168
4169   // move existing custom artwork tree into newly created sub-tree
4170   artwork_new->node_group->next = *artwork_node;
4171
4172   // change custom artwork tree to contain only newly created node
4173   *artwork_node = artwork_new;
4174 }
4175
4176 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4177                                             ArtworkDirTree *node_parent,
4178                                             LevelDirTree *level_node,
4179                                             boolean empty_level_set_mode)
4180 {
4181   int type = (*artwork_node)->type;
4182
4183   // recursively check all level directories for artwork sub-directories
4184
4185   while (level_node)
4186   {
4187     boolean empty_level_set = (level_node->levels == 0);
4188
4189     // check all tree entries for artwork, but skip parent link entries
4190     if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4191     {
4192       TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4193       boolean cached = (artwork_new != NULL);
4194
4195       if (cached)
4196       {
4197         pushTreeInfo(artwork_node, artwork_new);
4198       }
4199       else
4200       {
4201         TreeInfo *topnode_last = *artwork_node;
4202         char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4203                               ARTWORK_DIRECTORY(type));
4204
4205         LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4206
4207         if (topnode_last != *artwork_node)      // check for newly added node
4208         {
4209           artwork_new = *artwork_node;
4210
4211           setString(&artwork_new->identifier,   level_node->subdir);
4212           setString(&artwork_new->name,         level_node->name);
4213           setString(&artwork_new->name_sorting, level_node->name_sorting);
4214
4215           artwork_new->sort_priority = level_node->sort_priority;
4216           artwork_new->in_user_dir = level_node->in_user_dir;
4217
4218           update_artworkinfo_cache = TRUE;
4219         }
4220
4221         free(path);
4222       }
4223
4224       // insert artwork info (from old cache or filesystem) into new cache
4225       if (artwork_new != NULL)
4226         setArtworkInfoCacheEntry(artwork_new, level_node, type);
4227     }
4228
4229     DrawInitTextItem(level_node->name);
4230
4231     if (level_node->node_group != NULL)
4232     {
4233       TreeInfo *artwork_new = newTreeInfo();
4234
4235       if (node_parent)
4236         setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4237       else
4238         setTreeInfoToDefaults(artwork_new, type);
4239
4240       artwork_new->level_group = TRUE;
4241
4242       setString(&artwork_new->identifier,   level_node->subdir);
4243
4244       if (node_parent == NULL)          // check for top tree node
4245       {
4246         char *top_node_name = (empty_level_set_mode ?
4247                                "artwork for certain level sets" :
4248                                "artwork included in level sets");
4249
4250         setString(&artwork_new->name,         top_node_name);
4251         setString(&artwork_new->name_sorting, top_node_name);
4252       }
4253       else
4254       {
4255         setString(&artwork_new->name,         level_node->name);
4256         setString(&artwork_new->name_sorting, level_node->name_sorting);
4257       }
4258
4259       pushTreeInfo(artwork_node, artwork_new);
4260
4261       // create node to link back to current custom artwork directory
4262       createParentTreeInfoNode(artwork_new);
4263
4264       // recursively step into sub-directory and look for more custom artwork
4265       LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4266                                       level_node->node_group,
4267                                       empty_level_set_mode);
4268
4269       // if sub-tree has no custom artwork at all, remove it
4270       if (artwork_new->node_group->next == NULL)
4271         removeTreeInfo(artwork_node);
4272     }
4273
4274     level_node = level_node->next;
4275   }
4276 }
4277
4278 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4279 {
4280   // move peviously loaded artwork tree into separate sub-tree
4281   MoveArtworkInfoIntoSubTree(artwork_node);
4282
4283   // load artwork from level sets into separate sub-trees
4284   LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4285   LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4286
4287   // add top tree node over all sub-trees and set parent links
4288   *artwork_node = addTopTreeInfoNode(*artwork_node);
4289 }
4290
4291 void LoadLevelArtworkInfo(void)
4292 {
4293   print_timestamp_init("LoadLevelArtworkInfo");
4294
4295   DrawInitTextHead("Looking for custom level artwork");
4296
4297   print_timestamp_time("DrawTimeText");
4298
4299   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4300   print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4301   LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4302   print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4303   LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4304   print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4305
4306   SaveArtworkInfoCache();
4307
4308   print_timestamp_time("SaveArtworkInfoCache");
4309
4310   // needed for reloading level artwork not known at ealier stage
4311   ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4312   ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4313   ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4314
4315   print_timestamp_time("getTreeInfoFromIdentifier");
4316
4317   sortTreeInfo(&artwork.gfx_first);
4318   sortTreeInfo(&artwork.snd_first);
4319   sortTreeInfo(&artwork.mus_first);
4320
4321   print_timestamp_time("sortTreeInfo");
4322
4323 #if ENABLE_UNUSED_CODE
4324   dumpTreeInfo(artwork.gfx_first, 0);
4325   dumpTreeInfo(artwork.snd_first, 0);
4326   dumpTreeInfo(artwork.mus_first, 0);
4327 #endif
4328
4329   print_timestamp_done("LoadLevelArtworkInfo");
4330 }
4331
4332 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4333                                        char *tree_subdir_new, int type)
4334 {
4335   if (tree_node_old == NULL)
4336   {
4337     if (type == TREE_TYPE_LEVEL_DIR)
4338     {
4339       // get level info tree node of personal user level set
4340       tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4341
4342       // this may happen if "setup.internal.create_user_levelset" is FALSE
4343       // or if file "levelinfo.conf" is missing in personal user level set
4344       if (tree_node_old == NULL)
4345         tree_node_old = leveldir_first->node_group;
4346     }
4347     else
4348     {
4349       // get artwork info tree node of first artwork set
4350       tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4351     }
4352   }
4353
4354   if (tree_dir == NULL)
4355     tree_dir = TREE_USERDIR(type);
4356
4357   if (tree_node_old   == NULL ||
4358       tree_dir        == NULL ||
4359       tree_subdir_new == NULL)          // should not happen
4360     return FALSE;
4361
4362   int draw_deactivation_mask = GetDrawDeactivationMask();
4363
4364   // override draw deactivation mask (temporarily disable drawing)
4365   SetDrawDeactivationMask(REDRAW_ALL);
4366
4367   if (type == TREE_TYPE_LEVEL_DIR)
4368   {
4369     // load new level set config and add it next to first user level set
4370     LoadLevelInfoFromLevelConf(&tree_node_old->next,
4371                                tree_node_old->node_parent,
4372                                tree_dir, tree_subdir_new);
4373   }
4374   else
4375   {
4376     // load new artwork set config and add it next to first artwork set
4377     LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4378                                    tree_node_old->node_parent,
4379                                    tree_dir, tree_subdir_new, type);
4380   }
4381
4382   // set draw deactivation mask to previous value
4383   SetDrawDeactivationMask(draw_deactivation_mask);
4384
4385   // get first node of level or artwork info tree
4386   TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4387
4388   // get tree info node of newly added level or artwork set
4389   TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4390                                                       tree_subdir_new);
4391
4392   if (tree_node_new == NULL)            // should not happen
4393     return FALSE;
4394
4395   // correct top link and parent node link of newly created tree node
4396   tree_node_new->node_top    = tree_node_old->node_top;
4397   tree_node_new->node_parent = tree_node_old->node_parent;
4398
4399   // sort tree info to adjust position of newly added tree set
4400   sortTreeInfo(tree_node_first);
4401
4402   return TRUE;
4403 }
4404
4405 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4406                           char *tree_subdir_new, int type)
4407 {
4408   if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4409     Fail("internal tree info structure corrupted -- aborting");
4410 }
4411
4412 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4413 {
4414   AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4415 }
4416
4417 char *getArtworkIdentifierForUserLevelSet(int type)
4418 {
4419   char *classic_artwork_set = getClassicArtworkSet(type);
4420
4421   // check for custom artwork configured in "levelinfo.conf"
4422   char *leveldir_artwork_set =
4423     *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4424   boolean has_leveldir_artwork_set =
4425     (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4426                                                classic_artwork_set));
4427
4428   // check for custom artwork in sub-directory "graphics" etc.
4429   TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4430   char *leveldir_identifier = leveldir_current->identifier;
4431   boolean has_artwork_subdir =
4432     (getTreeInfoFromIdentifier(artwork_first_node,
4433                                leveldir_identifier) != NULL);
4434
4435   return (has_leveldir_artwork_set ? leveldir_artwork_set :
4436           has_artwork_subdir       ? leveldir_identifier :
4437           classic_artwork_set);
4438 }
4439
4440 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4441 {
4442   char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4443   TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4444   TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4445
4446   if (ti == NULL)
4447   {
4448     ti = getTreeInfoFromIdentifier(artwork_first_node,
4449                                    ARTWORK_DEFAULT_SUBDIR(type));
4450     if (ti == NULL)
4451       Fail("cannot find default graphics -- should not happen");
4452   }
4453
4454   return ti;
4455 }
4456
4457 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4458 {
4459   char *graphics_set =
4460     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4461   char *sounds_set =
4462     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4463   char *music_set =
4464     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4465
4466   return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4467           !strEqual(sounds_set,   SND_CLASSIC_SUBDIR) ||
4468           !strEqual(music_set,    MUS_CLASSIC_SUBDIR));
4469 }
4470
4471 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4472                            char *level_author, int num_levels)
4473 {
4474   char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4475   char *filename_tmp = getStringCat2(filename, ".tmp");
4476   FILE *file = NULL;
4477   FILE *file_tmp = NULL;
4478   char line[MAX_LINE_LEN];
4479   boolean success = FALSE;
4480   LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4481                                                      level_subdir);
4482   // update values in level directory tree
4483
4484   if (level_name != NULL)
4485     setString(&leveldir->name, level_name);
4486
4487   if (level_author != NULL)
4488     setString(&leveldir->author, level_author);
4489
4490   if (num_levels != -1)
4491     leveldir->levels = num_levels;
4492
4493   // update values that depend on other values
4494
4495   setString(&leveldir->name_sorting, leveldir->name);
4496
4497   leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4498
4499   // sort order of level sets may have changed
4500   sortTreeInfo(&leveldir_first);
4501
4502   if ((file     = fopen(filename,     MODE_READ)) &&
4503       (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4504   {
4505     while (fgets(line, MAX_LINE_LEN, file))
4506     {
4507       if (strPrefix(line, "name:") && level_name != NULL)
4508         fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4509       else if (strPrefix(line, "author:") && level_author != NULL)
4510         fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4511       else if (strPrefix(line, "levels:") && num_levels != -1)
4512         fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4513       else
4514         fputs(line, file_tmp);
4515     }
4516
4517     success = TRUE;
4518   }
4519
4520   if (file)
4521     fclose(file);
4522
4523   if (file_tmp)
4524     fclose(file_tmp);
4525
4526   if (success)
4527     success = (rename(filename_tmp, filename) == 0);
4528
4529   free(filename);
4530   free(filename_tmp);
4531
4532   return success;
4533 }
4534
4535 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4536                            char *level_author, int num_levels,
4537                            boolean use_artwork_set)
4538 {
4539   LevelDirTree *level_info;
4540   char *filename;
4541   FILE *file;
4542   int i;
4543
4544   // create user level sub-directory, if needed
4545   createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4546
4547   filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4548
4549   if (!(file = fopen(filename, MODE_WRITE)))
4550   {
4551     Warn("cannot write level info file '%s'", filename);
4552
4553     free(filename);
4554
4555     return FALSE;
4556   }
4557
4558   level_info = newTreeInfo();
4559
4560   // always start with reliable default values
4561   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4562
4563   setString(&level_info->name, level_name);
4564   setString(&level_info->author, level_author);
4565   level_info->levels = num_levels;
4566   level_info->first_level = 1;
4567   level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4568   level_info->readonly = FALSE;
4569
4570   if (use_artwork_set)
4571   {
4572     level_info->graphics_set =
4573       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4574     level_info->sounds_set =
4575       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4576     level_info->music_set =
4577       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4578   }
4579
4580   token_value_position = TOKEN_VALUE_POSITION_SHORT;
4581
4582   fprintFileHeader(file, LEVELINFO_FILENAME);
4583
4584   ldi = *level_info;
4585   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4586   {
4587     if (i == LEVELINFO_TOKEN_NAME ||
4588         i == LEVELINFO_TOKEN_AUTHOR ||
4589         i == LEVELINFO_TOKEN_LEVELS ||
4590         i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4591         i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4592         i == LEVELINFO_TOKEN_READONLY ||
4593         (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4594                              i == LEVELINFO_TOKEN_SOUNDS_SET ||
4595                              i == LEVELINFO_TOKEN_MUSIC_SET)))
4596       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4597
4598     // just to make things nicer :)
4599     if (i == LEVELINFO_TOKEN_AUTHOR ||
4600         i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4601         (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4602       fprintf(file, "\n");      
4603   }
4604
4605   token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4606
4607   fclose(file);
4608
4609   SetFilePermissions(filename, PERMS_PRIVATE);
4610
4611   freeTreeInfo(level_info);
4612   free(filename);
4613
4614   return TRUE;
4615 }
4616
4617 static void SaveUserLevelInfo(void)
4618 {
4619   CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4620 }
4621
4622 char *getSetupValue(int type, void *value)
4623 {
4624   static char value_string[MAX_LINE_LEN];
4625
4626   if (value == NULL)
4627     return NULL;
4628
4629   switch (type)
4630   {
4631     case TYPE_BOOLEAN:
4632       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4633       break;
4634
4635     case TYPE_SWITCH:
4636       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4637       break;
4638
4639     case TYPE_SWITCH3:
4640       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
4641                             *(int *)value == FALSE ? "off" : "on"));
4642       break;
4643
4644     case TYPE_YES_NO:
4645       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4646       break;
4647
4648     case TYPE_YES_NO_AUTO:
4649       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
4650                             *(int *)value == FALSE ? "no" : "yes"));
4651       break;
4652
4653     case TYPE_ECS_AGA:
4654       strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4655       break;
4656
4657     case TYPE_KEY:
4658       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4659       break;
4660
4661     case TYPE_KEY_X11:
4662       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4663       break;
4664
4665     case TYPE_INTEGER:
4666       sprintf(value_string, "%d", *(int *)value);
4667       break;
4668
4669     case TYPE_STRING:
4670       if (*(char **)value == NULL)
4671         return NULL;
4672
4673       strcpy(value_string, *(char **)value);
4674       break;
4675
4676     case TYPE_PLAYER:
4677       sprintf(value_string, "player_%d", *(int *)value + 1);
4678       break;
4679
4680     default:
4681       value_string[0] = '\0';
4682       break;
4683   }
4684
4685   if (type & TYPE_GHOSTED)
4686     strcpy(value_string, "n/a");
4687
4688   return value_string;
4689 }
4690
4691 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4692 {
4693   int i;
4694   char *line;
4695   static char token_string[MAX_LINE_LEN];
4696   int token_type = token_info[token_nr].type;
4697   void *setup_value = token_info[token_nr].value;
4698   char *token_text = token_info[token_nr].text;
4699   char *value_string = getSetupValue(token_type, setup_value);
4700
4701   // build complete token string
4702   sprintf(token_string, "%s%s", prefix, token_text);
4703
4704   // build setup entry line
4705   line = getFormattedSetupEntry(token_string, value_string);
4706
4707   if (token_type == TYPE_KEY_X11)
4708   {
4709     Key key = *(Key *)setup_value;
4710     char *keyname = getKeyNameFromKey(key);
4711
4712     // add comment, if useful
4713     if (!strEqual(keyname, "(undefined)") &&
4714         !strEqual(keyname, "(unknown)"))
4715     {
4716       // add at least one whitespace
4717       strcat(line, " ");
4718       for (i = strlen(line); i < token_comment_position; i++)
4719         strcat(line, " ");
4720
4721       strcat(line, "# ");
4722       strcat(line, keyname);
4723     }
4724   }
4725
4726   return line;
4727 }
4728
4729 static void InitLastPlayedLevels_ParentNode(void)
4730 {
4731   LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4732   LevelDirTree *leveldir_new = NULL;
4733
4734   // check if parent node for last played levels already exists
4735   if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4736     return;
4737
4738   leveldir_new = newTreeInfo();
4739
4740   setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4741
4742   leveldir_new->level_group = TRUE;
4743   leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4744
4745   setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4746   setString(&leveldir_new->name, "<< (last played level sets)");
4747   setString(&leveldir_new->name_sorting, leveldir_new->name);
4748
4749   pushTreeInfo(leveldir_top, leveldir_new);
4750
4751   // create node to link back to current level directory
4752   createParentTreeInfoNode(leveldir_new);
4753 }
4754
4755 void UpdateLastPlayedLevels_TreeInfo(void)
4756 {
4757   char **last_level_series = setup.level_setup.last_level_series;
4758   LevelDirTree *leveldir_last;
4759   TreeInfo **node_new = NULL;
4760   int i;
4761
4762   if (last_level_series[0] == NULL)
4763     return;
4764
4765   InitLastPlayedLevels_ParentNode();
4766
4767   leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4768                                                TOKEN_STR_LAST_LEVEL_SERIES,
4769                                                TREE_NODE_TYPE_GROUP);
4770   if (leveldir_last == NULL)
4771     return;
4772
4773   node_new = &leveldir_last->node_group->next;
4774
4775   freeTreeInfo(*node_new);
4776
4777   *node_new = NULL;
4778
4779   for (i = 0; last_level_series[i] != NULL; i++)
4780   {
4781     LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4782                                                         last_level_series[i]);
4783     if (node_last == NULL)
4784       continue;
4785
4786     *node_new = getTreeInfoCopy(node_last);     // copy complete node
4787
4788     (*node_new)->node_top = &leveldir_first;    // correct top node link
4789     (*node_new)->node_parent = leveldir_last;   // correct parent node link
4790
4791     (*node_new)->is_copy = TRUE;                // mark entry as node copy
4792
4793     (*node_new)->node_group = NULL;
4794     (*node_new)->next = NULL;
4795
4796     (*node_new)->cl_first = -1;                 // force setting tree cursor
4797
4798     node_new = &((*node_new)->next);
4799   }
4800 }
4801
4802 static void UpdateLastPlayedLevels_List(void)
4803 {
4804   char **last_level_series = setup.level_setup.last_level_series;
4805   int pos = MAX_LEVELDIR_HISTORY - 1;
4806   int i;
4807
4808   // search for potentially already existing entry in list of level sets
4809   for (i = 0; last_level_series[i] != NULL; i++)
4810     if (strEqual(last_level_series[i], leveldir_current->identifier))
4811       pos = i;
4812
4813   // move list of level sets one entry down (using potentially free entry)
4814   for (i = pos; i > 0; i--)
4815     setString(&last_level_series[i], last_level_series[i - 1]);
4816
4817   // put last played level set at top position
4818   setString(&last_level_series[0], leveldir_current->identifier);
4819 }
4820
4821 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4822 {
4823   static char *identifier = NULL;
4824
4825   if (store)
4826   {
4827     setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4828
4829     return NULL;        // not used
4830   }
4831   else
4832   {
4833     TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4834                                                       identifier,
4835                                                       TREE_NODE_TYPE_COPY);
4836     return (node_new != NULL ? node_new : node);
4837   }
4838 }
4839
4840 void StoreLastPlayedLevels(TreeInfo *node)
4841 {
4842   StoreOrRestoreLastPlayedLevels(node, TRUE);
4843 }
4844
4845 void RestoreLastPlayedLevels(TreeInfo **node)
4846 {
4847   *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4848 }
4849
4850 void LoadLevelSetup_LastSeries(void)
4851 {
4852   // --------------------------------------------------------------------------
4853   // ~/.<program>/levelsetup.conf
4854   // --------------------------------------------------------------------------
4855
4856   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4857   SetupFileHash *level_setup_hash = NULL;
4858   int pos = 0;
4859   int i;
4860
4861   // always start with reliable default values
4862   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4863
4864   // start with empty history of last played level sets
4865   setString(&setup.level_setup.last_level_series[0], NULL);
4866
4867   if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4868   {
4869     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4870                                                  DEFAULT_LEVELSET);
4871     if (leveldir_current == NULL)
4872       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4873   }
4874
4875   if ((level_setup_hash = loadSetupFileHash(filename)))
4876   {
4877     char *last_level_series =
4878       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4879
4880     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4881                                                  last_level_series);
4882     if (leveldir_current == NULL)
4883       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4884
4885     for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4886     {
4887       char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4888       LevelDirTree *leveldir_last;
4889
4890       sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4891
4892       last_level_series = getHashEntry(level_setup_hash, token);
4893
4894       leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4895                                                 last_level_series);
4896       if (leveldir_last != NULL)
4897         setString(&setup.level_setup.last_level_series[pos++],
4898                   last_level_series);
4899     }
4900
4901     setString(&setup.level_setup.last_level_series[pos], NULL);
4902
4903     freeSetupFileHash(level_setup_hash);
4904   }
4905   else
4906   {
4907     Debug("setup", "using default setup values");
4908   }
4909
4910   free(filename);
4911 }
4912
4913 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4914 {
4915   // --------------------------------------------------------------------------
4916   // ~/.<program>/levelsetup.conf
4917   // --------------------------------------------------------------------------
4918
4919   // check if the current level directory structure is available at this point
4920   if (leveldir_current == NULL)
4921     return;
4922
4923   char **last_level_series = setup.level_setup.last_level_series;
4924   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4925   FILE *file;
4926   int i;
4927
4928   InitUserDataDirectory();
4929
4930   UpdateLastPlayedLevels_List();
4931
4932   if (!(file = fopen(filename, MODE_WRITE)))
4933   {
4934     Warn("cannot write setup file '%s'", filename);
4935
4936     free(filename);
4937
4938     return;
4939   }
4940
4941   fprintFileHeader(file, LEVELSETUP_FILENAME);
4942
4943   if (deactivate_last_level_series)
4944     fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4945
4946   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4947                                                leveldir_current->identifier));
4948
4949   for (i = 0; last_level_series[i] != NULL; i++)
4950   {
4951     char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 1 + 10 + 1];
4952
4953     sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4954
4955     fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4956   }
4957
4958   fclose(file);
4959
4960   SetFilePermissions(filename, PERMS_PRIVATE);
4961
4962   free(filename);
4963 }
4964
4965 void SaveLevelSetup_LastSeries(void)
4966 {
4967   SaveLevelSetup_LastSeries_Ext(FALSE);
4968 }
4969
4970 void SaveLevelSetup_LastSeries_Deactivate(void)
4971 {
4972   SaveLevelSetup_LastSeries_Ext(TRUE);
4973 }
4974
4975 static void checkSeriesInfo(void)
4976 {
4977   static char *level_directory = NULL;
4978   Directory *dir;
4979 #if 0
4980   DirectoryEntry *dir_entry;
4981 #endif
4982
4983   checked_free(level_directory);
4984
4985   // check for more levels besides the 'levels' field of 'levelinfo.conf'
4986
4987   level_directory = getPath2((leveldir_current->in_user_dir ?
4988                               getUserLevelDir(NULL) :
4989                               options.level_directory),
4990                              leveldir_current->fullpath);
4991
4992   if ((dir = openDirectory(level_directory)) == NULL)
4993   {
4994     Warn("cannot read level directory '%s'", level_directory);
4995
4996     return;
4997   }
4998
4999 #if 0
5000   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
5001   {
5002     if (strlen(dir_entry->basename) > 4 &&
5003         dir_entry->basename[3] == '.' &&
5004         strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
5005     {
5006       char levelnum_str[4];
5007       int levelnum_value;
5008
5009       strncpy(levelnum_str, dir_entry->basename, 3);
5010       levelnum_str[3] = '\0';
5011
5012       levelnum_value = atoi(levelnum_str);
5013
5014       if (levelnum_value < leveldir_current->first_level)
5015       {
5016         Warn("additional level %d found", levelnum_value);
5017
5018         leveldir_current->first_level = levelnum_value;
5019       }
5020       else if (levelnum_value > leveldir_current->last_level)
5021       {
5022         Warn("additional level %d found", levelnum_value);
5023
5024         leveldir_current->last_level = levelnum_value;
5025       }
5026     }
5027   }
5028 #endif
5029
5030   closeDirectory(dir);
5031 }
5032
5033 void LoadLevelSetup_SeriesInfo(void)
5034 {
5035   char *filename;
5036   SetupFileHash *level_setup_hash = NULL;
5037   char *level_subdir = leveldir_current->subdir;
5038   int i;
5039
5040   // always start with reliable default values
5041   level_nr = leveldir_current->first_level;
5042
5043   for (i = 0; i < MAX_LEVELS; i++)
5044   {
5045     LevelStats_setPlayed(i, 0);
5046     LevelStats_setSolved(i, 0);
5047   }
5048
5049   checkSeriesInfo();
5050
5051   // --------------------------------------------------------------------------
5052   // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5053   // --------------------------------------------------------------------------
5054
5055   level_subdir = leveldir_current->subdir;
5056
5057   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5058
5059   if ((level_setup_hash = loadSetupFileHash(filename)))
5060   {
5061     char *token_value;
5062
5063     // get last played level in this level set
5064
5065     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
5066
5067     if (token_value)
5068     {
5069       level_nr = atoi(token_value);
5070
5071       if (level_nr < leveldir_current->first_level)
5072         level_nr = leveldir_current->first_level;
5073       if (level_nr > leveldir_current->last_level)
5074         level_nr = leveldir_current->last_level;
5075     }
5076
5077     // get handicap level in this level set
5078
5079     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
5080
5081     if (token_value)
5082     {
5083       int level_nr = atoi(token_value);
5084
5085       if (level_nr < leveldir_current->first_level)
5086         level_nr = leveldir_current->first_level;
5087       if (level_nr > leveldir_current->last_level + 1)
5088         level_nr = leveldir_current->last_level;
5089
5090       if (leveldir_current->user_defined || !leveldir_current->handicap)
5091         level_nr = leveldir_current->last_level;
5092
5093       leveldir_current->handicap_level = level_nr;
5094     }
5095
5096     // get number of played and solved levels in this level set
5097
5098     BEGIN_HASH_ITERATION(level_setup_hash, itr)
5099     {
5100       char *token = HASH_ITERATION_TOKEN(itr);
5101       char *value = HASH_ITERATION_VALUE(itr);
5102
5103       if (strlen(token) == 3 &&
5104           token[0] >= '0' && token[0] <= '9' &&
5105           token[1] >= '0' && token[1] <= '9' &&
5106           token[2] >= '0' && token[2] <= '9')
5107       {
5108         int level_nr = atoi(token);
5109
5110         if (value != NULL)
5111           LevelStats_setPlayed(level_nr, atoi(value));  // read 1st column
5112
5113         value = strchr(value, ' ');
5114
5115         if (value != NULL)
5116           LevelStats_setSolved(level_nr, atoi(value));  // read 2nd column
5117       }
5118     }
5119     END_HASH_ITERATION(hash, itr)
5120
5121     freeSetupFileHash(level_setup_hash);
5122   }
5123   else
5124   {
5125     Debug("setup", "using default setup values");
5126   }
5127
5128   free(filename);
5129 }
5130
5131 void SaveLevelSetup_SeriesInfo(void)
5132 {
5133   char *filename;
5134   char *level_subdir = leveldir_current->subdir;
5135   char *level_nr_str = int2str(level_nr, 0);
5136   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5137   FILE *file;
5138   int i;
5139
5140   // --------------------------------------------------------------------------
5141   // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5142   // --------------------------------------------------------------------------
5143
5144   InitLevelSetupDirectory(level_subdir);
5145
5146   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5147
5148   if (!(file = fopen(filename, MODE_WRITE)))
5149   {
5150     Warn("cannot write setup file '%s'", filename);
5151
5152     free(filename);
5153
5154     return;
5155   }
5156
5157   fprintFileHeader(file, LEVELSETUP_FILENAME);
5158
5159   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5160                                                level_nr_str));
5161   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5162                                                  handicap_level_str));
5163
5164   for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5165        i++)
5166   {
5167     if (LevelStats_getPlayed(i) > 0 ||
5168         LevelStats_getSolved(i) > 0)
5169     {
5170       char token[16];
5171       char value[16];
5172
5173       sprintf(token, "%03d", i);
5174       sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5175
5176       fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5177     }
5178   }
5179
5180   fclose(file);
5181
5182   SetFilePermissions(filename, PERMS_PRIVATE);
5183
5184   free(filename);
5185 }
5186
5187 int LevelStats_getPlayed(int nr)
5188 {
5189   return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5190 }
5191
5192 int LevelStats_getSolved(int nr)
5193 {
5194   return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5195 }
5196
5197 void LevelStats_setPlayed(int nr, int value)
5198 {
5199   if (nr >= 0 && nr < MAX_LEVELS)
5200     level_stats[nr].played = value;
5201 }
5202
5203 void LevelStats_setSolved(int nr, int value)
5204 {
5205   if (nr >= 0 && nr < MAX_LEVELS)
5206     level_stats[nr].solved = value;
5207 }
5208
5209 void LevelStats_incPlayed(int nr)
5210 {
5211   if (nr >= 0 && nr < MAX_LEVELS)
5212     level_stats[nr].played++;
5213 }
5214
5215 void LevelStats_incSolved(int nr)
5216 {
5217   if (nr >= 0 && nr < MAX_LEVELS)
5218     level_stats[nr].solved++;
5219 }
5220
5221 void LoadUserSetup(void)
5222 {
5223   // --------------------------------------------------------------------------
5224   // ~/.<program>/usersetup.conf
5225   // --------------------------------------------------------------------------
5226
5227   char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5228   SetupFileHash *user_setup_hash = NULL;
5229
5230   // always start with reliable default values
5231   user.nr = 0;
5232
5233   if ((user_setup_hash = loadSetupFileHash(filename)))
5234   {
5235     char *token_value;
5236
5237     // get last selected user number
5238     token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5239
5240     if (token_value)
5241       user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5242
5243     freeSetupFileHash(user_setup_hash);
5244   }
5245   else
5246   {
5247     Debug("setup", "using default setup values");
5248   }
5249
5250   free(filename);
5251 }
5252
5253 void SaveUserSetup(void)
5254 {
5255   // --------------------------------------------------------------------------
5256   // ~/.<program>/usersetup.conf
5257   // --------------------------------------------------------------------------
5258
5259   char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5260   FILE *file;
5261
5262   InitMainUserDataDirectory();
5263
5264   if (!(file = fopen(filename, MODE_WRITE)))
5265   {
5266     Warn("cannot write setup file '%s'", filename);
5267
5268     free(filename);
5269
5270     return;
5271   }
5272
5273   fprintFileHeader(file, USERSETUP_FILENAME);
5274
5275   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5276                                                i_to_a(user.nr)));
5277   fclose(file);
5278
5279   SetFilePermissions(filename, PERMS_PRIVATE);
5280
5281   free(filename);
5282 }