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