changed custom artwork list to tree structure similar to level sets
[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
47 #define LEVELCOLOR(n)   (IS_LEVELCLASS_TUTORIAL(n) ?            FC_BLUE :    \
48                          IS_LEVELCLASS_CLASSICS(n) ?            FC_RED :     \
49                          IS_LEVELCLASS_BD(n) ?                  FC_YELLOW :  \
50                          IS_LEVELCLASS_EM(n) ?                  FC_YELLOW :  \
51                          IS_LEVELCLASS_SP(n) ?                  FC_YELLOW :  \
52                          IS_LEVELCLASS_DX(n) ?                  FC_YELLOW :  \
53                          IS_LEVELCLASS_SB(n) ?                  FC_YELLOW :  \
54                          IS_LEVELCLASS_CONTRIB(n) ?             FC_GREEN :   \
55                          IS_LEVELCLASS_PRIVATE(n) ?             FC_RED :     \
56                          FC_BLUE)
57
58 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ?            0 :     \
59                          IS_LEVELCLASS_CLASSICS(n) ?            1 :     \
60                          IS_LEVELCLASS_BD(n) ?                  2 :     \
61                          IS_LEVELCLASS_EM(n) ?                  3 :     \
62                          IS_LEVELCLASS_SP(n) ?                  4 :     \
63                          IS_LEVELCLASS_DX(n) ?                  5 :     \
64                          IS_LEVELCLASS_SB(n) ?                  6 :     \
65                          IS_LEVELCLASS_CONTRIB(n) ?             7 :     \
66                          IS_LEVELCLASS_PRIVATE(n) ?             8 :     \
67                          9)
68
69 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ?          FC_RED :     \
70                          IS_ARTWORKCLASS_CONTRIB(n) ?           FC_GREEN :   \
71                          IS_ARTWORKCLASS_PRIVATE(n) ?           FC_RED :     \
72                          IS_ARTWORKCLASS_LEVEL(n) ?             FC_YELLOW :  \
73                          FC_BLUE)
74
75 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ?        0 :     \
76                            IS_ARTWORKCLASS_LEVEL(n) ?           1 :     \
77                            IS_ARTWORKCLASS_CONTRIB(n) ?         2 :     \
78                            IS_ARTWORKCLASS_PRIVATE(n) ?         3 :     \
79                            9)
80
81 #define TOKEN_VALUE_POSITION_SHORT              32
82 #define TOKEN_VALUE_POSITION_DEFAULT            40
83 #define TOKEN_COMMENT_POSITION_DEFAULT          60
84
85 #define MAX_COOKIE_LEN                          256
86
87
88 static void setTreeInfoToDefaults(TreeInfo *, int);
89 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
90 static int compareTreeInfoEntries(const void *, const void *);
91
92 static int token_value_position   = TOKEN_VALUE_POSITION_DEFAULT;
93 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
94
95 static SetupFileHash *artworkinfo_cache_old = NULL;
96 static SetupFileHash *artworkinfo_cache_new = NULL;
97 static SetupFileHash *optional_tokens_hash = NULL;
98 static boolean use_artworkinfo_cache = TRUE;
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 int numTreeInfo(TreeInfo *node)
1167 {
1168   int num = 0;
1169
1170   while (node)
1171   {
1172     num++;
1173     node = node->next;
1174   }
1175
1176   return num;
1177 }
1178
1179 boolean validLevelSeries(TreeInfo *node)
1180 {
1181   return (node != NULL && !node->node_group && !node->parent_link);
1182 }
1183
1184 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1185 {
1186   if (node == NULL)
1187     return NULL;
1188
1189   if (node->node_group)         // enter level group (step down into tree)
1190     return getFirstValidTreeInfoEntry(node->node_group);
1191   else if (node->parent_link)   // skip start entry of level group
1192   {
1193     if (node->next)             // get first real level series entry
1194       return getFirstValidTreeInfoEntry(node->next);
1195     else                        // leave empty level group and go on
1196       return getFirstValidTreeInfoEntry(node->node_parent->next);
1197   }
1198   else                          // this seems to be a regular level series
1199     return node;
1200 }
1201
1202 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1203 {
1204   if (node == NULL)
1205     return NULL;
1206
1207   if (node->node_parent == NULL)                // top level group
1208     return *node->node_top;
1209   else                                          // sub level group
1210     return node->node_parent->node_group;
1211 }
1212
1213 int numTreeInfoInGroup(TreeInfo *node)
1214 {
1215   return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1216 }
1217
1218 int getPosFromTreeInfo(TreeInfo *node)
1219 {
1220   TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1221   int pos = 0;
1222
1223   while (node_cmp)
1224   {
1225     if (node_cmp == node)
1226       return pos;
1227
1228     pos++;
1229     node_cmp = node_cmp->next;
1230   }
1231
1232   return 0;
1233 }
1234
1235 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1236 {
1237   TreeInfo *node_default = node;
1238   int pos_cmp = 0;
1239
1240   while (node)
1241   {
1242     if (pos_cmp == pos)
1243       return node;
1244
1245     pos_cmp++;
1246     node = node->next;
1247   }
1248
1249   return node_default;
1250 }
1251
1252 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1253                                               boolean include_node_groups)
1254 {
1255   if (identifier == NULL)
1256     return NULL;
1257
1258   while (node)
1259   {
1260     if (node->node_group)
1261     {
1262       if (include_node_groups && strEqual(identifier, node->identifier))
1263         return node;
1264
1265       TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1266                                                           identifier,
1267                                                           include_node_groups);
1268       if (node_group)
1269         return node_group;
1270     }
1271     else if (!node->parent_link)
1272     {
1273       if (strEqual(identifier, node->identifier))
1274         return node;
1275     }
1276
1277     node = node->next;
1278   }
1279
1280   return NULL;
1281 }
1282
1283 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1284 {
1285   return getTreeInfoFromIdentifierExt(node, identifier, FALSE);
1286 }
1287
1288 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1289                                TreeInfo *node, boolean skip_sets_without_levels)
1290 {
1291   TreeInfo *node_new;
1292
1293   if (node == NULL)
1294     return NULL;
1295
1296   if (!node->parent_link && !node->level_group &&
1297       skip_sets_without_levels && node->levels == 0)
1298     return cloneTreeNode(node_top, node_parent, node->next,
1299                          skip_sets_without_levels);
1300
1301   node_new = getTreeInfoCopy(node);             // copy complete node
1302
1303   node_new->node_top = node_top;                // correct top node link
1304   node_new->node_parent = node_parent;          // correct parent node link
1305
1306   if (node->level_group)
1307     node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1308                                          skip_sets_without_levels);
1309
1310   node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1311                                  skip_sets_without_levels);
1312   
1313   return node_new;
1314 }
1315
1316 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1317 {
1318   TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1319
1320   *ti_new = ti_cloned;
1321 }
1322
1323 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1324 {
1325   boolean settings_changed = FALSE;
1326
1327   while (node)
1328   {
1329     boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1330     boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1331     boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1332     boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1333     char *graphics_set = NULL;
1334
1335     if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1336       graphics_set = node->graphics_set_ecs;
1337
1338     if (node->graphics_set_aga && (want_aga || has_only_aga))
1339       graphics_set = node->graphics_set_aga;
1340
1341     if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1342     {
1343       setString(&node->graphics_set, graphics_set);
1344       settings_changed = TRUE;
1345     }
1346
1347     if (node->node_group != NULL)
1348       settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1349
1350     node = node->next;
1351   }
1352
1353   return settings_changed;
1354 }
1355
1356 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1357 {
1358   boolean settings_changed = FALSE;
1359
1360   while (node)
1361   {
1362     boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1363     boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1364     boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1365     boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1366     char *sounds_set = NULL;
1367
1368     if (node->sounds_set_default && (want_default || has_only_default))
1369       sounds_set = node->sounds_set_default;
1370
1371     if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1372       sounds_set = node->sounds_set_lowpass;
1373
1374     if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1375     {
1376       setString(&node->sounds_set, sounds_set);
1377       settings_changed = TRUE;
1378     }
1379
1380     if (node->node_group != NULL)
1381       settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1382
1383     node = node->next;
1384   }
1385
1386   return settings_changed;
1387 }
1388
1389 void dumpTreeInfo(TreeInfo *node, int depth)
1390 {
1391   char bullet_list[] = { '-', '*', 'o' };
1392   int i;
1393
1394   if (depth == 0)
1395     Debug("tree", "Dumping TreeInfo:");
1396
1397   while (node)
1398   {
1399     char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1400
1401     for (i = 0; i < depth * 2; i++)
1402       DebugContinued("", " ");
1403
1404     DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1405                    bullet, node->name, node->identifier,
1406                    (node->node_parent ? node->node_parent->identifier : "-"),
1407                    (node->node_group ? "[GROUP]" : ""));
1408
1409     /*
1410     // use for dumping artwork info tree
1411     Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1412           node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1413     */
1414
1415     if (node->node_group != NULL)
1416       dumpTreeInfo(node->node_group, depth + 1);
1417
1418     node = node->next;
1419   }
1420 }
1421
1422 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1423                                 int (*compare_function)(const void *,
1424                                                         const void *))
1425 {
1426   int num_nodes = numTreeInfo(*node_first);
1427   TreeInfo **sort_array;
1428   TreeInfo *node = *node_first;
1429   int i = 0;
1430
1431   if (num_nodes == 0)
1432     return;
1433
1434   // allocate array for sorting structure pointers
1435   sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1436
1437   // writing structure pointers to sorting array
1438   while (i < num_nodes && node)         // double boundary check...
1439   {
1440     sort_array[i] = node;
1441
1442     i++;
1443     node = node->next;
1444   }
1445
1446   // sorting the structure pointers in the sorting array
1447   qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1448         compare_function);
1449
1450   // update the linkage of list elements with the sorted node array
1451   for (i = 0; i < num_nodes - 1; i++)
1452     sort_array[i]->next = sort_array[i + 1];
1453   sort_array[num_nodes - 1]->next = NULL;
1454
1455   // update the linkage of the main list anchor pointer
1456   *node_first = sort_array[0];
1457
1458   free(sort_array);
1459
1460   // now recursively sort the level group structures
1461   node = *node_first;
1462   while (node)
1463   {
1464     if (node->node_group != NULL)
1465       sortTreeInfoBySortFunction(&node->node_group, compare_function);
1466
1467     node = node->next;
1468   }
1469 }
1470
1471 void sortTreeInfo(TreeInfo **node_first)
1472 {
1473   sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1474 }
1475
1476
1477 // ============================================================================
1478 // some stuff from "files.c"
1479 // ============================================================================
1480
1481 #if defined(PLATFORM_WIN32)
1482 #ifndef S_IRGRP
1483 #define S_IRGRP S_IRUSR
1484 #endif
1485 #ifndef S_IROTH
1486 #define S_IROTH S_IRUSR
1487 #endif
1488 #ifndef S_IWGRP
1489 #define S_IWGRP S_IWUSR
1490 #endif
1491 #ifndef S_IWOTH
1492 #define S_IWOTH S_IWUSR
1493 #endif
1494 #ifndef S_IXGRP
1495 #define S_IXGRP S_IXUSR
1496 #endif
1497 #ifndef S_IXOTH
1498 #define S_IXOTH S_IXUSR
1499 #endif
1500 #ifndef S_IRWXG
1501 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1502 #endif
1503 #ifndef S_ISGID
1504 #define S_ISGID 0
1505 #endif
1506 #endif  // PLATFORM_WIN32
1507
1508 // file permissions for newly written files
1509 #define MODE_R_ALL              (S_IRUSR | S_IRGRP | S_IROTH)
1510 #define MODE_W_ALL              (S_IWUSR | S_IWGRP | S_IWOTH)
1511 #define MODE_X_ALL              (S_IXUSR | S_IXGRP | S_IXOTH)
1512
1513 #define MODE_W_PRIVATE          (S_IWUSR)
1514 #define MODE_W_PUBLIC_FILE      (S_IWUSR | S_IWGRP)
1515 #define MODE_W_PUBLIC_DIR       (S_IWUSR | S_IWGRP | S_ISGID)
1516
1517 #define DIR_PERMS_PRIVATE       (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1518 #define DIR_PERMS_PUBLIC        (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1519 #define DIR_PERMS_PUBLIC_ALL    (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1520
1521 #define FILE_PERMS_PRIVATE      (MODE_R_ALL | MODE_W_PRIVATE)
1522 #define FILE_PERMS_PUBLIC       (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1523 #define FILE_PERMS_PUBLIC_ALL   (MODE_R_ALL | MODE_W_ALL)
1524
1525
1526 char *getHomeDir(void)
1527 {
1528   static char *dir = NULL;
1529
1530 #if defined(PLATFORM_WIN32)
1531   if (dir == NULL)
1532   {
1533     dir = checked_malloc(MAX_PATH + 1);
1534
1535     if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1536       strcpy(dir, ".");
1537   }
1538 #elif defined(PLATFORM_UNIX)
1539   if (dir == NULL)
1540   {
1541     if ((dir = getenv("HOME")) == NULL)
1542     {
1543       dir = getUnixHomeDir();
1544
1545       if (dir != NULL)
1546         dir = getStringCopy(dir);
1547       else
1548         dir = ".";
1549     }
1550   }
1551 #else
1552   dir = ".";
1553 #endif
1554
1555   return dir;
1556 }
1557
1558 char *getCommonDataDir(void)
1559 {
1560   static char *common_data_dir = NULL;
1561
1562 #if defined(PLATFORM_WIN32)
1563   if (common_data_dir == NULL)
1564   {
1565     char *dir = checked_malloc(MAX_PATH + 1);
1566
1567     if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1568         && !strEqual(dir, ""))          // empty for Windows 95/98
1569       common_data_dir = getPath2(dir, program.userdata_subdir);
1570     else
1571       common_data_dir = options.rw_base_directory;
1572   }
1573 #else
1574   if (common_data_dir == NULL)
1575     common_data_dir = options.rw_base_directory;
1576 #endif
1577
1578   return common_data_dir;
1579 }
1580
1581 char *getPersonalDataDir(void)
1582 {
1583   static char *personal_data_dir = NULL;
1584
1585 #if defined(PLATFORM_MACOSX)
1586   if (personal_data_dir == NULL)
1587     personal_data_dir = getPath2(getHomeDir(), "Documents");
1588 #else
1589   if (personal_data_dir == NULL)
1590     personal_data_dir = getHomeDir();
1591 #endif
1592
1593   return personal_data_dir;
1594 }
1595
1596 char *getMainUserGameDataDir(void)
1597 {
1598   static char *main_user_data_dir = NULL;
1599
1600 #if defined(PLATFORM_ANDROID)
1601   if (main_user_data_dir == NULL)
1602     main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1603                                   SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1604                                   SDL_AndroidGetExternalStoragePath() :
1605                                   SDL_AndroidGetInternalStoragePath());
1606 #else
1607   if (main_user_data_dir == NULL)
1608     main_user_data_dir = getPath2(getPersonalDataDir(),
1609                                   program.userdata_subdir);
1610 #endif
1611
1612   return main_user_data_dir;
1613 }
1614
1615 char *getUserGameDataDir(void)
1616 {
1617   if (user.nr == 0)
1618     return getMainUserGameDataDir();
1619   else
1620     return getUserDir(user.nr);
1621 }
1622
1623 char *getSetupDir(void)
1624 {
1625   return getUserGameDataDir();
1626 }
1627
1628 static mode_t posix_umask(mode_t mask)
1629 {
1630 #if defined(PLATFORM_UNIX)
1631   return umask(mask);
1632 #else
1633   return 0;
1634 #endif
1635 }
1636
1637 static int posix_mkdir(const char *pathname, mode_t mode)
1638 {
1639 #if defined(PLATFORM_WIN32)
1640   return mkdir(pathname);
1641 #else
1642   return mkdir(pathname, mode);
1643 #endif
1644 }
1645
1646 static boolean posix_process_running_setgid(void)
1647 {
1648 #if defined(PLATFORM_UNIX)
1649   return (getgid() != getegid());
1650 #else
1651   return FALSE;
1652 #endif
1653 }
1654
1655 void createDirectory(char *dir, char *text, int permission_class)
1656 {
1657   if (directoryExists(dir))
1658     return;
1659
1660   // leave "other" permissions in umask untouched, but ensure group parts
1661   // of USERDATA_DIR_MODE are not masked
1662   mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1663                      DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1664   mode_t last_umask = posix_umask(0);
1665   mode_t group_umask = ~(dir_mode & S_IRWXG);
1666   int running_setgid = posix_process_running_setgid();
1667
1668   if (permission_class == PERMS_PUBLIC)
1669   {
1670     // if we're setgid, protect files against "other"
1671     // else keep umask(0) to make the dir world-writable
1672
1673     if (running_setgid)
1674       posix_umask(last_umask & group_umask);
1675     else
1676       dir_mode = DIR_PERMS_PUBLIC_ALL;
1677   }
1678
1679   if (posix_mkdir(dir, dir_mode) != 0)
1680     Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1681
1682   if (permission_class == PERMS_PUBLIC && !running_setgid)
1683     chmod(dir, dir_mode);
1684
1685   posix_umask(last_umask);              // restore previous umask
1686 }
1687
1688 void InitMainUserDataDirectory(void)
1689 {
1690   createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1691 }
1692
1693 void InitUserDataDirectory(void)
1694 {
1695   createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1696
1697   if (user.nr != 0)
1698   {
1699     createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1700     createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1701   }
1702 }
1703
1704 void SetFilePermissions(char *filename, int permission_class)
1705 {
1706   int running_setgid = posix_process_running_setgid();
1707   int perms = (permission_class == PERMS_PRIVATE ?
1708                FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1709
1710   if (permission_class == PERMS_PUBLIC && !running_setgid)
1711     perms = FILE_PERMS_PUBLIC_ALL;
1712
1713   chmod(filename, perms);
1714 }
1715
1716 char *getCookie(char *file_type)
1717 {
1718   static char cookie[MAX_COOKIE_LEN + 1];
1719
1720   if (strlen(program.cookie_prefix) + 1 +
1721       strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1722     return "[COOKIE ERROR]";    // should never happen
1723
1724   sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1725           program.cookie_prefix, file_type,
1726           program.version_super, program.version_major);
1727
1728   return cookie;
1729 }
1730
1731 void fprintFileHeader(FILE *file, char *basename)
1732 {
1733   char *prefix = "# ";
1734   char *sep1 = "=";
1735
1736   fprintf_line_with_prefix(file, prefix, sep1, 77);
1737   fprintf(file, "%s%s\n", prefix, basename);
1738   fprintf_line_with_prefix(file, prefix, sep1, 77);
1739   fprintf(file, "\n");
1740 }
1741
1742 int getFileVersionFromCookieString(const char *cookie)
1743 {
1744   const char *ptr_cookie1, *ptr_cookie2;
1745   const char *pattern1 = "_FILE_VERSION_";
1746   const char *pattern2 = "?.?";
1747   const int len_cookie = strlen(cookie);
1748   const int len_pattern1 = strlen(pattern1);
1749   const int len_pattern2 = strlen(pattern2);
1750   const int len_pattern = len_pattern1 + len_pattern2;
1751   int version_super, version_major;
1752
1753   if (len_cookie <= len_pattern)
1754     return -1;
1755
1756   ptr_cookie1 = &cookie[len_cookie - len_pattern];
1757   ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1758
1759   if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1760     return -1;
1761
1762   if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1763       ptr_cookie2[1] != '.' ||
1764       ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1765     return -1;
1766
1767   version_super = ptr_cookie2[0] - '0';
1768   version_major = ptr_cookie2[2] - '0';
1769
1770   return VERSION_IDENT(version_super, version_major, 0, 0);
1771 }
1772
1773 boolean checkCookieString(const char *cookie, const char *template)
1774 {
1775   const char *pattern = "_FILE_VERSION_?.?";
1776   const int len_cookie = strlen(cookie);
1777   const int len_template = strlen(template);
1778   const int len_pattern = strlen(pattern);
1779
1780   if (len_cookie != len_template)
1781     return FALSE;
1782
1783   if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1784     return FALSE;
1785
1786   return TRUE;
1787 }
1788
1789
1790 // ----------------------------------------------------------------------------
1791 // setup file list and hash handling functions
1792 // ----------------------------------------------------------------------------
1793
1794 char *getFormattedSetupEntry(char *token, char *value)
1795 {
1796   int i;
1797   static char entry[MAX_LINE_LEN];
1798
1799   // if value is an empty string, just return token without value
1800   if (*value == '\0')
1801     return token;
1802
1803   // start with the token and some spaces to format output line
1804   sprintf(entry, "%s:", token);
1805   for (i = strlen(entry); i < token_value_position; i++)
1806     strcat(entry, " ");
1807
1808   // continue with the token's value
1809   strcat(entry, value);
1810
1811   return entry;
1812 }
1813
1814 SetupFileList *newSetupFileList(char *token, char *value)
1815 {
1816   SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1817
1818   new->token = getStringCopy(token);
1819   new->value = getStringCopy(value);
1820
1821   new->next = NULL;
1822
1823   return new;
1824 }
1825
1826 void freeSetupFileList(SetupFileList *list)
1827 {
1828   if (list == NULL)
1829     return;
1830
1831   checked_free(list->token);
1832   checked_free(list->value);
1833
1834   if (list->next)
1835     freeSetupFileList(list->next);
1836
1837   free(list);
1838 }
1839
1840 char *getListEntry(SetupFileList *list, char *token)
1841 {
1842   if (list == NULL)
1843     return NULL;
1844
1845   if (strEqual(list->token, token))
1846     return list->value;
1847   else
1848     return getListEntry(list->next, token);
1849 }
1850
1851 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1852 {
1853   if (list == NULL)
1854     return NULL;
1855
1856   if (strEqual(list->token, token))
1857   {
1858     checked_free(list->value);
1859
1860     list->value = getStringCopy(value);
1861
1862     return list;
1863   }
1864   else if (list->next == NULL)
1865     return (list->next = newSetupFileList(token, value));
1866   else
1867     return setListEntry(list->next, token, value);
1868 }
1869
1870 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1871 {
1872   if (list == NULL)
1873     return NULL;
1874
1875   if (list->next == NULL)
1876     return (list->next = newSetupFileList(token, value));
1877   else
1878     return addListEntry(list->next, token, value);
1879 }
1880
1881 #if ENABLE_UNUSED_CODE
1882 #ifdef DEBUG
1883 static void printSetupFileList(SetupFileList *list)
1884 {
1885   if (!list)
1886     return;
1887
1888   Debug("setup:printSetupFileList", "token: '%s'", list->token);
1889   Debug("setup:printSetupFileList", "value: '%s'", list->value);
1890
1891   printSetupFileList(list->next);
1892 }
1893 #endif
1894 #endif
1895
1896 #ifdef DEBUG
1897 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1898 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1899 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1900 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1901 #else
1902 #define insert_hash_entry hashtable_insert
1903 #define search_hash_entry hashtable_search
1904 #define change_hash_entry hashtable_change
1905 #define remove_hash_entry hashtable_remove
1906 #endif
1907
1908 unsigned int get_hash_from_key(void *key)
1909 {
1910   /*
1911     djb2
1912
1913     This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1914     'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1915     uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1916     it works better than many other constants, prime or not) has never been
1917     adequately explained.
1918
1919     If you just want to have a good hash function, and cannot wait, djb2
1920     is one of the best string hash functions i know. It has excellent
1921     distribution and speed on many different sets of keys and table sizes.
1922     You are not likely to do better with one of the "well known" functions
1923     such as PJW, K&R, etc.
1924
1925     Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1926   */
1927
1928   char *str = (char *)key;
1929   unsigned int hash = 5381;
1930   int c;
1931
1932   while ((c = *str++))
1933     hash = ((hash << 5) + hash) + c;    // hash * 33 + c
1934
1935   return hash;
1936 }
1937
1938 static int keys_are_equal(void *key1, void *key2)
1939 {
1940   return (strEqual((char *)key1, (char *)key2));
1941 }
1942
1943 SetupFileHash *newSetupFileHash(void)
1944 {
1945   SetupFileHash *new_hash =
1946     create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1947
1948   if (new_hash == NULL)
1949     Fail("create_hashtable() failed -- out of memory");
1950
1951   return new_hash;
1952 }
1953
1954 void freeSetupFileHash(SetupFileHash *hash)
1955 {
1956   if (hash == NULL)
1957     return;
1958
1959   hashtable_destroy(hash, 1);   // 1 == also free values stored in hash
1960 }
1961
1962 char *getHashEntry(SetupFileHash *hash, char *token)
1963 {
1964   if (hash == NULL)
1965     return NULL;
1966
1967   return search_hash_entry(hash, token);
1968 }
1969
1970 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1971 {
1972   char *value_copy;
1973
1974   if (hash == NULL)
1975     return;
1976
1977   value_copy = getStringCopy(value);
1978
1979   // change value; if it does not exist, insert it as new
1980   if (!change_hash_entry(hash, token, value_copy))
1981     if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1982       Fail("cannot insert into hash -- aborting");
1983 }
1984
1985 char *removeHashEntry(SetupFileHash *hash, char *token)
1986 {
1987   if (hash == NULL)
1988     return NULL;
1989
1990   return remove_hash_entry(hash, token);
1991 }
1992
1993 #if ENABLE_UNUSED_CODE
1994 #if DEBUG
1995 static void printSetupFileHash(SetupFileHash *hash)
1996 {
1997   BEGIN_HASH_ITERATION(hash, itr)
1998   {
1999     Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2000     Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2001   }
2002   END_HASH_ITERATION(hash, itr)
2003 }
2004 #endif
2005 #endif
2006
2007 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE            1
2008 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING            0
2009 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH             0
2010
2011 static boolean token_value_separator_found = FALSE;
2012 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2013 static boolean token_value_separator_warning = FALSE;
2014 #endif
2015 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2016 static boolean token_already_exists_warning = FALSE;
2017 #endif
2018
2019 static boolean getTokenValueFromSetupLineExt(char *line,
2020                                              char **token_ptr, char **value_ptr,
2021                                              char *filename, char *line_raw,
2022                                              int line_nr,
2023                                              boolean separator_required)
2024 {
2025   static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2026   char *token, *value, *line_ptr;
2027
2028   // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2029   if (line_raw == NULL)
2030   {
2031     strncpy(line_copy, line, MAX_LINE_LEN);
2032     line_copy[MAX_LINE_LEN] = '\0';
2033     line = line_copy;
2034
2035     strcpy(line_raw_copy, line_copy);
2036     line_raw = line_raw_copy;
2037   }
2038
2039   // cut trailing comment from input line
2040   for (line_ptr = line; *line_ptr; line_ptr++)
2041   {
2042     if (*line_ptr == '#')
2043     {
2044       *line_ptr = '\0';
2045       break;
2046     }
2047   }
2048
2049   // cut trailing whitespaces from input line
2050   for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2051     if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2052       *line_ptr = '\0';
2053
2054   // ignore empty lines
2055   if (*line == '\0')
2056     return FALSE;
2057
2058   // cut leading whitespaces from token
2059   for (token = line; *token; token++)
2060     if (*token != ' ' && *token != '\t')
2061       break;
2062
2063   // start with empty value as reliable default
2064   value = "";
2065
2066   token_value_separator_found = FALSE;
2067
2068   // find end of token to determine start of value
2069   for (line_ptr = token; *line_ptr; line_ptr++)
2070   {
2071     // first look for an explicit token/value separator, like ':' or '='
2072     if (*line_ptr == ':' || *line_ptr == '=')
2073     {
2074       *line_ptr = '\0';                 // terminate token string
2075       value = line_ptr + 1;             // set beginning of value
2076
2077       token_value_separator_found = TRUE;
2078
2079       break;
2080     }
2081   }
2082
2083 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2084   // fallback: if no token/value separator found, also allow whitespaces
2085   if (!token_value_separator_found && !separator_required)
2086   {
2087     for (line_ptr = token; *line_ptr; line_ptr++)
2088     {
2089       if (*line_ptr == ' ' || *line_ptr == '\t')
2090       {
2091         *line_ptr = '\0';               // terminate token string
2092         value = line_ptr + 1;           // set beginning of value
2093
2094         token_value_separator_found = TRUE;
2095
2096         break;
2097       }
2098     }
2099
2100 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2101     if (token_value_separator_found)
2102     {
2103       if (!token_value_separator_warning)
2104       {
2105         Debug("setup", "---");
2106
2107         if (filename != NULL)
2108         {
2109           Debug("setup", "missing token/value separator(s) in config file:");
2110           Debug("setup", "- config file: '%s'", filename);
2111         }
2112         else
2113         {
2114           Debug("setup", "missing token/value separator(s):");
2115         }
2116
2117         token_value_separator_warning = TRUE;
2118       }
2119
2120       if (filename != NULL)
2121         Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2122       else
2123         Debug("setup", "- line: '%s'", line_raw);
2124     }
2125 #endif
2126   }
2127 #endif
2128
2129   // cut trailing whitespaces from token
2130   for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2131     if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2132       *line_ptr = '\0';
2133
2134   // cut leading whitespaces from value
2135   for (; *value; value++)
2136     if (*value != ' ' && *value != '\t')
2137       break;
2138
2139   *token_ptr = token;
2140   *value_ptr = value;
2141
2142   return TRUE;
2143 }
2144
2145 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2146 {
2147   // while the internal (old) interface does not require a token/value
2148   // separator (for downwards compatibility with existing files which
2149   // don't use them), it is mandatory for the external (new) interface
2150
2151   return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2152 }
2153
2154 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2155                                  boolean top_recursion_level, boolean is_hash)
2156 {
2157   static SetupFileHash *include_filename_hash = NULL;
2158   char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2159   char *token, *value, *line_ptr;
2160   void *insert_ptr = NULL;
2161   boolean read_continued_line = FALSE;
2162   File *file;
2163   int line_nr = 0, token_count = 0, include_count = 0;
2164
2165 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2166   token_value_separator_warning = FALSE;
2167 #endif
2168
2169 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2170   token_already_exists_warning = FALSE;
2171 #endif
2172
2173   if (!(file = openFile(filename, MODE_READ)))
2174   {
2175 #if DEBUG_NO_CONFIG_FILE
2176     Debug("setup", "cannot open configuration file '%s'", filename);
2177 #endif
2178
2179     return FALSE;
2180   }
2181
2182   // use "insert pointer" to store list end for constant insertion complexity
2183   if (!is_hash)
2184     insert_ptr = setup_file_data;
2185
2186   // on top invocation, create hash to mark included files (to prevent loops)
2187   if (top_recursion_level)
2188     include_filename_hash = newSetupFileHash();
2189
2190   // mark this file as already included (to prevent including it again)
2191   setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2192
2193   while (!checkEndOfFile(file))
2194   {
2195     // read next line of input file
2196     if (!getStringFromFile(file, line, MAX_LINE_LEN))
2197       break;
2198
2199     // check if line was completely read and is terminated by line break
2200     if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2201       line_nr++;
2202
2203     // cut trailing line break (this can be newline and/or carriage return)
2204     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2205       if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2206         *line_ptr = '\0';
2207
2208     // copy raw input line for later use (mainly debugging output)
2209     strcpy(line_raw, line);
2210
2211     if (read_continued_line)
2212     {
2213       // append new line to existing line, if there is enough space
2214       if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2215         strcat(previous_line, line_ptr);
2216
2217       strcpy(line, previous_line);      // copy storage buffer to line
2218
2219       read_continued_line = FALSE;
2220     }
2221
2222     // if the last character is '\', continue at next line
2223     if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2224     {
2225       line[strlen(line) - 1] = '\0';    // cut off trailing backslash
2226       strcpy(previous_line, line);      // copy line to storage buffer
2227
2228       read_continued_line = TRUE;
2229
2230       continue;
2231     }
2232
2233     if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2234                                        line_raw, line_nr, FALSE))
2235       continue;
2236
2237     if (*token)
2238     {
2239       if (strEqual(token, "include"))
2240       {
2241         if (getHashEntry(include_filename_hash, value) == NULL)
2242         {
2243           char *basepath = getBasePath(filename);
2244           char *basename = getBaseName(value);
2245           char *filename_include = getPath2(basepath, basename);
2246
2247           loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2248
2249           free(basepath);
2250           free(basename);
2251           free(filename_include);
2252
2253           include_count++;
2254         }
2255         else
2256         {
2257           Warn("ignoring already processed file '%s'", value);
2258         }
2259       }
2260       else
2261       {
2262         if (is_hash)
2263         {
2264 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2265           char *old_value =
2266             getHashEntry((SetupFileHash *)setup_file_data, token);
2267
2268           if (old_value != NULL)
2269           {
2270             if (!token_already_exists_warning)
2271             {
2272               Debug("setup", "---");
2273               Debug("setup", "duplicate token(s) found in config file:");
2274               Debug("setup", "- config file: '%s'", filename);
2275
2276               token_already_exists_warning = TRUE;
2277             }
2278
2279             Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2280             Debug("setup", "  old value: '%s'", old_value);
2281             Debug("setup", "  new value: '%s'", value);
2282           }
2283 #endif
2284
2285           setHashEntry((SetupFileHash *)setup_file_data, token, value);
2286         }
2287         else
2288         {
2289           insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2290         }
2291
2292         token_count++;
2293       }
2294     }
2295   }
2296
2297   closeFile(file);
2298
2299 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2300   if (token_value_separator_warning)
2301     Debug("setup", "---");
2302 #endif
2303
2304 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2305   if (token_already_exists_warning)
2306     Debug("setup", "---");
2307 #endif
2308
2309   if (token_count == 0 && include_count == 0)
2310     Warn("configuration file '%s' is empty", filename);
2311
2312   if (top_recursion_level)
2313     freeSetupFileHash(include_filename_hash);
2314
2315   return TRUE;
2316 }
2317
2318 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2319 {
2320   FILE *file;
2321
2322   if (!(file = fopen(filename, MODE_WRITE)))
2323   {
2324     Warn("cannot write configuration file '%s'", filename);
2325
2326     return;
2327   }
2328
2329   BEGIN_HASH_ITERATION(hash, itr)
2330   {
2331     fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2332                                                  HASH_ITERATION_VALUE(itr)));
2333   }
2334   END_HASH_ITERATION(hash, itr)
2335
2336   fclose(file);
2337 }
2338
2339 SetupFileList *loadSetupFileList(char *filename)
2340 {
2341   SetupFileList *setup_file_list = newSetupFileList("", "");
2342   SetupFileList *first_valid_list_entry;
2343
2344   if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2345   {
2346     freeSetupFileList(setup_file_list);
2347
2348     return NULL;
2349   }
2350
2351   first_valid_list_entry = setup_file_list->next;
2352
2353   // free empty list header
2354   setup_file_list->next = NULL;
2355   freeSetupFileList(setup_file_list);
2356
2357   return first_valid_list_entry;
2358 }
2359
2360 SetupFileHash *loadSetupFileHash(char *filename)
2361 {
2362   SetupFileHash *setup_file_hash = newSetupFileHash();
2363
2364   if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2365   {
2366     freeSetupFileHash(setup_file_hash);
2367
2368     return NULL;
2369   }
2370
2371   return setup_file_hash;
2372 }
2373
2374
2375 // ============================================================================
2376 // setup file stuff
2377 // ============================================================================
2378
2379 #define TOKEN_STR_LAST_LEVEL_SERIES             "last_level_series"
2380 #define TOKEN_STR_LAST_PLAYED_LEVEL             "last_played_level"
2381 #define TOKEN_STR_HANDICAP_LEVEL                "handicap_level"
2382 #define TOKEN_STR_LAST_USER                     "last_user"
2383
2384 // level directory info
2385 #define LEVELINFO_TOKEN_IDENTIFIER              0
2386 #define LEVELINFO_TOKEN_NAME                    1
2387 #define LEVELINFO_TOKEN_NAME_SORTING            2
2388 #define LEVELINFO_TOKEN_AUTHOR                  3
2389 #define LEVELINFO_TOKEN_YEAR                    4
2390 #define LEVELINFO_TOKEN_PROGRAM_TITLE           5
2391 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT       6
2392 #define LEVELINFO_TOKEN_PROGRAM_COMPANY         7
2393 #define LEVELINFO_TOKEN_IMPORTED_FROM           8
2394 #define LEVELINFO_TOKEN_IMPORTED_BY             9
2395 #define LEVELINFO_TOKEN_TESTED_BY               10
2396 #define LEVELINFO_TOKEN_LEVELS                  11
2397 #define LEVELINFO_TOKEN_FIRST_LEVEL             12
2398 #define LEVELINFO_TOKEN_SORT_PRIORITY           13
2399 #define LEVELINFO_TOKEN_LATEST_ENGINE           14
2400 #define LEVELINFO_TOKEN_LEVEL_GROUP             15
2401 #define LEVELINFO_TOKEN_READONLY                16
2402 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS        17
2403 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA        18
2404 #define LEVELINFO_TOKEN_GRAPHICS_SET            19
2405 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT      20
2406 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS      21
2407 #define LEVELINFO_TOKEN_SOUNDS_SET              22
2408 #define LEVELINFO_TOKEN_MUSIC_SET               23
2409 #define LEVELINFO_TOKEN_FILENAME                24
2410 #define LEVELINFO_TOKEN_FILETYPE                25
2411 #define LEVELINFO_TOKEN_SPECIAL_FLAGS           26
2412 #define LEVELINFO_TOKEN_HANDICAP                27
2413 #define LEVELINFO_TOKEN_SKIP_LEVELS             28
2414 #define LEVELINFO_TOKEN_USE_EMC_TILES           29
2415
2416 #define NUM_LEVELINFO_TOKENS                    30
2417
2418 static LevelDirTree ldi;
2419
2420 static struct TokenInfo levelinfo_tokens[] =
2421 {
2422   // level directory info
2423   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
2424   { TYPE_STRING,        &ldi.name,              "name"                  },
2425   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
2426   { TYPE_STRING,        &ldi.author,            "author"                },
2427   { TYPE_STRING,        &ldi.year,              "year"                  },
2428   { TYPE_STRING,        &ldi.program_title,     "program_title"         },
2429   { TYPE_STRING,        &ldi.program_copyright, "program_copyright"     },
2430   { TYPE_STRING,        &ldi.program_company,   "program_company"       },
2431   { TYPE_STRING,        &ldi.imported_from,     "imported_from"         },
2432   { TYPE_STRING,        &ldi.imported_by,       "imported_by"           },
2433   { TYPE_STRING,        &ldi.tested_by,         "tested_by"             },
2434   { TYPE_INTEGER,       &ldi.levels,            "levels"                },
2435   { TYPE_INTEGER,       &ldi.first_level,       "first_level"           },
2436   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
2437   { TYPE_BOOLEAN,       &ldi.latest_engine,     "latest_engine"         },
2438   { TYPE_BOOLEAN,       &ldi.level_group,       "level_group"           },
2439   { TYPE_BOOLEAN,       &ldi.readonly,          "readonly"              },
2440   { TYPE_STRING,        &ldi.graphics_set_ecs,  "graphics_set.ecs"      },
2441   { TYPE_STRING,        &ldi.graphics_set_aga,  "graphics_set.aga"      },
2442   { TYPE_STRING,        &ldi.graphics_set,      "graphics_set"          },
2443   { TYPE_STRING,        &ldi.sounds_set_default,"sounds_set.default"    },
2444   { TYPE_STRING,        &ldi.sounds_set_lowpass,"sounds_set.lowpass"    },
2445   { TYPE_STRING,        &ldi.sounds_set,        "sounds_set"            },
2446   { TYPE_STRING,        &ldi.music_set,         "music_set"             },
2447   { TYPE_STRING,        &ldi.level_filename,    "filename"              },
2448   { TYPE_STRING,        &ldi.level_filetype,    "filetype"              },
2449   { TYPE_STRING,        &ldi.special_flags,     "special_flags"         },
2450   { TYPE_BOOLEAN,       &ldi.handicap,          "handicap"              },
2451   { TYPE_BOOLEAN,       &ldi.skip_levels,       "skip_levels"           },
2452   { TYPE_BOOLEAN,       &ldi.use_emc_tiles,     "use_emc_tiles"         }
2453 };
2454
2455 static struct TokenInfo artworkinfo_tokens[] =
2456 {
2457   // artwork directory info
2458   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
2459   { TYPE_STRING,        &ldi.subdir,            "subdir"                },
2460   { TYPE_STRING,        &ldi.name,              "name"                  },
2461   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
2462   { TYPE_STRING,        &ldi.author,            "author"                },
2463   { TYPE_STRING,        &ldi.program_title,     "program_title"         },
2464   { TYPE_STRING,        &ldi.program_copyright, "program_copyright"     },
2465   { TYPE_STRING,        &ldi.program_company,   "program_company"       },
2466   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
2467   { TYPE_STRING,        &ldi.basepath,          "basepath"              },
2468   { TYPE_STRING,        &ldi.fullpath,          "fullpath"              },
2469   { TYPE_BOOLEAN,       &ldi.in_user_dir,       "in_user_dir"           },
2470   { TYPE_INTEGER,       &ldi.color,             "color"                 },
2471   { TYPE_STRING,        &ldi.class_desc,        "class_desc"            },
2472
2473   { -1,                 NULL,                   NULL                    },
2474 };
2475
2476 static char *optional_tokens[] =
2477 {
2478   "program_title",
2479   "program_copyright",
2480   "program_company",
2481
2482   NULL
2483 };
2484
2485 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2486 {
2487   ti->type = type;
2488
2489   ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR    ? &leveldir_first :
2490                   ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2491                   ti->type == TREE_TYPE_SOUNDS_DIR   ? &artwork.snd_first :
2492                   ti->type == TREE_TYPE_MUSIC_DIR    ? &artwork.mus_first :
2493                   NULL);
2494
2495   ti->node_parent = NULL;
2496   ti->node_group = NULL;
2497   ti->next = NULL;
2498
2499   ti->cl_first = -1;
2500   ti->cl_cursor = -1;
2501
2502   ti->subdir = NULL;
2503   ti->fullpath = NULL;
2504   ti->basepath = NULL;
2505   ti->identifier = NULL;
2506   ti->name = getStringCopy(ANONYMOUS_NAME);
2507   ti->name_sorting = NULL;
2508   ti->author = getStringCopy(ANONYMOUS_NAME);
2509   ti->year = NULL;
2510
2511   ti->program_title = NULL;
2512   ti->program_copyright = NULL;
2513   ti->program_company = NULL;
2514
2515   ti->sort_priority = LEVELCLASS_UNDEFINED;     // default: least priority
2516   ti->latest_engine = FALSE;                    // default: get from level
2517   ti->parent_link = FALSE;
2518   ti->in_user_dir = FALSE;
2519   ti->user_defined = FALSE;
2520   ti->color = 0;
2521   ti->class_desc = NULL;
2522
2523   ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2524
2525   if (ti->type == TREE_TYPE_LEVEL_DIR)
2526   {
2527     ti->imported_from = NULL;
2528     ti->imported_by = NULL;
2529     ti->tested_by = NULL;
2530
2531     ti->graphics_set_ecs = NULL;
2532     ti->graphics_set_aga = NULL;
2533     ti->graphics_set = NULL;
2534     ti->sounds_set_default = NULL;
2535     ti->sounds_set_lowpass = NULL;
2536     ti->sounds_set = NULL;
2537     ti->music_set = NULL;
2538     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2539     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2540     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2541
2542     ti->level_filename = NULL;
2543     ti->level_filetype = NULL;
2544
2545     ti->special_flags = NULL;
2546
2547     ti->levels = 0;
2548     ti->first_level = 0;
2549     ti->last_level = 0;
2550     ti->level_group = FALSE;
2551     ti->handicap_level = 0;
2552     ti->readonly = TRUE;
2553     ti->handicap = TRUE;
2554     ti->skip_levels = FALSE;
2555
2556     ti->use_emc_tiles = FALSE;
2557   }
2558 }
2559
2560 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2561 {
2562   if (parent == NULL)
2563   {
2564     Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2565
2566     setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2567
2568     return;
2569   }
2570
2571   // copy all values from the parent structure
2572
2573   ti->type = parent->type;
2574
2575   ti->node_top = parent->node_top;
2576   ti->node_parent = parent;
2577   ti->node_group = NULL;
2578   ti->next = NULL;
2579
2580   ti->cl_first = -1;
2581   ti->cl_cursor = -1;
2582
2583   ti->subdir = NULL;
2584   ti->fullpath = NULL;
2585   ti->basepath = NULL;
2586   ti->identifier = NULL;
2587   ti->name = getStringCopy(ANONYMOUS_NAME);
2588   ti->name_sorting = NULL;
2589   ti->author = getStringCopy(parent->author);
2590   ti->year = getStringCopy(parent->year);
2591
2592   ti->program_title = getStringCopy(parent->program_title);
2593   ti->program_copyright = getStringCopy(parent->program_copyright);
2594   ti->program_company = getStringCopy(parent->program_company);
2595
2596   ti->sort_priority = parent->sort_priority;
2597   ti->latest_engine = parent->latest_engine;
2598   ti->parent_link = FALSE;
2599   ti->in_user_dir = parent->in_user_dir;
2600   ti->user_defined = parent->user_defined;
2601   ti->color = parent->color;
2602   ti->class_desc = getStringCopy(parent->class_desc);
2603
2604   ti->infotext = getStringCopy(parent->infotext);
2605
2606   if (ti->type == TREE_TYPE_LEVEL_DIR)
2607   {
2608     ti->imported_from = getStringCopy(parent->imported_from);
2609     ti->imported_by = getStringCopy(parent->imported_by);
2610     ti->tested_by = getStringCopy(parent->tested_by);
2611
2612     ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2613     ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2614     ti->graphics_set = getStringCopy(parent->graphics_set);
2615     ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2616     ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2617     ti->sounds_set = getStringCopy(parent->sounds_set);
2618     ti->music_set = getStringCopy(parent->music_set);
2619     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2620     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2621     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2622
2623     ti->level_filename = getStringCopy(parent->level_filename);
2624     ti->level_filetype = getStringCopy(parent->level_filetype);
2625
2626     ti->special_flags = getStringCopy(parent->special_flags);
2627
2628     ti->levels = parent->levels;
2629     ti->first_level = parent->first_level;
2630     ti->last_level = parent->last_level;
2631     ti->level_group = FALSE;
2632     ti->handicap_level = parent->handicap_level;
2633     ti->readonly = parent->readonly;
2634     ti->handicap = parent->handicap;
2635     ti->skip_levels = parent->skip_levels;
2636
2637     ti->use_emc_tiles = parent->use_emc_tiles;
2638   }
2639 }
2640
2641 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2642 {
2643   TreeInfo *ti_copy = newTreeInfo();
2644
2645   // copy all values from the original structure
2646
2647   ti_copy->type                 = ti->type;
2648
2649   ti_copy->node_top             = ti->node_top;
2650   ti_copy->node_parent          = ti->node_parent;
2651   ti_copy->node_group           = ti->node_group;
2652   ti_copy->next                 = ti->next;
2653
2654   ti_copy->cl_first             = ti->cl_first;
2655   ti_copy->cl_cursor            = ti->cl_cursor;
2656
2657   ti_copy->subdir               = getStringCopy(ti->subdir);
2658   ti_copy->fullpath             = getStringCopy(ti->fullpath);
2659   ti_copy->basepath             = getStringCopy(ti->basepath);
2660   ti_copy->identifier           = getStringCopy(ti->identifier);
2661   ti_copy->name                 = getStringCopy(ti->name);
2662   ti_copy->name_sorting         = getStringCopy(ti->name_sorting);
2663   ti_copy->author               = getStringCopy(ti->author);
2664   ti_copy->year                 = getStringCopy(ti->year);
2665
2666   ti_copy->program_title        = getStringCopy(ti->program_title);
2667   ti_copy->program_copyright    = getStringCopy(ti->program_copyright);
2668   ti_copy->program_company      = getStringCopy(ti->program_company);
2669
2670   ti_copy->imported_from        = getStringCopy(ti->imported_from);
2671   ti_copy->imported_by          = getStringCopy(ti->imported_by);
2672   ti_copy->tested_by            = getStringCopy(ti->tested_by);
2673
2674   ti_copy->graphics_set_ecs     = getStringCopy(ti->graphics_set_ecs);
2675   ti_copy->graphics_set_aga     = getStringCopy(ti->graphics_set_aga);
2676   ti_copy->graphics_set         = getStringCopy(ti->graphics_set);
2677   ti_copy->sounds_set_default   = getStringCopy(ti->sounds_set_default);
2678   ti_copy->sounds_set_lowpass   = getStringCopy(ti->sounds_set_lowpass);
2679   ti_copy->sounds_set           = getStringCopy(ti->sounds_set);
2680   ti_copy->music_set            = getStringCopy(ti->music_set);
2681   ti_copy->graphics_path        = getStringCopy(ti->graphics_path);
2682   ti_copy->sounds_path          = getStringCopy(ti->sounds_path);
2683   ti_copy->music_path           = getStringCopy(ti->music_path);
2684
2685   ti_copy->level_filename       = getStringCopy(ti->level_filename);
2686   ti_copy->level_filetype       = getStringCopy(ti->level_filetype);
2687
2688   ti_copy->special_flags        = getStringCopy(ti->special_flags);
2689
2690   ti_copy->levels               = ti->levels;
2691   ti_copy->first_level          = ti->first_level;
2692   ti_copy->last_level           = ti->last_level;
2693   ti_copy->sort_priority        = ti->sort_priority;
2694
2695   ti_copy->latest_engine        = ti->latest_engine;
2696
2697   ti_copy->level_group          = ti->level_group;
2698   ti_copy->parent_link          = ti->parent_link;
2699   ti_copy->in_user_dir          = ti->in_user_dir;
2700   ti_copy->user_defined         = ti->user_defined;
2701   ti_copy->readonly             = ti->readonly;
2702   ti_copy->handicap             = ti->handicap;
2703   ti_copy->skip_levels          = ti->skip_levels;
2704
2705   ti_copy->use_emc_tiles        = ti->use_emc_tiles;
2706
2707   ti_copy->color                = ti->color;
2708   ti_copy->class_desc           = getStringCopy(ti->class_desc);
2709   ti_copy->handicap_level       = ti->handicap_level;
2710
2711   ti_copy->infotext             = getStringCopy(ti->infotext);
2712
2713   return ti_copy;
2714 }
2715
2716 void freeTreeInfo(TreeInfo *ti)
2717 {
2718   if (ti == NULL)
2719     return;
2720
2721   checked_free(ti->subdir);
2722   checked_free(ti->fullpath);
2723   checked_free(ti->basepath);
2724   checked_free(ti->identifier);
2725
2726   checked_free(ti->name);
2727   checked_free(ti->name_sorting);
2728   checked_free(ti->author);
2729   checked_free(ti->year);
2730
2731   checked_free(ti->program_title);
2732   checked_free(ti->program_copyright);
2733   checked_free(ti->program_company);
2734
2735   checked_free(ti->class_desc);
2736
2737   checked_free(ti->infotext);
2738
2739   if (ti->type == TREE_TYPE_LEVEL_DIR)
2740   {
2741     checked_free(ti->imported_from);
2742     checked_free(ti->imported_by);
2743     checked_free(ti->tested_by);
2744
2745     checked_free(ti->graphics_set_ecs);
2746     checked_free(ti->graphics_set_aga);
2747     checked_free(ti->graphics_set);
2748     checked_free(ti->sounds_set_default);
2749     checked_free(ti->sounds_set_lowpass);
2750     checked_free(ti->sounds_set);
2751     checked_free(ti->music_set);
2752
2753     checked_free(ti->graphics_path);
2754     checked_free(ti->sounds_path);
2755     checked_free(ti->music_path);
2756
2757     checked_free(ti->level_filename);
2758     checked_free(ti->level_filetype);
2759
2760     checked_free(ti->special_flags);
2761   }
2762
2763   // recursively free child node
2764   if (ti->node_group)
2765     freeTreeInfo(ti->node_group);
2766
2767   // recursively free next node
2768   if (ti->next)
2769     freeTreeInfo(ti->next);
2770
2771   checked_free(ti);
2772 }
2773
2774 void setSetupInfo(struct TokenInfo *token_info,
2775                   int token_nr, char *token_value)
2776 {
2777   int token_type = token_info[token_nr].type;
2778   void *setup_value = token_info[token_nr].value;
2779
2780   if (token_value == NULL)
2781     return;
2782
2783   // set setup field to corresponding token value
2784   switch (token_type)
2785   {
2786     case TYPE_BOOLEAN:
2787     case TYPE_SWITCH:
2788       *(boolean *)setup_value = get_boolean_from_string(token_value);
2789       break;
2790
2791     case TYPE_SWITCH3:
2792       *(int *)setup_value = get_switch3_from_string(token_value);
2793       break;
2794
2795     case TYPE_KEY:
2796       *(Key *)setup_value = getKeyFromKeyName(token_value);
2797       break;
2798
2799     case TYPE_KEY_X11:
2800       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2801       break;
2802
2803     case TYPE_INTEGER:
2804       *(int *)setup_value = get_integer_from_string(token_value);
2805       break;
2806
2807     case TYPE_STRING:
2808       checked_free(*(char **)setup_value);
2809       *(char **)setup_value = getStringCopy(token_value);
2810       break;
2811
2812     case TYPE_PLAYER:
2813       *(int *)setup_value = get_player_nr_from_string(token_value);
2814       break;
2815
2816     default:
2817       break;
2818   }
2819 }
2820
2821 static int compareTreeInfoEntries(const void *object1, const void *object2)
2822 {
2823   const TreeInfo *entry1 = *((TreeInfo **)object1);
2824   const TreeInfo *entry2 = *((TreeInfo **)object2);
2825   int class_sorting1 = 0, class_sorting2 = 0;
2826   int compare_result;
2827
2828   if (entry1->type == TREE_TYPE_LEVEL_DIR)
2829   {
2830     class_sorting1 = LEVELSORTING(entry1);
2831     class_sorting2 = LEVELSORTING(entry2);
2832   }
2833   else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2834            entry1->type == TREE_TYPE_SOUNDS_DIR ||
2835            entry1->type == TREE_TYPE_MUSIC_DIR)
2836   {
2837     class_sorting1 = ARTWORKSORTING(entry1);
2838     class_sorting2 = ARTWORKSORTING(entry2);
2839   }
2840
2841   if (entry1->parent_link || entry2->parent_link)
2842     compare_result = (entry1->parent_link ? -1 : +1);
2843   else if (entry1->sort_priority == entry2->sort_priority)
2844   {
2845     char *name1 = getStringToLower(entry1->name_sorting);
2846     char *name2 = getStringToLower(entry2->name_sorting);
2847
2848     compare_result = strcmp(name1, name2);
2849
2850     free(name1);
2851     free(name2);
2852   }
2853   else if (class_sorting1 == class_sorting2)
2854     compare_result = entry1->sort_priority - entry2->sort_priority;
2855   else
2856     compare_result = class_sorting1 - class_sorting2;
2857
2858   return compare_result;
2859 }
2860
2861 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2862 {
2863   TreeInfo *ti_new;
2864
2865   if (node_parent == NULL)
2866     return NULL;
2867
2868   ti_new = newTreeInfo();
2869   setTreeInfoToDefaults(ti_new, node_parent->type);
2870
2871   ti_new->node_parent = node_parent;
2872   ti_new->parent_link = TRUE;
2873
2874   setString(&ti_new->identifier, node_parent->identifier);
2875   setString(&ti_new->name, ".. (parent directory)");
2876   setString(&ti_new->name_sorting, ti_new->name);
2877
2878   setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2879   setString(&ti_new->fullpath, node_parent->fullpath);
2880
2881   ti_new->sort_priority = node_parent->sort_priority;
2882   ti_new->latest_engine = node_parent->latest_engine;
2883
2884   setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2885
2886   pushTreeInfo(&node_parent->node_group, ti_new);
2887
2888   return ti_new;
2889 }
2890
2891 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2892 {
2893   TreeInfo *ti_new, *ti_new2;
2894
2895   if (node_first == NULL)
2896     return NULL;
2897
2898   ti_new = newTreeInfo();
2899   setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2900
2901   ti_new->node_parent = NULL;
2902   ti_new->parent_link = FALSE;
2903
2904   setString(&ti_new->identifier, node_first->identifier);
2905   setString(&ti_new->name, "level sets");
2906   setString(&ti_new->name_sorting, ti_new->name);
2907
2908   setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2909   setString(&ti_new->fullpath, ".");
2910
2911   ti_new->sort_priority = node_first->sort_priority;;
2912   ti_new->latest_engine = node_first->latest_engine;
2913
2914   setString(&ti_new->class_desc, "level sets");
2915
2916   ti_new->node_group = node_first;
2917   ti_new->level_group = TRUE;
2918
2919   ti_new2 = createParentTreeInfoNode(ti_new);
2920
2921   setString(&ti_new2->name, ".. (main menu)");
2922   setString(&ti_new2->name_sorting, ti_new2->name);
2923
2924   return ti_new;
2925 }
2926
2927
2928 // ----------------------------------------------------------------------------
2929 // functions for handling level and custom artwork info cache
2930 // ----------------------------------------------------------------------------
2931
2932 static void LoadArtworkInfoCache(void)
2933 {
2934   InitCacheDirectory();
2935
2936   if (artworkinfo_cache_old == NULL)
2937   {
2938     char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2939
2940     // try to load artwork info hash from already existing cache file
2941     artworkinfo_cache_old = loadSetupFileHash(filename);
2942
2943     // if no artwork info cache file was found, start with empty hash
2944     if (artworkinfo_cache_old == NULL)
2945       artworkinfo_cache_old = newSetupFileHash();
2946
2947     free(filename);
2948   }
2949
2950   if (artworkinfo_cache_new == NULL)
2951     artworkinfo_cache_new = newSetupFileHash();
2952 }
2953
2954 static void SaveArtworkInfoCache(void)
2955 {
2956   char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2957
2958   InitCacheDirectory();
2959
2960   saveSetupFileHash(artworkinfo_cache_new, filename);
2961
2962   free(filename);
2963 }
2964
2965 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2966 {
2967   static char *prefix = NULL;
2968
2969   checked_free(prefix);
2970
2971   prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2972
2973   return prefix;
2974 }
2975
2976 // (identical to above function, but separate string buffer needed -- nasty)
2977 static char *getCacheToken(char *prefix, char *suffix)
2978 {
2979   static char *token = NULL;
2980
2981   checked_free(token);
2982
2983   token = getStringCat2WithSeparator(prefix, suffix, ".");
2984
2985   return token;
2986 }
2987
2988 static char *getFileTimestampString(char *filename)
2989 {
2990   return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2991 }
2992
2993 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2994 {
2995   struct stat file_status;
2996
2997   if (timestamp_string == NULL)
2998     return TRUE;
2999
3000   if (stat(filename, &file_status) != 0)        // cannot stat file
3001     return TRUE;
3002
3003   return (file_status.st_mtime != atoi(timestamp_string));
3004 }
3005
3006 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3007 {
3008   char *identifier = level_node->subdir;
3009   char *type_string = ARTWORK_DIRECTORY(type);
3010   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3011   char *token_main = getCacheToken(token_prefix, "CACHED");
3012   char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3013   boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3014   TreeInfo *artwork_info = NULL;
3015
3016   if (!use_artworkinfo_cache)
3017     return NULL;
3018
3019   if (optional_tokens_hash == NULL)
3020   {
3021     int i;
3022
3023     // create hash from list of optional tokens (for quick access)
3024     optional_tokens_hash = newSetupFileHash();
3025     for (i = 0; optional_tokens[i] != NULL; i++)
3026       setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3027   }
3028
3029   if (cached)
3030   {
3031     int i;
3032
3033     artwork_info = newTreeInfo();
3034     setTreeInfoToDefaults(artwork_info, type);
3035
3036     // set all structure fields according to the token/value pairs
3037     ldi = *artwork_info;
3038     for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3039     {
3040       char *token_suffix = artworkinfo_tokens[i].text;
3041       char *token = getCacheToken(token_prefix, token_suffix);
3042       char *value = getHashEntry(artworkinfo_cache_old, token);
3043       boolean optional =
3044         (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3045
3046       setSetupInfo(artworkinfo_tokens, i, value);
3047
3048       // check if cache entry for this item is mandatory, but missing
3049       if (value == NULL && !optional)
3050       {
3051         Warn("missing cache entry '%s'", token);
3052
3053         cached = FALSE;
3054       }
3055     }
3056
3057     *artwork_info = ldi;
3058   }
3059
3060   if (cached)
3061   {
3062     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3063                                         LEVELINFO_FILENAME);
3064     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3065                                           ARTWORKINFO_FILENAME(type));
3066
3067     // check if corresponding "levelinfo.conf" file has changed
3068     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3069     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3070
3071     if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3072       cached = FALSE;
3073
3074     // check if corresponding "<artworkinfo>.conf" file has changed
3075     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3076     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3077
3078     if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3079       cached = FALSE;
3080
3081     checked_free(filename_levelinfo);
3082     checked_free(filename_artworkinfo);
3083   }
3084
3085   if (!cached && artwork_info != NULL)
3086   {
3087     freeTreeInfo(artwork_info);
3088
3089     return NULL;
3090   }
3091
3092   return artwork_info;
3093 }
3094
3095 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3096                                      LevelDirTree *level_node, int type)
3097 {
3098   char *identifier = level_node->subdir;
3099   char *type_string = ARTWORK_DIRECTORY(type);
3100   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3101   char *token_main = getCacheToken(token_prefix, "CACHED");
3102   boolean set_cache_timestamps = TRUE;
3103   int i;
3104
3105   setHashEntry(artworkinfo_cache_new, token_main, "true");
3106
3107   if (set_cache_timestamps)
3108   {
3109     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3110                                         LEVELINFO_FILENAME);
3111     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3112                                           ARTWORKINFO_FILENAME(type));
3113     char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3114     char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3115
3116     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3117     setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3118
3119     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3120     setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3121
3122     checked_free(filename_levelinfo);
3123     checked_free(filename_artworkinfo);
3124     checked_free(timestamp_levelinfo);
3125     checked_free(timestamp_artworkinfo);
3126   }
3127
3128   ldi = *artwork_info;
3129   for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3130   {
3131     char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3132     char *value = getSetupValue(artworkinfo_tokens[i].type,
3133                                 artworkinfo_tokens[i].value);
3134     if (value != NULL)
3135       setHashEntry(artworkinfo_cache_new, token, value);
3136   }
3137 }
3138
3139
3140 // ----------------------------------------------------------------------------
3141 // functions for loading level info and custom artwork info
3142 // ----------------------------------------------------------------------------
3143
3144 int GetZipFileTreeType(char *zip_filename)
3145 {
3146   static char *top_dir_path = NULL;
3147   static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3148   static char *conf_basename[NUM_BASE_TREE_TYPES] =
3149   {
3150     GRAPHICSINFO_FILENAME,
3151     SOUNDSINFO_FILENAME,
3152     MUSICINFO_FILENAME,
3153     LEVELINFO_FILENAME
3154   };
3155   int j;
3156
3157   checked_free(top_dir_path);
3158   top_dir_path = NULL;
3159
3160   for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3161   {
3162     checked_free(top_dir_conf_filename[j]);
3163     top_dir_conf_filename[j] = NULL;
3164   }
3165
3166   char **zip_entries = zip_list(zip_filename);
3167
3168   // check if zip file successfully opened
3169   if (zip_entries == NULL || zip_entries[0] == NULL)
3170     return TREE_TYPE_UNDEFINED;
3171
3172   // first zip file entry is expected to be top level directory
3173   char *top_dir = zip_entries[0];
3174
3175   // check if valid top level directory found in zip file
3176   if (!strSuffix(top_dir, "/"))
3177     return TREE_TYPE_UNDEFINED;
3178
3179   // get filenames of valid configuration files in top level directory
3180   for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3181     top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3182
3183   int tree_type = TREE_TYPE_UNDEFINED;
3184   int e = 0;
3185
3186   while (zip_entries[e] != NULL)
3187   {
3188     // check if every zip file entry is below top level directory
3189     if (!strPrefix(zip_entries[e], top_dir))
3190       return TREE_TYPE_UNDEFINED;
3191
3192     // check if this zip file entry is a valid configuration filename
3193     for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3194     {
3195       if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3196       {
3197         // only exactly one valid configuration file allowed
3198         if (tree_type != TREE_TYPE_UNDEFINED)
3199           return TREE_TYPE_UNDEFINED;
3200
3201         tree_type = j;
3202       }
3203     }
3204
3205     e++;
3206   }
3207
3208   return tree_type;
3209 }
3210
3211 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3212                                         int tree_type)
3213 {
3214   static char *top_dir_path = NULL;
3215   static char *top_dir_conf_filename = NULL;
3216
3217   checked_free(top_dir_path);
3218   checked_free(top_dir_conf_filename);
3219
3220   top_dir_path = NULL;
3221   top_dir_conf_filename = NULL;
3222
3223   char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3224                          ARTWORKINFO_FILENAME(tree_type));
3225
3226   // check if valid configuration filename determined
3227   if (conf_basename == NULL || strEqual(conf_basename, ""))
3228     return FALSE;
3229
3230   char **zip_entries = zip_list(zip_filename);
3231
3232   // check if zip file successfully opened
3233   if (zip_entries == NULL || zip_entries[0] == NULL)
3234     return FALSE;
3235
3236   // first zip file entry is expected to be top level directory
3237   char *top_dir = zip_entries[0];
3238
3239   // check if valid top level directory found in zip file
3240   if (!strSuffix(top_dir, "/"))
3241     return FALSE;
3242
3243   // get path of extracted top level directory
3244   top_dir_path = getPath2(directory, top_dir);
3245
3246   // remove trailing directory separator from top level directory path
3247   // (required to be able to check for file and directory in next step)
3248   top_dir_path[strlen(top_dir_path) - 1] = '\0';
3249
3250   // check if zip file's top level directory already exists in target directory
3251   if (fileExists(top_dir_path))         // (checks for file and directory)
3252     return FALSE;
3253
3254   // get filename of configuration file in top level directory
3255   top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3256
3257   boolean found_top_dir_conf_filename = FALSE;
3258   int i = 0;
3259
3260   while (zip_entries[i] != NULL)
3261   {
3262     // check if every zip file entry is below top level directory
3263     if (!strPrefix(zip_entries[i], top_dir))
3264       return FALSE;
3265
3266     // check if this zip file entry is the configuration filename
3267     if (strEqual(zip_entries[i], top_dir_conf_filename))
3268       found_top_dir_conf_filename = TRUE;
3269
3270     i++;
3271   }
3272
3273   // check if valid configuration filename was found in zip file
3274   if (!found_top_dir_conf_filename)
3275     return FALSE;
3276
3277   return TRUE;
3278 }
3279
3280 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3281                                   int tree_type)
3282 {
3283   boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3284                                                     tree_type);
3285
3286   if (!zip_file_valid)
3287   {
3288     Warn("zip file '%s' rejected!", zip_filename);
3289
3290     return NULL;
3291   }
3292
3293   char **zip_entries = zip_extract(zip_filename, directory);
3294
3295   if (zip_entries == NULL)
3296   {
3297     Warn("zip file '%s' could not be extracted!", zip_filename);
3298
3299     return NULL;
3300   }
3301
3302   Info("zip file '%s' successfully extracted!", zip_filename);
3303
3304   // first zip file entry contains top level directory
3305   char *top_dir = zip_entries[0];
3306
3307   // remove trailing directory separator from top level directory
3308   top_dir[strlen(top_dir) - 1] = '\0';
3309
3310   return top_dir;
3311 }
3312
3313 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3314 {
3315   Directory *dir;
3316   DirectoryEntry *dir_entry;
3317
3318   if ((dir = openDirectory(directory)) == NULL)
3319   {
3320     // display error if directory is main "options.graphics_directory" etc.
3321     if (tree_type == TREE_TYPE_LEVEL_DIR ||
3322         directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3323       Warn("cannot read directory '%s'", directory);
3324
3325     return;
3326   }
3327
3328   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
3329   {
3330     // skip non-zip files (and also directories with zip extension)
3331     if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3332       continue;
3333
3334     char *zip_filename = getPath2(directory, dir_entry->basename);
3335     char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3336     char *zip_filename_rejected  = getStringCat2(zip_filename, ".rejected");
3337
3338     // check if zip file hasn't already been extracted or rejected
3339     if (!fileExists(zip_filename_extracted) &&
3340         !fileExists(zip_filename_rejected))
3341     {
3342       char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3343                                                   tree_type);
3344       char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3345                                zip_filename_rejected);
3346       FILE *marker_file;
3347
3348       // create empty file to mark zip file as extracted or rejected
3349       if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3350         fclose(marker_file);
3351
3352       free(zip_filename);
3353       free(zip_filename_extracted);
3354       free(zip_filename_rejected);
3355     }
3356   }
3357
3358   closeDirectory(dir);
3359 }
3360
3361 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3362 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3363
3364 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3365                                           TreeInfo *node_parent,
3366                                           char *level_directory,
3367                                           char *directory_name)
3368 {
3369   char *directory_path = getPath2(level_directory, directory_name);
3370   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3371   SetupFileHash *setup_file_hash;
3372   LevelDirTree *leveldir_new = NULL;
3373   int i;
3374
3375   // unless debugging, silently ignore directories without "levelinfo.conf"
3376   if (!options.debug && !fileExists(filename))
3377   {
3378     free(directory_path);
3379     free(filename);
3380
3381     return FALSE;
3382   }
3383
3384   setup_file_hash = loadSetupFileHash(filename);
3385
3386   if (setup_file_hash == NULL)
3387   {
3388 #if DEBUG_NO_CONFIG_FILE
3389     Debug("setup", "ignoring level directory '%s'", directory_path);
3390 #endif
3391
3392     free(directory_path);
3393     free(filename);
3394
3395     return FALSE;
3396   }
3397
3398   leveldir_new = newTreeInfo();
3399
3400   if (node_parent)
3401     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3402   else
3403     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3404
3405   leveldir_new->subdir = getStringCopy(directory_name);
3406
3407   // set all structure fields according to the token/value pairs
3408   ldi = *leveldir_new;
3409   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3410     setSetupInfo(levelinfo_tokens, i,
3411                  getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3412   *leveldir_new = ldi;
3413
3414   if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3415     setString(&leveldir_new->name, leveldir_new->subdir);
3416
3417   if (leveldir_new->identifier == NULL)
3418     leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3419
3420   if (leveldir_new->name_sorting == NULL)
3421     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3422
3423   if (node_parent == NULL)              // top level group
3424   {
3425     leveldir_new->basepath = getStringCopy(level_directory);
3426     leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3427   }
3428   else                                  // sub level group
3429   {
3430     leveldir_new->basepath = getStringCopy(node_parent->basepath);
3431     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3432   }
3433
3434   leveldir_new->last_level =
3435     leveldir_new->first_level + leveldir_new->levels - 1;
3436
3437   leveldir_new->in_user_dir =
3438     (!strEqual(leveldir_new->basepath, options.level_directory));
3439
3440   // adjust some settings if user's private level directory was detected
3441   if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3442       leveldir_new->in_user_dir &&
3443       (strEqual(leveldir_new->subdir, getLoginName()) ||
3444        strEqual(leveldir_new->name,   getLoginName()) ||
3445        strEqual(leveldir_new->author, getRealName())))
3446   {
3447     leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3448     leveldir_new->readonly = FALSE;
3449   }
3450
3451   leveldir_new->user_defined =
3452     (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3453
3454   leveldir_new->color = LEVELCOLOR(leveldir_new);
3455
3456   setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3457
3458   leveldir_new->handicap_level =        // set handicap to default value
3459     (leveldir_new->user_defined || !leveldir_new->handicap ?
3460      leveldir_new->last_level : leveldir_new->first_level);
3461
3462   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3463
3464   pushTreeInfo(node_first, leveldir_new);
3465
3466   freeSetupFileHash(setup_file_hash);
3467
3468   if (leveldir_new->level_group)
3469   {
3470     // create node to link back to current level directory
3471     createParentTreeInfoNode(leveldir_new);
3472
3473     // recursively step into sub-directory and look for more level series
3474     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3475                               leveldir_new, directory_path);
3476   }
3477
3478   free(directory_path);
3479   free(filename);
3480
3481   return TRUE;
3482 }
3483
3484 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3485                                       TreeInfo *node_parent,
3486                                       char *level_directory)
3487 {
3488   // ---------- 1st stage: process any level set zip files ----------
3489
3490   ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3491
3492   // ---------- 2nd stage: check for level set directories ----------
3493
3494   Directory *dir;
3495   DirectoryEntry *dir_entry;
3496   boolean valid_entry_found = FALSE;
3497
3498   if ((dir = openDirectory(level_directory)) == NULL)
3499   {
3500     Warn("cannot read level directory '%s'", level_directory);
3501
3502     return;
3503   }
3504
3505   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
3506   {
3507     char *directory_name = dir_entry->basename;
3508     char *directory_path = getPath2(level_directory, directory_name);
3509
3510     // skip entries for current and parent directory
3511     if (strEqual(directory_name, ".") ||
3512         strEqual(directory_name, ".."))
3513     {
3514       free(directory_path);
3515
3516       continue;
3517     }
3518
3519     // find out if directory entry is itself a directory
3520     if (!dir_entry->is_directory)                       // not a directory
3521     {
3522       free(directory_path);
3523
3524       continue;
3525     }
3526
3527     free(directory_path);
3528
3529     if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3530         strEqual(directory_name, SOUNDS_DIRECTORY) ||
3531         strEqual(directory_name, MUSIC_DIRECTORY))
3532       continue;
3533
3534     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3535                                                     level_directory,
3536                                                     directory_name);
3537   }
3538
3539   closeDirectory(dir);
3540
3541   // special case: top level directory may directly contain "levelinfo.conf"
3542   if (node_parent == NULL && !valid_entry_found)
3543   {
3544     // check if this directory directly contains a file "levelinfo.conf"
3545     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3546                                                     level_directory, ".");
3547   }
3548
3549   if (!valid_entry_found)
3550     Warn("cannot find any valid level series in directory '%s'",
3551           level_directory);
3552 }
3553
3554 boolean AdjustGraphicsForEMC(void)
3555 {
3556   boolean settings_changed = FALSE;
3557
3558   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3559   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3560
3561   return settings_changed;
3562 }
3563
3564 boolean AdjustSoundsForEMC(void)
3565 {
3566   boolean settings_changed = FALSE;
3567
3568   settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3569   settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3570
3571   return settings_changed;
3572 }
3573
3574 void LoadLevelInfo(void)
3575 {
3576   InitUserLevelDirectory(getLoginName());
3577
3578   DrawInitText("Loading level series", 120, FC_GREEN);
3579
3580   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3581   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3582
3583   leveldir_first = createTopTreeInfoNode(leveldir_first);
3584
3585   /* after loading all level set information, clone the level directory tree
3586      and remove all level sets without levels (these may still contain artwork
3587      to be offered in the setup menu as "custom artwork", and are therefore
3588      checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3589   leveldir_first_all = leveldir_first;
3590   cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3591
3592   AdjustGraphicsForEMC();
3593   AdjustSoundsForEMC();
3594
3595   // before sorting, the first entries will be from the user directory
3596   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3597
3598   if (leveldir_first == NULL)
3599     Fail("cannot find any valid level series in any directory");
3600
3601   sortTreeInfo(&leveldir_first);
3602
3603 #if ENABLE_UNUSED_CODE
3604   dumpTreeInfo(leveldir_first, 0);
3605 #endif
3606 }
3607
3608 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3609                                               TreeInfo *node_parent,
3610                                               char *base_directory,
3611                                               char *directory_name, int type)
3612 {
3613   char *directory_path = getPath2(base_directory, directory_name);
3614   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3615   SetupFileHash *setup_file_hash = NULL;
3616   TreeInfo *artwork_new = NULL;
3617   int i;
3618
3619   if (fileExists(filename))
3620     setup_file_hash = loadSetupFileHash(filename);
3621
3622   if (setup_file_hash == NULL)  // no config file -- look for artwork files
3623   {
3624     Directory *dir;
3625     DirectoryEntry *dir_entry;
3626     boolean valid_file_found = FALSE;
3627
3628     if ((dir = openDirectory(directory_path)) != NULL)
3629     {
3630       while ((dir_entry = readDirectory(dir)) != NULL)
3631       {
3632         if (FileIsArtworkType(dir_entry->filename, type))
3633         {
3634           valid_file_found = TRUE;
3635
3636           break;
3637         }
3638       }
3639
3640       closeDirectory(dir);
3641     }
3642
3643     if (!valid_file_found)
3644     {
3645 #if DEBUG_NO_CONFIG_FILE
3646       if (!strEqual(directory_name, "."))
3647         Debug("setup", "ignoring artwork directory '%s'", directory_path);
3648 #endif
3649
3650       free(directory_path);
3651       free(filename);
3652
3653       return FALSE;
3654     }
3655   }
3656
3657   artwork_new = newTreeInfo();
3658
3659   if (node_parent)
3660     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3661   else
3662     setTreeInfoToDefaults(artwork_new, type);
3663
3664   artwork_new->subdir = getStringCopy(directory_name);
3665
3666   if (setup_file_hash)  // (before defining ".color" and ".class_desc")
3667   {
3668     // set all structure fields according to the token/value pairs
3669     ldi = *artwork_new;
3670     for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3671       setSetupInfo(levelinfo_tokens, i,
3672                    getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3673     *artwork_new = ldi;
3674
3675     if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3676       setString(&artwork_new->name, artwork_new->subdir);
3677
3678     if (artwork_new->identifier == NULL)
3679       artwork_new->identifier = getStringCopy(artwork_new->subdir);
3680
3681     if (artwork_new->name_sorting == NULL)
3682       artwork_new->name_sorting = getStringCopy(artwork_new->name);
3683   }
3684
3685   if (node_parent == NULL)              // top level group
3686   {
3687     artwork_new->basepath = getStringCopy(base_directory);
3688     artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3689   }
3690   else                                  // sub level group
3691   {
3692     artwork_new->basepath = getStringCopy(node_parent->basepath);
3693     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3694   }
3695
3696   artwork_new->in_user_dir =
3697     (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3698
3699   // (may use ".sort_priority" from "setup_file_hash" above)
3700   artwork_new->color = ARTWORKCOLOR(artwork_new);
3701
3702   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3703
3704   if (setup_file_hash == NULL)  // (after determining ".user_defined")
3705   {
3706     if (strEqual(artwork_new->subdir, "."))
3707     {
3708       if (artwork_new->user_defined)
3709       {
3710         setString(&artwork_new->identifier, "private");
3711         artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3712       }
3713       else
3714       {
3715         setString(&artwork_new->identifier, "classic");
3716         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3717       }
3718
3719       // set to new values after changing ".sort_priority"
3720       artwork_new->color = ARTWORKCOLOR(artwork_new);
3721
3722       setString(&artwork_new->class_desc,
3723                 getLevelClassDescription(artwork_new));
3724     }
3725     else
3726     {
3727       setString(&artwork_new->identifier, artwork_new->subdir);
3728     }
3729
3730     setString(&artwork_new->name, artwork_new->identifier);
3731     setString(&artwork_new->name_sorting, artwork_new->name);
3732   }
3733
3734   pushTreeInfo(node_first, artwork_new);
3735
3736   freeSetupFileHash(setup_file_hash);
3737
3738   free(directory_path);
3739   free(filename);
3740
3741   return TRUE;
3742 }
3743
3744 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3745                                           TreeInfo *node_parent,
3746                                           char *base_directory, int type)
3747 {
3748   // ---------- 1st stage: process any artwork set zip files ----------
3749
3750   ProcessZipFilesInDirectory(base_directory, type);
3751
3752   // ---------- 2nd stage: check for artwork set directories ----------
3753
3754   Directory *dir;
3755   DirectoryEntry *dir_entry;
3756   boolean valid_entry_found = FALSE;
3757
3758   if ((dir = openDirectory(base_directory)) == NULL)
3759   {
3760     // display error if directory is main "options.graphics_directory" etc.
3761     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3762       Warn("cannot read directory '%s'", base_directory);
3763
3764     return;
3765   }
3766
3767   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
3768   {
3769     char *directory_name = dir_entry->basename;
3770     char *directory_path = getPath2(base_directory, directory_name);
3771
3772     // skip directory entries for current and parent directory
3773     if (strEqual(directory_name, ".") ||
3774         strEqual(directory_name, ".."))
3775     {
3776       free(directory_path);
3777
3778       continue;
3779     }
3780
3781     // skip directory entries which are not a directory
3782     if (!dir_entry->is_directory)                       // not a directory
3783     {
3784       free(directory_path);
3785
3786       continue;
3787     }
3788
3789     free(directory_path);
3790
3791     // check if this directory contains artwork with or without config file
3792     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3793                                                         base_directory,
3794                                                         directory_name, type);
3795   }
3796
3797   closeDirectory(dir);
3798
3799   // check if this directory directly contains artwork itself
3800   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3801                                                       base_directory, ".",
3802                                                       type);
3803   if (!valid_entry_found)
3804     Warn("cannot find any valid artwork in directory '%s'", base_directory);
3805 }
3806
3807 static TreeInfo *getDummyArtworkInfo(int type)
3808 {
3809   // this is only needed when there is completely no artwork available
3810   TreeInfo *artwork_new = newTreeInfo();
3811
3812   setTreeInfoToDefaults(artwork_new, type);
3813
3814   setString(&artwork_new->subdir,   UNDEFINED_FILENAME);
3815   setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3816   setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3817
3818   setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
3819   setString(&artwork_new->name,         UNDEFINED_FILENAME);
3820   setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3821
3822   return artwork_new;
3823 }
3824
3825 void SetCurrentArtwork(int type)
3826 {
3827   ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3828   ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3829   char *setup_set = SETUP_ARTWORK_SET(setup, type);
3830   char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3831
3832   // set current artwork to artwork configured in setup menu
3833   *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3834
3835   // if not found, set current artwork to default artwork
3836   if (*current_ptr == NULL)
3837     *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3838
3839   // if not found, set current artwork to first artwork in tree
3840   if (*current_ptr == NULL)
3841     *current_ptr = getFirstValidTreeInfoEntry(first_node);
3842 }
3843
3844 void ChangeCurrentArtworkIfNeeded(int type)
3845 {
3846   char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3847   char *setup_set = SETUP_ARTWORK_SET(setup, type);
3848
3849   if (!strEqual(current_identifier, setup_set))
3850     SetCurrentArtwork(type);
3851 }
3852
3853 void LoadArtworkInfo(void)
3854 {
3855   LoadArtworkInfoCache();
3856
3857   DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3858
3859   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3860                                 options.graphics_directory,
3861                                 TREE_TYPE_GRAPHICS_DIR);
3862   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3863                                 getUserGraphicsDir(),
3864                                 TREE_TYPE_GRAPHICS_DIR);
3865
3866   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3867                                 options.sounds_directory,
3868                                 TREE_TYPE_SOUNDS_DIR);
3869   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3870                                 getUserSoundsDir(),
3871                                 TREE_TYPE_SOUNDS_DIR);
3872
3873   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3874                                 options.music_directory,
3875                                 TREE_TYPE_MUSIC_DIR);
3876   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3877                                 getUserMusicDir(),
3878                                 TREE_TYPE_MUSIC_DIR);
3879
3880   if (artwork.gfx_first == NULL)
3881     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3882   if (artwork.snd_first == NULL)
3883     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3884   if (artwork.mus_first == NULL)
3885     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3886
3887   // before sorting, the first entries will be from the user directory
3888   SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
3889   SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
3890   SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
3891
3892   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3893   artwork.snd_current_identifier = artwork.snd_current->identifier;
3894   artwork.mus_current_identifier = artwork.mus_current->identifier;
3895
3896 #if ENABLE_UNUSED_CODE
3897   Debug("setup:LoadArtworkInfo", "graphics set == %s",
3898         artwork.gfx_current_identifier);
3899   Debug("setup:LoadArtworkInfo", "sounds set == %s",
3900         artwork.snd_current_identifier);
3901   Debug("setup:LoadArtworkInfo", "music set == %s",
3902         artwork.mus_current_identifier);
3903 #endif
3904
3905   sortTreeInfo(&artwork.gfx_first);
3906   sortTreeInfo(&artwork.snd_first);
3907   sortTreeInfo(&artwork.mus_first);
3908
3909 #if ENABLE_UNUSED_CODE
3910   dumpTreeInfo(artwork.gfx_first, 0);
3911   dumpTreeInfo(artwork.snd_first, 0);
3912   dumpTreeInfo(artwork.mus_first, 0);
3913 #endif
3914 }
3915
3916 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3917                                          ArtworkDirTree *node_parent,
3918                                          LevelDirTree *level_node)
3919 {
3920   int type = (*artwork_node)->type;
3921
3922   // recursively check all level directories for artwork sub-directories
3923
3924   while (level_node)
3925   {
3926     // check all tree entries for artwork, but skip parent link entries
3927     if (!level_node->parent_link)
3928     {
3929       TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3930       boolean cached = (artwork_new != NULL);
3931
3932       if (cached)
3933       {
3934         pushTreeInfo(artwork_node, artwork_new);
3935       }
3936       else
3937       {
3938         TreeInfo *topnode_last = *artwork_node;
3939         char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3940                               ARTWORK_DIRECTORY(type));
3941
3942         LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3943
3944         if (topnode_last != *artwork_node)      // check for newly added node
3945         {
3946           artwork_new = *artwork_node;
3947
3948           setString(&artwork_new->identifier,   level_node->subdir);
3949           setString(&artwork_new->name,         level_node->name);
3950           setString(&artwork_new->name_sorting, level_node->name_sorting);
3951
3952           artwork_new->sort_priority = level_node->sort_priority;
3953           artwork_new->color = LEVELCOLOR(artwork_new);
3954         }
3955
3956         free(path);
3957       }
3958
3959       // insert artwork info (from old cache or filesystem) into new cache
3960       if (artwork_new != NULL)
3961         setArtworkInfoCacheEntry(artwork_new, level_node, type);
3962     }
3963
3964     DrawInitText(level_node->name, 150, FC_YELLOW);
3965
3966     if (level_node->node_group != NULL)
3967     {
3968       TreeInfo *artwork_new = newTreeInfo();
3969
3970       if (node_parent)
3971         setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3972       else
3973         setTreeInfoToDefaults(artwork_new, type);
3974
3975       artwork_new->level_group = TRUE;
3976
3977       setString(&artwork_new->identifier,   level_node->subdir);
3978       setString(&artwork_new->name,         level_node->name);
3979       setString(&artwork_new->name_sorting, level_node->name_sorting);
3980
3981       pushTreeInfo(artwork_node, artwork_new);
3982
3983       // create node to link back to current custom artwork directory
3984       createParentTreeInfoNode(artwork_new);
3985
3986       // recursively step into sub-directory and look for more custom artwork
3987       LoadArtworkInfoFromLevelInfo(&artwork_new->node_group, artwork_new,
3988                                    level_node->node_group);
3989
3990       // if sub-tree has no custom artwork at all, remove it
3991       if (artwork_new->node_group->next == NULL)
3992       {
3993         *artwork_node = artwork_new->next;
3994         artwork_new->next = NULL;
3995
3996         freeTreeInfo(artwork_new);
3997       }
3998     }
3999
4000     level_node = level_node->next;
4001   }
4002 }
4003
4004 void LoadLevelArtworkInfo(void)
4005 {
4006   print_timestamp_init("LoadLevelArtworkInfo");
4007
4008   DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4009
4010   print_timestamp_time("DrawTimeText");
4011
4012   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, NULL, leveldir_first_all);
4013   print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4014   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, NULL, leveldir_first_all);
4015   print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4016   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, NULL, leveldir_first_all);
4017   print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4018
4019   SaveArtworkInfoCache();
4020
4021   print_timestamp_time("SaveArtworkInfoCache");
4022
4023   // needed for reloading level artwork not known at ealier stage
4024   ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4025   ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4026   ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4027
4028   print_timestamp_time("getTreeInfoFromIdentifier");
4029
4030   sortTreeInfo(&artwork.gfx_first);
4031   sortTreeInfo(&artwork.snd_first);
4032   sortTreeInfo(&artwork.mus_first);
4033
4034   print_timestamp_time("sortTreeInfo");
4035
4036 #if ENABLE_UNUSED_CODE
4037   dumpTreeInfo(artwork.gfx_first, 0);
4038   dumpTreeInfo(artwork.snd_first, 0);
4039   dumpTreeInfo(artwork.mus_first, 0);
4040 #endif
4041
4042   print_timestamp_done("LoadLevelArtworkInfo");
4043 }
4044
4045 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4046                                        char *tree_subdir_new, int type)
4047 {
4048   if (tree_node_old == NULL)
4049   {
4050     if (type == TREE_TYPE_LEVEL_DIR)
4051     {
4052       // get level info tree node of personal user level set
4053       tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4054
4055       // this may happen if "setup.internal.create_user_levelset" is FALSE
4056       // or if file "levelinfo.conf" is missing in personal user level set
4057       if (tree_node_old == NULL)
4058         tree_node_old = leveldir_first->node_group;
4059     }
4060     else
4061     {
4062       // get artwork info tree node of first artwork set
4063       tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4064     }
4065   }
4066
4067   if (tree_dir == NULL)
4068     tree_dir = TREE_USERDIR(type);
4069
4070   if (tree_node_old   == NULL ||
4071       tree_dir        == NULL ||
4072       tree_subdir_new == NULL)          // should not happen
4073     return FALSE;
4074
4075   int draw_deactivation_mask = GetDrawDeactivationMask();
4076
4077   // override draw deactivation mask (temporarily disable drawing)
4078   SetDrawDeactivationMask(REDRAW_ALL);
4079
4080   if (type == TREE_TYPE_LEVEL_DIR)
4081   {
4082     // load new level set config and add it next to first user level set
4083     LoadLevelInfoFromLevelConf(&tree_node_old->next,
4084                                tree_node_old->node_parent,
4085                                tree_dir, tree_subdir_new);
4086   }
4087   else
4088   {
4089     // load new artwork set config and add it next to first artwork set
4090     LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4091                                    tree_node_old->node_parent,
4092                                    tree_dir, tree_subdir_new, type);
4093   }
4094
4095   // set draw deactivation mask to previous value
4096   SetDrawDeactivationMask(draw_deactivation_mask);
4097
4098   // get first node of level or artwork info tree
4099   TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4100
4101   // get tree info node of newly added level or artwork set
4102   TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4103                                                       tree_subdir_new);
4104
4105   if (tree_node_new == NULL)            // should not happen
4106     return FALSE;
4107
4108   // correct top link and parent node link of newly created tree node
4109   tree_node_new->node_top    = tree_node_old->node_top;
4110   tree_node_new->node_parent = tree_node_old->node_parent;
4111
4112   // sort tree info to adjust position of newly added tree set
4113   sortTreeInfo(tree_node_first);
4114
4115   return TRUE;
4116 }
4117
4118 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4119                           char *tree_subdir_new, int type)
4120 {
4121   if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4122     Fail("internal tree info structure corrupted -- aborting");
4123 }
4124
4125 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4126 {
4127   AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4128 }
4129
4130 char *getArtworkIdentifierForUserLevelSet(int type)
4131 {
4132   char *classic_artwork_set = getClassicArtworkSet(type);
4133
4134   // check for custom artwork configured in "levelinfo.conf"
4135   char *leveldir_artwork_set =
4136     *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4137   boolean has_leveldir_artwork_set =
4138     (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4139                                                classic_artwork_set));
4140
4141   // check for custom artwork in sub-directory "graphics" etc.
4142   TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4143   char *leveldir_identifier = leveldir_current->identifier;
4144   boolean has_artwork_subdir =
4145     (getTreeInfoFromIdentifier(artwork_first_node,
4146                                leveldir_identifier) != NULL);
4147
4148   return (has_leveldir_artwork_set ? leveldir_artwork_set :
4149           has_artwork_subdir       ? leveldir_identifier :
4150           classic_artwork_set);
4151 }
4152
4153 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4154 {
4155   char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4156   TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4157   TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4158
4159   if (ti == NULL)
4160   {
4161     ti = getTreeInfoFromIdentifier(artwork_first_node,
4162                                    ARTWORK_DEFAULT_SUBDIR(type));
4163     if (ti == NULL)
4164       Fail("cannot find default graphics -- should not happen");
4165   }
4166
4167   return ti;
4168 }
4169
4170 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4171 {
4172   char *graphics_set =
4173     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4174   char *sounds_set =
4175     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4176   char *music_set =
4177     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4178
4179   return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4180           !strEqual(sounds_set,   SND_CLASSIC_SUBDIR) ||
4181           !strEqual(music_set,    MUS_CLASSIC_SUBDIR));
4182 }
4183
4184 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4185                            char *level_author, int num_levels)
4186 {
4187   char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4188   char *filename_tmp = getStringCat2(filename, ".tmp");
4189   FILE *file = NULL;
4190   FILE *file_tmp = NULL;
4191   char line[MAX_LINE_LEN];
4192   boolean success = FALSE;
4193   LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4194                                                      level_subdir);
4195   // update values in level directory tree
4196
4197   if (level_name != NULL)
4198     setString(&leveldir->name, level_name);
4199
4200   if (level_author != NULL)
4201     setString(&leveldir->author, level_author);
4202
4203   if (num_levels != -1)
4204     leveldir->levels = num_levels;
4205
4206   // update values that depend on other values
4207
4208   setString(&leveldir->name_sorting, leveldir->name);
4209
4210   leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4211
4212   // sort order of level sets may have changed
4213   sortTreeInfo(&leveldir_first);
4214
4215   if ((file     = fopen(filename,     MODE_READ)) &&
4216       (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4217   {
4218     while (fgets(line, MAX_LINE_LEN, file))
4219     {
4220       if (strPrefix(line, "name:") && level_name != NULL)
4221         fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4222       else if (strPrefix(line, "author:") && level_author != NULL)
4223         fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4224       else if (strPrefix(line, "levels:") && num_levels != -1)
4225         fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4226       else
4227         fputs(line, file_tmp);
4228     }
4229
4230     success = TRUE;
4231   }
4232
4233   if (file)
4234     fclose(file);
4235
4236   if (file_tmp)
4237     fclose(file_tmp);
4238
4239   if (success)
4240     success = (rename(filename_tmp, filename) == 0);
4241
4242   free(filename);
4243   free(filename_tmp);
4244
4245   return success;
4246 }
4247
4248 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4249                            char *level_author, int num_levels,
4250                            boolean use_artwork_set)
4251 {
4252   LevelDirTree *level_info;
4253   char *filename;
4254   FILE *file;
4255   int i;
4256
4257   // create user level sub-directory, if needed
4258   createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4259
4260   filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4261
4262   if (!(file = fopen(filename, MODE_WRITE)))
4263   {
4264     Warn("cannot write level info file '%s'", filename);
4265
4266     free(filename);
4267
4268     return FALSE;
4269   }
4270
4271   level_info = newTreeInfo();
4272
4273   // always start with reliable default values
4274   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4275
4276   setString(&level_info->name, level_name);
4277   setString(&level_info->author, level_author);
4278   level_info->levels = num_levels;
4279   level_info->first_level = 1;
4280   level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4281   level_info->readonly = FALSE;
4282
4283   if (use_artwork_set)
4284   {
4285     level_info->graphics_set =
4286       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4287     level_info->sounds_set =
4288       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4289     level_info->music_set =
4290       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4291   }
4292
4293   token_value_position = TOKEN_VALUE_POSITION_SHORT;
4294
4295   fprintFileHeader(file, LEVELINFO_FILENAME);
4296
4297   ldi = *level_info;
4298   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4299   {
4300     if (i == LEVELINFO_TOKEN_NAME ||
4301         i == LEVELINFO_TOKEN_AUTHOR ||
4302         i == LEVELINFO_TOKEN_LEVELS ||
4303         i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4304         i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4305         i == LEVELINFO_TOKEN_READONLY ||
4306         (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4307                              i == LEVELINFO_TOKEN_SOUNDS_SET ||
4308                              i == LEVELINFO_TOKEN_MUSIC_SET)))
4309       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4310
4311     // just to make things nicer :)
4312     if (i == LEVELINFO_TOKEN_AUTHOR ||
4313         i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4314         (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4315       fprintf(file, "\n");      
4316   }
4317
4318   token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4319
4320   fclose(file);
4321
4322   SetFilePermissions(filename, PERMS_PRIVATE);
4323
4324   freeTreeInfo(level_info);
4325   free(filename);
4326
4327   return TRUE;
4328 }
4329
4330 static void SaveUserLevelInfo(void)
4331 {
4332   CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4333 }
4334
4335 char *getSetupValue(int type, void *value)
4336 {
4337   static char value_string[MAX_LINE_LEN];
4338
4339   if (value == NULL)
4340     return NULL;
4341
4342   switch (type)
4343   {
4344     case TYPE_BOOLEAN:
4345       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4346       break;
4347
4348     case TYPE_SWITCH:
4349       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4350       break;
4351
4352     case TYPE_SWITCH3:
4353       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
4354                             *(int *)value == FALSE ? "off" : "on"));
4355       break;
4356
4357     case TYPE_YES_NO:
4358       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4359       break;
4360
4361     case TYPE_YES_NO_AUTO:
4362       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
4363                             *(int *)value == FALSE ? "no" : "yes"));
4364       break;
4365
4366     case TYPE_ECS_AGA:
4367       strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4368       break;
4369
4370     case TYPE_KEY:
4371       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4372       break;
4373
4374     case TYPE_KEY_X11:
4375       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4376       break;
4377
4378     case TYPE_INTEGER:
4379       sprintf(value_string, "%d", *(int *)value);
4380       break;
4381
4382     case TYPE_STRING:
4383       if (*(char **)value == NULL)
4384         return NULL;
4385
4386       strcpy(value_string, *(char **)value);
4387       break;
4388
4389     case TYPE_PLAYER:
4390       sprintf(value_string, "player_%d", *(int *)value + 1);
4391       break;
4392
4393     default:
4394       value_string[0] = '\0';
4395       break;
4396   }
4397
4398   if (type & TYPE_GHOSTED)
4399     strcpy(value_string, "n/a");
4400
4401   return value_string;
4402 }
4403
4404 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4405 {
4406   int i;
4407   char *line;
4408   static char token_string[MAX_LINE_LEN];
4409   int token_type = token_info[token_nr].type;
4410   void *setup_value = token_info[token_nr].value;
4411   char *token_text = token_info[token_nr].text;
4412   char *value_string = getSetupValue(token_type, setup_value);
4413
4414   // build complete token string
4415   sprintf(token_string, "%s%s", prefix, token_text);
4416
4417   // build setup entry line
4418   line = getFormattedSetupEntry(token_string, value_string);
4419
4420   if (token_type == TYPE_KEY_X11)
4421   {
4422     Key key = *(Key *)setup_value;
4423     char *keyname = getKeyNameFromKey(key);
4424
4425     // add comment, if useful
4426     if (!strEqual(keyname, "(undefined)") &&
4427         !strEqual(keyname, "(unknown)"))
4428     {
4429       // add at least one whitespace
4430       strcat(line, " ");
4431       for (i = strlen(line); i < token_comment_position; i++)
4432         strcat(line, " ");
4433
4434       strcat(line, "# ");
4435       strcat(line, keyname);
4436     }
4437   }
4438
4439   return line;
4440 }
4441
4442 static void InitLastPlayedLevels_ParentNode(void)
4443 {
4444   LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4445   LevelDirTree *leveldir_new = NULL;
4446
4447   // check if parent node for last played levels already exists
4448   if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4449     return;
4450
4451   leveldir_new = newTreeInfo();
4452
4453   setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4454
4455   leveldir_new->level_group = TRUE;
4456
4457   setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4458   setString(&leveldir_new->name, "<< (last played level sets)");
4459
4460   pushTreeInfo(leveldir_top, leveldir_new);
4461
4462   // create node to link back to current level directory
4463   createParentTreeInfoNode(leveldir_new);
4464 }
4465
4466 void UpdateLastPlayedLevels_TreeInfo(void)
4467 {
4468   char **last_level_series = setup.level_setup.last_level_series;
4469   boolean reset_leveldir_current = FALSE;
4470   LevelDirTree *leveldir_last;
4471   TreeInfo **node_new = NULL;
4472   int i;
4473
4474   if (last_level_series[0] == NULL)
4475     return;
4476
4477   InitLastPlayedLevels_ParentNode();
4478
4479   // check if current level set is from "last played" sub-tree to be rebuilt
4480   reset_leveldir_current = strEqual(leveldir_current->node_parent->identifier,
4481                                     TOKEN_STR_LAST_LEVEL_SERIES);
4482
4483   leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4484                                                TOKEN_STR_LAST_LEVEL_SERIES,
4485                                                TRUE);
4486   if (leveldir_last == NULL)
4487     return;
4488
4489   node_new = &leveldir_last->node_group->next;
4490
4491   freeTreeInfo(*node_new);
4492
4493   for (i = 0; last_level_series[i] != NULL; i++)
4494   {
4495     LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4496                                                         last_level_series[i]);
4497
4498     *node_new = getTreeInfoCopy(node_last);     // copy complete node
4499
4500     (*node_new)->node_top = &leveldir_first;    // correct top node link
4501     (*node_new)->node_parent = leveldir_last;   // correct parent node link
4502
4503     (*node_new)->node_group = NULL;
4504     (*node_new)->next = NULL;
4505
4506     (*node_new)->cl_first = -1;                 // force setting tree cursor
4507
4508     node_new = &((*node_new)->next);
4509   }
4510
4511   if (reset_leveldir_current)
4512     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4513                                                  last_level_series[0]);
4514 }
4515
4516 static void UpdateLastPlayedLevels_List(void)
4517 {
4518   char **last_level_series = setup.level_setup.last_level_series;
4519   int pos = MAX_LEVELDIR_HISTORY - 1;
4520   int i;
4521
4522   // search for potentially already existing entry in list of level sets
4523   for (i = 0; last_level_series[i] != NULL; i++)
4524     if (strEqual(last_level_series[i], leveldir_current->identifier))
4525       pos = i;
4526
4527   // move list of level sets one entry down (using potentially free entry)
4528   for (i = pos; i > 0; i--)
4529     setString(&last_level_series[i], last_level_series[i - 1]);
4530
4531   // put last played level set at top position
4532   setString(&last_level_series[0], leveldir_current->identifier);
4533 }
4534
4535 void LoadLevelSetup_LastSeries(void)
4536 {
4537   // --------------------------------------------------------------------------
4538   // ~/.<program>/levelsetup.conf
4539   // --------------------------------------------------------------------------
4540
4541   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4542   SetupFileHash *level_setup_hash = NULL;
4543   int pos = 0;
4544   int i;
4545
4546   // always start with reliable default values
4547   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4548
4549   // start with empty history of last played level sets
4550   setString(&setup.level_setup.last_level_series[0], NULL);
4551
4552   if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4553   {
4554     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4555                                                  DEFAULT_LEVELSET);
4556     if (leveldir_current == NULL)
4557       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4558   }
4559
4560   if ((level_setup_hash = loadSetupFileHash(filename)))
4561   {
4562     char *last_level_series =
4563       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4564
4565     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4566                                                  last_level_series);
4567     if (leveldir_current == NULL)
4568       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4569
4570     for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4571     {
4572       char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4573       LevelDirTree *leveldir_last;
4574
4575       sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4576
4577       last_level_series = getHashEntry(level_setup_hash, token);
4578
4579       leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4580                                                 last_level_series);
4581       if (leveldir_last != NULL)
4582         setString(&setup.level_setup.last_level_series[pos++],
4583                   last_level_series);
4584     }
4585
4586     setString(&setup.level_setup.last_level_series[pos], NULL);
4587
4588     freeSetupFileHash(level_setup_hash);
4589   }
4590   else
4591   {
4592     Debug("setup", "using default setup values");
4593   }
4594
4595   free(filename);
4596 }
4597
4598 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4599 {
4600   // --------------------------------------------------------------------------
4601   // ~/.<program>/levelsetup.conf
4602   // --------------------------------------------------------------------------
4603
4604   // check if the current level directory structure is available at this point
4605   if (leveldir_current == NULL)
4606     return;
4607
4608   char **last_level_series = setup.level_setup.last_level_series;
4609   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4610   FILE *file;
4611   int i;
4612
4613   InitUserDataDirectory();
4614
4615   UpdateLastPlayedLevels_List();
4616
4617   if (!(file = fopen(filename, MODE_WRITE)))
4618   {
4619     Warn("cannot write setup file '%s'", filename);
4620
4621     free(filename);
4622
4623     return;
4624   }
4625
4626   fprintFileHeader(file, LEVELSETUP_FILENAME);
4627
4628   if (deactivate_last_level_series)
4629     fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4630
4631   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4632                                                leveldir_current->identifier));
4633
4634   for (i = 0; last_level_series[i] != NULL; i++)
4635   {
4636     char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4637
4638     sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4639
4640     fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4641   }
4642
4643   fclose(file);
4644
4645   SetFilePermissions(filename, PERMS_PRIVATE);
4646
4647   free(filename);
4648 }
4649
4650 void SaveLevelSetup_LastSeries(void)
4651 {
4652   SaveLevelSetup_LastSeries_Ext(FALSE);
4653 }
4654
4655 void SaveLevelSetup_LastSeries_Deactivate(void)
4656 {
4657   SaveLevelSetup_LastSeries_Ext(TRUE);
4658 }
4659
4660 static void checkSeriesInfo(void)
4661 {
4662   static char *level_directory = NULL;
4663   Directory *dir;
4664 #if 0
4665   DirectoryEntry *dir_entry;
4666 #endif
4667
4668   checked_free(level_directory);
4669
4670   // check for more levels besides the 'levels' field of 'levelinfo.conf'
4671
4672   level_directory = getPath2((leveldir_current->in_user_dir ?
4673                               getUserLevelDir(NULL) :
4674                               options.level_directory),
4675                              leveldir_current->fullpath);
4676
4677   if ((dir = openDirectory(level_directory)) == NULL)
4678   {
4679     Warn("cannot read level directory '%s'", level_directory);
4680
4681     return;
4682   }
4683
4684 #if 0
4685   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
4686   {
4687     if (strlen(dir_entry->basename) > 4 &&
4688         dir_entry->basename[3] == '.' &&
4689         strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4690     {
4691       char levelnum_str[4];
4692       int levelnum_value;
4693
4694       strncpy(levelnum_str, dir_entry->basename, 3);
4695       levelnum_str[3] = '\0';
4696
4697       levelnum_value = atoi(levelnum_str);
4698
4699       if (levelnum_value < leveldir_current->first_level)
4700       {
4701         Warn("additional level %d found", levelnum_value);
4702
4703         leveldir_current->first_level = levelnum_value;
4704       }
4705       else if (levelnum_value > leveldir_current->last_level)
4706       {
4707         Warn("additional level %d found", levelnum_value);
4708
4709         leveldir_current->last_level = levelnum_value;
4710       }
4711     }
4712   }
4713 #endif
4714
4715   closeDirectory(dir);
4716 }
4717
4718 void LoadLevelSetup_SeriesInfo(void)
4719 {
4720   char *filename;
4721   SetupFileHash *level_setup_hash = NULL;
4722   char *level_subdir = leveldir_current->subdir;
4723   int i;
4724
4725   // always start with reliable default values
4726   level_nr = leveldir_current->first_level;
4727
4728   for (i = 0; i < MAX_LEVELS; i++)
4729   {
4730     LevelStats_setPlayed(i, 0);
4731     LevelStats_setSolved(i, 0);
4732   }
4733
4734   checkSeriesInfo();
4735
4736   // --------------------------------------------------------------------------
4737   // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4738   // --------------------------------------------------------------------------
4739
4740   level_subdir = leveldir_current->subdir;
4741
4742   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4743
4744   if ((level_setup_hash = loadSetupFileHash(filename)))
4745   {
4746     char *token_value;
4747
4748     // get last played level in this level set
4749
4750     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4751
4752     if (token_value)
4753     {
4754       level_nr = atoi(token_value);
4755
4756       if (level_nr < leveldir_current->first_level)
4757         level_nr = leveldir_current->first_level;
4758       if (level_nr > leveldir_current->last_level)
4759         level_nr = leveldir_current->last_level;
4760     }
4761
4762     // get handicap level in this level set
4763
4764     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4765
4766     if (token_value)
4767     {
4768       int level_nr = atoi(token_value);
4769
4770       if (level_nr < leveldir_current->first_level)
4771         level_nr = leveldir_current->first_level;
4772       if (level_nr > leveldir_current->last_level + 1)
4773         level_nr = leveldir_current->last_level;
4774
4775       if (leveldir_current->user_defined || !leveldir_current->handicap)
4776         level_nr = leveldir_current->last_level;
4777
4778       leveldir_current->handicap_level = level_nr;
4779     }
4780
4781     // get number of played and solved levels in this level set
4782
4783     BEGIN_HASH_ITERATION(level_setup_hash, itr)
4784     {
4785       char *token = HASH_ITERATION_TOKEN(itr);
4786       char *value = HASH_ITERATION_VALUE(itr);
4787
4788       if (strlen(token) == 3 &&
4789           token[0] >= '0' && token[0] <= '9' &&
4790           token[1] >= '0' && token[1] <= '9' &&
4791           token[2] >= '0' && token[2] <= '9')
4792       {
4793         int level_nr = atoi(token);
4794
4795         if (value != NULL)
4796           LevelStats_setPlayed(level_nr, atoi(value));  // read 1st column
4797
4798         value = strchr(value, ' ');
4799
4800         if (value != NULL)
4801           LevelStats_setSolved(level_nr, atoi(value));  // read 2nd column
4802       }
4803     }
4804     END_HASH_ITERATION(hash, itr)
4805
4806     freeSetupFileHash(level_setup_hash);
4807   }
4808   else
4809   {
4810     Debug("setup", "using default setup values");
4811   }
4812
4813   free(filename);
4814 }
4815
4816 void SaveLevelSetup_SeriesInfo(void)
4817 {
4818   char *filename;
4819   char *level_subdir = leveldir_current->subdir;
4820   char *level_nr_str = int2str(level_nr, 0);
4821   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4822   FILE *file;
4823   int i;
4824
4825   // --------------------------------------------------------------------------
4826   // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4827   // --------------------------------------------------------------------------
4828
4829   InitLevelSetupDirectory(level_subdir);
4830
4831   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4832
4833   if (!(file = fopen(filename, MODE_WRITE)))
4834   {
4835     Warn("cannot write setup file '%s'", filename);
4836
4837     free(filename);
4838
4839     return;
4840   }
4841
4842   fprintFileHeader(file, LEVELSETUP_FILENAME);
4843
4844   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4845                                                level_nr_str));
4846   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4847                                                  handicap_level_str));
4848
4849   for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4850        i++)
4851   {
4852     if (LevelStats_getPlayed(i) > 0 ||
4853         LevelStats_getSolved(i) > 0)
4854     {
4855       char token[16];
4856       char value[16];
4857
4858       sprintf(token, "%03d", i);
4859       sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4860
4861       fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4862     }
4863   }
4864
4865   fclose(file);
4866
4867   SetFilePermissions(filename, PERMS_PRIVATE);
4868
4869   free(filename);
4870 }
4871
4872 int LevelStats_getPlayed(int nr)
4873 {
4874   return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4875 }
4876
4877 int LevelStats_getSolved(int nr)
4878 {
4879   return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4880 }
4881
4882 void LevelStats_setPlayed(int nr, int value)
4883 {
4884   if (nr >= 0 && nr < MAX_LEVELS)
4885     level_stats[nr].played = value;
4886 }
4887
4888 void LevelStats_setSolved(int nr, int value)
4889 {
4890   if (nr >= 0 && nr < MAX_LEVELS)
4891     level_stats[nr].solved = value;
4892 }
4893
4894 void LevelStats_incPlayed(int nr)
4895 {
4896   if (nr >= 0 && nr < MAX_LEVELS)
4897     level_stats[nr].played++;
4898 }
4899
4900 void LevelStats_incSolved(int nr)
4901 {
4902   if (nr >= 0 && nr < MAX_LEVELS)
4903     level_stats[nr].solved++;
4904 }
4905
4906 void LoadUserSetup(void)
4907 {
4908   // --------------------------------------------------------------------------
4909   // ~/.<program>/usersetup.conf
4910   // --------------------------------------------------------------------------
4911
4912   char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
4913   SetupFileHash *user_setup_hash = NULL;
4914
4915   // always start with reliable default values
4916   user.nr = 0;
4917
4918   if ((user_setup_hash = loadSetupFileHash(filename)))
4919   {
4920     char *token_value;
4921
4922     // get last selected user number
4923     token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
4924
4925     if (token_value)
4926       user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
4927
4928     freeSetupFileHash(user_setup_hash);
4929   }
4930   else
4931   {
4932     Debug("setup", "using default setup values");
4933   }
4934
4935   free(filename);
4936 }
4937
4938 void SaveUserSetup(void)
4939 {
4940   // --------------------------------------------------------------------------
4941   // ~/.<program>/usersetup.conf
4942   // --------------------------------------------------------------------------
4943
4944   char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
4945   FILE *file;
4946
4947   InitMainUserDataDirectory();
4948
4949   if (!(file = fopen(filename, MODE_WRITE)))
4950   {
4951     Warn("cannot write setup file '%s'", filename);
4952
4953     free(filename);
4954
4955     return;
4956   }
4957
4958   fprintFileHeader(file, USERSETUP_FILENAME);
4959
4960   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
4961                                                i_to_a(user.nr)));
4962   fclose(file);
4963
4964   SetFilePermissions(filename, PERMS_PRIVATE);
4965
4966   free(filename);
4967 }