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