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