added variable to store if request dialog is active or opening/closing
[rocksndiamonds.git] / src / libgame / setup.c
1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // setup.c
10 // ============================================================================
11
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <dirent.h>
15 #include <string.h>
16 #include <unistd.h>
17 #include <errno.h>
18
19 #include "platform.h"
20
21 #include "setup.h"
22 #include "joystick.h"
23 #include "text.h"
24 #include "misc.h"
25 #include "hash.h"
26 #include "zip/miniunz.h"
27
28
29 #define ENABLE_UNUSED_CODE      FALSE   // for currently unused functions
30 #define DEBUG_NO_CONFIG_FILE    FALSE   // for extra-verbose debug output
31
32 #define NUM_LEVELCLASS_DESC     8
33
34 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
35 {
36   "Tutorial Levels",
37   "Classic Originals",
38   "Contributions",
39   "Private Levels",
40   "Boulderdash",
41   "Emerald Mine",
42   "Supaplex",
43   "DX Boulderdash"
44 };
45
46
47 #define LEVELCOLOR(n)   (IS_LEVELCLASS_TUTORIAL(n) ?            FC_BLUE :    \
48                          IS_LEVELCLASS_CLASSICS(n) ?            FC_RED :     \
49                          IS_LEVELCLASS_BD(n) ?                  FC_YELLOW :  \
50                          IS_LEVELCLASS_EM(n) ?                  FC_YELLOW :  \
51                          IS_LEVELCLASS_SP(n) ?                  FC_YELLOW :  \
52                          IS_LEVELCLASS_DX(n) ?                  FC_YELLOW :  \
53                          IS_LEVELCLASS_SB(n) ?                  FC_YELLOW :  \
54                          IS_LEVELCLASS_CONTRIB(n) ?             FC_GREEN :   \
55                          IS_LEVELCLASS_PRIVATE(n) ?             FC_RED :     \
56                          FC_BLUE)
57
58 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ?            0 :     \
59                          IS_LEVELCLASS_CLASSICS(n) ?            1 :     \
60                          IS_LEVELCLASS_BD(n) ?                  2 :     \
61                          IS_LEVELCLASS_EM(n) ?                  3 :     \
62                          IS_LEVELCLASS_SP(n) ?                  4 :     \
63                          IS_LEVELCLASS_DX(n) ?                  5 :     \
64                          IS_LEVELCLASS_SB(n) ?                  6 :     \
65                          IS_LEVELCLASS_CONTRIB(n) ?             7 :     \
66                          IS_LEVELCLASS_PRIVATE(n) ?             8 :     \
67                          9)
68
69 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ?          FC_RED :     \
70                          IS_ARTWORKCLASS_CONTRIB(n) ?           FC_GREEN :   \
71                          IS_ARTWORKCLASS_PRIVATE(n) ?           FC_RED :     \
72                          IS_ARTWORKCLASS_LEVEL(n) ?             FC_YELLOW :  \
73                          FC_BLUE)
74
75 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ?        0 :     \
76                            IS_ARTWORKCLASS_LEVEL(n) ?           1 :     \
77                            IS_ARTWORKCLASS_CONTRIB(n) ?         2 :     \
78                            IS_ARTWORKCLASS_PRIVATE(n) ?         3 :     \
79                            9)
80
81 #define TOKEN_VALUE_POSITION_SHORT              32
82 #define TOKEN_VALUE_POSITION_DEFAULT            40
83 #define TOKEN_COMMENT_POSITION_DEFAULT          60
84
85 #define MAX_COOKIE_LEN                          256
86
87
88 static void setTreeInfoToDefaults(TreeInfo *, int);
89 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
90 static int compareTreeInfoEntries(const void *, const void *);
91
92 static int token_value_position   = TOKEN_VALUE_POSITION_DEFAULT;
93 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
94
95 static SetupFileHash *artworkinfo_cache_old = NULL;
96 static SetupFileHash *artworkinfo_cache_new = NULL;
97 static SetupFileHash *optional_tokens_hash = NULL;
98 static boolean use_artworkinfo_cache = TRUE;
99
100
101 // ----------------------------------------------------------------------------
102 // file functions
103 // ----------------------------------------------------------------------------
104
105 static char *getLevelClassDescription(TreeInfo *ti)
106 {
107   int position = ti->sort_priority / 100;
108
109   if (position >= 0 && position < NUM_LEVELCLASS_DESC)
110     return levelclass_desc[position];
111   else
112     return "Unknown Level Class";
113 }
114
115 static char *getScoreDir(char *level_subdir)
116 {
117   static char *score_dir = NULL;
118   static char *score_level_dir = NULL;
119   char *score_subdir = SCORES_DIRECTORY;
120
121   if (score_dir == NULL)
122   {
123     if (program.global_scores)
124       score_dir = getPath2(getCommonDataDir(),       score_subdir);
125     else
126       score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
127   }
128
129   if (level_subdir != NULL)
130   {
131     checked_free(score_level_dir);
132
133     score_level_dir = getPath2(score_dir, level_subdir);
134
135     return score_level_dir;
136   }
137
138   return score_dir;
139 }
140
141 static char *getUserSubdir(int nr)
142 {
143   static char user_subdir[16] = { 0 };
144
145   sprintf(user_subdir, "%03d", nr);
146
147   return user_subdir;
148 }
149
150 static char *getUserDir(int nr)
151 {
152   static char *user_dir = NULL;
153   char *main_data_dir = getMainUserGameDataDir();
154   char *users_subdir = USERS_DIRECTORY;
155   char *user_subdir = getUserSubdir(nr);
156
157   checked_free(user_dir);
158
159   if (nr != -1)
160     user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
161   else
162     user_dir = getPath2(main_data_dir, users_subdir);
163
164   return user_dir;
165 }
166
167 static char *getLevelSetupDir(char *level_subdir)
168 {
169   static char *levelsetup_dir = NULL;
170   char *data_dir = getUserGameDataDir();
171   char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
172
173   checked_free(levelsetup_dir);
174
175   if (level_subdir != NULL)
176     levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
177   else
178     levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
179
180   return levelsetup_dir;
181 }
182
183 static char *getCacheDir(void)
184 {
185   static char *cache_dir = NULL;
186
187   if (cache_dir == NULL)
188     cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
189
190   return cache_dir;
191 }
192
193 static char *getNetworkDir(void)
194 {
195   static char *network_dir = NULL;
196
197   if (network_dir == NULL)
198     network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
199
200   return network_dir;
201 }
202
203 char *getLevelDirFromTreeInfo(TreeInfo *node)
204 {
205   static char *level_dir = NULL;
206
207   if (node == NULL)
208     return options.level_directory;
209
210   checked_free(level_dir);
211
212   level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
213                         options.level_directory), node->fullpath);
214
215   return level_dir;
216 }
217
218 char *getUserLevelDir(char *level_subdir)
219 {
220   static char *userlevel_dir = NULL;
221   char *data_dir = getMainUserGameDataDir();
222   char *userlevel_subdir = LEVELS_DIRECTORY;
223
224   checked_free(userlevel_dir);
225
226   if (level_subdir != NULL)
227     userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
228   else
229     userlevel_dir = getPath2(data_dir, userlevel_subdir);
230
231   return userlevel_dir;
232 }
233
234 char *getNetworkLevelDir(char *level_subdir)
235 {
236   static char *network_level_dir = NULL;
237   char *data_dir = getNetworkDir();
238   char *networklevel_subdir = LEVELS_DIRECTORY;
239
240   checked_free(network_level_dir);
241
242   if (level_subdir != NULL)
243     network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
244   else
245     network_level_dir = getPath2(data_dir, networklevel_subdir);
246
247   return network_level_dir;
248 }
249
250 char *getCurrentLevelDir(void)
251 {
252   return getLevelDirFromTreeInfo(leveldir_current);
253 }
254
255 char *getNewUserLevelSubdir(void)
256 {
257   static char *new_level_subdir = NULL;
258   char *subdir_prefix = getLoginName();
259   char subdir_suffix[10];
260   int max_suffix_number = 1000;
261   int i = 0;
262
263   while (++i < max_suffix_number)
264   {
265     sprintf(subdir_suffix, "_%d", i);
266
267     checked_free(new_level_subdir);
268     new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
269
270     if (!directoryExists(getUserLevelDir(new_level_subdir)))
271       break;
272   }
273
274   return new_level_subdir;
275 }
276
277 static char *getTapeDir(char *level_subdir)
278 {
279   static char *tape_dir = NULL;
280   char *data_dir = getUserGameDataDir();
281   char *tape_subdir = TAPES_DIRECTORY;
282
283   checked_free(tape_dir);
284
285   if (level_subdir != NULL)
286     tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
287   else
288     tape_dir = getPath2(data_dir, tape_subdir);
289
290   return tape_dir;
291 }
292
293 static char *getSolutionTapeDir(void)
294 {
295   static char *tape_dir = NULL;
296   char *data_dir = getCurrentLevelDir();
297   char *tape_subdir = TAPES_DIRECTORY;
298
299   checked_free(tape_dir);
300
301   tape_dir = getPath2(data_dir, tape_subdir);
302
303   return tape_dir;
304 }
305
306 static char *getDefaultGraphicsDir(char *graphics_subdir)
307 {
308   static char *graphics_dir = NULL;
309
310   if (graphics_subdir == NULL)
311     return options.graphics_directory;
312
313   checked_free(graphics_dir);
314
315   graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
316
317   return graphics_dir;
318 }
319
320 static char *getDefaultSoundsDir(char *sounds_subdir)
321 {
322   static char *sounds_dir = NULL;
323
324   if (sounds_subdir == NULL)
325     return options.sounds_directory;
326
327   checked_free(sounds_dir);
328
329   sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
330
331   return sounds_dir;
332 }
333
334 static char *getDefaultMusicDir(char *music_subdir)
335 {
336   static char *music_dir = NULL;
337
338   if (music_subdir == NULL)
339     return options.music_directory;
340
341   checked_free(music_dir);
342
343   music_dir = getPath2(options.music_directory, music_subdir);
344
345   return music_dir;
346 }
347
348 static char *getClassicArtworkSet(int type)
349 {
350   return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
351           type == TREE_TYPE_SOUNDS_DIR   ? SND_CLASSIC_SUBDIR :
352           type == TREE_TYPE_MUSIC_DIR    ? MUS_CLASSIC_SUBDIR : "");
353 }
354
355 static char *getClassicArtworkDir(int type)
356 {
357   return (type == TREE_TYPE_GRAPHICS_DIR ?
358           getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
359           type == TREE_TYPE_SOUNDS_DIR ?
360           getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
361           type == TREE_TYPE_MUSIC_DIR ?
362           getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
363 }
364
365 char *getUserGraphicsDir(void)
366 {
367   static char *usergraphics_dir = NULL;
368
369   if (usergraphics_dir == NULL)
370     usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
371
372   return usergraphics_dir;
373 }
374
375 char *getUserSoundsDir(void)
376 {
377   static char *usersounds_dir = NULL;
378
379   if (usersounds_dir == NULL)
380     usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
381
382   return usersounds_dir;
383 }
384
385 char *getUserMusicDir(void)
386 {
387   static char *usermusic_dir = NULL;
388
389   if (usermusic_dir == NULL)
390     usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
391
392   return usermusic_dir;
393 }
394
395 static char *getSetupArtworkDir(TreeInfo *ti)
396 {
397   static char *artwork_dir = NULL;
398
399   if (ti == NULL)
400     return NULL;
401
402   checked_free(artwork_dir);
403
404   artwork_dir = getPath2(ti->basepath, ti->fullpath);
405
406   return artwork_dir;
407 }
408
409 char *setLevelArtworkDir(TreeInfo *ti)
410 {
411   char **artwork_path_ptr, **artwork_set_ptr;
412   TreeInfo *level_artwork;
413
414   if (ti == NULL || leveldir_current == NULL)
415     return NULL;
416
417   artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
418   artwork_set_ptr  = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
419
420   checked_free(*artwork_path_ptr);
421
422   if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
423   {
424     *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
425   }
426   else
427   {
428     /*
429       No (or non-existing) artwork configured in "levelinfo.conf". This would
430       normally result in using the artwork configured in the setup menu. But
431       if an artwork subdirectory exists (which might contain custom artwork
432       or an artwork configuration file), this level artwork must be treated
433       as relative to the default "classic" artwork, not to the artwork that
434       is currently configured in the setup menu.
435
436       Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
437       the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
438       the real "classic" artwork from the original R'n'D (like "gfx_classic").
439     */
440
441     char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
442
443     checked_free(*artwork_set_ptr);
444
445     if (directoryExists(dir))
446     {
447       *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
448       *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
449     }
450     else
451     {
452       *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
453       *artwork_set_ptr = NULL;
454     }
455
456     free(dir);
457   }
458
459   return *artwork_set_ptr;
460 }
461
462 static char *getLevelArtworkSet(int type)
463 {
464   if (leveldir_current == NULL)
465     return NULL;
466
467   return LEVELDIR_ARTWORK_SET(leveldir_current, type);
468 }
469
470 static char *getLevelArtworkDir(int type)
471 {
472   if (leveldir_current == NULL)
473     return UNDEFINED_FILENAME;
474
475   return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
476 }
477
478 char *getProgramMainDataPath(char *command_filename, char *base_path)
479 {
480   // check if the program's main data base directory is configured
481   if (!strEqual(base_path, "."))
482     return getStringCopy(base_path);
483
484   /* if the program is configured to start from current directory (default),
485      determine program package directory from program binary (some versions
486      of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
487      set the current working directory to the program package directory) */
488   char *main_data_path = getBasePath(command_filename);
489
490 #if defined(PLATFORM_MACOSX)
491   if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
492   {
493     char *main_data_path_old = main_data_path;
494
495     // cut relative path to Mac OS X application binary directory from path
496     main_data_path[strlen(main_data_path) -
497                    strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
498
499     // cut trailing path separator from path (but not if path is root directory)
500     if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
501       main_data_path[strlen(main_data_path) - 1] = '\0';
502
503     // replace empty path with current directory
504     if (strEqual(main_data_path, ""))
505       main_data_path = ".";
506
507     // add relative path to Mac OS X application resources directory to path
508     main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
509
510     free(main_data_path_old);
511   }
512 #endif
513
514   return main_data_path;
515 }
516
517 char *getProgramConfigFilename(char *command_filename)
518 {
519   static char *config_filename_1 = NULL;
520   static char *config_filename_2 = NULL;
521   static char *config_filename_3 = NULL;
522   static boolean initialized = FALSE;
523
524   if (!initialized)
525   {
526     char *command_filename_1 = getStringCopy(command_filename);
527
528     // strip trailing executable suffix from command filename
529     if (strSuffix(command_filename_1, ".exe"))
530       command_filename_1[strlen(command_filename_1) - 4] = '\0';
531
532     char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH);
533     char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY);
534
535     char *command_basepath = getBasePath(command_filename);
536     char *command_basename = getBaseNameNoSuffix(command_filename);
537     char *command_filename_2 = getPath2(command_basepath, command_basename);
538
539     config_filename_1 = getStringCat2(command_filename_1, ".conf");
540     config_filename_2 = getStringCat2(command_filename_2, ".conf");
541     config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
542
543     checked_free(ro_base_path);
544     checked_free(conf_directory);
545
546     checked_free(command_basepath);
547     checked_free(command_basename);
548
549     checked_free(command_filename_1);
550     checked_free(command_filename_2);
551
552     initialized = TRUE;
553   }
554
555   // 1st try: look for config file that exactly matches the binary filename
556   if (fileExists(config_filename_1))
557     return config_filename_1;
558
559   // 2nd try: look for config file that matches binary filename without suffix
560   if (fileExists(config_filename_2))
561     return config_filename_2;
562
563   // 3rd try: return setup config filename in global program config directory
564   return config_filename_3;
565 }
566
567 char *getTapeFilename(int nr)
568 {
569   static char *filename = NULL;
570   char basename[MAX_FILENAME_LEN];
571
572   checked_free(filename);
573
574   sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
575   filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
576
577   return filename;
578 }
579
580 char *getSolutionTapeFilename(int nr)
581 {
582   static char *filename = NULL;
583   char basename[MAX_FILENAME_LEN];
584
585   checked_free(filename);
586
587   sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
588   filename = getPath2(getSolutionTapeDir(), basename);
589
590   if (!fileExists(filename))
591   {
592     static char *filename_sln = NULL;
593
594     checked_free(filename_sln);
595
596     sprintf(basename, "%03d.sln", nr);
597     filename_sln = getPath2(getSolutionTapeDir(), basename);
598
599     if (fileExists(filename_sln))
600       return filename_sln;
601   }
602
603   return filename;
604 }
605
606 char *getScoreFilename(int nr)
607 {
608   static char *filename = NULL;
609   char basename[MAX_FILENAME_LEN];
610
611   checked_free(filename);
612
613   sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
614
615   // used instead of "leveldir_current->subdir" (for network games)
616   filename = getPath2(getScoreDir(levelset.identifier), basename);
617
618   return filename;
619 }
620
621 char *getSetupFilename(void)
622 {
623   static char *filename = NULL;
624
625   checked_free(filename);
626
627   filename = getPath2(getSetupDir(), SETUP_FILENAME);
628
629   return filename;
630 }
631
632 char *getDefaultSetupFilename(void)
633 {
634   return program.config_filename;
635 }
636
637 char *getEditorSetupFilename(void)
638 {
639   static char *filename = NULL;
640
641   checked_free(filename);
642   filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
643
644   if (fileExists(filename))
645     return filename;
646
647   checked_free(filename);
648   filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
649
650   return filename;
651 }
652
653 char *getHelpAnimFilename(void)
654 {
655   static char *filename = NULL;
656
657   checked_free(filename);
658
659   filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
660
661   return filename;
662 }
663
664 char *getHelpTextFilename(void)
665 {
666   static char *filename = NULL;
667
668   checked_free(filename);
669
670   filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
671
672   return filename;
673 }
674
675 char *getLevelSetInfoFilename(void)
676 {
677   static char *filename = NULL;
678   char *basenames[] =
679   {
680     "README",
681     "README.TXT",
682     "README.txt",
683     "Readme",
684     "Readme.txt",
685     "readme",
686     "readme.txt",
687
688     NULL
689   };
690   int i;
691
692   for (i = 0; basenames[i] != NULL; i++)
693   {
694     checked_free(filename);
695     filename = getPath2(getCurrentLevelDir(), basenames[i]);
696
697     if (fileExists(filename))
698       return filename;
699   }
700
701   return NULL;
702 }
703
704 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
705 {
706   static char basename[32];
707
708   sprintf(basename, "%s_%d.txt",
709           (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
710
711   return basename;
712 }
713
714 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
715 {
716   static char *filename = NULL;
717   char *basename;
718   boolean skip_setup_artwork = FALSE;
719
720   checked_free(filename);
721
722   basename = getLevelSetTitleMessageBasename(nr, initial);
723
724   if (!gfx.override_level_graphics)
725   {
726     // 1st try: look for special artwork in current level series directory
727     filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
728     if (fileExists(filename))
729       return filename;
730
731     free(filename);
732
733     // 2nd try: look for message file in current level set directory
734     filename = getPath2(getCurrentLevelDir(), basename);
735     if (fileExists(filename))
736       return filename;
737
738     free(filename);
739
740     // check if there is special artwork configured in level series config
741     if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
742     {
743       // 3rd try: look for special artwork configured in level series config
744       filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
745       if (fileExists(filename))
746         return filename;
747
748       free(filename);
749
750       // take missing artwork configured in level set config from default
751       skip_setup_artwork = TRUE;
752     }
753   }
754
755   if (!skip_setup_artwork)
756   {
757     // 4th try: look for special artwork in configured artwork directory
758     filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
759     if (fileExists(filename))
760       return filename;
761
762     free(filename);
763   }
764
765   // 5th try: look for default artwork in new default artwork directory
766   filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
767   if (fileExists(filename))
768     return filename;
769
770   free(filename);
771
772   // 6th try: look for default artwork in old default artwork directory
773   filename = getPath2(options.graphics_directory, basename);
774   if (fileExists(filename))
775     return filename;
776
777   return NULL;          // cannot find specified artwork file anywhere
778 }
779
780 static char *getCorrectedArtworkBasename(char *basename)
781 {
782   return basename;
783 }
784
785 char *getCustomImageFilename(char *basename)
786 {
787   static char *filename = NULL;
788   boolean skip_setup_artwork = FALSE;
789
790   checked_free(filename);
791
792   basename = getCorrectedArtworkBasename(basename);
793
794   if (!gfx.override_level_graphics)
795   {
796     // 1st try: look for special artwork in current level series directory
797     filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
798     if (fileExists(filename))
799       return filename;
800
801     free(filename);
802
803     // check if there is special artwork configured in level series config
804     if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
805     {
806       // 2nd try: look for special artwork configured in level series config
807       filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
808       if (fileExists(filename))
809         return filename;
810
811       free(filename);
812
813       // take missing artwork configured in level set config from default
814       skip_setup_artwork = TRUE;
815     }
816   }
817
818   if (!skip_setup_artwork)
819   {
820     // 3rd try: look for special artwork in configured artwork directory
821     filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
822     if (fileExists(filename))
823       return filename;
824
825     free(filename);
826   }
827
828   // 4th try: look for default artwork in new default artwork directory
829   filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
830   if (fileExists(filename))
831     return filename;
832
833   free(filename);
834
835   // 5th try: look for default artwork in old default artwork directory
836   filename = getImg2(options.graphics_directory, basename);
837   if (fileExists(filename))
838     return filename;
839
840   if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
841   {
842     free(filename);
843
844     Warn("cannot find artwork file '%s' (using fallback)", basename);
845
846     // 6th try: look for fallback artwork in old default artwork directory
847     // (needed to prevent errors when trying to access unused artwork files)
848     filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
849     if (fileExists(filename))
850       return filename;
851   }
852
853   return NULL;          // cannot find specified artwork file anywhere
854 }
855
856 char *getCustomSoundFilename(char *basename)
857 {
858   static char *filename = NULL;
859   boolean skip_setup_artwork = FALSE;
860
861   checked_free(filename);
862
863   basename = getCorrectedArtworkBasename(basename);
864
865   if (!gfx.override_level_sounds)
866   {
867     // 1st try: look for special artwork in current level series directory
868     filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
869     if (fileExists(filename))
870       return filename;
871
872     free(filename);
873
874     // check if there is special artwork configured in level series config
875     if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
876     {
877       // 2nd try: look for special artwork configured in level series config
878       filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
879       if (fileExists(filename))
880         return filename;
881
882       free(filename);
883
884       // take missing artwork configured in level set config from default
885       skip_setup_artwork = TRUE;
886     }
887   }
888
889   if (!skip_setup_artwork)
890   {
891     // 3rd try: look for special artwork in configured artwork directory
892     filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
893     if (fileExists(filename))
894       return filename;
895
896     free(filename);
897   }
898
899   // 4th try: look for default artwork in new default artwork directory
900   filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
901   if (fileExists(filename))
902     return filename;
903
904   free(filename);
905
906   // 5th try: look for default artwork in old default artwork directory
907   filename = getPath2(options.sounds_directory, basename);
908   if (fileExists(filename))
909     return filename;
910
911   if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
912   {
913     free(filename);
914
915     Warn("cannot find artwork file '%s' (using fallback)", basename);
916
917     // 6th try: look for fallback artwork in old default artwork directory
918     // (needed to prevent errors when trying to access unused artwork files)
919     filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
920     if (fileExists(filename))
921       return filename;
922   }
923
924   return NULL;          // cannot find specified artwork file anywhere
925 }
926
927 char *getCustomMusicFilename(char *basename)
928 {
929   static char *filename = NULL;
930   boolean skip_setup_artwork = FALSE;
931
932   checked_free(filename);
933
934   basename = getCorrectedArtworkBasename(basename);
935
936   if (!gfx.override_level_music)
937   {
938     // 1st try: look for special artwork in current level series directory
939     filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
940     if (fileExists(filename))
941       return filename;
942
943     free(filename);
944
945     // check if there is special artwork configured in level series config
946     if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
947     {
948       // 2nd try: look for special artwork configured in level series config
949       filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
950       if (fileExists(filename))
951         return filename;
952
953       free(filename);
954
955       // take missing artwork configured in level set config from default
956       skip_setup_artwork = TRUE;
957     }
958   }
959
960   if (!skip_setup_artwork)
961   {
962     // 3rd try: look for special artwork in configured artwork directory
963     filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
964     if (fileExists(filename))
965       return filename;
966
967     free(filename);
968   }
969
970   // 4th try: look for default artwork in new default artwork directory
971   filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
972   if (fileExists(filename))
973     return filename;
974
975   free(filename);
976
977   // 5th try: look for default artwork in old default artwork directory
978   filename = getPath2(options.music_directory, basename);
979   if (fileExists(filename))
980     return filename;
981
982   if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
983   {
984     free(filename);
985
986     Warn("cannot find artwork file '%s' (using fallback)", basename);
987
988     // 6th try: look for fallback artwork in old default artwork directory
989     // (needed to prevent errors when trying to access unused artwork files)
990     filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
991     if (fileExists(filename))
992       return filename;
993   }
994
995   return NULL;          // cannot find specified artwork file anywhere
996 }
997
998 char *getCustomArtworkFilename(char *basename, int type)
999 {
1000   if (type == ARTWORK_TYPE_GRAPHICS)
1001     return getCustomImageFilename(basename);
1002   else if (type == ARTWORK_TYPE_SOUNDS)
1003     return getCustomSoundFilename(basename);
1004   else if (type == ARTWORK_TYPE_MUSIC)
1005     return getCustomMusicFilename(basename);
1006   else
1007     return UNDEFINED_FILENAME;
1008 }
1009
1010 char *getCustomArtworkConfigFilename(int type)
1011 {
1012   return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1013 }
1014
1015 char *getCustomArtworkLevelConfigFilename(int type)
1016 {
1017   static char *filename = NULL;
1018
1019   checked_free(filename);
1020
1021   filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1022
1023   return filename;
1024 }
1025
1026 char *getCustomMusicDirectory(void)
1027 {
1028   static char *directory = NULL;
1029   boolean skip_setup_artwork = FALSE;
1030
1031   checked_free(directory);
1032
1033   if (!gfx.override_level_music)
1034   {
1035     // 1st try: look for special artwork in current level series directory
1036     directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1037     if (directoryExists(directory))
1038       return directory;
1039
1040     free(directory);
1041
1042     // check if there is special artwork configured in level series config
1043     if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1044     {
1045       // 2nd try: look for special artwork configured in level series config
1046       directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1047       if (directoryExists(directory))
1048         return directory;
1049
1050       free(directory);
1051
1052       // take missing artwork configured in level set config from default
1053       skip_setup_artwork = TRUE;
1054     }
1055   }
1056
1057   if (!skip_setup_artwork)
1058   {
1059     // 3rd try: look for special artwork in configured artwork directory
1060     directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1061     if (directoryExists(directory))
1062       return directory;
1063
1064     free(directory);
1065   }
1066
1067   // 4th try: look for default artwork in new default artwork directory
1068   directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1069   if (directoryExists(directory))
1070     return directory;
1071
1072   free(directory);
1073
1074   // 5th try: look for default artwork in old default artwork directory
1075   directory = getStringCopy(options.music_directory);
1076   if (directoryExists(directory))
1077     return directory;
1078
1079   return NULL;          // cannot find specified artwork file anywhere
1080 }
1081
1082 void InitTapeDirectory(char *level_subdir)
1083 {
1084   createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1085   createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1086   createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1087 }
1088
1089 void InitScoreDirectory(char *level_subdir)
1090 {
1091   int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
1092
1093   if (program.global_scores)
1094     createDirectory(getCommonDataDir(), "common data", permissions);
1095   else
1096     createDirectory(getMainUserGameDataDir(), "main user data", permissions);
1097
1098   createDirectory(getScoreDir(NULL), "main score", permissions);
1099   createDirectory(getScoreDir(level_subdir), "level score", permissions);
1100 }
1101
1102 static void SaveUserLevelInfo(void);
1103
1104 void InitUserLevelDirectory(char *level_subdir)
1105 {
1106   if (!directoryExists(getUserLevelDir(level_subdir)))
1107   {
1108     createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1109     createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1110     createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1111
1112     if (setup.internal.create_user_levelset)
1113       SaveUserLevelInfo();
1114   }
1115 }
1116
1117 void InitNetworkLevelDirectory(char *level_subdir)
1118 {
1119   if (!directoryExists(getNetworkLevelDir(level_subdir)))
1120   {
1121     createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1122     createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1123     createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1124     createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1125   }
1126 }
1127
1128 void InitLevelSetupDirectory(char *level_subdir)
1129 {
1130   createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1131   createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1132   createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1133 }
1134
1135 static void InitCacheDirectory(void)
1136 {
1137   createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1138   createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1139 }
1140
1141
1142 // ----------------------------------------------------------------------------
1143 // some functions to handle lists of level and artwork directories
1144 // ----------------------------------------------------------------------------
1145
1146 TreeInfo *newTreeInfo(void)
1147 {
1148   return checked_calloc(sizeof(TreeInfo));
1149 }
1150
1151 TreeInfo *newTreeInfo_setDefaults(int type)
1152 {
1153   TreeInfo *ti = newTreeInfo();
1154
1155   setTreeInfoToDefaults(ti, type);
1156
1157   return ti;
1158 }
1159
1160 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1161 {
1162   node_new->next = *node_first;
1163   *node_first = node_new;
1164 }
1165
1166 int numTreeInfo(TreeInfo *node)
1167 {
1168   int num = 0;
1169
1170   while (node)
1171   {
1172     num++;
1173     node = node->next;
1174   }
1175
1176   return num;
1177 }
1178
1179 boolean validLevelSeries(TreeInfo *node)
1180 {
1181   return (node != NULL && !node->node_group && !node->parent_link);
1182 }
1183
1184 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1185 {
1186   if (node == NULL)
1187     return NULL;
1188
1189   if (node->node_group)         // enter level group (step down into tree)
1190     return getFirstValidTreeInfoEntry(node->node_group);
1191   else if (node->parent_link)   // skip start entry of level group
1192   {
1193     if (node->next)             // get first real level series entry
1194       return getFirstValidTreeInfoEntry(node->next);
1195     else                        // leave empty level group and go on
1196       return getFirstValidTreeInfoEntry(node->node_parent->next);
1197   }
1198   else                          // this seems to be a regular level series
1199     return node;
1200 }
1201
1202 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1203 {
1204   if (node == NULL)
1205     return NULL;
1206
1207   if (node->node_parent == NULL)                // top level group
1208     return *node->node_top;
1209   else                                          // sub level group
1210     return node->node_parent->node_group;
1211 }
1212
1213 int numTreeInfoInGroup(TreeInfo *node)
1214 {
1215   return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1216 }
1217
1218 int getPosFromTreeInfo(TreeInfo *node)
1219 {
1220   TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1221   int pos = 0;
1222
1223   while (node_cmp)
1224   {
1225     if (node_cmp == node)
1226       return pos;
1227
1228     pos++;
1229     node_cmp = node_cmp->next;
1230   }
1231
1232   return 0;
1233 }
1234
1235 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1236 {
1237   TreeInfo *node_default = node;
1238   int pos_cmp = 0;
1239
1240   while (node)
1241   {
1242     if (pos_cmp == pos)
1243       return node;
1244
1245     pos_cmp++;
1246     node = node->next;
1247   }
1248
1249   return node_default;
1250 }
1251
1252 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 #define TOKEN_STR_LAST_USER                     "last_user"
2368
2369 // level directory info
2370 #define LEVELINFO_TOKEN_IDENTIFIER              0
2371 #define LEVELINFO_TOKEN_NAME                    1
2372 #define LEVELINFO_TOKEN_NAME_SORTING            2
2373 #define LEVELINFO_TOKEN_AUTHOR                  3
2374 #define LEVELINFO_TOKEN_YEAR                    4
2375 #define LEVELINFO_TOKEN_PROGRAM_TITLE           5
2376 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT       6
2377 #define LEVELINFO_TOKEN_PROGRAM_COMPANY         7
2378 #define LEVELINFO_TOKEN_IMPORTED_FROM           8
2379 #define LEVELINFO_TOKEN_IMPORTED_BY             9
2380 #define LEVELINFO_TOKEN_TESTED_BY               10
2381 #define LEVELINFO_TOKEN_LEVELS                  11
2382 #define LEVELINFO_TOKEN_FIRST_LEVEL             12
2383 #define LEVELINFO_TOKEN_SORT_PRIORITY           13
2384 #define LEVELINFO_TOKEN_LATEST_ENGINE           14
2385 #define LEVELINFO_TOKEN_LEVEL_GROUP             15
2386 #define LEVELINFO_TOKEN_READONLY                16
2387 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS        17
2388 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA        18
2389 #define LEVELINFO_TOKEN_GRAPHICS_SET            19
2390 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT      20
2391 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS      21
2392 #define LEVELINFO_TOKEN_SOUNDS_SET              22
2393 #define LEVELINFO_TOKEN_MUSIC_SET               23
2394 #define LEVELINFO_TOKEN_FILENAME                24
2395 #define LEVELINFO_TOKEN_FILETYPE                25
2396 #define LEVELINFO_TOKEN_SPECIAL_FLAGS           26
2397 #define LEVELINFO_TOKEN_HANDICAP                27
2398 #define LEVELINFO_TOKEN_SKIP_LEVELS             28
2399 #define LEVELINFO_TOKEN_USE_EMC_TILES           29
2400
2401 #define NUM_LEVELINFO_TOKENS                    30
2402
2403 static LevelDirTree ldi;
2404
2405 static struct TokenInfo levelinfo_tokens[] =
2406 {
2407   // level directory info
2408   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
2409   { TYPE_STRING,        &ldi.name,              "name"                  },
2410   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
2411   { TYPE_STRING,        &ldi.author,            "author"                },
2412   { TYPE_STRING,        &ldi.year,              "year"                  },
2413   { TYPE_STRING,        &ldi.program_title,     "program_title"         },
2414   { TYPE_STRING,        &ldi.program_copyright, "program_copyright"     },
2415   { TYPE_STRING,        &ldi.program_company,   "program_company"       },
2416   { TYPE_STRING,        &ldi.imported_from,     "imported_from"         },
2417   { TYPE_STRING,        &ldi.imported_by,       "imported_by"           },
2418   { TYPE_STRING,        &ldi.tested_by,         "tested_by"             },
2419   { TYPE_INTEGER,       &ldi.levels,            "levels"                },
2420   { TYPE_INTEGER,       &ldi.first_level,       "first_level"           },
2421   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
2422   { TYPE_BOOLEAN,       &ldi.latest_engine,     "latest_engine"         },
2423   { TYPE_BOOLEAN,       &ldi.level_group,       "level_group"           },
2424   { TYPE_BOOLEAN,       &ldi.readonly,          "readonly"              },
2425   { TYPE_STRING,        &ldi.graphics_set_ecs,  "graphics_set.ecs"      },
2426   { TYPE_STRING,        &ldi.graphics_set_aga,  "graphics_set.aga"      },
2427   { TYPE_STRING,        &ldi.graphics_set,      "graphics_set"          },
2428   { TYPE_STRING,        &ldi.sounds_set_default,"sounds_set.default"    },
2429   { TYPE_STRING,        &ldi.sounds_set_lowpass,"sounds_set.lowpass"    },
2430   { TYPE_STRING,        &ldi.sounds_set,        "sounds_set"            },
2431   { TYPE_STRING,        &ldi.music_set,         "music_set"             },
2432   { TYPE_STRING,        &ldi.level_filename,    "filename"              },
2433   { TYPE_STRING,        &ldi.level_filetype,    "filetype"              },
2434   { TYPE_STRING,        &ldi.special_flags,     "special_flags"         },
2435   { TYPE_BOOLEAN,       &ldi.handicap,          "handicap"              },
2436   { TYPE_BOOLEAN,       &ldi.skip_levels,       "skip_levels"           },
2437   { TYPE_BOOLEAN,       &ldi.use_emc_tiles,     "use_emc_tiles"         }
2438 };
2439
2440 static struct TokenInfo artworkinfo_tokens[] =
2441 {
2442   // artwork directory info
2443   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
2444   { TYPE_STRING,        &ldi.subdir,            "subdir"                },
2445   { TYPE_STRING,        &ldi.name,              "name"                  },
2446   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
2447   { TYPE_STRING,        &ldi.author,            "author"                },
2448   { TYPE_STRING,        &ldi.program_title,     "program_title"         },
2449   { TYPE_STRING,        &ldi.program_copyright, "program_copyright"     },
2450   { TYPE_STRING,        &ldi.program_company,   "program_company"       },
2451   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
2452   { TYPE_STRING,        &ldi.basepath,          "basepath"              },
2453   { TYPE_STRING,        &ldi.fullpath,          "fullpath"              },
2454   { TYPE_BOOLEAN,       &ldi.in_user_dir,       "in_user_dir"           },
2455   { TYPE_INTEGER,       &ldi.color,             "color"                 },
2456   { TYPE_STRING,        &ldi.class_desc,        "class_desc"            },
2457
2458   { -1,                 NULL,                   NULL                    },
2459 };
2460
2461 static char *optional_tokens[] =
2462 {
2463   "program_title",
2464   "program_copyright",
2465   "program_company",
2466
2467   NULL
2468 };
2469
2470 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2471 {
2472   ti->type = type;
2473
2474   ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR    ? &leveldir_first :
2475                   ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2476                   ti->type == TREE_TYPE_SOUNDS_DIR   ? &artwork.snd_first :
2477                   ti->type == TREE_TYPE_MUSIC_DIR    ? &artwork.mus_first :
2478                   NULL);
2479
2480   ti->node_parent = NULL;
2481   ti->node_group = NULL;
2482   ti->next = NULL;
2483
2484   ti->cl_first = -1;
2485   ti->cl_cursor = -1;
2486
2487   ti->subdir = NULL;
2488   ti->fullpath = NULL;
2489   ti->basepath = NULL;
2490   ti->identifier = NULL;
2491   ti->name = getStringCopy(ANONYMOUS_NAME);
2492   ti->name_sorting = NULL;
2493   ti->author = getStringCopy(ANONYMOUS_NAME);
2494   ti->year = NULL;
2495
2496   ti->program_title = NULL;
2497   ti->program_copyright = NULL;
2498   ti->program_company = NULL;
2499
2500   ti->sort_priority = LEVELCLASS_UNDEFINED;     // default: least priority
2501   ti->latest_engine = FALSE;                    // default: get from level
2502   ti->parent_link = FALSE;
2503   ti->in_user_dir = FALSE;
2504   ti->user_defined = FALSE;
2505   ti->color = 0;
2506   ti->class_desc = NULL;
2507
2508   ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2509
2510   if (ti->type == TREE_TYPE_LEVEL_DIR)
2511   {
2512     ti->imported_from = NULL;
2513     ti->imported_by = NULL;
2514     ti->tested_by = NULL;
2515
2516     ti->graphics_set_ecs = NULL;
2517     ti->graphics_set_aga = NULL;
2518     ti->graphics_set = NULL;
2519     ti->sounds_set_default = NULL;
2520     ti->sounds_set_lowpass = NULL;
2521     ti->sounds_set = NULL;
2522     ti->music_set = NULL;
2523     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2524     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2525     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2526
2527     ti->level_filename = NULL;
2528     ti->level_filetype = NULL;
2529
2530     ti->special_flags = NULL;
2531
2532     ti->levels = 0;
2533     ti->first_level = 0;
2534     ti->last_level = 0;
2535     ti->level_group = FALSE;
2536     ti->handicap_level = 0;
2537     ti->readonly = TRUE;
2538     ti->handicap = TRUE;
2539     ti->skip_levels = FALSE;
2540
2541     ti->use_emc_tiles = FALSE;
2542   }
2543 }
2544
2545 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2546 {
2547   if (parent == NULL)
2548   {
2549     Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2550
2551     setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2552
2553     return;
2554   }
2555
2556   // copy all values from the parent structure
2557
2558   ti->type = parent->type;
2559
2560   ti->node_top = parent->node_top;
2561   ti->node_parent = parent;
2562   ti->node_group = NULL;
2563   ti->next = NULL;
2564
2565   ti->cl_first = -1;
2566   ti->cl_cursor = -1;
2567
2568   ti->subdir = NULL;
2569   ti->fullpath = NULL;
2570   ti->basepath = NULL;
2571   ti->identifier = NULL;
2572   ti->name = getStringCopy(ANONYMOUS_NAME);
2573   ti->name_sorting = NULL;
2574   ti->author = getStringCopy(parent->author);
2575   ti->year = getStringCopy(parent->year);
2576
2577   ti->program_title = getStringCopy(parent->program_title);
2578   ti->program_copyright = getStringCopy(parent->program_copyright);
2579   ti->program_company = getStringCopy(parent->program_company);
2580
2581   ti->sort_priority = parent->sort_priority;
2582   ti->latest_engine = parent->latest_engine;
2583   ti->parent_link = FALSE;
2584   ti->in_user_dir = parent->in_user_dir;
2585   ti->user_defined = parent->user_defined;
2586   ti->color = parent->color;
2587   ti->class_desc = getStringCopy(parent->class_desc);
2588
2589   ti->infotext = getStringCopy(parent->infotext);
2590
2591   if (ti->type == TREE_TYPE_LEVEL_DIR)
2592   {
2593     ti->imported_from = getStringCopy(parent->imported_from);
2594     ti->imported_by = getStringCopy(parent->imported_by);
2595     ti->tested_by = getStringCopy(parent->tested_by);
2596
2597     ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2598     ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2599     ti->graphics_set = getStringCopy(parent->graphics_set);
2600     ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2601     ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2602     ti->sounds_set = getStringCopy(parent->sounds_set);
2603     ti->music_set = getStringCopy(parent->music_set);
2604     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2605     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2606     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2607
2608     ti->level_filename = getStringCopy(parent->level_filename);
2609     ti->level_filetype = getStringCopy(parent->level_filetype);
2610
2611     ti->special_flags = getStringCopy(parent->special_flags);
2612
2613     ti->levels = parent->levels;
2614     ti->first_level = parent->first_level;
2615     ti->last_level = parent->last_level;
2616     ti->level_group = FALSE;
2617     ti->handicap_level = parent->handicap_level;
2618     ti->readonly = parent->readonly;
2619     ti->handicap = parent->handicap;
2620     ti->skip_levels = parent->skip_levels;
2621
2622     ti->use_emc_tiles = parent->use_emc_tiles;
2623   }
2624 }
2625
2626 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2627 {
2628   TreeInfo *ti_copy = newTreeInfo();
2629
2630   // copy all values from the original structure
2631
2632   ti_copy->type                 = ti->type;
2633
2634   ti_copy->node_top             = ti->node_top;
2635   ti_copy->node_parent          = ti->node_parent;
2636   ti_copy->node_group           = ti->node_group;
2637   ti_copy->next                 = ti->next;
2638
2639   ti_copy->cl_first             = ti->cl_first;
2640   ti_copy->cl_cursor            = ti->cl_cursor;
2641
2642   ti_copy->subdir               = getStringCopy(ti->subdir);
2643   ti_copy->fullpath             = getStringCopy(ti->fullpath);
2644   ti_copy->basepath             = getStringCopy(ti->basepath);
2645   ti_copy->identifier           = getStringCopy(ti->identifier);
2646   ti_copy->name                 = getStringCopy(ti->name);
2647   ti_copy->name_sorting         = getStringCopy(ti->name_sorting);
2648   ti_copy->author               = getStringCopy(ti->author);
2649   ti_copy->year                 = getStringCopy(ti->year);
2650
2651   ti_copy->program_title        = getStringCopy(ti->program_title);
2652   ti_copy->program_copyright    = getStringCopy(ti->program_copyright);
2653   ti_copy->program_company      = getStringCopy(ti->program_company);
2654
2655   ti_copy->imported_from        = getStringCopy(ti->imported_from);
2656   ti_copy->imported_by          = getStringCopy(ti->imported_by);
2657   ti_copy->tested_by            = getStringCopy(ti->tested_by);
2658
2659   ti_copy->graphics_set_ecs     = getStringCopy(ti->graphics_set_ecs);
2660   ti_copy->graphics_set_aga     = getStringCopy(ti->graphics_set_aga);
2661   ti_copy->graphics_set         = getStringCopy(ti->graphics_set);
2662   ti_copy->sounds_set_default   = getStringCopy(ti->sounds_set_default);
2663   ti_copy->sounds_set_lowpass   = getStringCopy(ti->sounds_set_lowpass);
2664   ti_copy->sounds_set           = getStringCopy(ti->sounds_set);
2665   ti_copy->music_set            = getStringCopy(ti->music_set);
2666   ti_copy->graphics_path        = getStringCopy(ti->graphics_path);
2667   ti_copy->sounds_path          = getStringCopy(ti->sounds_path);
2668   ti_copy->music_path           = getStringCopy(ti->music_path);
2669
2670   ti_copy->level_filename       = getStringCopy(ti->level_filename);
2671   ti_copy->level_filetype       = getStringCopy(ti->level_filetype);
2672
2673   ti_copy->special_flags        = getStringCopy(ti->special_flags);
2674
2675   ti_copy->levels               = ti->levels;
2676   ti_copy->first_level          = ti->first_level;
2677   ti_copy->last_level           = ti->last_level;
2678   ti_copy->sort_priority        = ti->sort_priority;
2679
2680   ti_copy->latest_engine        = ti->latest_engine;
2681
2682   ti_copy->level_group          = ti->level_group;
2683   ti_copy->parent_link          = ti->parent_link;
2684   ti_copy->in_user_dir          = ti->in_user_dir;
2685   ti_copy->user_defined         = ti->user_defined;
2686   ti_copy->readonly             = ti->readonly;
2687   ti_copy->handicap             = ti->handicap;
2688   ti_copy->skip_levels          = ti->skip_levels;
2689
2690   ti_copy->use_emc_tiles        = ti->use_emc_tiles;
2691
2692   ti_copy->color                = ti->color;
2693   ti_copy->class_desc           = getStringCopy(ti->class_desc);
2694   ti_copy->handicap_level       = ti->handicap_level;
2695
2696   ti_copy->infotext             = getStringCopy(ti->infotext);
2697
2698   return ti_copy;
2699 }
2700
2701 void freeTreeInfo(TreeInfo *ti)
2702 {
2703   if (ti == NULL)
2704     return;
2705
2706   checked_free(ti->subdir);
2707   checked_free(ti->fullpath);
2708   checked_free(ti->basepath);
2709   checked_free(ti->identifier);
2710
2711   checked_free(ti->name);
2712   checked_free(ti->name_sorting);
2713   checked_free(ti->author);
2714   checked_free(ti->year);
2715
2716   checked_free(ti->program_title);
2717   checked_free(ti->program_copyright);
2718   checked_free(ti->program_company);
2719
2720   checked_free(ti->class_desc);
2721
2722   checked_free(ti->infotext);
2723
2724   if (ti->type == TREE_TYPE_LEVEL_DIR)
2725   {
2726     checked_free(ti->imported_from);
2727     checked_free(ti->imported_by);
2728     checked_free(ti->tested_by);
2729
2730     checked_free(ti->graphics_set_ecs);
2731     checked_free(ti->graphics_set_aga);
2732     checked_free(ti->graphics_set);
2733     checked_free(ti->sounds_set_default);
2734     checked_free(ti->sounds_set_lowpass);
2735     checked_free(ti->sounds_set);
2736     checked_free(ti->music_set);
2737
2738     checked_free(ti->graphics_path);
2739     checked_free(ti->sounds_path);
2740     checked_free(ti->music_path);
2741
2742     checked_free(ti->level_filename);
2743     checked_free(ti->level_filetype);
2744
2745     checked_free(ti->special_flags);
2746   }
2747
2748   // recursively free child node
2749   if (ti->node_group)
2750     freeTreeInfo(ti->node_group);
2751
2752   // recursively free next node
2753   if (ti->next)
2754     freeTreeInfo(ti->next);
2755
2756   checked_free(ti);
2757 }
2758
2759 void setSetupInfo(struct TokenInfo *token_info,
2760                   int token_nr, char *token_value)
2761 {
2762   int token_type = token_info[token_nr].type;
2763   void *setup_value = token_info[token_nr].value;
2764
2765   if (token_value == NULL)
2766     return;
2767
2768   // set setup field to corresponding token value
2769   switch (token_type)
2770   {
2771     case TYPE_BOOLEAN:
2772     case TYPE_SWITCH:
2773       *(boolean *)setup_value = get_boolean_from_string(token_value);
2774       break;
2775
2776     case TYPE_SWITCH3:
2777       *(int *)setup_value = get_switch3_from_string(token_value);
2778       break;
2779
2780     case TYPE_KEY:
2781       *(Key *)setup_value = getKeyFromKeyName(token_value);
2782       break;
2783
2784     case TYPE_KEY_X11:
2785       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2786       break;
2787
2788     case TYPE_INTEGER:
2789       *(int *)setup_value = get_integer_from_string(token_value);
2790       break;
2791
2792     case TYPE_STRING:
2793       checked_free(*(char **)setup_value);
2794       *(char **)setup_value = getStringCopy(token_value);
2795       break;
2796
2797     case TYPE_PLAYER:
2798       *(int *)setup_value = get_player_nr_from_string(token_value);
2799       break;
2800
2801     default:
2802       break;
2803   }
2804 }
2805
2806 static int compareTreeInfoEntries(const void *object1, const void *object2)
2807 {
2808   const TreeInfo *entry1 = *((TreeInfo **)object1);
2809   const TreeInfo *entry2 = *((TreeInfo **)object2);
2810   int class_sorting1 = 0, class_sorting2 = 0;
2811   int compare_result;
2812
2813   if (entry1->type == TREE_TYPE_LEVEL_DIR)
2814   {
2815     class_sorting1 = LEVELSORTING(entry1);
2816     class_sorting2 = LEVELSORTING(entry2);
2817   }
2818   else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2819            entry1->type == TREE_TYPE_SOUNDS_DIR ||
2820            entry1->type == TREE_TYPE_MUSIC_DIR)
2821   {
2822     class_sorting1 = ARTWORKSORTING(entry1);
2823     class_sorting2 = ARTWORKSORTING(entry2);
2824   }
2825
2826   if (entry1->parent_link || entry2->parent_link)
2827     compare_result = (entry1->parent_link ? -1 : +1);
2828   else if (entry1->sort_priority == entry2->sort_priority)
2829   {
2830     char *name1 = getStringToLower(entry1->name_sorting);
2831     char *name2 = getStringToLower(entry2->name_sorting);
2832
2833     compare_result = strcmp(name1, name2);
2834
2835     free(name1);
2836     free(name2);
2837   }
2838   else if (class_sorting1 == class_sorting2)
2839     compare_result = entry1->sort_priority - entry2->sort_priority;
2840   else
2841     compare_result = class_sorting1 - class_sorting2;
2842
2843   return compare_result;
2844 }
2845
2846 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2847 {
2848   TreeInfo *ti_new;
2849
2850   if (node_parent == NULL)
2851     return NULL;
2852
2853   ti_new = newTreeInfo();
2854   setTreeInfoToDefaults(ti_new, node_parent->type);
2855
2856   ti_new->node_parent = node_parent;
2857   ti_new->parent_link = TRUE;
2858
2859   setString(&ti_new->identifier, node_parent->identifier);
2860   setString(&ti_new->name, ".. (parent directory)");
2861   setString(&ti_new->name_sorting, ti_new->name);
2862
2863   setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2864   setString(&ti_new->fullpath, node_parent->fullpath);
2865
2866   ti_new->sort_priority = node_parent->sort_priority;
2867   ti_new->latest_engine = node_parent->latest_engine;
2868
2869   setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2870
2871   pushTreeInfo(&node_parent->node_group, ti_new);
2872
2873   return ti_new;
2874 }
2875
2876 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2877 {
2878   TreeInfo *ti_new, *ti_new2;
2879
2880   if (node_first == NULL)
2881     return NULL;
2882
2883   ti_new = newTreeInfo();
2884   setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2885
2886   ti_new->node_parent = NULL;
2887   ti_new->parent_link = FALSE;
2888
2889   setString(&ti_new->identifier, node_first->identifier);
2890   setString(&ti_new->name, "level sets");
2891   setString(&ti_new->name_sorting, ti_new->name);
2892
2893   setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2894   setString(&ti_new->fullpath, ".");
2895
2896   ti_new->sort_priority = node_first->sort_priority;;
2897   ti_new->latest_engine = node_first->latest_engine;
2898
2899   setString(&ti_new->class_desc, "level sets");
2900
2901   ti_new->node_group = node_first;
2902   ti_new->level_group = TRUE;
2903
2904   ti_new2 = createParentTreeInfoNode(ti_new);
2905
2906   setString(&ti_new2->name, ".. (main menu)");
2907   setString(&ti_new2->name_sorting, ti_new2->name);
2908
2909   return ti_new;
2910 }
2911
2912
2913 // ----------------------------------------------------------------------------
2914 // functions for handling level and custom artwork info cache
2915 // ----------------------------------------------------------------------------
2916
2917 static void LoadArtworkInfoCache(void)
2918 {
2919   InitCacheDirectory();
2920
2921   if (artworkinfo_cache_old == NULL)
2922   {
2923     char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2924
2925     // try to load artwork info hash from already existing cache file
2926     artworkinfo_cache_old = loadSetupFileHash(filename);
2927
2928     // if no artwork info cache file was found, start with empty hash
2929     if (artworkinfo_cache_old == NULL)
2930       artworkinfo_cache_old = newSetupFileHash();
2931
2932     free(filename);
2933   }
2934
2935   if (artworkinfo_cache_new == NULL)
2936     artworkinfo_cache_new = newSetupFileHash();
2937 }
2938
2939 static void SaveArtworkInfoCache(void)
2940 {
2941   char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2942
2943   InitCacheDirectory();
2944
2945   saveSetupFileHash(artworkinfo_cache_new, filename);
2946
2947   free(filename);
2948 }
2949
2950 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2951 {
2952   static char *prefix = NULL;
2953
2954   checked_free(prefix);
2955
2956   prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2957
2958   return prefix;
2959 }
2960
2961 // (identical to above function, but separate string buffer needed -- nasty)
2962 static char *getCacheToken(char *prefix, char *suffix)
2963 {
2964   static char *token = NULL;
2965
2966   checked_free(token);
2967
2968   token = getStringCat2WithSeparator(prefix, suffix, ".");
2969
2970   return token;
2971 }
2972
2973 static char *getFileTimestampString(char *filename)
2974 {
2975   return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2976 }
2977
2978 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2979 {
2980   struct stat file_status;
2981
2982   if (timestamp_string == NULL)
2983     return TRUE;
2984
2985   if (stat(filename, &file_status) != 0)        // cannot stat file
2986     return TRUE;
2987
2988   return (file_status.st_mtime != atoi(timestamp_string));
2989 }
2990
2991 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2992 {
2993   char *identifier = level_node->subdir;
2994   char *type_string = ARTWORK_DIRECTORY(type);
2995   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2996   char *token_main = getCacheToken(token_prefix, "CACHED");
2997   char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2998   boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2999   TreeInfo *artwork_info = NULL;
3000
3001   if (!use_artworkinfo_cache)
3002     return NULL;
3003
3004   if (optional_tokens_hash == NULL)
3005   {
3006     int i;
3007
3008     // create hash from list of optional tokens (for quick access)
3009     optional_tokens_hash = newSetupFileHash();
3010     for (i = 0; optional_tokens[i] != NULL; i++)
3011       setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3012   }
3013
3014   if (cached)
3015   {
3016     int i;
3017
3018     artwork_info = newTreeInfo();
3019     setTreeInfoToDefaults(artwork_info, type);
3020
3021     // set all structure fields according to the token/value pairs
3022     ldi = *artwork_info;
3023     for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3024     {
3025       char *token_suffix = artworkinfo_tokens[i].text;
3026       char *token = getCacheToken(token_prefix, token_suffix);
3027       char *value = getHashEntry(artworkinfo_cache_old, token);
3028       boolean optional =
3029         (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3030
3031       setSetupInfo(artworkinfo_tokens, i, value);
3032
3033       // check if cache entry for this item is mandatory, but missing
3034       if (value == NULL && !optional)
3035       {
3036         Warn("missing cache entry '%s'", token);
3037
3038         cached = FALSE;
3039       }
3040     }
3041
3042     *artwork_info = ldi;
3043   }
3044
3045   if (cached)
3046   {
3047     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3048                                         LEVELINFO_FILENAME);
3049     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3050                                           ARTWORKINFO_FILENAME(type));
3051
3052     // check if corresponding "levelinfo.conf" file has changed
3053     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3054     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3055
3056     if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3057       cached = FALSE;
3058
3059     // check if corresponding "<artworkinfo>.conf" file has changed
3060     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3061     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3062
3063     if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3064       cached = FALSE;
3065
3066     checked_free(filename_levelinfo);
3067     checked_free(filename_artworkinfo);
3068   }
3069
3070   if (!cached && artwork_info != NULL)
3071   {
3072     freeTreeInfo(artwork_info);
3073
3074     return NULL;
3075   }
3076
3077   return artwork_info;
3078 }
3079
3080 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3081                                      LevelDirTree *level_node, int type)
3082 {
3083   char *identifier = level_node->subdir;
3084   char *type_string = ARTWORK_DIRECTORY(type);
3085   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3086   char *token_main = getCacheToken(token_prefix, "CACHED");
3087   boolean set_cache_timestamps = TRUE;
3088   int i;
3089
3090   setHashEntry(artworkinfo_cache_new, token_main, "true");
3091
3092   if (set_cache_timestamps)
3093   {
3094     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3095                                         LEVELINFO_FILENAME);
3096     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3097                                           ARTWORKINFO_FILENAME(type));
3098     char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3099     char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3100
3101     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3102     setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3103
3104     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3105     setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3106
3107     checked_free(filename_levelinfo);
3108     checked_free(filename_artworkinfo);
3109     checked_free(timestamp_levelinfo);
3110     checked_free(timestamp_artworkinfo);
3111   }
3112
3113   ldi = *artwork_info;
3114   for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3115   {
3116     char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3117     char *value = getSetupValue(artworkinfo_tokens[i].type,
3118                                 artworkinfo_tokens[i].value);
3119     if (value != NULL)
3120       setHashEntry(artworkinfo_cache_new, token, value);
3121   }
3122 }
3123
3124
3125 // ----------------------------------------------------------------------------
3126 // functions for loading level info and custom artwork info
3127 // ----------------------------------------------------------------------------
3128
3129 int GetZipFileTreeType(char *zip_filename)
3130 {
3131   static char *top_dir_path = NULL;
3132   static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3133   static char *conf_basename[NUM_BASE_TREE_TYPES] =
3134   {
3135     GRAPHICSINFO_FILENAME,
3136     SOUNDSINFO_FILENAME,
3137     MUSICINFO_FILENAME,
3138     LEVELINFO_FILENAME
3139   };
3140   int j;
3141
3142   checked_free(top_dir_path);
3143   top_dir_path = NULL;
3144
3145   for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3146   {
3147     checked_free(top_dir_conf_filename[j]);
3148     top_dir_conf_filename[j] = NULL;
3149   }
3150
3151   char **zip_entries = zip_list(zip_filename);
3152
3153   // check if zip file successfully opened
3154   if (zip_entries == NULL || zip_entries[0] == NULL)
3155     return TREE_TYPE_UNDEFINED;
3156
3157   // first zip file entry is expected to be top level directory
3158   char *top_dir = zip_entries[0];
3159
3160   // check if valid top level directory found in zip file
3161   if (!strSuffix(top_dir, "/"))
3162     return TREE_TYPE_UNDEFINED;
3163
3164   // get filenames of valid configuration files in top level directory
3165   for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3166     top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3167
3168   int tree_type = TREE_TYPE_UNDEFINED;
3169   int e = 0;
3170
3171   while (zip_entries[e] != NULL)
3172   {
3173     // check if every zip file entry is below top level directory
3174     if (!strPrefix(zip_entries[e], top_dir))
3175       return TREE_TYPE_UNDEFINED;
3176
3177     // check if this zip file entry is a valid configuration filename
3178     for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3179     {
3180       if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3181       {
3182         // only exactly one valid configuration file allowed
3183         if (tree_type != TREE_TYPE_UNDEFINED)
3184           return TREE_TYPE_UNDEFINED;
3185
3186         tree_type = j;
3187       }
3188     }
3189
3190     e++;
3191   }
3192
3193   return tree_type;
3194 }
3195
3196 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3197                                         int tree_type)
3198 {
3199   static char *top_dir_path = NULL;
3200   static char *top_dir_conf_filename = NULL;
3201
3202   checked_free(top_dir_path);
3203   checked_free(top_dir_conf_filename);
3204
3205   top_dir_path = NULL;
3206   top_dir_conf_filename = NULL;
3207
3208   char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3209                          ARTWORKINFO_FILENAME(tree_type));
3210
3211   // check if valid configuration filename determined
3212   if (conf_basename == NULL || strEqual(conf_basename, ""))
3213     return FALSE;
3214
3215   char **zip_entries = zip_list(zip_filename);
3216
3217   // check if zip file successfully opened
3218   if (zip_entries == NULL || zip_entries[0] == NULL)
3219     return FALSE;
3220
3221   // first zip file entry is expected to be top level directory
3222   char *top_dir = zip_entries[0];
3223
3224   // check if valid top level directory found in zip file
3225   if (!strSuffix(top_dir, "/"))
3226     return FALSE;
3227
3228   // get path of extracted top level directory
3229   top_dir_path = getPath2(directory, top_dir);
3230
3231   // remove trailing directory separator from top level directory path
3232   // (required to be able to check for file and directory in next step)
3233   top_dir_path[strlen(top_dir_path) - 1] = '\0';
3234
3235   // check if zip file's top level directory already exists in target directory
3236   if (fileExists(top_dir_path))         // (checks for file and directory)
3237     return FALSE;
3238
3239   // get filename of configuration file in top level directory
3240   top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3241
3242   boolean found_top_dir_conf_filename = FALSE;
3243   int i = 0;
3244
3245   while (zip_entries[i] != NULL)
3246   {
3247     // check if every zip file entry is below top level directory
3248     if (!strPrefix(zip_entries[i], top_dir))
3249       return FALSE;
3250
3251     // check if this zip file entry is the configuration filename
3252     if (strEqual(zip_entries[i], top_dir_conf_filename))
3253       found_top_dir_conf_filename = TRUE;
3254
3255     i++;
3256   }
3257
3258   // check if valid configuration filename was found in zip file
3259   if (!found_top_dir_conf_filename)
3260     return FALSE;
3261
3262   return TRUE;
3263 }
3264
3265 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3266                                   int tree_type)
3267 {
3268   boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3269                                                     tree_type);
3270
3271   if (!zip_file_valid)
3272   {
3273     Warn("zip file '%s' rejected!", zip_filename);
3274
3275     return NULL;
3276   }
3277
3278   char **zip_entries = zip_extract(zip_filename, directory);
3279
3280   if (zip_entries == NULL)
3281   {
3282     Warn("zip file '%s' could not be extracted!", zip_filename);
3283
3284     return NULL;
3285   }
3286
3287   Info("zip file '%s' successfully extracted!", zip_filename);
3288
3289   // first zip file entry contains top level directory
3290   char *top_dir = zip_entries[0];
3291
3292   // remove trailing directory separator from top level directory
3293   top_dir[strlen(top_dir) - 1] = '\0';
3294
3295   return top_dir;
3296 }
3297
3298 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3299 {
3300   Directory *dir;
3301   DirectoryEntry *dir_entry;
3302
3303   if ((dir = openDirectory(directory)) == NULL)
3304   {
3305     // display error if directory is main "options.graphics_directory" etc.
3306     if (tree_type == TREE_TYPE_LEVEL_DIR ||
3307         directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3308       Warn("cannot read directory '%s'", directory);
3309
3310     return;
3311   }
3312
3313   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
3314   {
3315     // skip non-zip files (and also directories with zip extension)
3316     if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3317       continue;
3318
3319     char *zip_filename = getPath2(directory, dir_entry->basename);
3320     char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3321     char *zip_filename_rejected  = getStringCat2(zip_filename, ".rejected");
3322
3323     // check if zip file hasn't already been extracted or rejected
3324     if (!fileExists(zip_filename_extracted) &&
3325         !fileExists(zip_filename_rejected))
3326     {
3327       char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3328                                                   tree_type);
3329       char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3330                                zip_filename_rejected);
3331       FILE *marker_file;
3332
3333       // create empty file to mark zip file as extracted or rejected
3334       if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3335         fclose(marker_file);
3336
3337       free(zip_filename);
3338       free(zip_filename_extracted);
3339       free(zip_filename_rejected);
3340     }
3341   }
3342
3343   closeDirectory(dir);
3344 }
3345
3346 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3347 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3348
3349 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3350                                           TreeInfo *node_parent,
3351                                           char *level_directory,
3352                                           char *directory_name)
3353 {
3354   char *directory_path = getPath2(level_directory, directory_name);
3355   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3356   SetupFileHash *setup_file_hash;
3357   LevelDirTree *leveldir_new = NULL;
3358   int i;
3359
3360   // unless debugging, silently ignore directories without "levelinfo.conf"
3361   if (!options.debug && !fileExists(filename))
3362   {
3363     free(directory_path);
3364     free(filename);
3365
3366     return FALSE;
3367   }
3368
3369   setup_file_hash = loadSetupFileHash(filename);
3370
3371   if (setup_file_hash == NULL)
3372   {
3373 #if DEBUG_NO_CONFIG_FILE
3374     Debug("setup", "ignoring level directory '%s'", directory_path);
3375 #endif
3376
3377     free(directory_path);
3378     free(filename);
3379
3380     return FALSE;
3381   }
3382
3383   leveldir_new = newTreeInfo();
3384
3385   if (node_parent)
3386     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3387   else
3388     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3389
3390   leveldir_new->subdir = getStringCopy(directory_name);
3391
3392   // set all structure fields according to the token/value pairs
3393   ldi = *leveldir_new;
3394   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3395     setSetupInfo(levelinfo_tokens, i,
3396                  getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3397   *leveldir_new = ldi;
3398
3399   if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3400     setString(&leveldir_new->name, leveldir_new->subdir);
3401
3402   if (leveldir_new->identifier == NULL)
3403     leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3404
3405   if (leveldir_new->name_sorting == NULL)
3406     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3407
3408   if (node_parent == NULL)              // top level group
3409   {
3410     leveldir_new->basepath = getStringCopy(level_directory);
3411     leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3412   }
3413   else                                  // sub level group
3414   {
3415     leveldir_new->basepath = getStringCopy(node_parent->basepath);
3416     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3417   }
3418
3419   leveldir_new->last_level =
3420     leveldir_new->first_level + leveldir_new->levels - 1;
3421
3422   leveldir_new->in_user_dir =
3423     (!strEqual(leveldir_new->basepath, options.level_directory));
3424
3425   // adjust some settings if user's private level directory was detected
3426   if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3427       leveldir_new->in_user_dir &&
3428       (strEqual(leveldir_new->subdir, getLoginName()) ||
3429        strEqual(leveldir_new->name,   getLoginName()) ||
3430        strEqual(leveldir_new->author, getRealName())))
3431   {
3432     leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3433     leveldir_new->readonly = FALSE;
3434   }
3435
3436   leveldir_new->user_defined =
3437     (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3438
3439   leveldir_new->color = LEVELCOLOR(leveldir_new);
3440
3441   setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3442
3443   leveldir_new->handicap_level =        // set handicap to default value
3444     (leveldir_new->user_defined || !leveldir_new->handicap ?
3445      leveldir_new->last_level : leveldir_new->first_level);
3446
3447   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3448
3449   pushTreeInfo(node_first, leveldir_new);
3450
3451   freeSetupFileHash(setup_file_hash);
3452
3453   if (leveldir_new->level_group)
3454   {
3455     // create node to link back to current level directory
3456     createParentTreeInfoNode(leveldir_new);
3457
3458     // recursively step into sub-directory and look for more level series
3459     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3460                               leveldir_new, directory_path);
3461   }
3462
3463   free(directory_path);
3464   free(filename);
3465
3466   return TRUE;
3467 }
3468
3469 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3470                                       TreeInfo *node_parent,
3471                                       char *level_directory)
3472 {
3473   // ---------- 1st stage: process any level set zip files ----------
3474
3475   ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3476
3477   // ---------- 2nd stage: check for level set directories ----------
3478
3479   Directory *dir;
3480   DirectoryEntry *dir_entry;
3481   boolean valid_entry_found = FALSE;
3482
3483   if ((dir = openDirectory(level_directory)) == NULL)
3484   {
3485     Warn("cannot read level directory '%s'", level_directory);
3486
3487     return;
3488   }
3489
3490   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
3491   {
3492     char *directory_name = dir_entry->basename;
3493     char *directory_path = getPath2(level_directory, directory_name);
3494
3495     // skip entries for current and parent directory
3496     if (strEqual(directory_name, ".") ||
3497         strEqual(directory_name, ".."))
3498     {
3499       free(directory_path);
3500
3501       continue;
3502     }
3503
3504     // find out if directory entry is itself a directory
3505     if (!dir_entry->is_directory)                       // not a directory
3506     {
3507       free(directory_path);
3508
3509       continue;
3510     }
3511
3512     free(directory_path);
3513
3514     if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3515         strEqual(directory_name, SOUNDS_DIRECTORY) ||
3516         strEqual(directory_name, MUSIC_DIRECTORY))
3517       continue;
3518
3519     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3520                                                     level_directory,
3521                                                     directory_name);
3522   }
3523
3524   closeDirectory(dir);
3525
3526   // special case: top level directory may directly contain "levelinfo.conf"
3527   if (node_parent == NULL && !valid_entry_found)
3528   {
3529     // check if this directory directly contains a file "levelinfo.conf"
3530     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3531                                                     level_directory, ".");
3532   }
3533
3534   if (!valid_entry_found)
3535     Warn("cannot find any valid level series in directory '%s'",
3536           level_directory);
3537 }
3538
3539 boolean AdjustGraphicsForEMC(void)
3540 {
3541   boolean settings_changed = FALSE;
3542
3543   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3544   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3545
3546   return settings_changed;
3547 }
3548
3549 boolean AdjustSoundsForEMC(void)
3550 {
3551   boolean settings_changed = FALSE;
3552
3553   settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3554   settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3555
3556   return settings_changed;
3557 }
3558
3559 void LoadLevelInfo(void)
3560 {
3561   InitUserLevelDirectory(getLoginName());
3562
3563   DrawInitText("Loading level series", 120, FC_GREEN);
3564
3565   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3566   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3567
3568   leveldir_first = createTopTreeInfoNode(leveldir_first);
3569
3570   /* after loading all level set information, clone the level directory tree
3571      and remove all level sets without levels (these may still contain artwork
3572      to be offered in the setup menu as "custom artwork", and are therefore
3573      checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3574   leveldir_first_all = leveldir_first;
3575   cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3576
3577   AdjustGraphicsForEMC();
3578   AdjustSoundsForEMC();
3579
3580   // before sorting, the first entries will be from the user directory
3581   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3582
3583   if (leveldir_first == NULL)
3584     Fail("cannot find any valid level series in any directory");
3585
3586   sortTreeInfo(&leveldir_first);
3587
3588 #if ENABLE_UNUSED_CODE
3589   dumpTreeInfo(leveldir_first, 0);
3590 #endif
3591 }
3592
3593 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3594                                               TreeInfo *node_parent,
3595                                               char *base_directory,
3596                                               char *directory_name, int type)
3597 {
3598   char *directory_path = getPath2(base_directory, directory_name);
3599   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3600   SetupFileHash *setup_file_hash = NULL;
3601   TreeInfo *artwork_new = NULL;
3602   int i;
3603
3604   if (fileExists(filename))
3605     setup_file_hash = loadSetupFileHash(filename);
3606
3607   if (setup_file_hash == NULL)  // no config file -- look for artwork files
3608   {
3609     Directory *dir;
3610     DirectoryEntry *dir_entry;
3611     boolean valid_file_found = FALSE;
3612
3613     if ((dir = openDirectory(directory_path)) != NULL)
3614     {
3615       while ((dir_entry = readDirectory(dir)) != NULL)
3616       {
3617         if (FileIsArtworkType(dir_entry->filename, type))
3618         {
3619           valid_file_found = TRUE;
3620
3621           break;
3622         }
3623       }
3624
3625       closeDirectory(dir);
3626     }
3627
3628     if (!valid_file_found)
3629     {
3630 #if DEBUG_NO_CONFIG_FILE
3631       if (!strEqual(directory_name, "."))
3632         Debug("setup", "ignoring artwork directory '%s'", directory_path);
3633 #endif
3634
3635       free(directory_path);
3636       free(filename);
3637
3638       return FALSE;
3639     }
3640   }
3641
3642   artwork_new = newTreeInfo();
3643
3644   if (node_parent)
3645     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3646   else
3647     setTreeInfoToDefaults(artwork_new, type);
3648
3649   artwork_new->subdir = getStringCopy(directory_name);
3650
3651   if (setup_file_hash)  // (before defining ".color" and ".class_desc")
3652   {
3653     // set all structure fields according to the token/value pairs
3654     ldi = *artwork_new;
3655     for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3656       setSetupInfo(levelinfo_tokens, i,
3657                    getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3658     *artwork_new = ldi;
3659
3660     if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3661       setString(&artwork_new->name, artwork_new->subdir);
3662
3663     if (artwork_new->identifier == NULL)
3664       artwork_new->identifier = getStringCopy(artwork_new->subdir);
3665
3666     if (artwork_new->name_sorting == NULL)
3667       artwork_new->name_sorting = getStringCopy(artwork_new->name);
3668   }
3669
3670   if (node_parent == NULL)              // top level group
3671   {
3672     artwork_new->basepath = getStringCopy(base_directory);
3673     artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3674   }
3675   else                                  // sub level group
3676   {
3677     artwork_new->basepath = getStringCopy(node_parent->basepath);
3678     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3679   }
3680
3681   artwork_new->in_user_dir =
3682     (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3683
3684   // (may use ".sort_priority" from "setup_file_hash" above)
3685   artwork_new->color = ARTWORKCOLOR(artwork_new);
3686
3687   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3688
3689   if (setup_file_hash == NULL)  // (after determining ".user_defined")
3690   {
3691     if (strEqual(artwork_new->subdir, "."))
3692     {
3693       if (artwork_new->user_defined)
3694       {
3695         setString(&artwork_new->identifier, "private");
3696         artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3697       }
3698       else
3699       {
3700         setString(&artwork_new->identifier, "classic");
3701         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3702       }
3703
3704       // set to new values after changing ".sort_priority"
3705       artwork_new->color = ARTWORKCOLOR(artwork_new);
3706
3707       setString(&artwork_new->class_desc,
3708                 getLevelClassDescription(artwork_new));
3709     }
3710     else
3711     {
3712       setString(&artwork_new->identifier, artwork_new->subdir);
3713     }
3714
3715     setString(&artwork_new->name, artwork_new->identifier);
3716     setString(&artwork_new->name_sorting, artwork_new->name);
3717   }
3718
3719   pushTreeInfo(node_first, artwork_new);
3720
3721   freeSetupFileHash(setup_file_hash);
3722
3723   free(directory_path);
3724   free(filename);
3725
3726   return TRUE;
3727 }
3728
3729 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3730                                           TreeInfo *node_parent,
3731                                           char *base_directory, int type)
3732 {
3733   // ---------- 1st stage: process any artwork set zip files ----------
3734
3735   ProcessZipFilesInDirectory(base_directory, type);
3736
3737   // ---------- 2nd stage: check for artwork set directories ----------
3738
3739   Directory *dir;
3740   DirectoryEntry *dir_entry;
3741   boolean valid_entry_found = FALSE;
3742
3743   if ((dir = openDirectory(base_directory)) == NULL)
3744   {
3745     // display error if directory is main "options.graphics_directory" etc.
3746     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3747       Warn("cannot read directory '%s'", base_directory);
3748
3749     return;
3750   }
3751
3752   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
3753   {
3754     char *directory_name = dir_entry->basename;
3755     char *directory_path = getPath2(base_directory, directory_name);
3756
3757     // skip directory entries for current and parent directory
3758     if (strEqual(directory_name, ".") ||
3759         strEqual(directory_name, ".."))
3760     {
3761       free(directory_path);
3762
3763       continue;
3764     }
3765
3766     // skip directory entries which are not a directory
3767     if (!dir_entry->is_directory)                       // not a directory
3768     {
3769       free(directory_path);
3770
3771       continue;
3772     }
3773
3774     free(directory_path);
3775
3776     // check if this directory contains artwork with or without config file
3777     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3778                                                         base_directory,
3779                                                         directory_name, type);
3780   }
3781
3782   closeDirectory(dir);
3783
3784   // check if this directory directly contains artwork itself
3785   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3786                                                       base_directory, ".",
3787                                                       type);
3788   if (!valid_entry_found)
3789     Warn("cannot find any valid artwork in directory '%s'", base_directory);
3790 }
3791
3792 static TreeInfo *getDummyArtworkInfo(int type)
3793 {
3794   // this is only needed when there is completely no artwork available
3795   TreeInfo *artwork_new = newTreeInfo();
3796
3797   setTreeInfoToDefaults(artwork_new, type);
3798
3799   setString(&artwork_new->subdir,   UNDEFINED_FILENAME);
3800   setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3801   setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3802
3803   setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
3804   setString(&artwork_new->name,         UNDEFINED_FILENAME);
3805   setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3806
3807   return artwork_new;
3808 }
3809
3810 void SetCurrentArtwork(int type)
3811 {
3812   ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3813   ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3814   char *setup_set = SETUP_ARTWORK_SET(setup, type);
3815   char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3816
3817   // set current artwork to artwork configured in setup menu
3818   *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3819
3820   // if not found, set current artwork to default artwork
3821   if (*current_ptr == NULL)
3822     *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3823
3824   // if not found, set current artwork to first artwork in tree
3825   if (*current_ptr == NULL)
3826     *current_ptr = getFirstValidTreeInfoEntry(first_node);
3827 }
3828
3829 void ChangeCurrentArtworkIfNeeded(int type)
3830 {
3831   char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3832   char *setup_set = SETUP_ARTWORK_SET(setup, type);
3833
3834   if (!strEqual(current_identifier, setup_set))
3835     SetCurrentArtwork(type);
3836 }
3837
3838 void LoadArtworkInfo(void)
3839 {
3840   LoadArtworkInfoCache();
3841
3842   DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3843
3844   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3845                                 options.graphics_directory,
3846                                 TREE_TYPE_GRAPHICS_DIR);
3847   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3848                                 getUserGraphicsDir(),
3849                                 TREE_TYPE_GRAPHICS_DIR);
3850
3851   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3852                                 options.sounds_directory,
3853                                 TREE_TYPE_SOUNDS_DIR);
3854   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3855                                 getUserSoundsDir(),
3856                                 TREE_TYPE_SOUNDS_DIR);
3857
3858   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3859                                 options.music_directory,
3860                                 TREE_TYPE_MUSIC_DIR);
3861   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3862                                 getUserMusicDir(),
3863                                 TREE_TYPE_MUSIC_DIR);
3864
3865   if (artwork.gfx_first == NULL)
3866     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3867   if (artwork.snd_first == NULL)
3868     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3869   if (artwork.mus_first == NULL)
3870     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3871
3872   // before sorting, the first entries will be from the user directory
3873   SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
3874   SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
3875   SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
3876
3877   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3878   artwork.snd_current_identifier = artwork.snd_current->identifier;
3879   artwork.mus_current_identifier = artwork.mus_current->identifier;
3880
3881 #if ENABLE_UNUSED_CODE
3882   Debug("setup:LoadArtworkInfo", "graphics set == %s",
3883         artwork.gfx_current_identifier);
3884   Debug("setup:LoadArtworkInfo", "sounds set == %s",
3885         artwork.snd_current_identifier);
3886   Debug("setup:LoadArtworkInfo", "music set == %s",
3887         artwork.mus_current_identifier);
3888 #endif
3889
3890   sortTreeInfo(&artwork.gfx_first);
3891   sortTreeInfo(&artwork.snd_first);
3892   sortTreeInfo(&artwork.mus_first);
3893
3894 #if ENABLE_UNUSED_CODE
3895   dumpTreeInfo(artwork.gfx_first, 0);
3896   dumpTreeInfo(artwork.snd_first, 0);
3897   dumpTreeInfo(artwork.mus_first, 0);
3898 #endif
3899 }
3900
3901 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3902                                          LevelDirTree *level_node)
3903 {
3904   int type = (*artwork_node)->type;
3905
3906   // recursively check all level directories for artwork sub-directories
3907
3908   while (level_node)
3909   {
3910     // check all tree entries for artwork, but skip parent link entries
3911     if (!level_node->parent_link)
3912     {
3913       TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3914       boolean cached = (artwork_new != NULL);
3915
3916       if (cached)
3917       {
3918         pushTreeInfo(artwork_node, artwork_new);
3919       }
3920       else
3921       {
3922         TreeInfo *topnode_last = *artwork_node;
3923         char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3924                               ARTWORK_DIRECTORY(type));
3925
3926         LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3927
3928         if (topnode_last != *artwork_node)      // check for newly added node
3929         {
3930           artwork_new = *artwork_node;
3931
3932           setString(&artwork_new->identifier,   level_node->subdir);
3933           setString(&artwork_new->name,         level_node->name);
3934           setString(&artwork_new->name_sorting, level_node->name_sorting);
3935
3936           artwork_new->sort_priority = level_node->sort_priority;
3937           artwork_new->color = LEVELCOLOR(artwork_new);
3938         }
3939
3940         free(path);
3941       }
3942
3943       // insert artwork info (from old cache or filesystem) into new cache
3944       if (artwork_new != NULL)
3945         setArtworkInfoCacheEntry(artwork_new, level_node, type);
3946     }
3947
3948     DrawInitText(level_node->name, 150, FC_YELLOW);
3949
3950     if (level_node->node_group != NULL)
3951       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3952
3953     level_node = level_node->next;
3954   }
3955 }
3956
3957 void LoadLevelArtworkInfo(void)
3958 {
3959   print_timestamp_init("LoadLevelArtworkInfo");
3960
3961   DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3962
3963   print_timestamp_time("DrawTimeText");
3964
3965   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3966   print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3967   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3968   print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3969   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3970   print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3971
3972   SaveArtworkInfoCache();
3973
3974   print_timestamp_time("SaveArtworkInfoCache");
3975
3976   // needed for reloading level artwork not known at ealier stage
3977   ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
3978   ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
3979   ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
3980
3981   print_timestamp_time("getTreeInfoFromIdentifier");
3982
3983   sortTreeInfo(&artwork.gfx_first);
3984   sortTreeInfo(&artwork.snd_first);
3985   sortTreeInfo(&artwork.mus_first);
3986
3987   print_timestamp_time("sortTreeInfo");
3988
3989 #if ENABLE_UNUSED_CODE
3990   dumpTreeInfo(artwork.gfx_first, 0);
3991   dumpTreeInfo(artwork.snd_first, 0);
3992   dumpTreeInfo(artwork.mus_first, 0);
3993 #endif
3994
3995   print_timestamp_done("LoadLevelArtworkInfo");
3996 }
3997
3998 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
3999                                        char *tree_subdir_new, int type)
4000 {
4001   if (tree_node_old == NULL)
4002   {
4003     if (type == TREE_TYPE_LEVEL_DIR)
4004     {
4005       // get level info tree node of personal user level set
4006       tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4007
4008       // this may happen if "setup.internal.create_user_levelset" is FALSE
4009       // or if file "levelinfo.conf" is missing in personal user level set
4010       if (tree_node_old == NULL)
4011         tree_node_old = leveldir_first->node_group;
4012     }
4013     else
4014     {
4015       // get artwork info tree node of first artwork set
4016       tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4017     }
4018   }
4019
4020   if (tree_dir == NULL)
4021     tree_dir = TREE_USERDIR(type);
4022
4023   if (tree_node_old   == NULL ||
4024       tree_dir        == NULL ||
4025       tree_subdir_new == NULL)          // should not happen
4026     return FALSE;
4027
4028   int draw_deactivation_mask = GetDrawDeactivationMask();
4029
4030   // override draw deactivation mask (temporarily disable drawing)
4031   SetDrawDeactivationMask(REDRAW_ALL);
4032
4033   if (type == TREE_TYPE_LEVEL_DIR)
4034   {
4035     // load new level set config and add it next to first user level set
4036     LoadLevelInfoFromLevelConf(&tree_node_old->next,
4037                                tree_node_old->node_parent,
4038                                tree_dir, tree_subdir_new);
4039   }
4040   else
4041   {
4042     // load new artwork set config and add it next to first artwork set
4043     LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4044                                    tree_node_old->node_parent,
4045                                    tree_dir, tree_subdir_new, type);
4046   }
4047
4048   // set draw deactivation mask to previous value
4049   SetDrawDeactivationMask(draw_deactivation_mask);
4050
4051   // get first node of level or artwork info tree
4052   TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4053
4054   // get tree info node of newly added level or artwork set
4055   TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4056                                                       tree_subdir_new);
4057
4058   if (tree_node_new == NULL)            // should not happen
4059     return FALSE;
4060
4061   // correct top link and parent node link of newly created tree node
4062   tree_node_new->node_top    = tree_node_old->node_top;
4063   tree_node_new->node_parent = tree_node_old->node_parent;
4064
4065   // sort tree info to adjust position of newly added tree set
4066   sortTreeInfo(tree_node_first);
4067
4068   return TRUE;
4069 }
4070
4071 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4072                           char *tree_subdir_new, int type)
4073 {
4074   if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4075     Fail("internal tree info structure corrupted -- aborting");
4076 }
4077
4078 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4079 {
4080   AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4081 }
4082
4083 char *getArtworkIdentifierForUserLevelSet(int type)
4084 {
4085   char *classic_artwork_set = getClassicArtworkSet(type);
4086
4087   // check for custom artwork configured in "levelinfo.conf"
4088   char *leveldir_artwork_set =
4089     *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4090   boolean has_leveldir_artwork_set =
4091     (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4092                                                classic_artwork_set));
4093
4094   // check for custom artwork in sub-directory "graphics" etc.
4095   TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4096   char *leveldir_identifier = leveldir_current->identifier;
4097   boolean has_artwork_subdir =
4098     (getTreeInfoFromIdentifier(artwork_first_node,
4099                                leveldir_identifier) != NULL);
4100
4101   return (has_leveldir_artwork_set ? leveldir_artwork_set :
4102           has_artwork_subdir       ? leveldir_identifier :
4103           classic_artwork_set);
4104 }
4105
4106 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4107 {
4108   char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4109   TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4110   TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4111
4112   if (ti == NULL)
4113   {
4114     ti = getTreeInfoFromIdentifier(artwork_first_node,
4115                                    ARTWORK_DEFAULT_SUBDIR(type));
4116     if (ti == NULL)
4117       Fail("cannot find default graphics -- should not happen");
4118   }
4119
4120   return ti;
4121 }
4122
4123 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4124 {
4125   char *graphics_set =
4126     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4127   char *sounds_set =
4128     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4129   char *music_set =
4130     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4131
4132   return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4133           !strEqual(sounds_set,   SND_CLASSIC_SUBDIR) ||
4134           !strEqual(music_set,    MUS_CLASSIC_SUBDIR));
4135 }
4136
4137 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4138                            char *level_author, int num_levels)
4139 {
4140   char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4141   char *filename_tmp = getStringCat2(filename, ".tmp");
4142   FILE *file = NULL;
4143   FILE *file_tmp = NULL;
4144   char line[MAX_LINE_LEN];
4145   boolean success = FALSE;
4146   LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4147                                                      level_subdir);
4148   // update values in level directory tree
4149
4150   if (level_name != NULL)
4151     setString(&leveldir->name, level_name);
4152
4153   if (level_author != NULL)
4154     setString(&leveldir->author, level_author);
4155
4156   if (num_levels != -1)
4157     leveldir->levels = num_levels;
4158
4159   // update values that depend on other values
4160
4161   setString(&leveldir->name_sorting, leveldir->name);
4162
4163   leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4164
4165   // sort order of level sets may have changed
4166   sortTreeInfo(&leveldir_first);
4167
4168   if ((file     = fopen(filename,     MODE_READ)) &&
4169       (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4170   {
4171     while (fgets(line, MAX_LINE_LEN, file))
4172     {
4173       if (strPrefix(line, "name:") && level_name != NULL)
4174         fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4175       else if (strPrefix(line, "author:") && level_author != NULL)
4176         fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4177       else if (strPrefix(line, "levels:") && num_levels != -1)
4178         fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4179       else
4180         fputs(line, file_tmp);
4181     }
4182
4183     success = TRUE;
4184   }
4185
4186   if (file)
4187     fclose(file);
4188
4189   if (file_tmp)
4190     fclose(file_tmp);
4191
4192   if (success)
4193     success = (rename(filename_tmp, filename) == 0);
4194
4195   free(filename);
4196   free(filename_tmp);
4197
4198   return success;
4199 }
4200
4201 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4202                            char *level_author, int num_levels,
4203                            boolean use_artwork_set)
4204 {
4205   LevelDirTree *level_info;
4206   char *filename;
4207   FILE *file;
4208   int i;
4209
4210   // create user level sub-directory, if needed
4211   createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4212
4213   filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4214
4215   if (!(file = fopen(filename, MODE_WRITE)))
4216   {
4217     Warn("cannot write level info file '%s'", filename);
4218
4219     free(filename);
4220
4221     return FALSE;
4222   }
4223
4224   level_info = newTreeInfo();
4225
4226   // always start with reliable default values
4227   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4228
4229   setString(&level_info->name, level_name);
4230   setString(&level_info->author, level_author);
4231   level_info->levels = num_levels;
4232   level_info->first_level = 1;
4233   level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4234   level_info->readonly = FALSE;
4235
4236   if (use_artwork_set)
4237   {
4238     level_info->graphics_set =
4239       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4240     level_info->sounds_set =
4241       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4242     level_info->music_set =
4243       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4244   }
4245
4246   token_value_position = TOKEN_VALUE_POSITION_SHORT;
4247
4248   fprintFileHeader(file, LEVELINFO_FILENAME);
4249
4250   ldi = *level_info;
4251   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4252   {
4253     if (i == LEVELINFO_TOKEN_NAME ||
4254         i == LEVELINFO_TOKEN_AUTHOR ||
4255         i == LEVELINFO_TOKEN_LEVELS ||
4256         i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4257         i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4258         i == LEVELINFO_TOKEN_READONLY ||
4259         (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4260                              i == LEVELINFO_TOKEN_SOUNDS_SET ||
4261                              i == LEVELINFO_TOKEN_MUSIC_SET)))
4262       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4263
4264     // just to make things nicer :)
4265     if (i == LEVELINFO_TOKEN_AUTHOR ||
4266         i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4267         (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4268       fprintf(file, "\n");      
4269   }
4270
4271   token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4272
4273   fclose(file);
4274
4275   SetFilePermissions(filename, PERMS_PRIVATE);
4276
4277   freeTreeInfo(level_info);
4278   free(filename);
4279
4280   return TRUE;
4281 }
4282
4283 static void SaveUserLevelInfo(void)
4284 {
4285   CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4286 }
4287
4288 char *getSetupValue(int type, void *value)
4289 {
4290   static char value_string[MAX_LINE_LEN];
4291
4292   if (value == NULL)
4293     return NULL;
4294
4295   switch (type)
4296   {
4297     case TYPE_BOOLEAN:
4298       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4299       break;
4300
4301     case TYPE_SWITCH:
4302       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4303       break;
4304
4305     case TYPE_SWITCH3:
4306       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
4307                             *(int *)value == FALSE ? "off" : "on"));
4308       break;
4309
4310     case TYPE_YES_NO:
4311       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4312       break;
4313
4314     case TYPE_YES_NO_AUTO:
4315       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
4316                             *(int *)value == FALSE ? "no" : "yes"));
4317       break;
4318
4319     case TYPE_ECS_AGA:
4320       strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4321       break;
4322
4323     case TYPE_KEY:
4324       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4325       break;
4326
4327     case TYPE_KEY_X11:
4328       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4329       break;
4330
4331     case TYPE_INTEGER:
4332       sprintf(value_string, "%d", *(int *)value);
4333       break;
4334
4335     case TYPE_STRING:
4336       if (*(char **)value == NULL)
4337         return NULL;
4338
4339       strcpy(value_string, *(char **)value);
4340       break;
4341
4342     case TYPE_PLAYER:
4343       sprintf(value_string, "player_%d", *(int *)value + 1);
4344       break;
4345
4346     default:
4347       value_string[0] = '\0';
4348       break;
4349   }
4350
4351   if (type & TYPE_GHOSTED)
4352     strcpy(value_string, "n/a");
4353
4354   return value_string;
4355 }
4356
4357 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4358 {
4359   int i;
4360   char *line;
4361   static char token_string[MAX_LINE_LEN];
4362   int token_type = token_info[token_nr].type;
4363   void *setup_value = token_info[token_nr].value;
4364   char *token_text = token_info[token_nr].text;
4365   char *value_string = getSetupValue(token_type, setup_value);
4366
4367   // build complete token string
4368   sprintf(token_string, "%s%s", prefix, token_text);
4369
4370   // build setup entry line
4371   line = getFormattedSetupEntry(token_string, value_string);
4372
4373   if (token_type == TYPE_KEY_X11)
4374   {
4375     Key key = *(Key *)setup_value;
4376     char *keyname = getKeyNameFromKey(key);
4377
4378     // add comment, if useful
4379     if (!strEqual(keyname, "(undefined)") &&
4380         !strEqual(keyname, "(unknown)"))
4381     {
4382       // add at least one whitespace
4383       strcat(line, " ");
4384       for (i = strlen(line); i < token_comment_position; i++)
4385         strcat(line, " ");
4386
4387       strcat(line, "# ");
4388       strcat(line, keyname);
4389     }
4390   }
4391
4392   return line;
4393 }
4394
4395 void LoadLevelSetup_LastSeries(void)
4396 {
4397   // --------------------------------------------------------------------------
4398   // ~/.<program>/levelsetup.conf
4399   // --------------------------------------------------------------------------
4400
4401   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4402   SetupFileHash *level_setup_hash = NULL;
4403
4404   // always start with reliable default values
4405   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4406
4407   if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4408   {
4409     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4410                                                  DEFAULT_LEVELSET);
4411     if (leveldir_current == NULL)
4412       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4413   }
4414
4415   if ((level_setup_hash = loadSetupFileHash(filename)))
4416   {
4417     char *last_level_series =
4418       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4419
4420     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4421                                                  last_level_series);
4422     if (leveldir_current == NULL)
4423       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4424
4425     freeSetupFileHash(level_setup_hash);
4426   }
4427   else
4428   {
4429     Debug("setup", "using default setup values");
4430   }
4431
4432   free(filename);
4433 }
4434
4435 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4436 {
4437   // --------------------------------------------------------------------------
4438   // ~/.<program>/levelsetup.conf
4439   // --------------------------------------------------------------------------
4440
4441   // check if the current level directory structure is available at this point
4442   if (leveldir_current == NULL)
4443     return;
4444
4445   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4446   char *level_subdir = leveldir_current->subdir;
4447   FILE *file;
4448
4449   InitUserDataDirectory();
4450
4451   if (!(file = fopen(filename, MODE_WRITE)))
4452   {
4453     Warn("cannot write setup file '%s'", filename);
4454
4455     free(filename);
4456
4457     return;
4458   }
4459
4460   fprintFileHeader(file, LEVELSETUP_FILENAME);
4461
4462   if (deactivate_last_level_series)
4463     fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4464
4465   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4466                                                level_subdir));
4467
4468   fclose(file);
4469
4470   SetFilePermissions(filename, PERMS_PRIVATE);
4471
4472   free(filename);
4473 }
4474
4475 void SaveLevelSetup_LastSeries(void)
4476 {
4477   SaveLevelSetup_LastSeries_Ext(FALSE);
4478 }
4479
4480 void SaveLevelSetup_LastSeries_Deactivate(void)
4481 {
4482   SaveLevelSetup_LastSeries_Ext(TRUE);
4483 }
4484
4485 static void checkSeriesInfo(void)
4486 {
4487   static char *level_directory = NULL;
4488   Directory *dir;
4489 #if 0
4490   DirectoryEntry *dir_entry;
4491 #endif
4492
4493   checked_free(level_directory);
4494
4495   // check for more levels besides the 'levels' field of 'levelinfo.conf'
4496
4497   level_directory = getPath2((leveldir_current->in_user_dir ?
4498                               getUserLevelDir(NULL) :
4499                               options.level_directory),
4500                              leveldir_current->fullpath);
4501
4502   if ((dir = openDirectory(level_directory)) == NULL)
4503   {
4504     Warn("cannot read level directory '%s'", level_directory);
4505
4506     return;
4507   }
4508
4509 #if 0
4510   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
4511   {
4512     if (strlen(dir_entry->basename) > 4 &&
4513         dir_entry->basename[3] == '.' &&
4514         strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4515     {
4516       char levelnum_str[4];
4517       int levelnum_value;
4518
4519       strncpy(levelnum_str, dir_entry->basename, 3);
4520       levelnum_str[3] = '\0';
4521
4522       levelnum_value = atoi(levelnum_str);
4523
4524       if (levelnum_value < leveldir_current->first_level)
4525       {
4526         Warn("additional level %d found", levelnum_value);
4527
4528         leveldir_current->first_level = levelnum_value;
4529       }
4530       else if (levelnum_value > leveldir_current->last_level)
4531       {
4532         Warn("additional level %d found", levelnum_value);
4533
4534         leveldir_current->last_level = levelnum_value;
4535       }
4536     }
4537   }
4538 #endif
4539
4540   closeDirectory(dir);
4541 }
4542
4543 void LoadLevelSetup_SeriesInfo(void)
4544 {
4545   char *filename;
4546   SetupFileHash *level_setup_hash = NULL;
4547   char *level_subdir = leveldir_current->subdir;
4548   int i;
4549
4550   // always start with reliable default values
4551   level_nr = leveldir_current->first_level;
4552
4553   for (i = 0; i < MAX_LEVELS; i++)
4554   {
4555     LevelStats_setPlayed(i, 0);
4556     LevelStats_setSolved(i, 0);
4557   }
4558
4559   checkSeriesInfo();
4560
4561   // --------------------------------------------------------------------------
4562   // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4563   // --------------------------------------------------------------------------
4564
4565   level_subdir = leveldir_current->subdir;
4566
4567   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4568
4569   if ((level_setup_hash = loadSetupFileHash(filename)))
4570   {
4571     char *token_value;
4572
4573     // get last played level in this level set
4574
4575     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4576
4577     if (token_value)
4578     {
4579       level_nr = atoi(token_value);
4580
4581       if (level_nr < leveldir_current->first_level)
4582         level_nr = leveldir_current->first_level;
4583       if (level_nr > leveldir_current->last_level)
4584         level_nr = leveldir_current->last_level;
4585     }
4586
4587     // get handicap level in this level set
4588
4589     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4590
4591     if (token_value)
4592     {
4593       int level_nr = atoi(token_value);
4594
4595       if (level_nr < leveldir_current->first_level)
4596         level_nr = leveldir_current->first_level;
4597       if (level_nr > leveldir_current->last_level + 1)
4598         level_nr = leveldir_current->last_level;
4599
4600       if (leveldir_current->user_defined || !leveldir_current->handicap)
4601         level_nr = leveldir_current->last_level;
4602
4603       leveldir_current->handicap_level = level_nr;
4604     }
4605
4606     // get number of played and solved levels in this level set
4607
4608     BEGIN_HASH_ITERATION(level_setup_hash, itr)
4609     {
4610       char *token = HASH_ITERATION_TOKEN(itr);
4611       char *value = HASH_ITERATION_VALUE(itr);
4612
4613       if (strlen(token) == 3 &&
4614           token[0] >= '0' && token[0] <= '9' &&
4615           token[1] >= '0' && token[1] <= '9' &&
4616           token[2] >= '0' && token[2] <= '9')
4617       {
4618         int level_nr = atoi(token);
4619
4620         if (value != NULL)
4621           LevelStats_setPlayed(level_nr, atoi(value));  // read 1st column
4622
4623         value = strchr(value, ' ');
4624
4625         if (value != NULL)
4626           LevelStats_setSolved(level_nr, atoi(value));  // read 2nd column
4627       }
4628     }
4629     END_HASH_ITERATION(hash, itr)
4630
4631     freeSetupFileHash(level_setup_hash);
4632   }
4633   else
4634   {
4635     Debug("setup", "using default setup values");
4636   }
4637
4638   free(filename);
4639 }
4640
4641 void SaveLevelSetup_SeriesInfo(void)
4642 {
4643   char *filename;
4644   char *level_subdir = leveldir_current->subdir;
4645   char *level_nr_str = int2str(level_nr, 0);
4646   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4647   FILE *file;
4648   int i;
4649
4650   // --------------------------------------------------------------------------
4651   // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4652   // --------------------------------------------------------------------------
4653
4654   InitLevelSetupDirectory(level_subdir);
4655
4656   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4657
4658   if (!(file = fopen(filename, MODE_WRITE)))
4659   {
4660     Warn("cannot write setup file '%s'", filename);
4661
4662     free(filename);
4663
4664     return;
4665   }
4666
4667   fprintFileHeader(file, LEVELSETUP_FILENAME);
4668
4669   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4670                                                level_nr_str));
4671   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4672                                                  handicap_level_str));
4673
4674   for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4675        i++)
4676   {
4677     if (LevelStats_getPlayed(i) > 0 ||
4678         LevelStats_getSolved(i) > 0)
4679     {
4680       char token[16];
4681       char value[16];
4682
4683       sprintf(token, "%03d", i);
4684       sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4685
4686       fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4687     }
4688   }
4689
4690   fclose(file);
4691
4692   SetFilePermissions(filename, PERMS_PRIVATE);
4693
4694   free(filename);
4695 }
4696
4697 int LevelStats_getPlayed(int nr)
4698 {
4699   return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4700 }
4701
4702 int LevelStats_getSolved(int nr)
4703 {
4704   return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4705 }
4706
4707 void LevelStats_setPlayed(int nr, int value)
4708 {
4709   if (nr >= 0 && nr < MAX_LEVELS)
4710     level_stats[nr].played = value;
4711 }
4712
4713 void LevelStats_setSolved(int nr, int value)
4714 {
4715   if (nr >= 0 && nr < MAX_LEVELS)
4716     level_stats[nr].solved = value;
4717 }
4718
4719 void LevelStats_incPlayed(int nr)
4720 {
4721   if (nr >= 0 && nr < MAX_LEVELS)
4722     level_stats[nr].played++;
4723 }
4724
4725 void LevelStats_incSolved(int nr)
4726 {
4727   if (nr >= 0 && nr < MAX_LEVELS)
4728     level_stats[nr].solved++;
4729 }
4730
4731 void LoadUserSetup(void)
4732 {
4733   // --------------------------------------------------------------------------
4734   // ~/.<program>/usersetup.conf
4735   // --------------------------------------------------------------------------
4736
4737   char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
4738   SetupFileHash *user_setup_hash = NULL;
4739
4740   // always start with reliable default values
4741   user.nr = 0;
4742
4743   if ((user_setup_hash = loadSetupFileHash(filename)))
4744   {
4745     char *token_value;
4746
4747     // get last selected user number
4748     token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
4749
4750     if (token_value)
4751       user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
4752
4753     freeSetupFileHash(user_setup_hash);
4754   }
4755   else
4756   {
4757     Debug("setup", "using default setup values");
4758   }
4759
4760   free(filename);
4761 }
4762
4763 void SaveUserSetup(void)
4764 {
4765   // --------------------------------------------------------------------------
4766   // ~/.<program>/usersetup.conf
4767   // --------------------------------------------------------------------------
4768
4769   char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
4770   FILE *file;
4771
4772   InitMainUserDataDirectory();
4773
4774   if (!(file = fopen(filename, MODE_WRITE)))
4775   {
4776     Warn("cannot write setup file '%s'", filename);
4777
4778     free(filename);
4779
4780     return;
4781   }
4782
4783   fprintFileHeader(file, USERSETUP_FILENAME);
4784
4785   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
4786                                                i_to_a(user.nr)));
4787   fclose(file);
4788
4789   SetFilePermissions(filename, PERMS_PRIVATE);
4790
4791   free(filename);
4792 }