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