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