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