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