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