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