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