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