acee4a9fd2634ddb52320b2a9df1722a28052685
[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_SKIP_LEVELS             30
2734 #define LEVELINFO_TOKEN_USE_EMC_TILES           31
2735
2736 #define NUM_LEVELINFO_TOKENS                    32
2737
2738 static LevelDirTree ldi;
2739
2740 static struct TokenInfo levelinfo_tokens[] =
2741 {
2742   // level directory info
2743   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
2744   { TYPE_STRING,        &ldi.name,              "name"                  },
2745   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
2746   { TYPE_STRING,        &ldi.author,            "author"                },
2747   { TYPE_STRING,        &ldi.year,              "year"                  },
2748   { TYPE_STRING,        &ldi.program_title,     "program_title"         },
2749   { TYPE_STRING,        &ldi.program_copyright, "program_copyright"     },
2750   { TYPE_STRING,        &ldi.program_company,   "program_company"       },
2751   { TYPE_STRING,        &ldi.imported_from,     "imported_from"         },
2752   { TYPE_STRING,        &ldi.imported_by,       "imported_by"           },
2753   { TYPE_STRING,        &ldi.tested_by,         "tested_by"             },
2754   { TYPE_INTEGER,       &ldi.levels,            "levels"                },
2755   { TYPE_INTEGER,       &ldi.first_level,       "first_level"           },
2756   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
2757   { TYPE_BOOLEAN,       &ldi.latest_engine,     "latest_engine"         },
2758   { TYPE_BOOLEAN,       &ldi.level_group,       "level_group"           },
2759   { TYPE_BOOLEAN,       &ldi.readonly,          "readonly"              },
2760   { TYPE_STRING,        &ldi.graphics_set_ecs,  "graphics_set.ecs"      },
2761   { TYPE_STRING,        &ldi.graphics_set_aga,  "graphics_set.aga"      },
2762   { TYPE_STRING,        &ldi.graphics_set,      "graphics_set"          },
2763   { TYPE_STRING,        &ldi.sounds_set_default,"sounds_set.default"    },
2764   { TYPE_STRING,        &ldi.sounds_set_lowpass,"sounds_set.lowpass"    },
2765   { TYPE_STRING,        &ldi.sounds_set,        "sounds_set"            },
2766   { TYPE_STRING,        &ldi.music_set,         "music_set"             },
2767   { TYPE_STRING,        &ldi.level_filename,    "filename"              },
2768   { TYPE_STRING,        &ldi.level_filetype,    "filetype"              },
2769   { TYPE_STRING,        &ldi.special_flags,     "special_flags"         },
2770   { TYPE_STRING,        &ldi.empty_level_name,  "empty_level_name"      },
2771   { TYPE_BOOLEAN,       &ldi.force_level_name,  "force_level_name"      },
2772   { TYPE_BOOLEAN,       &ldi.handicap,          "handicap"              },
2773   { TYPE_BOOLEAN,       &ldi.skip_levels,       "skip_levels"           },
2774   { TYPE_BOOLEAN,       &ldi.use_emc_tiles,     "use_emc_tiles"         }
2775 };
2776
2777 static struct TokenInfo artworkinfo_tokens[] =
2778 {
2779   // artwork directory info
2780   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
2781   { TYPE_STRING,        &ldi.subdir,            "subdir"                },
2782   { TYPE_STRING,        &ldi.name,              "name"                  },
2783   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
2784   { TYPE_STRING,        &ldi.author,            "author"                },
2785   { TYPE_STRING,        &ldi.program_title,     "program_title"         },
2786   { TYPE_STRING,        &ldi.program_copyright, "program_copyright"     },
2787   { TYPE_STRING,        &ldi.program_company,   "program_company"       },
2788   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
2789   { TYPE_STRING,        &ldi.basepath,          "basepath"              },
2790   { TYPE_STRING,        &ldi.fullpath,          "fullpath"              },
2791   { TYPE_BOOLEAN,       &ldi.in_user_dir,       "in_user_dir"           },
2792   { TYPE_STRING,        &ldi.class_desc,        "class_desc"            },
2793
2794   { -1,                 NULL,                   NULL                    },
2795 };
2796
2797 static char *optional_tokens[] =
2798 {
2799   "program_title",
2800   "program_copyright",
2801   "program_company",
2802
2803   NULL
2804 };
2805
2806 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2807 {
2808   ti->type = type;
2809
2810   ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR    ? &leveldir_first :
2811                   ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2812                   ti->type == TREE_TYPE_SOUNDS_DIR   ? &artwork.snd_first :
2813                   ti->type == TREE_TYPE_MUSIC_DIR    ? &artwork.mus_first :
2814                   NULL);
2815
2816   ti->node_parent = NULL;
2817   ti->node_group = NULL;
2818   ti->next = NULL;
2819
2820   ti->cl_first = -1;
2821   ti->cl_cursor = -1;
2822
2823   ti->subdir = NULL;
2824   ti->fullpath = NULL;
2825   ti->basepath = NULL;
2826   ti->identifier = NULL;
2827   ti->name = getStringCopy(ANONYMOUS_NAME);
2828   ti->name_sorting = NULL;
2829   ti->author = getStringCopy(ANONYMOUS_NAME);
2830   ti->year = NULL;
2831
2832   ti->program_title = NULL;
2833   ti->program_copyright = NULL;
2834   ti->program_company = NULL;
2835
2836   ti->sort_priority = LEVELCLASS_UNDEFINED;     // default: least priority
2837   ti->latest_engine = FALSE;                    // default: get from level
2838   ti->parent_link = FALSE;
2839   ti->is_copy = FALSE;
2840   ti->in_user_dir = FALSE;
2841   ti->user_defined = FALSE;
2842   ti->color = 0;
2843   ti->class_desc = NULL;
2844
2845   ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2846
2847   if (ti->type == TREE_TYPE_LEVEL_DIR)
2848   {
2849     ti->imported_from = NULL;
2850     ti->imported_by = NULL;
2851     ti->tested_by = NULL;
2852
2853     ti->graphics_set_ecs = NULL;
2854     ti->graphics_set_aga = NULL;
2855     ti->graphics_set = NULL;
2856     ti->sounds_set_default = NULL;
2857     ti->sounds_set_lowpass = NULL;
2858     ti->sounds_set = NULL;
2859     ti->music_set = NULL;
2860     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2861     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2862     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2863
2864     ti->level_filename = NULL;
2865     ti->level_filetype = NULL;
2866
2867     ti->special_flags = NULL;
2868
2869     ti->empty_level_name = NULL;
2870     ti->force_level_name = FALSE;
2871
2872     ti->levels = 0;
2873     ti->first_level = 0;
2874     ti->last_level = 0;
2875     ti->level_group = FALSE;
2876     ti->handicap_level = 0;
2877     ti->readonly = TRUE;
2878     ti->handicap = TRUE;
2879     ti->skip_levels = FALSE;
2880
2881     ti->use_emc_tiles = FALSE;
2882   }
2883 }
2884
2885 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2886 {
2887   if (parent == NULL)
2888   {
2889     Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2890
2891     setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2892
2893     return;
2894   }
2895
2896   // copy all values from the parent structure
2897
2898   ti->type = parent->type;
2899
2900   ti->node_top = parent->node_top;
2901   ti->node_parent = parent;
2902   ti->node_group = NULL;
2903   ti->next = NULL;
2904
2905   ti->cl_first = -1;
2906   ti->cl_cursor = -1;
2907
2908   ti->subdir = NULL;
2909   ti->fullpath = NULL;
2910   ti->basepath = NULL;
2911   ti->identifier = NULL;
2912   ti->name = getStringCopy(ANONYMOUS_NAME);
2913   ti->name_sorting = NULL;
2914   ti->author = getStringCopy(parent->author);
2915   ti->year = getStringCopy(parent->year);
2916
2917   ti->program_title = getStringCopy(parent->program_title);
2918   ti->program_copyright = getStringCopy(parent->program_copyright);
2919   ti->program_company = getStringCopy(parent->program_company);
2920
2921   ti->sort_priority = parent->sort_priority;
2922   ti->latest_engine = parent->latest_engine;
2923   ti->parent_link = FALSE;
2924   ti->is_copy = FALSE;
2925   ti->in_user_dir = parent->in_user_dir;
2926   ti->user_defined = parent->user_defined;
2927   ti->color = parent->color;
2928   ti->class_desc = getStringCopy(parent->class_desc);
2929
2930   ti->infotext = getStringCopy(parent->infotext);
2931
2932   if (ti->type == TREE_TYPE_LEVEL_DIR)
2933   {
2934     ti->imported_from = getStringCopy(parent->imported_from);
2935     ti->imported_by = getStringCopy(parent->imported_by);
2936     ti->tested_by = getStringCopy(parent->tested_by);
2937
2938     ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2939     ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2940     ti->graphics_set = getStringCopy(parent->graphics_set);
2941     ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2942     ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2943     ti->sounds_set = getStringCopy(parent->sounds_set);
2944     ti->music_set = getStringCopy(parent->music_set);
2945     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2946     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2947     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2948
2949     ti->level_filename = getStringCopy(parent->level_filename);
2950     ti->level_filetype = getStringCopy(parent->level_filetype);
2951
2952     ti->special_flags = getStringCopy(parent->special_flags);
2953
2954     ti->empty_level_name = getStringCopy(parent->empty_level_name);
2955     ti->force_level_name = parent->force_level_name;
2956
2957     ti->levels = parent->levels;
2958     ti->first_level = parent->first_level;
2959     ti->last_level = parent->last_level;
2960     ti->level_group = FALSE;
2961     ti->handicap_level = parent->handicap_level;
2962     ti->readonly = parent->readonly;
2963     ti->handicap = parent->handicap;
2964     ti->skip_levels = parent->skip_levels;
2965
2966     ti->use_emc_tiles = parent->use_emc_tiles;
2967   }
2968 }
2969
2970 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2971 {
2972   TreeInfo *ti_copy = newTreeInfo();
2973
2974   // copy all values from the original structure
2975
2976   ti_copy->type                 = ti->type;
2977
2978   ti_copy->node_top             = ti->node_top;
2979   ti_copy->node_parent          = ti->node_parent;
2980   ti_copy->node_group           = ti->node_group;
2981   ti_copy->next                 = ti->next;
2982
2983   ti_copy->cl_first             = ti->cl_first;
2984   ti_copy->cl_cursor            = ti->cl_cursor;
2985
2986   ti_copy->subdir               = getStringCopy(ti->subdir);
2987   ti_copy->fullpath             = getStringCopy(ti->fullpath);
2988   ti_copy->basepath             = getStringCopy(ti->basepath);
2989   ti_copy->identifier           = getStringCopy(ti->identifier);
2990   ti_copy->name                 = getStringCopy(ti->name);
2991   ti_copy->name_sorting         = getStringCopy(ti->name_sorting);
2992   ti_copy->author               = getStringCopy(ti->author);
2993   ti_copy->year                 = getStringCopy(ti->year);
2994
2995   ti_copy->program_title        = getStringCopy(ti->program_title);
2996   ti_copy->program_copyright    = getStringCopy(ti->program_copyright);
2997   ti_copy->program_company      = getStringCopy(ti->program_company);
2998
2999   ti_copy->imported_from        = getStringCopy(ti->imported_from);
3000   ti_copy->imported_by          = getStringCopy(ti->imported_by);
3001   ti_copy->tested_by            = getStringCopy(ti->tested_by);
3002
3003   ti_copy->graphics_set_ecs     = getStringCopy(ti->graphics_set_ecs);
3004   ti_copy->graphics_set_aga     = getStringCopy(ti->graphics_set_aga);
3005   ti_copy->graphics_set         = getStringCopy(ti->graphics_set);
3006   ti_copy->sounds_set_default   = getStringCopy(ti->sounds_set_default);
3007   ti_copy->sounds_set_lowpass   = getStringCopy(ti->sounds_set_lowpass);
3008   ti_copy->sounds_set           = getStringCopy(ti->sounds_set);
3009   ti_copy->music_set            = getStringCopy(ti->music_set);
3010   ti_copy->graphics_path        = getStringCopy(ti->graphics_path);
3011   ti_copy->sounds_path          = getStringCopy(ti->sounds_path);
3012   ti_copy->music_path           = getStringCopy(ti->music_path);
3013
3014   ti_copy->level_filename       = getStringCopy(ti->level_filename);
3015   ti_copy->level_filetype       = getStringCopy(ti->level_filetype);
3016
3017   ti_copy->special_flags        = getStringCopy(ti->special_flags);
3018
3019   ti_copy->empty_level_name     = getStringCopy(ti->empty_level_name);
3020   ti_copy->force_level_name     = ti->force_level_name;
3021
3022   ti_copy->levels               = ti->levels;
3023   ti_copy->first_level          = ti->first_level;
3024   ti_copy->last_level           = ti->last_level;
3025   ti_copy->sort_priority        = ti->sort_priority;
3026
3027   ti_copy->latest_engine        = ti->latest_engine;
3028
3029   ti_copy->level_group          = ti->level_group;
3030   ti_copy->parent_link          = ti->parent_link;
3031   ti_copy->is_copy              = ti->is_copy;
3032   ti_copy->in_user_dir          = ti->in_user_dir;
3033   ti_copy->user_defined         = ti->user_defined;
3034   ti_copy->readonly             = ti->readonly;
3035   ti_copy->handicap             = ti->handicap;
3036   ti_copy->skip_levels          = ti->skip_levels;
3037
3038   ti_copy->use_emc_tiles        = ti->use_emc_tiles;
3039
3040   ti_copy->color                = ti->color;
3041   ti_copy->class_desc           = getStringCopy(ti->class_desc);
3042   ti_copy->handicap_level       = ti->handicap_level;
3043
3044   ti_copy->infotext             = getStringCopy(ti->infotext);
3045
3046   return ti_copy;
3047 }
3048
3049 void freeTreeInfo(TreeInfo *ti)
3050 {
3051   if (ti == NULL)
3052     return;
3053
3054   checked_free(ti->subdir);
3055   checked_free(ti->fullpath);
3056   checked_free(ti->basepath);
3057   checked_free(ti->identifier);
3058
3059   checked_free(ti->name);
3060   checked_free(ti->name_sorting);
3061   checked_free(ti->author);
3062   checked_free(ti->year);
3063
3064   checked_free(ti->program_title);
3065   checked_free(ti->program_copyright);
3066   checked_free(ti->program_company);
3067
3068   checked_free(ti->class_desc);
3069
3070   checked_free(ti->infotext);
3071
3072   if (ti->type == TREE_TYPE_LEVEL_DIR)
3073   {
3074     checked_free(ti->imported_from);
3075     checked_free(ti->imported_by);
3076     checked_free(ti->tested_by);
3077
3078     checked_free(ti->graphics_set_ecs);
3079     checked_free(ti->graphics_set_aga);
3080     checked_free(ti->graphics_set);
3081     checked_free(ti->sounds_set_default);
3082     checked_free(ti->sounds_set_lowpass);
3083     checked_free(ti->sounds_set);
3084     checked_free(ti->music_set);
3085
3086     checked_free(ti->graphics_path);
3087     checked_free(ti->sounds_path);
3088     checked_free(ti->music_path);
3089
3090     checked_free(ti->level_filename);
3091     checked_free(ti->level_filetype);
3092
3093     checked_free(ti->special_flags);
3094   }
3095
3096   // recursively free child node
3097   if (ti->node_group)
3098     freeTreeInfo(ti->node_group);
3099
3100   // recursively free next node
3101   if (ti->next)
3102     freeTreeInfo(ti->next);
3103
3104   checked_free(ti);
3105 }
3106
3107 void setSetupInfo(struct TokenInfo *token_info,
3108                   int token_nr, char *token_value)
3109 {
3110   int token_type = token_info[token_nr].type;
3111   void *setup_value = token_info[token_nr].value;
3112
3113   if (token_value == NULL)
3114     return;
3115
3116   // set setup field to corresponding token value
3117   switch (token_type)
3118   {
3119     case TYPE_BOOLEAN:
3120     case TYPE_SWITCH:
3121       *(boolean *)setup_value = get_boolean_from_string(token_value);
3122       break;
3123
3124     case TYPE_SWITCH3:
3125       *(int *)setup_value = get_switch3_from_string(token_value);
3126       break;
3127
3128     case TYPE_KEY:
3129       *(Key *)setup_value = getKeyFromKeyName(token_value);
3130       break;
3131
3132     case TYPE_KEY_X11:
3133       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
3134       break;
3135
3136     case TYPE_INTEGER:
3137       *(int *)setup_value = get_integer_from_string(token_value);
3138       break;
3139
3140     case TYPE_STRING:
3141       checked_free(*(char **)setup_value);
3142       *(char **)setup_value = getStringCopy(token_value);
3143       break;
3144
3145     case TYPE_PLAYER:
3146       *(int *)setup_value = get_player_nr_from_string(token_value);
3147       break;
3148
3149     default:
3150       break;
3151   }
3152 }
3153
3154 static int compareTreeInfoEntries(const void *object1, const void *object2)
3155 {
3156   const TreeInfo *entry1 = *((TreeInfo **)object1);
3157   const TreeInfo *entry2 = *((TreeInfo **)object2);
3158   int tree_sorting1 = TREE_SORTING(entry1);
3159   int tree_sorting2 = TREE_SORTING(entry2);
3160
3161   if (tree_sorting1 != tree_sorting2)
3162     return (tree_sorting1 - tree_sorting2);
3163   else
3164     return strcasecmp(entry1->name_sorting, entry2->name_sorting);
3165 }
3166
3167 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
3168 {
3169   TreeInfo *ti_new;
3170
3171   if (node_parent == NULL)
3172     return NULL;
3173
3174   ti_new = newTreeInfo();
3175   setTreeInfoToDefaults(ti_new, node_parent->type);
3176
3177   ti_new->node_parent = node_parent;
3178   ti_new->parent_link = TRUE;
3179
3180   setString(&ti_new->identifier, node_parent->identifier);
3181   setString(&ti_new->name, BACKLINK_TEXT_PARENT);
3182   setString(&ti_new->name_sorting, ti_new->name);
3183
3184   setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
3185   setString(&ti_new->fullpath, node_parent->fullpath);
3186
3187   ti_new->sort_priority = LEVELCLASS_PARENT;
3188   ti_new->latest_engine = node_parent->latest_engine;
3189
3190   setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3191
3192   pushTreeInfo(&node_parent->node_group, ti_new);
3193
3194   return ti_new;
3195 }
3196
3197 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
3198 {
3199   if (node_first == NULL)
3200     return NULL;
3201
3202   TreeInfo *ti_new = newTreeInfo();
3203   int type = node_first->type;
3204
3205   setTreeInfoToDefaults(ti_new, type);
3206
3207   ti_new->node_parent = NULL;
3208   ti_new->parent_link = FALSE;
3209
3210   setString(&ti_new->identifier, "top_tree_node");
3211   setString(&ti_new->name, TREE_INFOTEXT(type));
3212   setString(&ti_new->name_sorting, ti_new->name);
3213
3214   setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3215   setString(&ti_new->fullpath, ".");
3216
3217   ti_new->sort_priority = LEVELCLASS_TOP;
3218   ti_new->latest_engine = node_first->latest_engine;
3219
3220   setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3221
3222   ti_new->node_group = node_first;
3223   ti_new->level_group = TRUE;
3224
3225   TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3226
3227   setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3228   setString(&ti_new2->name_sorting, ti_new2->name);
3229
3230   return ti_new;
3231 }
3232
3233 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3234 {
3235   while (node)
3236   {
3237     if (node->node_group)
3238       setTreeInfoParentNodes(node->node_group, node);
3239
3240     node->node_parent = node_parent;
3241
3242     node = node->next;
3243   }
3244 }
3245
3246 TreeInfo *addTopTreeInfoNode(TreeInfo *node_first)
3247 {
3248   // add top tree node with back link node in previous tree
3249   node_first = createTopTreeInfoNode(node_first);
3250
3251   // set all parent links (back links) in complete tree
3252   setTreeInfoParentNodes(node_first, NULL);
3253
3254   return node_first;
3255 }
3256
3257
3258 // ----------------------------------------------------------------------------
3259 // functions for handling level and custom artwork info cache
3260 // ----------------------------------------------------------------------------
3261
3262 static void LoadArtworkInfoCache(void)
3263 {
3264   InitCacheDirectory();
3265
3266   if (artworkinfo_cache_old == NULL)
3267   {
3268     char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3269
3270     // try to load artwork info hash from already existing cache file
3271     artworkinfo_cache_old = loadSetupFileHash(filename);
3272
3273     // try to get program version that artwork info cache was written with
3274     char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3275
3276     // check program version of artwork info cache against current version
3277     if (!strEqual(version, program.version_string))
3278     {
3279       freeSetupFileHash(artworkinfo_cache_old);
3280
3281       artworkinfo_cache_old = NULL;
3282     }
3283
3284     // if no artwork info cache file was found, start with empty hash
3285     if (artworkinfo_cache_old == NULL)
3286       artworkinfo_cache_old = newSetupFileHash();
3287
3288     free(filename);
3289   }
3290
3291   if (artworkinfo_cache_new == NULL)
3292     artworkinfo_cache_new = newSetupFileHash();
3293
3294   update_artworkinfo_cache = FALSE;
3295 }
3296
3297 static void SaveArtworkInfoCache(void)
3298 {
3299   if (!update_artworkinfo_cache)
3300     return;
3301
3302   char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3303
3304   InitCacheDirectory();
3305
3306   saveSetupFileHash(artworkinfo_cache_new, filename);
3307
3308   free(filename);
3309 }
3310
3311 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3312 {
3313   static char *prefix = NULL;
3314
3315   checked_free(prefix);
3316
3317   prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3318
3319   return prefix;
3320 }
3321
3322 // (identical to above function, but separate string buffer needed -- nasty)
3323 static char *getCacheToken(char *prefix, char *suffix)
3324 {
3325   static char *token = NULL;
3326
3327   checked_free(token);
3328
3329   token = getStringCat2WithSeparator(prefix, suffix, ".");
3330
3331   return token;
3332 }
3333
3334 static char *getFileTimestampString(char *filename)
3335 {
3336   return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3337 }
3338
3339 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3340 {
3341   struct stat file_status;
3342
3343   if (timestamp_string == NULL)
3344     return TRUE;
3345
3346   if (!fileExists(filename))                    // file does not exist
3347     return (atoi(timestamp_string) != 0);
3348
3349   if (stat(filename, &file_status) != 0)        // cannot stat file
3350     return TRUE;
3351
3352   return (file_status.st_mtime != atoi(timestamp_string));
3353 }
3354
3355 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3356 {
3357   char *identifier = level_node->subdir;
3358   char *type_string = ARTWORK_DIRECTORY(type);
3359   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3360   char *token_main = getCacheToken(token_prefix, "CACHED");
3361   char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3362   boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3363   TreeInfo *artwork_info = NULL;
3364
3365   if (!use_artworkinfo_cache)
3366     return NULL;
3367
3368   if (optional_tokens_hash == NULL)
3369   {
3370     int i;
3371
3372     // create hash from list of optional tokens (for quick access)
3373     optional_tokens_hash = newSetupFileHash();
3374     for (i = 0; optional_tokens[i] != NULL; i++)
3375       setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3376   }
3377
3378   if (cached)
3379   {
3380     int i;
3381
3382     artwork_info = newTreeInfo();
3383     setTreeInfoToDefaults(artwork_info, type);
3384
3385     // set all structure fields according to the token/value pairs
3386     ldi = *artwork_info;
3387     for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3388     {
3389       char *token_suffix = artworkinfo_tokens[i].text;
3390       char *token = getCacheToken(token_prefix, token_suffix);
3391       char *value = getHashEntry(artworkinfo_cache_old, token);
3392       boolean optional =
3393         (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3394
3395       setSetupInfo(artworkinfo_tokens, i, value);
3396
3397       // check if cache entry for this item is mandatory, but missing
3398       if (value == NULL && !optional)
3399       {
3400         Warn("missing cache entry '%s'", token);
3401
3402         cached = FALSE;
3403       }
3404     }
3405
3406     *artwork_info = ldi;
3407   }
3408
3409   if (cached)
3410   {
3411     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3412                                         LEVELINFO_FILENAME);
3413     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3414                                           ARTWORKINFO_FILENAME(type));
3415
3416     // check if corresponding "levelinfo.conf" file has changed
3417     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3418     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3419
3420     if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3421       cached = FALSE;
3422
3423     // check if corresponding "<artworkinfo>.conf" file has changed
3424     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3425     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3426
3427     if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3428       cached = FALSE;
3429
3430     checked_free(filename_levelinfo);
3431     checked_free(filename_artworkinfo);
3432   }
3433
3434   if (!cached && artwork_info != NULL)
3435   {
3436     freeTreeInfo(artwork_info);
3437
3438     return NULL;
3439   }
3440
3441   return artwork_info;
3442 }
3443
3444 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3445                                      LevelDirTree *level_node, int type)
3446 {
3447   char *identifier = level_node->subdir;
3448   char *type_string = ARTWORK_DIRECTORY(type);
3449   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3450   char *token_main = getCacheToken(token_prefix, "CACHED");
3451   boolean set_cache_timestamps = TRUE;
3452   int i;
3453
3454   setHashEntry(artworkinfo_cache_new, token_main, "true");
3455
3456   if (set_cache_timestamps)
3457   {
3458     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3459                                         LEVELINFO_FILENAME);
3460     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3461                                           ARTWORKINFO_FILENAME(type));
3462     char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3463     char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3464
3465     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3466     setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3467
3468     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3469     setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3470
3471     checked_free(filename_levelinfo);
3472     checked_free(filename_artworkinfo);
3473     checked_free(timestamp_levelinfo);
3474     checked_free(timestamp_artworkinfo);
3475   }
3476
3477   ldi = *artwork_info;
3478   for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3479   {
3480     char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3481     char *value = getSetupValue(artworkinfo_tokens[i].type,
3482                                 artworkinfo_tokens[i].value);
3483     if (value != NULL)
3484       setHashEntry(artworkinfo_cache_new, token, value);
3485   }
3486 }
3487
3488
3489 // ----------------------------------------------------------------------------
3490 // functions for loading level info and custom artwork info
3491 // ----------------------------------------------------------------------------
3492
3493 int GetZipFileTreeType(char *zip_filename)
3494 {
3495   static char *top_dir_path = NULL;
3496   static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3497   static char *conf_basename[NUM_BASE_TREE_TYPES] =
3498   {
3499     GRAPHICSINFO_FILENAME,
3500     SOUNDSINFO_FILENAME,
3501     MUSICINFO_FILENAME,
3502     LEVELINFO_FILENAME
3503   };
3504   int j;
3505
3506   checked_free(top_dir_path);
3507   top_dir_path = NULL;
3508
3509   for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3510   {
3511     checked_free(top_dir_conf_filename[j]);
3512     top_dir_conf_filename[j] = NULL;
3513   }
3514
3515   char **zip_entries = zip_list(zip_filename);
3516
3517   // check if zip file successfully opened
3518   if (zip_entries == NULL || zip_entries[0] == NULL)
3519     return TREE_TYPE_UNDEFINED;
3520
3521   // first zip file entry is expected to be top level directory
3522   char *top_dir = zip_entries[0];
3523
3524   // check if valid top level directory found in zip file
3525   if (!strSuffix(top_dir, "/"))
3526     return TREE_TYPE_UNDEFINED;
3527
3528   // get filenames of valid configuration files in top level directory
3529   for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3530     top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3531
3532   int tree_type = TREE_TYPE_UNDEFINED;
3533   int e = 0;
3534
3535   while (zip_entries[e] != NULL)
3536   {
3537     // check if every zip file entry is below top level directory
3538     if (!strPrefix(zip_entries[e], top_dir))
3539       return TREE_TYPE_UNDEFINED;
3540
3541     // check if this zip file entry is a valid configuration filename
3542     for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3543     {
3544       if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3545       {
3546         // only exactly one valid configuration file allowed
3547         if (tree_type != TREE_TYPE_UNDEFINED)
3548           return TREE_TYPE_UNDEFINED;
3549
3550         tree_type = j;
3551       }
3552     }
3553
3554     e++;
3555   }
3556
3557   return tree_type;
3558 }
3559
3560 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3561                                         int tree_type)
3562 {
3563   static char *top_dir_path = NULL;
3564   static char *top_dir_conf_filename = NULL;
3565
3566   checked_free(top_dir_path);
3567   checked_free(top_dir_conf_filename);
3568
3569   top_dir_path = NULL;
3570   top_dir_conf_filename = NULL;
3571
3572   char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3573                          ARTWORKINFO_FILENAME(tree_type));
3574
3575   // check if valid configuration filename determined
3576   if (conf_basename == NULL || strEqual(conf_basename, ""))
3577     return FALSE;
3578
3579   char **zip_entries = zip_list(zip_filename);
3580
3581   // check if zip file successfully opened
3582   if (zip_entries == NULL || zip_entries[0] == NULL)
3583     return FALSE;
3584
3585   // first zip file entry is expected to be top level directory
3586   char *top_dir = zip_entries[0];
3587
3588   // check if valid top level directory found in zip file
3589   if (!strSuffix(top_dir, "/"))
3590     return FALSE;
3591
3592   // get path of extracted top level directory
3593   top_dir_path = getPath2(directory, top_dir);
3594
3595   // remove trailing directory separator from top level directory path
3596   // (required to be able to check for file and directory in next step)
3597   top_dir_path[strlen(top_dir_path) - 1] = '\0';
3598
3599   // check if zip file's top level directory already exists in target directory
3600   if (fileExists(top_dir_path))         // (checks for file and directory)
3601     return FALSE;
3602
3603   // get filename of configuration file in top level directory
3604   top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3605
3606   boolean found_top_dir_conf_filename = FALSE;
3607   int i = 0;
3608
3609   while (zip_entries[i] != NULL)
3610   {
3611     // check if every zip file entry is below top level directory
3612     if (!strPrefix(zip_entries[i], top_dir))
3613       return FALSE;
3614
3615     // check if this zip file entry is the configuration filename
3616     if (strEqual(zip_entries[i], top_dir_conf_filename))
3617       found_top_dir_conf_filename = TRUE;
3618
3619     i++;
3620   }
3621
3622   // check if valid configuration filename was found in zip file
3623   if (!found_top_dir_conf_filename)
3624     return FALSE;
3625
3626   return TRUE;
3627 }
3628
3629 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3630                                   int tree_type)
3631 {
3632   boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3633                                                     tree_type);
3634
3635   if (!zip_file_valid)
3636   {
3637     Warn("zip file '%s' rejected!", zip_filename);
3638
3639     return NULL;
3640   }
3641
3642   char **zip_entries = zip_extract(zip_filename, directory);
3643
3644   if (zip_entries == NULL)
3645   {
3646     Warn("zip file '%s' could not be extracted!", zip_filename);
3647
3648     return NULL;
3649   }
3650
3651   Info("zip file '%s' successfully extracted!", zip_filename);
3652
3653   // first zip file entry contains top level directory
3654   char *top_dir = zip_entries[0];
3655
3656   // remove trailing directory separator from top level directory
3657   top_dir[strlen(top_dir) - 1] = '\0';
3658
3659   return top_dir;
3660 }
3661
3662 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3663 {
3664   Directory *dir;
3665   DirectoryEntry *dir_entry;
3666
3667   if ((dir = openDirectory(directory)) == NULL)
3668   {
3669     // display error if directory is main "options.graphics_directory" etc.
3670     if (tree_type == TREE_TYPE_LEVEL_DIR ||
3671         directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3672       Warn("cannot read directory '%s'", directory);
3673
3674     return;
3675   }
3676
3677   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
3678   {
3679     // skip non-zip files (and also directories with zip extension)
3680     if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3681       continue;
3682
3683     char *zip_filename = getPath2(directory, dir_entry->basename);
3684     char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3685     char *zip_filename_rejected  = getStringCat2(zip_filename, ".rejected");
3686
3687     // check if zip file hasn't already been extracted or rejected
3688     if (!fileExists(zip_filename_extracted) &&
3689         !fileExists(zip_filename_rejected))
3690     {
3691       char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3692                                                   tree_type);
3693       char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3694                                zip_filename_rejected);
3695       FILE *marker_file;
3696
3697       // create empty file to mark zip file as extracted or rejected
3698       if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3699         fclose(marker_file);
3700
3701       free(zip_filename);
3702       free(zip_filename_extracted);
3703       free(zip_filename_rejected);
3704     }
3705   }
3706
3707   closeDirectory(dir);
3708 }
3709
3710 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3711 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3712
3713 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3714                                           TreeInfo *node_parent,
3715                                           char *level_directory,
3716                                           char *directory_name)
3717 {
3718   char *directory_path = getPath2(level_directory, directory_name);
3719   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3720   SetupFileHash *setup_file_hash;
3721   LevelDirTree *leveldir_new = NULL;
3722   int i;
3723
3724   // unless debugging, silently ignore directories without "levelinfo.conf"
3725   if (!options.debug && !fileExists(filename))
3726   {
3727     free(directory_path);
3728     free(filename);
3729
3730     return FALSE;
3731   }
3732
3733   setup_file_hash = loadSetupFileHash(filename);
3734
3735   if (setup_file_hash == NULL)
3736   {
3737 #if DEBUG_NO_CONFIG_FILE
3738     Debug("setup", "ignoring level directory '%s'", directory_path);
3739 #endif
3740
3741     free(directory_path);
3742     free(filename);
3743
3744     return FALSE;
3745   }
3746
3747   leveldir_new = newTreeInfo();
3748
3749   if (node_parent)
3750     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3751   else
3752     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3753
3754   leveldir_new->subdir = getStringCopy(directory_name);
3755
3756   // set all structure fields according to the token/value pairs
3757   ldi = *leveldir_new;
3758   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3759     setSetupInfo(levelinfo_tokens, i,
3760                  getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3761   *leveldir_new = ldi;
3762
3763   if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3764     setString(&leveldir_new->name, leveldir_new->subdir);
3765
3766   if (leveldir_new->identifier == NULL)
3767     leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3768
3769   if (leveldir_new->name_sorting == NULL)
3770     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3771
3772   if (node_parent == NULL)              // top level group
3773   {
3774     leveldir_new->basepath = getStringCopy(level_directory);
3775     leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3776   }
3777   else                                  // sub level group
3778   {
3779     leveldir_new->basepath = getStringCopy(node_parent->basepath);
3780     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3781   }
3782
3783   leveldir_new->last_level =
3784     leveldir_new->first_level + leveldir_new->levels - 1;
3785
3786   leveldir_new->in_user_dir =
3787     (!strEqual(leveldir_new->basepath, options.level_directory));
3788
3789   // adjust some settings if user's private level directory was detected
3790   if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3791       leveldir_new->in_user_dir &&
3792       (strEqual(leveldir_new->subdir, getLoginName()) ||
3793        strEqual(leveldir_new->name,   getLoginName()) ||
3794        strEqual(leveldir_new->author, getRealName())))
3795   {
3796     leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3797     leveldir_new->readonly = FALSE;
3798   }
3799
3800   leveldir_new->user_defined =
3801     (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3802
3803   setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3804
3805   leveldir_new->handicap_level =        // set handicap to default value
3806     (leveldir_new->user_defined || !leveldir_new->handicap ?
3807      leveldir_new->last_level : leveldir_new->first_level);
3808
3809   DrawInitTextItem(leveldir_new->name);
3810
3811   pushTreeInfo(node_first, leveldir_new);
3812
3813   freeSetupFileHash(setup_file_hash);
3814
3815   if (leveldir_new->level_group)
3816   {
3817     // create node to link back to current level directory
3818     createParentTreeInfoNode(leveldir_new);
3819
3820     // recursively step into sub-directory and look for more level series
3821     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3822                               leveldir_new, directory_path);
3823   }
3824
3825   free(directory_path);
3826   free(filename);
3827
3828   return TRUE;
3829 }
3830
3831 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3832                                       TreeInfo *node_parent,
3833                                       char *level_directory)
3834 {
3835   // ---------- 1st stage: process any level set zip files ----------
3836
3837   ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3838
3839   // ---------- 2nd stage: check for level set directories ----------
3840
3841   Directory *dir;
3842   DirectoryEntry *dir_entry;
3843   boolean valid_entry_found = FALSE;
3844
3845   if ((dir = openDirectory(level_directory)) == NULL)
3846   {
3847     Warn("cannot read level directory '%s'", level_directory);
3848
3849     return;
3850   }
3851
3852   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
3853   {
3854     char *directory_name = dir_entry->basename;
3855     char *directory_path = getPath2(level_directory, directory_name);
3856
3857     // skip entries for current and parent directory
3858     if (strEqual(directory_name, ".") ||
3859         strEqual(directory_name, ".."))
3860     {
3861       free(directory_path);
3862
3863       continue;
3864     }
3865
3866     // find out if directory entry is itself a directory
3867     if (!dir_entry->is_directory)                       // not a directory
3868     {
3869       free(directory_path);
3870
3871       continue;
3872     }
3873
3874     free(directory_path);
3875
3876     if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3877         strEqual(directory_name, SOUNDS_DIRECTORY) ||
3878         strEqual(directory_name, MUSIC_DIRECTORY))
3879       continue;
3880
3881     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3882                                                     level_directory,
3883                                                     directory_name);
3884   }
3885
3886   closeDirectory(dir);
3887
3888   // special case: top level directory may directly contain "levelinfo.conf"
3889   if (node_parent == NULL && !valid_entry_found)
3890   {
3891     // check if this directory directly contains a file "levelinfo.conf"
3892     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3893                                                     level_directory, ".");
3894   }
3895
3896   if (!valid_entry_found)
3897     Warn("cannot find any valid level series in directory '%s'",
3898           level_directory);
3899 }
3900
3901 boolean AdjustGraphicsForEMC(void)
3902 {
3903   boolean settings_changed = FALSE;
3904
3905   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3906   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3907
3908   return settings_changed;
3909 }
3910
3911 boolean AdjustSoundsForEMC(void)
3912 {
3913   boolean settings_changed = FALSE;
3914
3915   settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3916   settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3917
3918   return settings_changed;
3919 }
3920
3921 void LoadLevelInfo(void)
3922 {
3923   InitUserLevelDirectory(getLoginName());
3924
3925   DrawInitTextHead("Loading level series");
3926
3927   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3928   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3929
3930   leveldir_first = createTopTreeInfoNode(leveldir_first);
3931
3932   /* after loading all level set information, clone the level directory tree
3933      and remove all level sets without levels (these may still contain artwork
3934      to be offered in the setup menu as "custom artwork", and are therefore
3935      checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3936   leveldir_first_all = leveldir_first;
3937   cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3938
3939   AdjustGraphicsForEMC();
3940   AdjustSoundsForEMC();
3941
3942   // before sorting, the first entries will be from the user directory
3943   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3944
3945   if (leveldir_first == NULL)
3946     Fail("cannot find any valid level series in any directory");
3947
3948   sortTreeInfo(&leveldir_first);
3949
3950 #if ENABLE_UNUSED_CODE
3951   dumpTreeInfo(leveldir_first, 0);
3952 #endif
3953 }
3954
3955 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3956                                               TreeInfo *node_parent,
3957                                               char *base_directory,
3958                                               char *directory_name, int type)
3959 {
3960   char *directory_path = getPath2(base_directory, directory_name);
3961   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3962   SetupFileHash *setup_file_hash = NULL;
3963   TreeInfo *artwork_new = NULL;
3964   int i;
3965
3966   if (fileExists(filename))
3967     setup_file_hash = loadSetupFileHash(filename);
3968
3969   if (setup_file_hash == NULL)  // no config file -- look for artwork files
3970   {
3971     Directory *dir;
3972     DirectoryEntry *dir_entry;
3973     boolean valid_file_found = FALSE;
3974
3975     if ((dir = openDirectory(directory_path)) != NULL)
3976     {
3977       while ((dir_entry = readDirectory(dir)) != NULL)
3978       {
3979         if (FileIsArtworkType(dir_entry->filename, type))
3980         {
3981           valid_file_found = TRUE;
3982
3983           break;
3984         }
3985       }
3986
3987       closeDirectory(dir);
3988     }
3989
3990     if (!valid_file_found)
3991     {
3992 #if DEBUG_NO_CONFIG_FILE
3993       if (!strEqual(directory_name, "."))
3994         Debug("setup", "ignoring artwork directory '%s'", directory_path);
3995 #endif
3996
3997       free(directory_path);
3998       free(filename);
3999
4000       return FALSE;
4001     }
4002   }
4003
4004   artwork_new = newTreeInfo();
4005
4006   if (node_parent)
4007     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4008   else
4009     setTreeInfoToDefaults(artwork_new, type);
4010
4011   artwork_new->subdir = getStringCopy(directory_name);
4012
4013   if (setup_file_hash)  // (before defining ".color" and ".class_desc")
4014   {
4015     // set all structure fields according to the token/value pairs
4016     ldi = *artwork_new;
4017     for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4018       setSetupInfo(levelinfo_tokens, i,
4019                    getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
4020     *artwork_new = ldi;
4021
4022     if (strEqual(artwork_new->name, ANONYMOUS_NAME))
4023       setString(&artwork_new->name, artwork_new->subdir);
4024
4025     if (artwork_new->identifier == NULL)
4026       artwork_new->identifier = getStringCopy(artwork_new->subdir);
4027
4028     if (artwork_new->name_sorting == NULL)
4029       artwork_new->name_sorting = getStringCopy(artwork_new->name);
4030   }
4031
4032   if (node_parent == NULL)              // top level group
4033   {
4034     artwork_new->basepath = getStringCopy(base_directory);
4035     artwork_new->fullpath = getStringCopy(artwork_new->subdir);
4036   }
4037   else                                  // sub level group
4038   {
4039     artwork_new->basepath = getStringCopy(node_parent->basepath);
4040     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
4041   }
4042
4043   artwork_new->in_user_dir =
4044     (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
4045
4046   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
4047
4048   if (setup_file_hash == NULL)  // (after determining ".user_defined")
4049   {
4050     if (strEqual(artwork_new->subdir, "."))
4051     {
4052       if (artwork_new->user_defined)
4053       {
4054         setString(&artwork_new->identifier, "private");
4055         artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
4056       }
4057       else
4058       {
4059         setString(&artwork_new->identifier, "classic");
4060         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
4061       }
4062
4063       setString(&artwork_new->class_desc,
4064                 getLevelClassDescription(artwork_new));
4065     }
4066     else
4067     {
4068       setString(&artwork_new->identifier, artwork_new->subdir);
4069     }
4070
4071     setString(&artwork_new->name, artwork_new->identifier);
4072     setString(&artwork_new->name_sorting, artwork_new->name);
4073   }
4074
4075   pushTreeInfo(node_first, artwork_new);
4076
4077   freeSetupFileHash(setup_file_hash);
4078
4079   free(directory_path);
4080   free(filename);
4081
4082   return TRUE;
4083 }
4084
4085 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
4086                                           TreeInfo *node_parent,
4087                                           char *base_directory, int type)
4088 {
4089   // ---------- 1st stage: process any artwork set zip files ----------
4090
4091   ProcessZipFilesInDirectory(base_directory, type);
4092
4093   // ---------- 2nd stage: check for artwork set directories ----------
4094
4095   Directory *dir;
4096   DirectoryEntry *dir_entry;
4097   boolean valid_entry_found = FALSE;
4098
4099   if ((dir = openDirectory(base_directory)) == NULL)
4100   {
4101     // display error if directory is main "options.graphics_directory" etc.
4102     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
4103       Warn("cannot read directory '%s'", base_directory);
4104
4105     return;
4106   }
4107
4108   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
4109   {
4110     char *directory_name = dir_entry->basename;
4111     char *directory_path = getPath2(base_directory, directory_name);
4112
4113     // skip directory entries for current and parent directory
4114     if (strEqual(directory_name, ".") ||
4115         strEqual(directory_name, ".."))
4116     {
4117       free(directory_path);
4118
4119       continue;
4120     }
4121
4122     // skip directory entries which are not a directory
4123     if (!dir_entry->is_directory)                       // not a directory
4124     {
4125       free(directory_path);
4126
4127       continue;
4128     }
4129
4130     free(directory_path);
4131
4132     // check if this directory contains artwork with or without config file
4133     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4134                                                         base_directory,
4135                                                         directory_name, type);
4136   }
4137
4138   closeDirectory(dir);
4139
4140   // check if this directory directly contains artwork itself
4141   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4142                                                       base_directory, ".",
4143                                                       type);
4144   if (!valid_entry_found)
4145     Warn("cannot find any valid artwork in directory '%s'", base_directory);
4146 }
4147
4148 static TreeInfo *getDummyArtworkInfo(int type)
4149 {
4150   // this is only needed when there is completely no artwork available
4151   TreeInfo *artwork_new = newTreeInfo();
4152
4153   setTreeInfoToDefaults(artwork_new, type);
4154
4155   setString(&artwork_new->subdir,   UNDEFINED_FILENAME);
4156   setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
4157   setString(&artwork_new->basepath, UNDEFINED_FILENAME);
4158
4159   setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
4160   setString(&artwork_new->name,         UNDEFINED_FILENAME);
4161   setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4162
4163   return artwork_new;
4164 }
4165
4166 void SetCurrentArtwork(int type)
4167 {
4168   ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
4169   ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
4170   char *setup_set = SETUP_ARTWORK_SET(setup, type);
4171   char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
4172
4173   // set current artwork to artwork configured in setup menu
4174   *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
4175
4176   // if not found, set current artwork to default artwork
4177   if (*current_ptr == NULL)
4178     *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
4179
4180   // if not found, set current artwork to first artwork in tree
4181   if (*current_ptr == NULL)
4182     *current_ptr = getFirstValidTreeInfoEntry(first_node);
4183 }
4184
4185 void ChangeCurrentArtworkIfNeeded(int type)
4186 {
4187   char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
4188   char *setup_set = SETUP_ARTWORK_SET(setup, type);
4189
4190   if (!strEqual(current_identifier, setup_set))
4191     SetCurrentArtwork(type);
4192 }
4193
4194 void LoadArtworkInfo(void)
4195 {
4196   LoadArtworkInfoCache();
4197
4198   DrawInitTextHead("Looking for custom artwork");
4199
4200   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4201                                 options.graphics_directory,
4202                                 TREE_TYPE_GRAPHICS_DIR);
4203   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4204                                 getUserGraphicsDir(),
4205                                 TREE_TYPE_GRAPHICS_DIR);
4206
4207   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4208                                 options.sounds_directory,
4209                                 TREE_TYPE_SOUNDS_DIR);
4210   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4211                                 getUserSoundsDir(),
4212                                 TREE_TYPE_SOUNDS_DIR);
4213
4214   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4215                                 options.music_directory,
4216                                 TREE_TYPE_MUSIC_DIR);
4217   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4218                                 getUserMusicDir(),
4219                                 TREE_TYPE_MUSIC_DIR);
4220
4221   if (artwork.gfx_first == NULL)
4222     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4223   if (artwork.snd_first == NULL)
4224     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4225   if (artwork.mus_first == NULL)
4226     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4227
4228   // before sorting, the first entries will be from the user directory
4229   SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4230   SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4231   SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4232
4233   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4234   artwork.snd_current_identifier = artwork.snd_current->identifier;
4235   artwork.mus_current_identifier = artwork.mus_current->identifier;
4236
4237 #if ENABLE_UNUSED_CODE
4238   Debug("setup:LoadArtworkInfo", "graphics set == %s",
4239         artwork.gfx_current_identifier);
4240   Debug("setup:LoadArtworkInfo", "sounds set == %s",
4241         artwork.snd_current_identifier);
4242   Debug("setup:LoadArtworkInfo", "music set == %s",
4243         artwork.mus_current_identifier);
4244 #endif
4245
4246   sortTreeInfo(&artwork.gfx_first);
4247   sortTreeInfo(&artwork.snd_first);
4248   sortTreeInfo(&artwork.mus_first);
4249
4250 #if ENABLE_UNUSED_CODE
4251   dumpTreeInfo(artwork.gfx_first, 0);
4252   dumpTreeInfo(artwork.snd_first, 0);
4253   dumpTreeInfo(artwork.mus_first, 0);
4254 #endif
4255 }
4256
4257 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4258 {
4259   ArtworkDirTree *artwork_new = newTreeInfo();
4260   char *top_node_name = "standalone artwork";
4261
4262   setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4263
4264   artwork_new->level_group = TRUE;
4265
4266   setString(&artwork_new->identifier,   top_node_name);
4267   setString(&artwork_new->name,         top_node_name);
4268   setString(&artwork_new->name_sorting, top_node_name);
4269
4270   // create node to link back to current custom artwork directory
4271   createParentTreeInfoNode(artwork_new);
4272
4273   // move existing custom artwork tree into newly created sub-tree
4274   artwork_new->node_group->next = *artwork_node;
4275
4276   // change custom artwork tree to contain only newly created node
4277   *artwork_node = artwork_new;
4278 }
4279
4280 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4281                                             ArtworkDirTree *node_parent,
4282                                             LevelDirTree *level_node,
4283                                             boolean empty_level_set_mode)
4284 {
4285   int type = (*artwork_node)->type;
4286
4287   // recursively check all level directories for artwork sub-directories
4288
4289   while (level_node)
4290   {
4291     boolean empty_level_set = (level_node->levels == 0);
4292
4293     // check all tree entries for artwork, but skip parent link entries
4294     if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4295     {
4296       TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4297       boolean cached = (artwork_new != NULL);
4298
4299       if (cached)
4300       {
4301         pushTreeInfo(artwork_node, artwork_new);
4302       }
4303       else
4304       {
4305         TreeInfo *topnode_last = *artwork_node;
4306         char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4307                               ARTWORK_DIRECTORY(type));
4308
4309         LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4310
4311         if (topnode_last != *artwork_node)      // check for newly added node
4312         {
4313           artwork_new = *artwork_node;
4314
4315           setString(&artwork_new->identifier,   level_node->subdir);
4316           setString(&artwork_new->name,         level_node->name);
4317           setString(&artwork_new->name_sorting, level_node->name_sorting);
4318
4319           artwork_new->sort_priority = level_node->sort_priority;
4320           artwork_new->in_user_dir = level_node->in_user_dir;
4321
4322           update_artworkinfo_cache = TRUE;
4323         }
4324
4325         free(path);
4326       }
4327
4328       // insert artwork info (from old cache or filesystem) into new cache
4329       if (artwork_new != NULL)
4330         setArtworkInfoCacheEntry(artwork_new, level_node, type);
4331     }
4332
4333     DrawInitTextItem(level_node->name);
4334
4335     if (level_node->node_group != NULL)
4336     {
4337       TreeInfo *artwork_new = newTreeInfo();
4338
4339       if (node_parent)
4340         setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4341       else
4342         setTreeInfoToDefaults(artwork_new, type);
4343
4344       artwork_new->level_group = TRUE;
4345
4346       setString(&artwork_new->identifier,   level_node->subdir);
4347
4348       if (node_parent == NULL)          // check for top tree node
4349       {
4350         char *top_node_name = (empty_level_set_mode ?
4351                                "artwork for certain level sets" :
4352                                "artwork included in level sets");
4353
4354         setString(&artwork_new->name,         top_node_name);
4355         setString(&artwork_new->name_sorting, top_node_name);
4356       }
4357       else
4358       {
4359         setString(&artwork_new->name,         level_node->name);
4360         setString(&artwork_new->name_sorting, level_node->name_sorting);
4361       }
4362
4363       pushTreeInfo(artwork_node, artwork_new);
4364
4365       // create node to link back to current custom artwork directory
4366       createParentTreeInfoNode(artwork_new);
4367
4368       // recursively step into sub-directory and look for more custom artwork
4369       LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4370                                       level_node->node_group,
4371                                       empty_level_set_mode);
4372
4373       // if sub-tree has no custom artwork at all, remove it
4374       if (artwork_new->node_group->next == NULL)
4375         removeTreeInfo(artwork_node);
4376     }
4377
4378     level_node = level_node->next;
4379   }
4380 }
4381
4382 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4383 {
4384   // move peviously loaded artwork tree into separate sub-tree
4385   MoveArtworkInfoIntoSubTree(artwork_node);
4386
4387   // load artwork from level sets into separate sub-trees
4388   LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4389   LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4390
4391   // add top tree node over all sub-trees and set parent links
4392   *artwork_node = addTopTreeInfoNode(*artwork_node);
4393 }
4394
4395 void LoadLevelArtworkInfo(void)
4396 {
4397   print_timestamp_init("LoadLevelArtworkInfo");
4398
4399   DrawInitTextHead("Looking for custom level artwork");
4400
4401   print_timestamp_time("DrawTimeText");
4402
4403   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4404   print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4405   LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4406   print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4407   LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4408   print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4409
4410   SaveArtworkInfoCache();
4411
4412   print_timestamp_time("SaveArtworkInfoCache");
4413
4414   // needed for reloading level artwork not known at ealier stage
4415   ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4416   ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4417   ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4418
4419   print_timestamp_time("getTreeInfoFromIdentifier");
4420
4421   sortTreeInfo(&artwork.gfx_first);
4422   sortTreeInfo(&artwork.snd_first);
4423   sortTreeInfo(&artwork.mus_first);
4424
4425   print_timestamp_time("sortTreeInfo");
4426
4427 #if ENABLE_UNUSED_CODE
4428   dumpTreeInfo(artwork.gfx_first, 0);
4429   dumpTreeInfo(artwork.snd_first, 0);
4430   dumpTreeInfo(artwork.mus_first, 0);
4431 #endif
4432
4433   print_timestamp_done("LoadLevelArtworkInfo");
4434 }
4435
4436 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4437                                        char *tree_subdir_new, int type)
4438 {
4439   if (tree_node_old == NULL)
4440   {
4441     if (type == TREE_TYPE_LEVEL_DIR)
4442     {
4443       // get level info tree node of personal user level set
4444       tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4445
4446       // this may happen if "setup.internal.create_user_levelset" is FALSE
4447       // or if file "levelinfo.conf" is missing in personal user level set
4448       if (tree_node_old == NULL)
4449         tree_node_old = leveldir_first->node_group;
4450     }
4451     else
4452     {
4453       // get artwork info tree node of first artwork set
4454       tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4455     }
4456   }
4457
4458   if (tree_dir == NULL)
4459     tree_dir = TREE_USERDIR(type);
4460
4461   if (tree_node_old   == NULL ||
4462       tree_dir        == NULL ||
4463       tree_subdir_new == NULL)          // should not happen
4464     return FALSE;
4465
4466   int draw_deactivation_mask = GetDrawDeactivationMask();
4467
4468   // override draw deactivation mask (temporarily disable drawing)
4469   SetDrawDeactivationMask(REDRAW_ALL);
4470
4471   if (type == TREE_TYPE_LEVEL_DIR)
4472   {
4473     // load new level set config and add it next to first user level set
4474     LoadLevelInfoFromLevelConf(&tree_node_old->next,
4475                                tree_node_old->node_parent,
4476                                tree_dir, tree_subdir_new);
4477   }
4478   else
4479   {
4480     // load new artwork set config and add it next to first artwork set
4481     LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4482                                    tree_node_old->node_parent,
4483                                    tree_dir, tree_subdir_new, type);
4484   }
4485
4486   // set draw deactivation mask to previous value
4487   SetDrawDeactivationMask(draw_deactivation_mask);
4488
4489   // get first node of level or artwork info tree
4490   TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4491
4492   // get tree info node of newly added level or artwork set
4493   TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4494                                                       tree_subdir_new);
4495
4496   if (tree_node_new == NULL)            // should not happen
4497     return FALSE;
4498
4499   // correct top link and parent node link of newly created tree node
4500   tree_node_new->node_top    = tree_node_old->node_top;
4501   tree_node_new->node_parent = tree_node_old->node_parent;
4502
4503   // sort tree info to adjust position of newly added tree set
4504   sortTreeInfo(tree_node_first);
4505
4506   return TRUE;
4507 }
4508
4509 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4510                           char *tree_subdir_new, int type)
4511 {
4512   if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4513     Fail("internal tree info structure corrupted -- aborting");
4514 }
4515
4516 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4517 {
4518   AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4519 }
4520
4521 char *getArtworkIdentifierForUserLevelSet(int type)
4522 {
4523   char *classic_artwork_set = getClassicArtworkSet(type);
4524
4525   // check for custom artwork configured in "levelinfo.conf"
4526   char *leveldir_artwork_set =
4527     *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4528   boolean has_leveldir_artwork_set =
4529     (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4530                                                classic_artwork_set));
4531
4532   // check for custom artwork in sub-directory "graphics" etc.
4533   TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4534   char *leveldir_identifier = leveldir_current->identifier;
4535   boolean has_artwork_subdir =
4536     (getTreeInfoFromIdentifier(artwork_first_node,
4537                                leveldir_identifier) != NULL);
4538
4539   return (has_leveldir_artwork_set ? leveldir_artwork_set :
4540           has_artwork_subdir       ? leveldir_identifier :
4541           classic_artwork_set);
4542 }
4543
4544 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4545 {
4546   char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4547   TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4548   TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4549
4550   if (ti == NULL)
4551   {
4552     ti = getTreeInfoFromIdentifier(artwork_first_node,
4553                                    ARTWORK_DEFAULT_SUBDIR(type));
4554     if (ti == NULL)
4555       Fail("cannot find default graphics -- should not happen");
4556   }
4557
4558   return ti;
4559 }
4560
4561 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4562 {
4563   char *graphics_set =
4564     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4565   char *sounds_set =
4566     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4567   char *music_set =
4568     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4569
4570   return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4571           !strEqual(sounds_set,   SND_CLASSIC_SUBDIR) ||
4572           !strEqual(music_set,    MUS_CLASSIC_SUBDIR));
4573 }
4574
4575 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4576                            char *level_author, int num_levels)
4577 {
4578   char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4579   char *filename_tmp = getStringCat2(filename, ".tmp");
4580   FILE *file = NULL;
4581   FILE *file_tmp = NULL;
4582   char line[MAX_LINE_LEN];
4583   boolean success = FALSE;
4584   LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4585                                                      level_subdir);
4586   // update values in level directory tree
4587
4588   if (level_name != NULL)
4589     setString(&leveldir->name, level_name);
4590
4591   if (level_author != NULL)
4592     setString(&leveldir->author, level_author);
4593
4594   if (num_levels != -1)
4595     leveldir->levels = num_levels;
4596
4597   // update values that depend on other values
4598
4599   setString(&leveldir->name_sorting, leveldir->name);
4600
4601   leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4602
4603   // sort order of level sets may have changed
4604   sortTreeInfo(&leveldir_first);
4605
4606   if ((file     = fopen(filename,     MODE_READ)) &&
4607       (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4608   {
4609     while (fgets(line, MAX_LINE_LEN, file))
4610     {
4611       if (strPrefix(line, "name:") && level_name != NULL)
4612         fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4613       else if (strPrefix(line, "author:") && level_author != NULL)
4614         fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4615       else if (strPrefix(line, "levels:") && num_levels != -1)
4616         fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4617       else
4618         fputs(line, file_tmp);
4619     }
4620
4621     success = TRUE;
4622   }
4623
4624   if (file)
4625     fclose(file);
4626
4627   if (file_tmp)
4628     fclose(file_tmp);
4629
4630   if (success)
4631     success = (rename(filename_tmp, filename) == 0);
4632
4633   free(filename);
4634   free(filename_tmp);
4635
4636   return success;
4637 }
4638
4639 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4640                            char *level_author, int num_levels,
4641                            boolean use_artwork_set)
4642 {
4643   LevelDirTree *level_info;
4644   char *filename;
4645   FILE *file;
4646   int i;
4647
4648   // create user level sub-directory, if needed
4649   createDirectory(getUserLevelDir(level_subdir), "user level");
4650
4651   filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4652
4653   if (!(file = fopen(filename, MODE_WRITE)))
4654   {
4655     Warn("cannot write level info file '%s'", filename);
4656
4657     free(filename);
4658
4659     return FALSE;
4660   }
4661
4662   level_info = newTreeInfo();
4663
4664   // always start with reliable default values
4665   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4666
4667   setString(&level_info->name, level_name);
4668   setString(&level_info->author, level_author);
4669   level_info->levels = num_levels;
4670   level_info->first_level = 1;
4671   level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4672   level_info->readonly = FALSE;
4673
4674   if (use_artwork_set)
4675   {
4676     level_info->graphics_set =
4677       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4678     level_info->sounds_set =
4679       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4680     level_info->music_set =
4681       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4682   }
4683
4684   token_value_position = TOKEN_VALUE_POSITION_SHORT;
4685
4686   fprintFileHeader(file, LEVELINFO_FILENAME);
4687
4688   ldi = *level_info;
4689   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4690   {
4691     if (i == LEVELINFO_TOKEN_NAME ||
4692         i == LEVELINFO_TOKEN_AUTHOR ||
4693         i == LEVELINFO_TOKEN_LEVELS ||
4694         i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4695         i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4696         i == LEVELINFO_TOKEN_READONLY ||
4697         (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4698                              i == LEVELINFO_TOKEN_SOUNDS_SET ||
4699                              i == LEVELINFO_TOKEN_MUSIC_SET)))
4700       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4701
4702     // just to make things nicer :)
4703     if (i == LEVELINFO_TOKEN_AUTHOR ||
4704         i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4705         (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4706       fprintf(file, "\n");      
4707   }
4708
4709   token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4710
4711   fclose(file);
4712
4713   SetFilePermissions(filename, PERMS_PRIVATE);
4714
4715   freeTreeInfo(level_info);
4716   free(filename);
4717
4718   return TRUE;
4719 }
4720
4721 static void SaveUserLevelInfo(void)
4722 {
4723   CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4724 }
4725
4726 char *getSetupValue(int type, void *value)
4727 {
4728   static char value_string[MAX_LINE_LEN];
4729
4730   if (value == NULL)
4731     return NULL;
4732
4733   switch (type)
4734   {
4735     case TYPE_BOOLEAN:
4736       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4737       break;
4738
4739     case TYPE_SWITCH:
4740       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4741       break;
4742
4743     case TYPE_SWITCH3:
4744       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
4745                             *(int *)value == FALSE ? "off" : "on"));
4746       break;
4747
4748     case TYPE_YES_NO:
4749       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4750       break;
4751
4752     case TYPE_YES_NO_AUTO:
4753       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
4754                             *(int *)value == FALSE ? "no" : "yes"));
4755       break;
4756
4757     case TYPE_ECS_AGA:
4758       strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4759       break;
4760
4761     case TYPE_KEY:
4762       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4763       break;
4764
4765     case TYPE_KEY_X11:
4766       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4767       break;
4768
4769     case TYPE_INTEGER:
4770       sprintf(value_string, "%d", *(int *)value);
4771       break;
4772
4773     case TYPE_STRING:
4774       if (*(char **)value == NULL)
4775         return NULL;
4776
4777       strcpy(value_string, *(char **)value);
4778       break;
4779
4780     case TYPE_PLAYER:
4781       sprintf(value_string, "player_%d", *(int *)value + 1);
4782       break;
4783
4784     default:
4785       value_string[0] = '\0';
4786       break;
4787   }
4788
4789   if (type & TYPE_GHOSTED)
4790     strcpy(value_string, "n/a");
4791
4792   return value_string;
4793 }
4794
4795 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4796 {
4797   int i;
4798   char *line;
4799   static char token_string[MAX_LINE_LEN];
4800   int token_type = token_info[token_nr].type;
4801   void *setup_value = token_info[token_nr].value;
4802   char *token_text = token_info[token_nr].text;
4803   char *value_string = getSetupValue(token_type, setup_value);
4804
4805   // build complete token string
4806   sprintf(token_string, "%s%s", prefix, token_text);
4807
4808   // build setup entry line
4809   line = getFormattedSetupEntry(token_string, value_string);
4810
4811   if (token_type == TYPE_KEY_X11)
4812   {
4813     Key key = *(Key *)setup_value;
4814     char *keyname = getKeyNameFromKey(key);
4815
4816     // add comment, if useful
4817     if (!strEqual(keyname, "(undefined)") &&
4818         !strEqual(keyname, "(unknown)"))
4819     {
4820       // add at least one whitespace
4821       strcat(line, " ");
4822       for (i = strlen(line); i < token_comment_position; i++)
4823         strcat(line, " ");
4824
4825       strcat(line, "# ");
4826       strcat(line, keyname);
4827     }
4828   }
4829
4830   return line;
4831 }
4832
4833 static void InitLastPlayedLevels_ParentNode(void)
4834 {
4835   LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4836   LevelDirTree *leveldir_new = NULL;
4837
4838   // check if parent node for last played levels already exists
4839   if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4840     return;
4841
4842   leveldir_new = newTreeInfo();
4843
4844   setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4845
4846   leveldir_new->level_group = TRUE;
4847   leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4848
4849   setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4850   setString(&leveldir_new->name, "<< (last played level sets)");
4851   setString(&leveldir_new->name_sorting, leveldir_new->name);
4852
4853   pushTreeInfo(leveldir_top, leveldir_new);
4854
4855   // create node to link back to current level directory
4856   createParentTreeInfoNode(leveldir_new);
4857 }
4858
4859 void UpdateLastPlayedLevels_TreeInfo(void)
4860 {
4861   char **last_level_series = setup.level_setup.last_level_series;
4862   LevelDirTree *leveldir_last;
4863   TreeInfo **node_new = NULL;
4864   int i;
4865
4866   if (last_level_series[0] == NULL)
4867     return;
4868
4869   InitLastPlayedLevels_ParentNode();
4870
4871   leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4872                                                TOKEN_STR_LAST_LEVEL_SERIES,
4873                                                TREE_NODE_TYPE_GROUP);
4874   if (leveldir_last == NULL)
4875     return;
4876
4877   node_new = &leveldir_last->node_group->next;
4878
4879   freeTreeInfo(*node_new);
4880
4881   *node_new = NULL;
4882
4883   for (i = 0; last_level_series[i] != NULL; i++)
4884   {
4885     LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4886                                                         last_level_series[i]);
4887     if (node_last == NULL)
4888       continue;
4889
4890     *node_new = getTreeInfoCopy(node_last);     // copy complete node
4891
4892     (*node_new)->node_top = &leveldir_first;    // correct top node link
4893     (*node_new)->node_parent = leveldir_last;   // correct parent node link
4894
4895     (*node_new)->is_copy = TRUE;                // mark entry as node copy
4896
4897     (*node_new)->node_group = NULL;
4898     (*node_new)->next = NULL;
4899
4900     (*node_new)->cl_first = -1;                 // force setting tree cursor
4901
4902     node_new = &((*node_new)->next);
4903   }
4904 }
4905
4906 static void UpdateLastPlayedLevels_List(void)
4907 {
4908   char **last_level_series = setup.level_setup.last_level_series;
4909   int pos = MAX_LEVELDIR_HISTORY - 1;
4910   int i;
4911
4912   // search for potentially already existing entry in list of level sets
4913   for (i = 0; last_level_series[i] != NULL; i++)
4914     if (strEqual(last_level_series[i], leveldir_current->identifier))
4915       pos = i;
4916
4917   // move list of level sets one entry down (using potentially free entry)
4918   for (i = pos; i > 0; i--)
4919     setString(&last_level_series[i], last_level_series[i - 1]);
4920
4921   // put last played level set at top position
4922   setString(&last_level_series[0], leveldir_current->identifier);
4923 }
4924
4925 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4926 {
4927   static char *identifier = NULL;
4928
4929   if (store)
4930   {
4931     setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4932
4933     return NULL;        // not used
4934   }
4935   else
4936   {
4937     TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4938                                                       identifier,
4939                                                       TREE_NODE_TYPE_COPY);
4940     return (node_new != NULL ? node_new : node);
4941   }
4942 }
4943
4944 void StoreLastPlayedLevels(TreeInfo *node)
4945 {
4946   StoreOrRestoreLastPlayedLevels(node, TRUE);
4947 }
4948
4949 void RestoreLastPlayedLevels(TreeInfo **node)
4950 {
4951   *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4952 }
4953
4954 void LoadLevelSetup_LastSeries(void)
4955 {
4956   // --------------------------------------------------------------------------
4957   // ~/.<program>/levelsetup.conf
4958   // --------------------------------------------------------------------------
4959
4960   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4961   SetupFileHash *level_setup_hash = NULL;
4962   int pos = 0;
4963   int i;
4964
4965   // always start with reliable default values
4966   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4967
4968   // start with empty history of last played level sets
4969   setString(&setup.level_setup.last_level_series[0], NULL);
4970
4971   if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4972   {
4973     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4974                                                  DEFAULT_LEVELSET);
4975     if (leveldir_current == NULL)
4976       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4977   }
4978
4979   if ((level_setup_hash = loadSetupFileHash(filename)))
4980   {
4981     char *last_level_series =
4982       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4983
4984     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4985                                                  last_level_series);
4986     if (leveldir_current == NULL)
4987       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4988
4989     for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4990     {
4991       char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4992       LevelDirTree *leveldir_last;
4993
4994       sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4995
4996       last_level_series = getHashEntry(level_setup_hash, token);
4997
4998       leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4999                                                 last_level_series);
5000       if (leveldir_last != NULL)
5001         setString(&setup.level_setup.last_level_series[pos++],
5002                   last_level_series);
5003     }
5004
5005     setString(&setup.level_setup.last_level_series[pos], NULL);
5006
5007     freeSetupFileHash(level_setup_hash);
5008   }
5009   else
5010   {
5011     Debug("setup", "using default setup values");
5012   }
5013
5014   free(filename);
5015 }
5016
5017 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
5018 {
5019   // --------------------------------------------------------------------------
5020   // ~/.<program>/levelsetup.conf
5021   // --------------------------------------------------------------------------
5022
5023   // check if the current level directory structure is available at this point
5024   if (leveldir_current == NULL)
5025     return;
5026
5027   char **last_level_series = setup.level_setup.last_level_series;
5028   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
5029   FILE *file;
5030   int i;
5031
5032   InitUserDataDirectory();
5033
5034   UpdateLastPlayedLevels_List();
5035
5036   if (!(file = fopen(filename, MODE_WRITE)))
5037   {
5038     Warn("cannot write setup file '%s'", filename);
5039
5040     free(filename);
5041
5042     return;
5043   }
5044
5045   fprintFileHeader(file, LEVELSETUP_FILENAME);
5046
5047   if (deactivate_last_level_series)
5048     fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
5049
5050   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
5051                                                leveldir_current->identifier));
5052
5053   for (i = 0; last_level_series[i] != NULL; i++)
5054   {
5055     char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 1 + 10 + 1];
5056
5057     sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
5058
5059     fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
5060   }
5061
5062   fclose(file);
5063
5064   SetFilePermissions(filename, PERMS_PRIVATE);
5065
5066   free(filename);
5067 }
5068
5069 void SaveLevelSetup_LastSeries(void)
5070 {
5071   SaveLevelSetup_LastSeries_Ext(FALSE);
5072 }
5073
5074 void SaveLevelSetup_LastSeries_Deactivate(void)
5075 {
5076   SaveLevelSetup_LastSeries_Ext(TRUE);
5077 }
5078
5079 static void checkSeriesInfo(void)
5080 {
5081   static char *level_directory = NULL;
5082   Directory *dir;
5083 #if 0
5084   DirectoryEntry *dir_entry;
5085 #endif
5086
5087   checked_free(level_directory);
5088
5089   // check for more levels besides the 'levels' field of 'levelinfo.conf'
5090
5091   level_directory = getPath2((leveldir_current->in_user_dir ?
5092                               getUserLevelDir(NULL) :
5093                               options.level_directory),
5094                              leveldir_current->fullpath);
5095
5096   if ((dir = openDirectory(level_directory)) == NULL)
5097   {
5098     Warn("cannot read level directory '%s'", level_directory);
5099
5100     return;
5101   }
5102
5103 #if 0
5104   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
5105   {
5106     if (strlen(dir_entry->basename) > 4 &&
5107         dir_entry->basename[3] == '.' &&
5108         strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
5109     {
5110       char levelnum_str[4];
5111       int levelnum_value;
5112
5113       strncpy(levelnum_str, dir_entry->basename, 3);
5114       levelnum_str[3] = '\0';
5115
5116       levelnum_value = atoi(levelnum_str);
5117
5118       if (levelnum_value < leveldir_current->first_level)
5119       {
5120         Warn("additional level %d found", levelnum_value);
5121
5122         leveldir_current->first_level = levelnum_value;
5123       }
5124       else if (levelnum_value > leveldir_current->last_level)
5125       {
5126         Warn("additional level %d found", levelnum_value);
5127
5128         leveldir_current->last_level = levelnum_value;
5129       }
5130     }
5131   }
5132 #endif
5133
5134   closeDirectory(dir);
5135 }
5136
5137 void LoadLevelSetup_SeriesInfo(void)
5138 {
5139   char *filename;
5140   SetupFileHash *level_setup_hash = NULL;
5141   char *level_subdir = leveldir_current->subdir;
5142   int i;
5143
5144   // always start with reliable default values
5145   level_nr = leveldir_current->first_level;
5146
5147   for (i = 0; i < MAX_LEVELS; i++)
5148   {
5149     LevelStats_setPlayed(i, 0);
5150     LevelStats_setSolved(i, 0);
5151   }
5152
5153   checkSeriesInfo();
5154
5155   // --------------------------------------------------------------------------
5156   // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5157   // --------------------------------------------------------------------------
5158
5159   level_subdir = leveldir_current->subdir;
5160
5161   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5162
5163   if ((level_setup_hash = loadSetupFileHash(filename)))
5164   {
5165     char *token_value;
5166
5167     // get last played level in this level set
5168
5169     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
5170
5171     if (token_value)
5172     {
5173       level_nr = atoi(token_value);
5174
5175       if (level_nr < leveldir_current->first_level)
5176         level_nr = leveldir_current->first_level;
5177       if (level_nr > leveldir_current->last_level)
5178         level_nr = leveldir_current->last_level;
5179     }
5180
5181     // get handicap level in this level set
5182
5183     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
5184
5185     if (token_value)
5186     {
5187       int level_nr = atoi(token_value);
5188
5189       if (level_nr < leveldir_current->first_level)
5190         level_nr = leveldir_current->first_level;
5191       if (level_nr > leveldir_current->last_level + 1)
5192         level_nr = leveldir_current->last_level;
5193
5194       if (leveldir_current->user_defined || !leveldir_current->handicap)
5195         level_nr = leveldir_current->last_level;
5196
5197       leveldir_current->handicap_level = level_nr;
5198     }
5199
5200     // get number of played and solved levels in this level set
5201
5202     BEGIN_HASH_ITERATION(level_setup_hash, itr)
5203     {
5204       char *token = HASH_ITERATION_TOKEN(itr);
5205       char *value = HASH_ITERATION_VALUE(itr);
5206
5207       if (strlen(token) == 3 &&
5208           token[0] >= '0' && token[0] <= '9' &&
5209           token[1] >= '0' && token[1] <= '9' &&
5210           token[2] >= '0' && token[2] <= '9')
5211       {
5212         int level_nr = atoi(token);
5213
5214         if (value != NULL)
5215           LevelStats_setPlayed(level_nr, atoi(value));  // read 1st column
5216
5217         value = strchr(value, ' ');
5218
5219         if (value != NULL)
5220           LevelStats_setSolved(level_nr, atoi(value));  // read 2nd column
5221       }
5222     }
5223     END_HASH_ITERATION(hash, itr)
5224
5225     freeSetupFileHash(level_setup_hash);
5226   }
5227   else
5228   {
5229     Debug("setup", "using default setup values");
5230   }
5231
5232   free(filename);
5233 }
5234
5235 void SaveLevelSetup_SeriesInfo(void)
5236 {
5237   char *filename;
5238   char *level_subdir = leveldir_current->subdir;
5239   char *level_nr_str = int2str(level_nr, 0);
5240   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5241   FILE *file;
5242   int i;
5243
5244   // --------------------------------------------------------------------------
5245   // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5246   // --------------------------------------------------------------------------
5247
5248   InitLevelSetupDirectory(level_subdir);
5249
5250   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5251
5252   if (!(file = fopen(filename, MODE_WRITE)))
5253   {
5254     Warn("cannot write setup file '%s'", filename);
5255
5256     free(filename);
5257
5258     return;
5259   }
5260
5261   fprintFileHeader(file, LEVELSETUP_FILENAME);
5262
5263   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5264                                                level_nr_str));
5265   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5266                                                  handicap_level_str));
5267
5268   for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5269        i++)
5270   {
5271     if (LevelStats_getPlayed(i) > 0 ||
5272         LevelStats_getSolved(i) > 0)
5273     {
5274       char token[16];
5275       char value[16];
5276
5277       sprintf(token, "%03d", i);
5278       sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5279
5280       fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5281     }
5282   }
5283
5284   fclose(file);
5285
5286   SetFilePermissions(filename, PERMS_PRIVATE);
5287
5288   free(filename);
5289 }
5290
5291 int LevelStats_getPlayed(int nr)
5292 {
5293   return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5294 }
5295
5296 int LevelStats_getSolved(int nr)
5297 {
5298   return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5299 }
5300
5301 void LevelStats_setPlayed(int nr, int value)
5302 {
5303   if (nr >= 0 && nr < MAX_LEVELS)
5304     level_stats[nr].played = value;
5305 }
5306
5307 void LevelStats_setSolved(int nr, int value)
5308 {
5309   if (nr >= 0 && nr < MAX_LEVELS)
5310     level_stats[nr].solved = value;
5311 }
5312
5313 void LevelStats_incPlayed(int nr)
5314 {
5315   if (nr >= 0 && nr < MAX_LEVELS)
5316     level_stats[nr].played++;
5317 }
5318
5319 void LevelStats_incSolved(int nr)
5320 {
5321   if (nr >= 0 && nr < MAX_LEVELS)
5322     level_stats[nr].solved++;
5323 }
5324
5325 void LoadUserSetup(void)
5326 {
5327   // --------------------------------------------------------------------------
5328   // ~/.<program>/usersetup.conf
5329   // --------------------------------------------------------------------------
5330
5331   char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5332   SetupFileHash *user_setup_hash = NULL;
5333
5334   // always start with reliable default values
5335   user.nr = 0;
5336
5337   if ((user_setup_hash = loadSetupFileHash(filename)))
5338   {
5339     char *token_value;
5340
5341     // get last selected user number
5342     token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5343
5344     if (token_value)
5345       user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5346
5347     freeSetupFileHash(user_setup_hash);
5348   }
5349   else
5350   {
5351     Debug("setup", "using default setup values");
5352   }
5353
5354   free(filename);
5355 }
5356
5357 void SaveUserSetup(void)
5358 {
5359   // --------------------------------------------------------------------------
5360   // ~/.<program>/usersetup.conf
5361   // --------------------------------------------------------------------------
5362
5363   char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5364   FILE *file;
5365
5366   InitMainUserDataDirectory();
5367
5368   if (!(file = fopen(filename, MODE_WRITE)))
5369   {
5370     Warn("cannot write setup file '%s'", filename);
5371
5372     free(filename);
5373
5374     return;
5375   }
5376
5377   fprintFileHeader(file, USERSETUP_FILENAME);
5378
5379   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5380                                                i_to_a(user.nr)));
5381   fclose(file);
5382
5383   SetFilePermissions(filename, PERMS_PRIVATE);
5384
5385   free(filename);
5386 }