eec6cb5a6ca1f6b5d187037bc32dcf35ebc172e5
[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 char *getUserGraphicsDir(void)
344 {
345   static char *usergraphics_dir = NULL;
346
347   if (usergraphics_dir == NULL)
348     usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
349
350   return usergraphics_dir;
351 }
352
353 char *getUserSoundsDir(void)
354 {
355   static char *usersounds_dir = NULL;
356
357   if (usersounds_dir == NULL)
358     usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
359
360   return usersounds_dir;
361 }
362
363 char *getUserMusicDir(void)
364 {
365   static char *usermusic_dir = NULL;
366
367   if (usermusic_dir == NULL)
368     usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
369
370   return usermusic_dir;
371 }
372
373 static char *getSetupArtworkDir(TreeInfo *ti)
374 {
375   static char *artwork_dir = NULL;
376
377   if (ti == NULL)
378     return NULL;
379
380   checked_free(artwork_dir);
381
382   artwork_dir = getPath2(ti->basepath, ti->fullpath);
383
384   return artwork_dir;
385 }
386
387 char *setLevelArtworkDir(TreeInfo *ti)
388 {
389   char **artwork_path_ptr, **artwork_set_ptr;
390   TreeInfo *level_artwork;
391
392   if (ti == NULL || leveldir_current == NULL)
393     return NULL;
394
395   artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
396   artwork_set_ptr  = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
397
398   checked_free(*artwork_path_ptr);
399
400   if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
401   {
402     *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
403   }
404   else
405   {
406     /*
407       No (or non-existing) artwork configured in "levelinfo.conf". This would
408       normally result in using the artwork configured in the setup menu. But
409       if an artwork subdirectory exists (which might contain custom artwork
410       or an artwork configuration file), this level artwork must be treated
411       as relative to the default "classic" artwork, not to the artwork that
412       is currently configured in the setup menu.
413
414       Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
415       the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
416       the real "classic" artwork from the original R'n'D (like "gfx_classic").
417     */
418
419     char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
420
421     checked_free(*artwork_set_ptr);
422
423     if (directoryExists(dir))
424     {
425       *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
426       *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
427     }
428     else
429     {
430       *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
431       *artwork_set_ptr = NULL;
432     }
433
434     free(dir);
435   }
436
437   return *artwork_set_ptr;
438 }
439
440 static char *getLevelArtworkSet(int type)
441 {
442   if (leveldir_current == NULL)
443     return NULL;
444
445   return LEVELDIR_ARTWORK_SET(leveldir_current, type);
446 }
447
448 static char *getLevelArtworkDir(int type)
449 {
450   if (leveldir_current == NULL)
451     return UNDEFINED_FILENAME;
452
453   return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
454 }
455
456 char *getProgramMainDataPath(char *command_filename, char *base_path)
457 {
458   // check if the program's main data base directory is configured
459   if (!strEqual(base_path, "."))
460     return 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 int GetZipFileTreeType(char *zip_filename)
2986 {
2987   static char *top_dir_path = NULL;
2988   static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
2989   static char *conf_basename[NUM_BASE_TREE_TYPES] =
2990   {
2991     GRAPHICSINFO_FILENAME,
2992     SOUNDSINFO_FILENAME,
2993     MUSICINFO_FILENAME,
2994     LEVELINFO_FILENAME
2995   };
2996   int j;
2997
2998   checked_free(top_dir_path);
2999   top_dir_path = NULL;
3000
3001   for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3002   {
3003     checked_free(top_dir_conf_filename[j]);
3004     top_dir_conf_filename[j] = NULL;
3005   }
3006
3007   char **zip_entries = zip_list(zip_filename);
3008
3009   // check if zip file successfully opened
3010   if (zip_entries == NULL || zip_entries[0] == NULL)
3011     return TREE_TYPE_UNDEFINED;
3012
3013   // first zip file entry is expected to be top level directory
3014   char *top_dir = zip_entries[0];
3015
3016   // check if valid top level directory found in zip file
3017   if (!strSuffix(top_dir, "/"))
3018     return TREE_TYPE_UNDEFINED;
3019
3020   // get filenames of valid configuration files in top level directory
3021   for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3022     top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3023
3024   int tree_type = TREE_TYPE_UNDEFINED;
3025   int e = 0;
3026
3027   while (zip_entries[e] != NULL)
3028   {
3029     // check if every zip file entry is below top level directory
3030     if (!strPrefix(zip_entries[e], top_dir))
3031       return TREE_TYPE_UNDEFINED;
3032
3033     // check if this zip file entry is a valid configuration filename
3034     for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3035     {
3036       if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3037       {
3038         // only exactly one valid configuration file allowed
3039         if (tree_type != TREE_TYPE_UNDEFINED)
3040           return TREE_TYPE_UNDEFINED;
3041
3042         tree_type = j;
3043       }
3044     }
3045
3046     e++;
3047   }
3048
3049   return tree_type;
3050 }
3051
3052 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3053                                         int tree_type)
3054 {
3055   static char *top_dir_path = NULL;
3056   static char *top_dir_conf_filename = NULL;
3057
3058   checked_free(top_dir_path);
3059   checked_free(top_dir_conf_filename);
3060
3061   top_dir_path = NULL;
3062   top_dir_conf_filename = NULL;
3063
3064   char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3065                          ARTWORKINFO_FILENAME(tree_type));
3066
3067   // check if valid configuration filename determined
3068   if (conf_basename == NULL || strEqual(conf_basename, ""))
3069     return FALSE;
3070
3071   char **zip_entries = zip_list(zip_filename);
3072
3073   // check if zip file successfully opened
3074   if (zip_entries == NULL || zip_entries[0] == NULL)
3075     return FALSE;
3076
3077   // first zip file entry is expected to be top level directory
3078   char *top_dir = zip_entries[0];
3079
3080   // check if valid top level directory found in zip file
3081   if (!strSuffix(top_dir, "/"))
3082     return FALSE;
3083
3084   // get path of extracted top level directory
3085   top_dir_path = getPath2(directory, top_dir);
3086
3087   // remove trailing directory separator from top level directory path
3088   // (required to be able to check for file and directory in next step)
3089   top_dir_path[strlen(top_dir_path) - 1] = '\0';
3090
3091   // check if zip file's top level directory already exists in target directory
3092   if (fileExists(top_dir_path))         // (checks for file and directory)
3093     return FALSE;
3094
3095   // get filename of configuration file in top level directory
3096   top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3097
3098   boolean found_top_dir_conf_filename = FALSE;
3099   int i = 0;
3100
3101   while (zip_entries[i] != NULL)
3102   {
3103     // check if every zip file entry is below top level directory
3104     if (!strPrefix(zip_entries[i], top_dir))
3105       return FALSE;
3106
3107     // check if this zip file entry is the configuration filename
3108     if (strEqual(zip_entries[i], top_dir_conf_filename))
3109       found_top_dir_conf_filename = TRUE;
3110
3111     i++;
3112   }
3113
3114   // check if valid configuration filename was found in zip file
3115   if (!found_top_dir_conf_filename)
3116     return FALSE;
3117
3118   return TRUE;
3119 }
3120
3121 boolean ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3122                                     int tree_type)
3123 {
3124   boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3125                                                     tree_type);
3126
3127   Error(ERR_DEBUG, "zip file '%s': %s", zip_filename,
3128         (zip_file_valid ? "EXTRACT" : "REJECT"));
3129
3130   if (!zip_file_valid)
3131     return FALSE;
3132
3133   char **zip_entries = zip_extract(zip_filename, directory);
3134
3135   boolean zip_file_extracted = (zip_entries != NULL);
3136
3137   if (zip_file_extracted)
3138     Error(ERR_DEBUG, "zip file successfully extracted!");
3139   else
3140     Error(ERR_DEBUG, "zip file could not be extracted!");
3141
3142   return zip_file_extracted;
3143 }
3144
3145 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3146 {
3147   Directory *dir;
3148   DirectoryEntry *dir_entry;
3149
3150   if ((dir = openDirectory(directory)) == NULL)
3151   {
3152     // display error if directory is main "options.graphics_directory" etc.
3153     if (tree_type == TREE_TYPE_LEVEL_DIR ||
3154         directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3155       Error(ERR_WARN, "cannot read directory '%s'", directory);
3156
3157     return;
3158   }
3159
3160   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
3161   {
3162     // skip non-zip files (and also directories with zip extension)
3163     if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3164       continue;
3165
3166     char *zip_filename = getPath2(directory, dir_entry->basename);
3167     char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3168     char *zip_filename_rejected  = getStringCat2(zip_filename, ".rejected");
3169
3170     // check if zip file hasn't already been extracted or rejected
3171     if (!fileExists(zip_filename_extracted) &&
3172         !fileExists(zip_filename_rejected))
3173     {
3174       boolean zip_file_extracted = ExtractZipFileIntoDirectory(zip_filename,
3175                                                                directory,
3176                                                                tree_type);
3177       char *marker_filename = (zip_file_extracted ? zip_filename_extracted :
3178                                zip_filename_rejected);
3179       FILE *marker_file;
3180
3181       // create empty file to mark zip file as extracted or rejected
3182       if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3183         fclose(marker_file);
3184
3185       free(zip_filename);
3186       free(zip_filename_extracted);
3187       free(zip_filename_rejected);
3188     }
3189   }
3190
3191   closeDirectory(dir);
3192 }
3193
3194 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3195 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3196
3197 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3198                                           TreeInfo *node_parent,
3199                                           char *level_directory,
3200                                           char *directory_name)
3201 {
3202   char *directory_path = getPath2(level_directory, directory_name);
3203   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3204   SetupFileHash *setup_file_hash;
3205   LevelDirTree *leveldir_new = NULL;
3206   int i;
3207
3208   // unless debugging, silently ignore directories without "levelinfo.conf"
3209   if (!options.debug && !fileExists(filename))
3210   {
3211     free(directory_path);
3212     free(filename);
3213
3214     return FALSE;
3215   }
3216
3217   setup_file_hash = loadSetupFileHash(filename);
3218
3219   if (setup_file_hash == NULL)
3220   {
3221 #if DEBUG_NO_CONFIG_FILE
3222     Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3223 #endif
3224
3225     free(directory_path);
3226     free(filename);
3227
3228     return FALSE;
3229   }
3230
3231   leveldir_new = newTreeInfo();
3232
3233   if (node_parent)
3234     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3235   else
3236     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3237
3238   leveldir_new->subdir = getStringCopy(directory_name);
3239
3240   // set all structure fields according to the token/value pairs
3241   ldi = *leveldir_new;
3242   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3243     setSetupInfo(levelinfo_tokens, i,
3244                  getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3245   *leveldir_new = ldi;
3246
3247   if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3248     setString(&leveldir_new->name, leveldir_new->subdir);
3249
3250   if (leveldir_new->identifier == NULL)
3251     leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3252
3253   if (leveldir_new->name_sorting == NULL)
3254     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3255
3256   if (node_parent == NULL)              // top level group
3257   {
3258     leveldir_new->basepath = getStringCopy(level_directory);
3259     leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3260   }
3261   else                                  // sub level group
3262   {
3263     leveldir_new->basepath = getStringCopy(node_parent->basepath);
3264     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3265   }
3266
3267   leveldir_new->last_level =
3268     leveldir_new->first_level + leveldir_new->levels - 1;
3269
3270   leveldir_new->in_user_dir =
3271     (!strEqual(leveldir_new->basepath, options.level_directory));
3272
3273   // adjust some settings if user's private level directory was detected
3274   if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3275       leveldir_new->in_user_dir &&
3276       (strEqual(leveldir_new->subdir, getLoginName()) ||
3277        strEqual(leveldir_new->name,   getLoginName()) ||
3278        strEqual(leveldir_new->author, getRealName())))
3279   {
3280     leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3281     leveldir_new->readonly = FALSE;
3282   }
3283
3284   leveldir_new->user_defined =
3285     (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3286
3287   leveldir_new->color = LEVELCOLOR(leveldir_new);
3288
3289   setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3290
3291   leveldir_new->handicap_level =        // set handicap to default value
3292     (leveldir_new->user_defined || !leveldir_new->handicap ?
3293      leveldir_new->last_level : leveldir_new->first_level);
3294
3295   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3296
3297   pushTreeInfo(node_first, leveldir_new);
3298
3299   freeSetupFileHash(setup_file_hash);
3300
3301   if (leveldir_new->level_group)
3302   {
3303     // create node to link back to current level directory
3304     createParentTreeInfoNode(leveldir_new);
3305
3306     // recursively step into sub-directory and look for more level series
3307     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3308                               leveldir_new, directory_path);
3309   }
3310
3311   free(directory_path);
3312   free(filename);
3313
3314   return TRUE;
3315 }
3316
3317 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3318                                       TreeInfo *node_parent,
3319                                       char *level_directory)
3320 {
3321   // ---------- 1st stage: process any level set zip files ----------
3322
3323   ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3324
3325   // ---------- 2nd stage: check for level set directories ----------
3326
3327   Directory *dir;
3328   DirectoryEntry *dir_entry;
3329   boolean valid_entry_found = FALSE;
3330
3331   if ((dir = openDirectory(level_directory)) == NULL)
3332   {
3333     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3334
3335     return;
3336   }
3337
3338   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
3339   {
3340     char *directory_name = dir_entry->basename;
3341     char *directory_path = getPath2(level_directory, directory_name);
3342
3343     // skip entries for current and parent directory
3344     if (strEqual(directory_name, ".") ||
3345         strEqual(directory_name, ".."))
3346     {
3347       free(directory_path);
3348
3349       continue;
3350     }
3351
3352     // find out if directory entry is itself a directory
3353     if (!dir_entry->is_directory)                       // not a directory
3354     {
3355       free(directory_path);
3356
3357       continue;
3358     }
3359
3360     free(directory_path);
3361
3362     if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3363         strEqual(directory_name, SOUNDS_DIRECTORY) ||
3364         strEqual(directory_name, MUSIC_DIRECTORY))
3365       continue;
3366
3367     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3368                                                     level_directory,
3369                                                     directory_name);
3370   }
3371
3372   closeDirectory(dir);
3373
3374   // special case: top level directory may directly contain "levelinfo.conf"
3375   if (node_parent == NULL && !valid_entry_found)
3376   {
3377     // check if this directory directly contains a file "levelinfo.conf"
3378     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3379                                                     level_directory, ".");
3380   }
3381
3382   if (!valid_entry_found)
3383     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3384           level_directory);
3385 }
3386
3387 boolean AdjustGraphicsForEMC(void)
3388 {
3389   boolean settings_changed = FALSE;
3390
3391   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3392   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3393
3394   return settings_changed;
3395 }
3396
3397 void LoadLevelInfo(void)
3398 {
3399   InitUserLevelDirectory(getLoginName());
3400
3401   DrawInitText("Loading level series", 120, FC_GREEN);
3402
3403   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3404   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3405
3406   leveldir_first = createTopTreeInfoNode(leveldir_first);
3407
3408   /* after loading all level set information, clone the level directory tree
3409      and remove all level sets without levels (these may still contain artwork
3410      to be offered in the setup menu as "custom artwork", and are therefore
3411      checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3412   leveldir_first_all = leveldir_first;
3413   cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3414
3415   AdjustGraphicsForEMC();
3416
3417   // before sorting, the first entries will be from the user directory
3418   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3419
3420   if (leveldir_first == NULL)
3421     Error(ERR_EXIT, "cannot find any valid level series in any directory");
3422
3423   sortTreeInfo(&leveldir_first);
3424
3425 #if ENABLE_UNUSED_CODE
3426   dumpTreeInfo(leveldir_first, 0);
3427 #endif
3428 }
3429
3430 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3431                                               TreeInfo *node_parent,
3432                                               char *base_directory,
3433                                               char *directory_name, int type)
3434 {
3435   char *directory_path = getPath2(base_directory, directory_name);
3436   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3437   SetupFileHash *setup_file_hash = NULL;
3438   TreeInfo *artwork_new = NULL;
3439   int i;
3440
3441   if (fileExists(filename))
3442     setup_file_hash = loadSetupFileHash(filename);
3443
3444   if (setup_file_hash == NULL)  // no config file -- look for artwork files
3445   {
3446     Directory *dir;
3447     DirectoryEntry *dir_entry;
3448     boolean valid_file_found = FALSE;
3449
3450     if ((dir = openDirectory(directory_path)) != NULL)
3451     {
3452       while ((dir_entry = readDirectory(dir)) != NULL)
3453       {
3454         if (FileIsArtworkType(dir_entry->filename, type))
3455         {
3456           valid_file_found = TRUE;
3457
3458           break;
3459         }
3460       }
3461
3462       closeDirectory(dir);
3463     }
3464
3465     if (!valid_file_found)
3466     {
3467 #if DEBUG_NO_CONFIG_FILE
3468       if (!strEqual(directory_name, "."))
3469         Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3470 #endif
3471
3472       free(directory_path);
3473       free(filename);
3474
3475       return FALSE;
3476     }
3477   }
3478
3479   artwork_new = newTreeInfo();
3480
3481   if (node_parent)
3482     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3483   else
3484     setTreeInfoToDefaults(artwork_new, type);
3485
3486   artwork_new->subdir = getStringCopy(directory_name);
3487
3488   if (setup_file_hash)  // (before defining ".color" and ".class_desc")
3489   {
3490     // set all structure fields according to the token/value pairs
3491     ldi = *artwork_new;
3492     for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3493       setSetupInfo(levelinfo_tokens, i,
3494                    getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3495     *artwork_new = ldi;
3496
3497     if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3498       setString(&artwork_new->name, artwork_new->subdir);
3499
3500     if (artwork_new->identifier == NULL)
3501       artwork_new->identifier = getStringCopy(artwork_new->subdir);
3502
3503     if (artwork_new->name_sorting == NULL)
3504       artwork_new->name_sorting = getStringCopy(artwork_new->name);
3505   }
3506
3507   if (node_parent == NULL)              // top level group
3508   {
3509     artwork_new->basepath = getStringCopy(base_directory);
3510     artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3511   }
3512   else                                  // sub level group
3513   {
3514     artwork_new->basepath = getStringCopy(node_parent->basepath);
3515     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3516   }
3517
3518   artwork_new->in_user_dir =
3519     (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3520
3521   // (may use ".sort_priority" from "setup_file_hash" above)
3522   artwork_new->color = ARTWORKCOLOR(artwork_new);
3523
3524   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3525
3526   if (setup_file_hash == NULL)  // (after determining ".user_defined")
3527   {
3528     if (strEqual(artwork_new->subdir, "."))
3529     {
3530       if (artwork_new->user_defined)
3531       {
3532         setString(&artwork_new->identifier, "private");
3533         artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3534       }
3535       else
3536       {
3537         setString(&artwork_new->identifier, "classic");
3538         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3539       }
3540
3541       // set to new values after changing ".sort_priority"
3542       artwork_new->color = ARTWORKCOLOR(artwork_new);
3543
3544       setString(&artwork_new->class_desc,
3545                 getLevelClassDescription(artwork_new));
3546     }
3547     else
3548     {
3549       setString(&artwork_new->identifier, artwork_new->subdir);
3550     }
3551
3552     setString(&artwork_new->name, artwork_new->identifier);
3553     setString(&artwork_new->name_sorting, artwork_new->name);
3554   }
3555
3556   pushTreeInfo(node_first, artwork_new);
3557
3558   freeSetupFileHash(setup_file_hash);
3559
3560   free(directory_path);
3561   free(filename);
3562
3563   return TRUE;
3564 }
3565
3566 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3567                                           TreeInfo *node_parent,
3568                                           char *base_directory, int type)
3569 {
3570   // ---------- 1st stage: process any artwork set zip files ----------
3571
3572   ProcessZipFilesInDirectory(base_directory, type);
3573
3574   // ---------- 2nd stage: check for artwork set directories ----------
3575
3576   Directory *dir;
3577   DirectoryEntry *dir_entry;
3578   boolean valid_entry_found = FALSE;
3579
3580   if ((dir = openDirectory(base_directory)) == NULL)
3581   {
3582     // display error if directory is main "options.graphics_directory" etc.
3583     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3584       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3585
3586     return;
3587   }
3588
3589   while ((dir_entry = readDirectory(dir)) != NULL)      // loop all entries
3590   {
3591     char *directory_name = dir_entry->basename;
3592     char *directory_path = getPath2(base_directory, directory_name);
3593
3594     // skip directory entries for current and parent directory
3595     if (strEqual(directory_name, ".") ||
3596         strEqual(directory_name, ".."))
3597     {
3598       free(directory_path);
3599
3600       continue;
3601     }
3602
3603     // skip directory entries which are not a directory
3604     if (!dir_entry->is_directory)                       // not a directory
3605     {
3606       free(directory_path);
3607
3608       continue;
3609     }
3610
3611     free(directory_path);
3612
3613     // check if this directory contains artwork with or without config file
3614     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3615                                                         base_directory,
3616                                                         directory_name, type);
3617   }
3618
3619   closeDirectory(dir);
3620
3621   // check if this directory directly contains artwork itself
3622   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3623                                                       base_directory, ".",
3624                                                       type);
3625   if (!valid_entry_found)
3626     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3627           base_directory);
3628 }
3629
3630 static TreeInfo *getDummyArtworkInfo(int type)
3631 {
3632   // this is only needed when there is completely no artwork available
3633   TreeInfo *artwork_new = newTreeInfo();
3634
3635   setTreeInfoToDefaults(artwork_new, type);
3636
3637   setString(&artwork_new->subdir,   UNDEFINED_FILENAME);
3638   setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3639   setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3640
3641   setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
3642   setString(&artwork_new->name,         UNDEFINED_FILENAME);
3643   setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3644
3645   return artwork_new;
3646 }
3647
3648 void LoadArtworkInfo(void)
3649 {
3650   LoadArtworkInfoCache();
3651
3652   DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3653
3654   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3655                                 options.graphics_directory,
3656                                 TREE_TYPE_GRAPHICS_DIR);
3657   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3658                                 getUserGraphicsDir(),
3659                                 TREE_TYPE_GRAPHICS_DIR);
3660
3661   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3662                                 options.sounds_directory,
3663                                 TREE_TYPE_SOUNDS_DIR);
3664   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3665                                 getUserSoundsDir(),
3666                                 TREE_TYPE_SOUNDS_DIR);
3667
3668   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3669                                 options.music_directory,
3670                                 TREE_TYPE_MUSIC_DIR);
3671   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3672                                 getUserMusicDir(),
3673                                 TREE_TYPE_MUSIC_DIR);
3674
3675   if (artwork.gfx_first == NULL)
3676     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3677   if (artwork.snd_first == NULL)
3678     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3679   if (artwork.mus_first == NULL)
3680     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3681
3682   // before sorting, the first entries will be from the user directory
3683   artwork.gfx_current =
3684     getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3685   if (artwork.gfx_current == NULL)
3686     artwork.gfx_current =
3687       getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3688   if (artwork.gfx_current == NULL)
3689     artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3690
3691   artwork.snd_current =
3692     getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3693   if (artwork.snd_current == NULL)
3694     artwork.snd_current =
3695       getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3696   if (artwork.snd_current == NULL)
3697     artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3698
3699   artwork.mus_current =
3700     getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3701   if (artwork.mus_current == NULL)
3702     artwork.mus_current =
3703       getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3704   if (artwork.mus_current == NULL)
3705     artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3706
3707   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3708   artwork.snd_current_identifier = artwork.snd_current->identifier;
3709   artwork.mus_current_identifier = artwork.mus_current->identifier;
3710
3711 #if ENABLE_UNUSED_CODE
3712   printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3713   printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3714   printf("music set == %s\n\n", artwork.mus_current_identifier);
3715 #endif
3716
3717   sortTreeInfo(&artwork.gfx_first);
3718   sortTreeInfo(&artwork.snd_first);
3719   sortTreeInfo(&artwork.mus_first);
3720
3721 #if ENABLE_UNUSED_CODE
3722   dumpTreeInfo(artwork.gfx_first, 0);
3723   dumpTreeInfo(artwork.snd_first, 0);
3724   dumpTreeInfo(artwork.mus_first, 0);
3725 #endif
3726 }
3727
3728 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3729                                          LevelDirTree *level_node)
3730 {
3731   int type = (*artwork_node)->type;
3732
3733   // recursively check all level directories for artwork sub-directories
3734
3735   while (level_node)
3736   {
3737     // check all tree entries for artwork, but skip parent link entries
3738     if (!level_node->parent_link)
3739     {
3740       TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3741       boolean cached = (artwork_new != NULL);
3742
3743       if (cached)
3744       {
3745         pushTreeInfo(artwork_node, artwork_new);
3746       }
3747       else
3748       {
3749         TreeInfo *topnode_last = *artwork_node;
3750         char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3751                               ARTWORK_DIRECTORY(type));
3752
3753         LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3754
3755         if (topnode_last != *artwork_node)      // check for newly added node
3756         {
3757           artwork_new = *artwork_node;
3758
3759           setString(&artwork_new->identifier,   level_node->subdir);
3760           setString(&artwork_new->name,         level_node->name);
3761           setString(&artwork_new->name_sorting, level_node->name_sorting);
3762
3763           artwork_new->sort_priority = level_node->sort_priority;
3764           artwork_new->color = LEVELCOLOR(artwork_new);
3765         }
3766
3767         free(path);
3768       }
3769
3770       // insert artwork info (from old cache or filesystem) into new cache
3771       if (artwork_new != NULL)
3772         setArtworkInfoCacheEntry(artwork_new, level_node, type);
3773     }
3774
3775     DrawInitText(level_node->name, 150, FC_YELLOW);
3776
3777     if (level_node->node_group != NULL)
3778       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3779
3780     level_node = level_node->next;
3781   }
3782 }
3783
3784 void LoadLevelArtworkInfo(void)
3785 {
3786   print_timestamp_init("LoadLevelArtworkInfo");
3787
3788   DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3789
3790   print_timestamp_time("DrawTimeText");
3791
3792   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3793   print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3794   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3795   print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3796   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3797   print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3798
3799   SaveArtworkInfoCache();
3800
3801   print_timestamp_time("SaveArtworkInfoCache");
3802
3803   // needed for reloading level artwork not known at ealier stage
3804
3805   if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3806   {
3807     artwork.gfx_current =
3808       getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3809     if (artwork.gfx_current == NULL)
3810       artwork.gfx_current =
3811         getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3812     if (artwork.gfx_current == NULL)
3813       artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3814   }
3815
3816   if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3817   {
3818     artwork.snd_current =
3819       getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3820     if (artwork.snd_current == NULL)
3821       artwork.snd_current =
3822         getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3823     if (artwork.snd_current == NULL)
3824       artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3825   }
3826
3827   if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3828   {
3829     artwork.mus_current =
3830       getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3831     if (artwork.mus_current == NULL)
3832       artwork.mus_current =
3833         getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3834     if (artwork.mus_current == NULL)
3835       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3836   }
3837
3838   print_timestamp_time("getTreeInfoFromIdentifier");
3839
3840   sortTreeInfo(&artwork.gfx_first);
3841   sortTreeInfo(&artwork.snd_first);
3842   sortTreeInfo(&artwork.mus_first);
3843
3844   print_timestamp_time("sortTreeInfo");
3845
3846 #if ENABLE_UNUSED_CODE
3847   dumpTreeInfo(artwork.gfx_first, 0);
3848   dumpTreeInfo(artwork.snd_first, 0);
3849   dumpTreeInfo(artwork.mus_first, 0);
3850 #endif
3851
3852   print_timestamp_done("LoadLevelArtworkInfo");
3853 }
3854
3855 static boolean AddUserLevelSetToLevelInfoExt(char *level_subdir_new)
3856 {
3857   // get level info tree node of first (original) user level set
3858   char *level_subdir_old = getLoginName();
3859   LevelDirTree *leveldir_old = getTreeInfoFromIdentifier(leveldir_first,
3860                                                          level_subdir_old);
3861   if (leveldir_old == NULL)             // should not happen
3862     return FALSE;
3863
3864   int draw_deactivation_mask = GetDrawDeactivationMask();
3865
3866   // override draw deactivation mask (temporarily disable drawing)
3867   SetDrawDeactivationMask(REDRAW_ALL);
3868
3869   // load new level set config and add it next to first user level set
3870   LoadLevelInfoFromLevelConf(&leveldir_old->next, NULL,
3871                              leveldir_old->basepath, level_subdir_new);
3872
3873   // set draw deactivation mask to previous value
3874   SetDrawDeactivationMask(draw_deactivation_mask);
3875
3876   // get level info tree node of newly added user level set
3877   LevelDirTree *leveldir_new = getTreeInfoFromIdentifier(leveldir_first,
3878                                                          level_subdir_new);
3879   if (leveldir_new == NULL)             // should not happen
3880     return FALSE;
3881
3882   // correct top link and parent node link of newly created tree node
3883   leveldir_new->node_top    = leveldir_old->node_top;
3884   leveldir_new->node_parent = leveldir_old->node_parent;
3885
3886   // sort level info tree to adjust position of newly added level set
3887   sortTreeInfo(&leveldir_first);
3888
3889   return TRUE;
3890 }
3891
3892 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
3893 {
3894   if (!AddUserLevelSetToLevelInfoExt(level_subdir_new))
3895     Error(ERR_EXIT, "internal level set structure corrupted -- aborting");
3896 }
3897
3898 char *getArtworkIdentifierForUserLevelSet(int type)
3899 {
3900   char *classic_artwork_set = getClassicArtworkSet(type);
3901
3902   // check for custom artwork configured in "levelinfo.conf"
3903   char *leveldir_artwork_set =
3904     *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
3905   boolean has_leveldir_artwork_set =
3906     (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
3907                                                classic_artwork_set));
3908
3909   // check for custom artwork in sub-directory "graphics" etc.
3910   TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
3911   char *leveldir_identifier = leveldir_current->identifier;
3912   boolean has_artwork_subdir =
3913     (getTreeInfoFromIdentifier(artwork_first_node,
3914                                leveldir_identifier) != NULL);
3915
3916   return (has_leveldir_artwork_set ? leveldir_artwork_set :
3917           has_artwork_subdir       ? leveldir_identifier :
3918           classic_artwork_set);
3919 }
3920
3921 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
3922 {
3923   char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
3924   TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
3925
3926   return getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
3927 }
3928
3929 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
3930 {
3931   char *graphics_set =
3932     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
3933   char *sounds_set =
3934     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
3935   char *music_set =
3936     getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
3937
3938   return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
3939           !strEqual(sounds_set,   SND_CLASSIC_SUBDIR) ||
3940           !strEqual(music_set,    MUS_CLASSIC_SUBDIR));
3941 }
3942
3943 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
3944                            char *level_author, int num_levels)
3945 {
3946   char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
3947   char *filename_tmp = getStringCat2(filename, ".tmp");
3948   FILE *file = NULL;
3949   FILE *file_tmp = NULL;
3950   char line[MAX_LINE_LEN];
3951   boolean success = FALSE;
3952   LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
3953                                                      level_subdir);
3954   // update values in level directory tree
3955
3956   if (level_name != NULL)
3957     setString(&leveldir->name, level_name);
3958
3959   if (level_author != NULL)
3960     setString(&leveldir->author, level_author);
3961
3962   if (num_levels != -1)
3963     leveldir->levels = num_levels;
3964
3965   // update values that depend on other values
3966
3967   setString(&leveldir->name_sorting, leveldir->name);
3968
3969   leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
3970
3971   // sort order of level sets may have changed
3972   sortTreeInfo(&leveldir_first);
3973
3974   if ((file     = fopen(filename,     MODE_READ)) &&
3975       (file_tmp = fopen(filename_tmp, MODE_WRITE)))
3976   {
3977     while (fgets(line, MAX_LINE_LEN, file))
3978     {
3979       if (strPrefix(line, "name:") && level_name != NULL)
3980         fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
3981       else if (strPrefix(line, "author:") && level_author != NULL)
3982         fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
3983       else if (strPrefix(line, "levels:") && num_levels != -1)
3984         fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
3985       else
3986         fputs(line, file_tmp);
3987     }
3988
3989     success = TRUE;
3990   }
3991
3992   if (file)
3993     fclose(file);
3994
3995   if (file_tmp)
3996     fclose(file_tmp);
3997
3998   if (success)
3999     success = (rename(filename_tmp, filename) == 0);
4000
4001   free(filename);
4002   free(filename_tmp);
4003
4004   return success;
4005 }
4006
4007 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4008                            char *level_author, int num_levels,
4009                            boolean use_artwork_set)
4010 {
4011   LevelDirTree *level_info;
4012   char *filename;
4013   FILE *file;
4014   int i;
4015
4016   // create user level sub-directory, if needed
4017   createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4018
4019   filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4020
4021   if (!(file = fopen(filename, MODE_WRITE)))
4022   {
4023     Error(ERR_WARN, "cannot write level info file '%s'", filename);
4024     free(filename);
4025
4026     return FALSE;
4027   }
4028
4029   level_info = newTreeInfo();
4030
4031   // always start with reliable default values
4032   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4033
4034   setString(&level_info->name, level_name);
4035   setString(&level_info->author, level_author);
4036   level_info->levels = num_levels;
4037   level_info->first_level = 1;
4038   level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4039   level_info->readonly = FALSE;
4040
4041   if (use_artwork_set)
4042   {
4043     level_info->graphics_set =
4044       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4045     level_info->sounds_set =
4046       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4047     level_info->music_set =
4048       getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4049   }
4050
4051   token_value_position = TOKEN_VALUE_POSITION_SHORT;
4052
4053   fprintFileHeader(file, LEVELINFO_FILENAME);
4054
4055   ldi = *level_info;
4056   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4057   {
4058     if (i == LEVELINFO_TOKEN_NAME ||
4059         i == LEVELINFO_TOKEN_AUTHOR ||
4060         i == LEVELINFO_TOKEN_LEVELS ||
4061         i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4062         i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4063         i == LEVELINFO_TOKEN_READONLY ||
4064         (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4065                              i == LEVELINFO_TOKEN_SOUNDS_SET ||
4066                              i == LEVELINFO_TOKEN_MUSIC_SET)))
4067       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4068
4069     // just to make things nicer :)
4070     if (i == LEVELINFO_TOKEN_AUTHOR ||
4071         i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4072         (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4073       fprintf(file, "\n");      
4074   }
4075
4076   token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4077
4078   fclose(file);
4079
4080   SetFilePermissions(filename, PERMS_PRIVATE);
4081
4082   freeTreeInfo(level_info);
4083   free(filename);
4084
4085   return TRUE;
4086 }
4087
4088 static void SaveUserLevelInfo(void)
4089 {
4090   CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4091 }
4092
4093 char *getSetupValue(int type, void *value)
4094 {
4095   static char value_string[MAX_LINE_LEN];
4096
4097   if (value == NULL)
4098     return NULL;
4099
4100   switch (type)
4101   {
4102     case TYPE_BOOLEAN:
4103       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4104       break;
4105
4106     case TYPE_SWITCH:
4107       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4108       break;
4109
4110     case TYPE_SWITCH3:
4111       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
4112                             *(int *)value == FALSE ? "off" : "on"));
4113       break;
4114
4115     case TYPE_YES_NO:
4116       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4117       break;
4118
4119     case TYPE_YES_NO_AUTO:
4120       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
4121                             *(int *)value == FALSE ? "no" : "yes"));
4122       break;
4123
4124     case TYPE_ECS_AGA:
4125       strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4126       break;
4127
4128     case TYPE_KEY:
4129       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4130       break;
4131
4132     case TYPE_KEY_X11:
4133       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4134       break;
4135
4136     case TYPE_INTEGER:
4137       sprintf(value_string, "%d", *(int *)value);
4138       break;
4139
4140     case TYPE_STRING:
4141       if (*(char **)value == NULL)
4142         return NULL;
4143
4144       strcpy(value_string, *(char **)value);
4145       break;
4146
4147     case TYPE_PLAYER:
4148       sprintf(value_string, "player_%d", *(int *)value + 1);
4149       break;
4150
4151     default:
4152       value_string[0] = '\0';
4153       break;
4154   }
4155
4156   if (type & TYPE_GHOSTED)
4157     strcpy(value_string, "n/a");
4158
4159   return value_string;
4160 }
4161
4162 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4163 {
4164   int i;
4165   char *line;
4166   static char token_string[MAX_LINE_LEN];
4167   int token_type = token_info[token_nr].type;
4168   void *setup_value = token_info[token_nr].value;
4169   char *token_text = token_info[token_nr].text;
4170   char *value_string = getSetupValue(token_type, setup_value);
4171
4172   // build complete token string
4173   sprintf(token_string, "%s%s", prefix, token_text);
4174
4175   // build setup entry line
4176   line = getFormattedSetupEntry(token_string, value_string);
4177
4178   if (token_type == TYPE_KEY_X11)
4179   {
4180     Key key = *(Key *)setup_value;
4181     char *keyname = getKeyNameFromKey(key);
4182
4183     // add comment, if useful
4184     if (!strEqual(keyname, "(undefined)") &&
4185         !strEqual(keyname, "(unknown)"))
4186     {
4187       // add at least one whitespace
4188       strcat(line, " ");
4189       for (i = strlen(line); i < token_comment_position; i++)
4190         strcat(line, " ");
4191
4192       strcat(line, "# ");
4193       strcat(line, keyname);
4194     }
4195   }
4196
4197   return line;
4198 }
4199
4200 void LoadLevelSetup_LastSeries(void)
4201 {
4202   // --------------------------------------------------------------------------
4203   // ~/.<program>/levelsetup.conf
4204   // --------------------------------------------------------------------------
4205
4206   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4207   SetupFileHash *level_setup_hash = NULL;
4208
4209   // always start with reliable default values
4210   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4211
4212   if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4213   {
4214     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4215                                                  DEFAULT_LEVELSET);
4216     if (leveldir_current == NULL)
4217       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4218   }
4219
4220   if ((level_setup_hash = loadSetupFileHash(filename)))
4221   {
4222     char *last_level_series =
4223       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4224
4225     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4226                                                  last_level_series);
4227     if (leveldir_current == NULL)
4228       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4229
4230     freeSetupFileHash(level_setup_hash);
4231   }
4232   else
4233   {
4234     Error(ERR_DEBUG, "using default setup values");
4235   }
4236
4237   free(filename);
4238 }
4239
4240 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4241 {
4242   // --------------------------------------------------------------------------
4243   // ~/.<program>/levelsetup.conf
4244   // --------------------------------------------------------------------------
4245
4246   // check if the current level directory structure is available at this point
4247   if (leveldir_current == NULL)
4248     return;
4249
4250   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4251   char *level_subdir = leveldir_current->subdir;
4252   FILE *file;
4253
4254   InitUserDataDirectory();
4255
4256   if (!(file = fopen(filename, MODE_WRITE)))
4257   {
4258     Error(ERR_WARN, "cannot write setup file '%s'", filename);
4259
4260     free(filename);
4261
4262     return;
4263   }
4264
4265   fprintFileHeader(file, LEVELSETUP_FILENAME);
4266
4267   if (deactivate_last_level_series)
4268     fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4269
4270   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4271                                                level_subdir));
4272
4273   fclose(file);
4274
4275   SetFilePermissions(filename, PERMS_PRIVATE);
4276
4277   free(filename);
4278 }
4279
4280 void SaveLevelSetup_LastSeries(void)
4281 {
4282   SaveLevelSetup_LastSeries_Ext(FALSE);
4283 }
4284
4285 void SaveLevelSetup_LastSeries_Deactivate(void)
4286 {
4287   SaveLevelSetup_LastSeries_Ext(TRUE);
4288 }
4289
4290 static void checkSeriesInfo(void)
4291 {
4292   static char *level_directory = NULL;
4293   Directory *dir;
4294
4295   // check for more levels besides the 'levels' field of 'levelinfo.conf'
4296
4297   level_directory = getPath2((leveldir_current->in_user_dir ?
4298                               getUserLevelDir(NULL) :
4299                               options.level_directory),
4300                              leveldir_current->fullpath);
4301
4302   if ((dir = openDirectory(level_directory)) == NULL)
4303   {
4304     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4305
4306     return;
4307   }
4308
4309   closeDirectory(dir);
4310 }
4311
4312 void LoadLevelSetup_SeriesInfo(void)
4313 {
4314   char *filename;
4315   SetupFileHash *level_setup_hash = NULL;
4316   char *level_subdir = leveldir_current->subdir;
4317   int i;
4318
4319   // always start with reliable default values
4320   level_nr = leveldir_current->first_level;
4321
4322   for (i = 0; i < MAX_LEVELS; i++)
4323   {
4324     LevelStats_setPlayed(i, 0);
4325     LevelStats_setSolved(i, 0);
4326   }
4327
4328   checkSeriesInfo();
4329
4330   // --------------------------------------------------------------------------
4331   // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4332   // --------------------------------------------------------------------------
4333
4334   level_subdir = leveldir_current->subdir;
4335
4336   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4337
4338   if ((level_setup_hash = loadSetupFileHash(filename)))
4339   {
4340     char *token_value;
4341
4342     // get last played level in this level set
4343
4344     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4345
4346     if (token_value)
4347     {
4348       level_nr = atoi(token_value);
4349
4350       if (level_nr < leveldir_current->first_level)
4351         level_nr = leveldir_current->first_level;
4352       if (level_nr > leveldir_current->last_level)
4353         level_nr = leveldir_current->last_level;
4354     }
4355
4356     // get handicap level in this level set
4357
4358     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4359
4360     if (token_value)
4361     {
4362       int level_nr = atoi(token_value);
4363
4364       if (level_nr < leveldir_current->first_level)
4365         level_nr = leveldir_current->first_level;
4366       if (level_nr > leveldir_current->last_level + 1)
4367         level_nr = leveldir_current->last_level;
4368
4369       if (leveldir_current->user_defined || !leveldir_current->handicap)
4370         level_nr = leveldir_current->last_level;
4371
4372       leveldir_current->handicap_level = level_nr;
4373     }
4374
4375     // get number of played and solved levels in this level set
4376
4377     BEGIN_HASH_ITERATION(level_setup_hash, itr)
4378     {
4379       char *token = HASH_ITERATION_TOKEN(itr);
4380       char *value = HASH_ITERATION_VALUE(itr);
4381
4382       if (strlen(token) == 3 &&
4383           token[0] >= '0' && token[0] <= '9' &&
4384           token[1] >= '0' && token[1] <= '9' &&
4385           token[2] >= '0' && token[2] <= '9')
4386       {
4387         int level_nr = atoi(token);
4388
4389         if (value != NULL)
4390           LevelStats_setPlayed(level_nr, atoi(value));  // read 1st column
4391
4392         value = strchr(value, ' ');
4393
4394         if (value != NULL)
4395           LevelStats_setSolved(level_nr, atoi(value));  // read 2nd column
4396       }
4397     }
4398     END_HASH_ITERATION(hash, itr)
4399
4400     freeSetupFileHash(level_setup_hash);
4401   }
4402   else
4403   {
4404     Error(ERR_DEBUG, "using default setup values");
4405   }
4406
4407   free(filename);
4408 }
4409
4410 void SaveLevelSetup_SeriesInfo(void)
4411 {
4412   char *filename;
4413   char *level_subdir = leveldir_current->subdir;
4414   char *level_nr_str = int2str(level_nr, 0);
4415   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4416   FILE *file;
4417   int i;
4418
4419   // --------------------------------------------------------------------------
4420   // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4421   // --------------------------------------------------------------------------
4422
4423   InitLevelSetupDirectory(level_subdir);
4424
4425   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4426
4427   if (!(file = fopen(filename, MODE_WRITE)))
4428   {
4429     Error(ERR_WARN, "cannot write setup file '%s'", filename);
4430     free(filename);
4431     return;
4432   }
4433
4434   fprintFileHeader(file, LEVELSETUP_FILENAME);
4435
4436   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4437                                                level_nr_str));
4438   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4439                                                  handicap_level_str));
4440
4441   for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4442        i++)
4443   {
4444     if (LevelStats_getPlayed(i) > 0 ||
4445         LevelStats_getSolved(i) > 0)
4446     {
4447       char token[16];
4448       char value[16];
4449
4450       sprintf(token, "%03d", i);
4451       sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4452
4453       fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4454     }
4455   }
4456
4457   fclose(file);
4458
4459   SetFilePermissions(filename, PERMS_PRIVATE);
4460
4461   free(filename);
4462 }
4463
4464 int LevelStats_getPlayed(int nr)
4465 {
4466   return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4467 }
4468
4469 int LevelStats_getSolved(int nr)
4470 {
4471   return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4472 }
4473
4474 void LevelStats_setPlayed(int nr, int value)
4475 {
4476   if (nr >= 0 && nr < MAX_LEVELS)
4477     level_stats[nr].played = value;
4478 }
4479
4480 void LevelStats_setSolved(int nr, int value)
4481 {
4482   if (nr >= 0 && nr < MAX_LEVELS)
4483     level_stats[nr].solved = value;
4484 }
4485
4486 void LevelStats_incPlayed(int nr)
4487 {
4488   if (nr >= 0 && nr < MAX_LEVELS)
4489     level_stats[nr].played++;
4490 }
4491
4492 void LevelStats_incSolved(int nr)
4493 {
4494   if (nr >= 0 && nr < MAX_LEVELS)
4495     level_stats[nr].solved++;
4496 }