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