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