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