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