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