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