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