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