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