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