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